/*************************************************************************\ * Copyright (c) 2008 UChicago Argonne LLC, as Operator of Argonne * National Laboratory. * Copyright (c) 2002 The Regents of the University of California, as * Operator of Los Alamos National Laboratory. * EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ /* dbScan.c */ /* tasks and subroutines to scan the database */ /* * Original Author: Bob Dalesio * Current Author: Marty Kraimer * Date: 07/18/91 */ #include #include #include #include #include #include #include "epicsStdioRedirect.h" #include "dbDefs.h" #include "ellLib.h" #include "taskwd.h" #include "epicsMutex.h" #include "epicsEvent.h" #include "epicsExit.h" #include "epicsInterrupt.h" #include "epicsThread.h" #include "epicsTime.h" #include "cantProceed.h" #include "epicsRingPointer.h" #include "epicsPrint.h" #include "dbBase.h" #include "dbStaticLib.h" #include "dbFldTypes.h" #include "link.h" #include "devSup.h" #include "dbCommon.h" #define epicsExportSharedSymbols #include "dbAddr.h" #include "callback.h" #include "dbAccessDefs.h" #include "dbLock.h" #include "recGbl.h" #include "dbScan.h" /* Task Control */ enum ctl {ctlRun, ctlPause, ctlExit}; /* Task Startup/Shutdown Synchronization */ static epicsEventId startStopEvent; volatile enum ctl scanCtl; /* SCAN ONCE */ static int onceQueueSize = 1000; static epicsEventId onceSem; static epicsRingPointerId onceQ; static epicsThreadId onceTaskId; static void *exitOnce; /* All other scan types */ typedef struct scan_list{ epicsMutexId lock; ELLLIST list; short modified;/*has list been modified?*/ } scan_list; /*scan_elements are allocated and the address stored in dbCommon.spvt*/ typedef struct scan_element{ ELLNODE node; scan_list *pscan_list; struct dbCommon *precord; } scan_element; /* PERIODIC */ typedef struct periodic_scan_list { scan_list scan_list; double period; volatile enum ctl scanCtl; epicsEventId loopEvent; } periodic_scan_list; static int nPeriodic = 0; static periodic_scan_list **papPeriodic; /* pointer to array of pointers */ static epicsThreadId *periodicTaskId; /* array of thread ids */ static char *priorityName[NUM_CALLBACK_PRIORITIES] = { "Low", "Medium", "High" }; /* EVENT */ #define MAX_EVENTS 256 typedef struct event_scan_list { CALLBACK callback; scan_list scan_list; } event_scan_list; static event_scan_list *pevent_list[NUM_CALLBACK_PRIORITIES][MAX_EVENTS]; /* IO_EVENT*/ typedef struct io_scan_list { CALLBACK callback; scan_list scan_list; struct io_scan_list *next; } io_scan_list; static io_scan_list *iosl_head[NUM_CALLBACK_PRIORITIES] = { NULL, NULL, NULL }; /* Private routines */ static void onceTask(void *); static void initOnce(void); static void periodicTask(void *arg); static void initPeriodic(void); static void spawnPeriodic(int ind); static void initEvent(void); static void eventCallback(CALLBACK *pcallback); static void ioeventCallback(CALLBACK *pcallback); static void printList(scan_list *psl, char *message); static void scanList(scan_list *psl); static void buildScanLists(void); static void addToList(struct dbCommon *precord, scan_list *psl); static void deleteFromList(struct dbCommon *precord, scan_list *psl); static void scanShutdown(void *arg) { int i; interruptAccept = FALSE; for (i = 0; i < nPeriodic; i++) { papPeriodic[i]->scanCtl = ctlExit; epicsEventSignal(papPeriodic[i]->loopEvent); epicsEventWait(startStopEvent); } scanOnce((dbCommon *)&exitOnce); epicsEventWait(startStopEvent); } long scanInit(void) { int i; startStopEvent = epicsEventMustCreate(epicsEventEmpty); scanCtl = ctlPause; initOnce(); initPeriodic(); initEvent(); buildScanLists(); for (i = 0; i < nPeriodic; i++) spawnPeriodic(i); epicsAtExit(scanShutdown, NULL); return 0; } void scanRun(void) { int i; interruptAccept = TRUE; scanCtl = ctlRun; for (i = 0; i < nPeriodic; i++) papPeriodic[i]->scanCtl = ctlRun; } void scanPause(void) { int i; for (i = nPeriodic - 1; i >= 0; --i) papPeriodic[i]->scanCtl = ctlPause; scanCtl = ctlPause; interruptAccept = FALSE; } void scanAdd(struct dbCommon *precord) { int scan; /* get the list on which this record belongs */ scan = precord->scan; if (scan == menuScanPassive) return; if (scan < 0 || scan >= nPeriodic + SCAN_1ST_PERIODIC) { recGblRecordError(-1, (void *)precord, "scanAdd detected illegal SCAN value"); } else if (scan == menuScanEvent) { int evnt; int prio; event_scan_list *pesl; evnt = precord->evnt; if (evnt < 0 || evnt >= MAX_EVENTS) { recGblRecordError(S_db_badField, (void *)precord, "scanAdd detected illegal EVNT value"); precord->scan = menuScanPassive; return; } prio = precord->prio; if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) { recGblRecordError(-1, (void *)precord, "scanAdd: illegal prio field"); precord->scan = menuScanPassive; return; } pesl = pevent_list[prio][evnt]; if (pesl == NULL) { pesl = dbCalloc(1, sizeof(event_scan_list)); pevent_list[prio][evnt] = pesl; pesl->scan_list.lock = epicsMutexMustCreate(); callbackSetCallback(eventCallback, &pesl->callback); callbackSetPriority(prio, &pesl->callback); callbackSetUser(pesl, &pesl->callback); ellInit(&pesl->scan_list.list); } addToList(precord, &pesl->scan_list); } else if (scan == menuScanI_O_Intr) { io_scan_list *piosl = NULL; int prio; DEVSUPFUN get_ioint_info; if (precord->dset == NULL){ recGblRecordError(-1, (void *)precord, "scanAdd: I/O Intr not valid (no DSET) "); precord->scan = menuScanPassive; return; } get_ioint_info = precord->dset->get_ioint_info; if (get_ioint_info == NULL) { recGblRecordError(-1, (void *)precord, "scanAdd: I/O Intr not valid (no get_ioint_info)"); precord->scan = menuScanPassive; return; } if (get_ioint_info(0, precord, &piosl)) { precord->scan = menuScanPassive; return; } if (piosl == NULL) { recGblRecordError(-1, (void *)precord, "scanAdd: I/O Intr not valid"); precord->scan = menuScanPassive; return; } prio = precord->prio; if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) { recGblRecordError(-1, (void *)precord, "scanAdd: illegal prio field"); precord->scan = menuScanPassive; return; } piosl += prio; /* get piosl for correct priority*/ addToList(precord, &piosl->scan_list); } else if (scan >= SCAN_1ST_PERIODIC) { addToList(precord, &papPeriodic[scan - SCAN_1ST_PERIODIC]->scan_list); } return; } void scanDelete(struct dbCommon *precord) { short scan; /* get the list on which this record belongs */ scan = precord->scan; if (scan == menuScanPassive) return; if (scan < 0 || scan >= nPeriodic + SCAN_1ST_PERIODIC) { recGblRecordError(-1, (void *)precord, "scanDelete detected illegal SCAN value"); } else if (scan == menuScanEvent) { int evnt; int prio; event_scan_list *pesl; scan_list *psl = 0; evnt = precord->evnt; if (evnt < 0 || evnt >= MAX_EVENTS) { recGblRecordError(S_db_badField, (void *)precord, "scanAdd detected illegal EVNT value"); precord->scan = menuScanPassive; return; } prio = precord->prio; if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) { recGblRecordError(-1, (void *)precord, "scanAdd: illegal prio field"); precord->scan = menuScanPassive; return; } pesl = pevent_list[prio][evnt]; if (pesl) psl = &pesl->scan_list; if (!pesl || !psl) recGblRecordError(-1, (void *)precord, "scanDelete for bad evnt"); else deleteFromList(precord, psl); } else if (scan == menuScanI_O_Intr) { io_scan_list *piosl=NULL; int prio; DEVSUPFUN get_ioint_info; if (precord->dset==NULL) { recGblRecordError(-1, (void *)precord, "scanDelete: I/O Intr not valid (no DSET)"); return; } get_ioint_info=precord->dset->get_ioint_info; if (get_ioint_info == NULL) { recGblRecordError(-1, (void *)precord, "scanDelete: I/O Intr not valid (no get_ioint_info)"); return; } if (get_ioint_info(1, precord, &piosl)) return; if (piosl == NULL) { recGblRecordError(-1, (void *)precord, "scanDelete: I/O Intr not valid"); return; } prio = precord->prio; if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) { recGblRecordError(-1, (void *)precord, "scanDelete: get_ioint_info returned illegal priority"); return; } piosl += prio; /*get piosl for correct priority*/ deleteFromList(precord, &piosl->scan_list); } else if (scan >= SCAN_1ST_PERIODIC) { deleteFromList(precord, &papPeriodic[scan - SCAN_1ST_PERIODIC]->scan_list); } return; } double scanPeriod(int scan) { scan -= SCAN_1ST_PERIODIC; if (scan < 0 || scan >= nPeriodic) return 0.0; return papPeriodic[scan]->period; } int scanppl(double period) /* print periodic list */ { periodic_scan_list *ppsl; char message[80]; int i; for (i = 0; i < nPeriodic; i++) { ppsl = papPeriodic[i]; if (ppsl == NULL) continue; if (period > 0.0 && (fabs(period - ppsl->period) >.05)) continue; sprintf(message, "Scan Period = %g seconds ", ppsl->period); printList(&ppsl->scan_list, message); } return 0; } int scanpel(int event_number) /* print event list */ { char message[80]; int prio, evnt; event_scan_list *pesl; for (evnt = 0; evnt < MAX_EVENTS; evnt++) { if (event_number && evntevent_number) break; for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) { pesl = pevent_list[prio][evnt]; if (!pesl) continue; if (ellCount(&pesl->scan_list.list) == 0) continue; sprintf(message, "Event %d Priority %s", evnt, priorityName[prio]); printList(&pesl->scan_list, message); } } return 0; } int scanpiol(void) /* print io_event list */ { io_scan_list *piosl; int prio; char message[80]; for(prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) { piosl = iosl_head[prio]; if (piosl == NULL) continue; sprintf(message, "IO Event: Priority %s", priorityName[prio]); while(piosl != NULL) { printList(&piosl->scan_list, message); piosl = piosl->next; } } return 0; } static void eventCallback(CALLBACK *pcallback) { event_scan_list *pesl; callbackGetUser(pesl, pcallback); scanList(&pesl->scan_list); } static void initEvent(void) { int evnt, prio; for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) { for (evnt = 0; evnt < MAX_EVENTS; evnt++) { pevent_list[prio][evnt] = NULL; } } } void post_event(int event) { int prio; event_scan_list *pesl; if (scanCtl != ctlRun) return; if (event < 0 || event >= MAX_EVENTS) { errMessage(-1, "illegal event passed to post_event"); return; } for (prio=0; prioscan_list.list) >0) callbackRequest((void *)pesl); } } void scanIoInit(IOSCANPVT *ppioscanpvt) { int prio; /* Allocate an array of io_scan_lists, one for each priority. */ /* IOSCANPVT will hold the address of this array of structures */ *ppioscanpvt = dbCalloc(NUM_CALLBACK_PRIORITIES, sizeof(io_scan_list)); for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) { io_scan_list *piosl = &(*ppioscanpvt)[prio]; callbackSetCallback(ioeventCallback, &piosl->callback); callbackSetPriority(prio, &piosl->callback); callbackSetUser(piosl, &piosl->callback); ellInit(&piosl->scan_list.list); piosl->scan_list.lock = epicsMutexMustCreate(); piosl->next = iosl_head[prio]; iosl_head[prio] = piosl; } } void scanIoRequest(IOSCANPVT pioscanpvt) { int prio; if (scanCtl != ctlRun) return; for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) { io_scan_list *piosl = &pioscanpvt[prio]; if (ellCount(&piosl->scan_list.list) > 0) callbackRequest(&piosl->callback); } } void scanOnce(struct dbCommon *precord) { static int newOverflow = TRUE; int lockKey; int pushOK; lockKey = epicsInterruptLock(); pushOK = epicsRingPointerPush(onceQ, precord); epicsInterruptUnlock(lockKey); if (!pushOK) { if (newOverflow) errlogPrintf("scanOnce: Ring buffer overflow\n"); newOverflow = FALSE; } else { newOverflow = TRUE; } epicsEventSignal(onceSem); } static void onceTask(void *arg) { taskwdInsert(0, NULL, NULL); epicsEventSignal(startStopEvent); while (TRUE) { void *precord; epicsEventMustWait(onceSem); while ((precord = epicsRingPointerPop(onceQ))) { if (precord == &exitOnce) goto shutdown; dbScanLock(precord); dbProcess(precord); dbScanUnlock(precord); } } shutdown: taskwdRemove(0); epicsEventSignal(startStopEvent); } int scanOnceSetQueueSize(int size) { onceQueueSize = size; return 0; } static void initOnce(void) { if ((onceQ = epicsRingPointerCreate(onceQueueSize)) == NULL) { cantProceed("initOnce: Ring buffer create failed\n"); } onceSem = epicsEventMustCreate(epicsEventEmpty); onceTaskId = epicsThreadCreate("scanOnce", epicsThreadPriorityScanHigh, epicsThreadGetStackSize(epicsThreadStackBig), onceTask, 0); epicsEventWait(startStopEvent); } static void periodicTask(void *arg) { periodic_scan_list *ppsl = (periodic_scan_list *)arg; epicsTimeStamp start_time, end_time; double delay; taskwdInsert(0, NULL, NULL); epicsEventSignal(startStopEvent); while (ppsl->scanCtl != ctlExit) { epicsTimeGetCurrent(&start_time); if (ppsl->scanCtl == ctlRun) scanList(&ppsl->scan_list); epicsTimeGetCurrent(&end_time); delay = ppsl->period - epicsTimeDiffInSeconds(&end_time, &start_time); if (delay <= 0.0) delay = 0.1; epicsEventWaitWithTimeout(ppsl->loopEvent, delay); } taskwdRemove(0); epicsEventSignal(startStopEvent); } static void initPeriodic(void) { dbMenu *pmenu; periodic_scan_list *ppsl; int i; pmenu = dbFindMenu(pdbbase, "menuScan"); if (!pmenu) { epicsPrintf("initPeriodic: menuScan not present\n"); return; } nPeriodic = pmenu->nChoice - SCAN_1ST_PERIODIC; papPeriodic = dbCalloc(nPeriodic, sizeof(periodic_scan_list*)); periodicTaskId = dbCalloc(nPeriodic, sizeof(void *)); for (i = 0; i < nPeriodic; i++) { ppsl = dbCalloc(1, sizeof(periodic_scan_list)); ppsl->scan_list.lock = epicsMutexMustCreate(); ellInit(&ppsl->scan_list.list); epicsScanDouble(pmenu->papChoiceValue[i + SCAN_1ST_PERIODIC], &ppsl->period); ppsl->scanCtl = ctlPause; ppsl->loopEvent = epicsEventMustCreate(epicsEventEmpty); papPeriodic[i] = ppsl; } } static void spawnPeriodic(int ind) { periodic_scan_list *ppsl; char taskName[20]; ppsl = papPeriodic[ind]; sprintf(taskName, "scan%g", ppsl->period); periodicTaskId[ind] = epicsThreadCreate( taskName, epicsThreadPriorityScanLow + ind, epicsThreadGetStackSize(epicsThreadStackBig), periodicTask, (void *)ppsl); epicsEventWait(startStopEvent); } static void ioeventCallback(CALLBACK *pcallback) { io_scan_list *piosl; callbackGetUser(piosl, pcallback); scanList(&piosl->scan_list); } static void printList(scan_list *psl, char *message) { scan_element *pse; epicsMutexMustLock(psl->lock); pse = (scan_element *)ellFirst(&psl->list); epicsMutexUnlock(psl->lock); if (pse == NULL) return; printf("%s\n", message); while (pse != NULL) { printf(" %-28s\n", pse->precord->name); epicsMutexMustLock(psl->lock); if (pse->pscan_list != psl) { epicsMutexUnlock(psl->lock); printf("Scan list changed while processing."); return; } pse = (scan_element *)ellNext(&pse->node); epicsMutexUnlock(psl->lock); } } static void scanList(scan_list *psl) { /* When reading this code remember that the call to dbProcess can result * in the SCAN field being changed in an arbitrary number of records. */ scan_element *pse; scan_element *prev = NULL; scan_element *next = NULL; epicsMutexMustLock(psl->lock); psl->modified = FALSE; pse = (scan_element *)ellFirst(&psl->list); if (pse) next = (scan_element *)ellNext(&pse->node); epicsMutexUnlock(psl->lock); while (pse) { struct dbCommon *precord = pse->precord; dbScanLock(precord); dbProcess(precord); dbScanUnlock(precord); epicsMutexMustLock(psl->lock); if (!psl->modified) { prev = pse; pse = (scan_element *)ellNext(&pse->node); if (pse) next = (scan_element *)ellNext(&pse->node); } else if (pse->pscan_list == psl) { /*This scan element is still in same scan list*/ prev = pse; pse = (scan_element *)ellNext(&pse->node); if (pse) next = (scan_element *)ellNext(&pse->node); psl->modified = FALSE; } else if (prev && prev->pscan_list == psl) { /*Previous scan element is still in same scan list*/ pse = (scan_element *)ellNext(&prev->node); if (pse) { prev = (scan_element *)ellPrevious(&pse->node); next = (scan_element *)ellNext(&pse->node); } psl->modified = FALSE; } else if (next && next->pscan_list == psl) { /*Next scan element is still in same scan list*/ pse = next; prev = (scan_element *)ellPrevious(&pse->node); next = (scan_element *)ellNext(&pse->node); psl->modified = FALSE; } else { /*Too many changes. Just wait till next period*/ epicsMutexUnlock(psl->lock); return; } epicsMutexUnlock(psl->lock); } } static void buildScanLists(void) { dbRecordType *pdbRecordType; /*Look for first record*/ for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { dbRecordNode *pdbRecordNode; for (pdbRecordNode = (dbRecordNode *)ellFirst(&pdbRecordType->recList); pdbRecordNode; pdbRecordNode = (dbRecordNode *)ellNext(&pdbRecordNode->node)) { dbCommon *precord = pdbRecordNode->precord; if (!precord->name[0] || pdbRecordNode->flags & DBRN_FLAGS_ISALIAS) continue; scanAdd(precord); } } } static void addToList(struct dbCommon *precord, scan_list *psl) { scan_element *pse, *ptemp; epicsMutexMustLock(psl->lock); pse = precord->spvt; if (pse == NULL) { pse = dbCalloc(1, sizeof(scan_element)); precord->spvt = pse; pse->precord = precord; } pse->pscan_list = psl; ptemp = (scan_element *)ellFirst(&psl->list); while (ptemp) { if (ptemp->precord->phas > precord->phas) { ellInsert(&psl->list, ellPrevious(&ptemp->node), &pse->node); break; } ptemp = (scan_element *)ellNext(&ptemp->node); } if (ptemp == NULL) ellAdd(&psl->list, (void *)pse); psl->modified = TRUE; epicsMutexUnlock(psl->lock); } static void deleteFromList(struct dbCommon *precord, scan_list *psl) { scan_element *pse; epicsMutexMustLock(psl->lock); pse = precord->spvt; if (pse == NULL) { epicsMutexUnlock(psl->lock); errlogPrintf("dbScan: Tried to delete record from wrong scan list!\n" "\t%s.SPVT = NULL, but psl = %p\n", precord->name, (void *)psl); return; } if (pse->pscan_list != psl) { epicsMutexUnlock(psl->lock); errlogPrintf("dbScan: Tried to delete record from wrong scan list!\n" "\t%s.SPVT->pscan_list = %p but psl = %p\n", precord->name, (void *)pse, (void *)psl); return; } pse->pscan_list = NULL; ellDelete(&psl->list, (void *)pse); psl->modified = TRUE; epicsMutexUnlock(psl->lock); }