Files
epics-base/modules/database/src/ioc/db/dbScan.c
Michael Davidsaver e257e30cc9 Merge remote-tracking branch 'lp-Com/use-monotonic' into 7.0
* lp-Com/use-monotonic:
  VxWorks: Measure CPU time-base frequency if necessary
  replace most internal getCurrent() -> getMonotonic()
  dbScan periodic scan use monotonic time
  monotonic adapt to regular time APIs
2019-10-06 20:10:20 -07:00

1114 lines
31 KiB
C
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*************************************************************************\
* Copyright (c) 2012 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.
* Copyright (c) 2013 Helmholtz-Zentrum Berlin
* für Materialien und Energie GmbH.
* 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 Authors: Bob Dalesio & Marty Kraimer
*/
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <math.h>
#include <ctype.h>
#include "cantProceed.h"
#include "dbDefs.h"
#include "ellLib.h"
#include "epicsAtomic.h"
#include "epicsEvent.h"
#include "epicsMutex.h"
#include "epicsPrint.h"
#include "epicsRingBytes.h"
#include "epicsStdio.h"
#include "epicsStdlib.h"
#include "epicsString.h"
#include "epicsThread.h"
#include "epicsTime.h"
#include "taskwd.h"
#define epicsExportSharedSymbols
#include "callback.h"
#include "dbAccessDefs.h"
#include "dbAddr.h"
#include "dbBase.h"
#include "dbCommon.h"
#include "dbFldTypes.h"
#include "dbLock.h"
#include "dbScan.h"
#include "dbStaticLib.h"
#include "devSup.h"
#include "link.h"
#include "recGbl.h"
/* Task Control */
enum ctl {ctlInit, ctlRun, ctlPause, ctlExit};
/* Task Startup/Shutdown Synchronization */
static epicsEventId startStopEvent;
static volatile enum ctl scanCtl;
/* SCAN ONCE */
static int onceQueueSize = 1000;
static epicsEventId onceSem;
static epicsRingBytesId onceQ;
static int onceQOverruns = 0;
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 */
#define OVERRUN_REPORT_DELAY 10.0 /* Time between initial reports */
#define OVERRUN_REPORT_MAX 3600.0 /* Maximum time between reports */
typedef struct periodic_scan_list {
scan_list scan_list;
double period;
const char *name;
unsigned long overruns;
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 */
typedef struct event_list {
epicsCallback callback[NUM_CALLBACK_PRIORITIES];
scan_list scan_list[NUM_CALLBACK_PRIORITIES];
struct event_list *next;
char eventname[1]; /* actually arbitrary size */
} event_list;
static event_list * volatile pevent_list[256];
static epicsMutexId event_lock;
/* IO_EVENT*/
typedef struct io_scan_list {
epicsCallback callback;
scan_list scan_list;
} io_scan_list;
typedef struct ioscan_head {
struct ioscan_head *next;
struct io_scan_list iosl[NUM_CALLBACK_PRIORITIES];
io_scan_complete cb;
void *arg;
} ioscan_head;
static ioscan_head *pioscan_list = NULL;
static epicsMutexId ioscan_lock;
/* Private routines */
static void onceTask(void *);
static void initOnce(void);
static void periodicTask(void *arg);
static void initPeriodic(void);
static void deletePeriodic(void);
static void spawnPeriodic(int ind);
static void eventCallback(epicsCallback *pcallback);
static void ioscanInit(void);
static void ioscanCallback(epicsCallback *pcallback);
static void ioscanDestroy(void);
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);
void scanStop(void)
{
int i;
if (scanCtl == ctlExit) return;
scanCtl = ctlExit;
interruptAccept = FALSE;
for (i = 0; i < nPeriodic; i++) {
periodic_scan_list *ppsl = papPeriodic[i];
if (!ppsl) continue;
ppsl->scanCtl = ctlExit;
epicsEventSignal(ppsl->loopEvent);
epicsEventWait(startStopEvent);
}
scanOnce((dbCommon *)&exitOnce);
epicsEventWait(startStopEvent);
}
void scanCleanup(void)
{
deletePeriodic();
ioscanDestroy();
epicsRingBytesDelete(onceQ);
free(periodicTaskId);
papPeriodic = NULL;
periodicTaskId = NULL;
}
long scanInit(void)
{
int i;
if(!startStopEvent)
startStopEvent = epicsEventMustCreate(epicsEventEmpty);
scanCtl = ctlPause;
initPeriodic();
initOnce();
buildScanLists();
for (i = 0; i < nPeriodic; i++)
spawnPeriodic(i);
return 0;
}
void scanRun(void)
{
int i;
interruptAccept = TRUE;
scanCtl = ctlRun;
for (i = 0; i < nPeriodic; i++) {
periodic_scan_list *ppsl = papPeriodic[i];
if (!ppsl) continue;
ppsl->scanCtl = ctlRun;
}
}
void scanPause(void)
{
int i;
for (i = nPeriodic - 1; i >= 0; --i) {
periodic_scan_list *ppsl = papPeriodic[i];
if (!ppsl) continue;
ppsl->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) {
char* eventname;
int prio;
event_list *pel;
eventname = precord->evnt;
prio = precord->prio;
if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) {
recGblRecordError(-1, (void *)precord,
"scanAdd: illegal prio field");
return;
}
pel = eventNameToHandle(eventname);
if (pel) addToList(precord, &pel->scan_list[prio]);
} else if (scan == menuScanI_O_Intr) {
ioscan_head *piosh = 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, &piosh)) {
precord->scan = menuScanPassive;
return;
}
if (piosh == 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;
}
addToList(precord, &piosh->iosl[prio].scan_list);
} else if (scan >= SCAN_1ST_PERIODIC) {
periodic_scan_list *ppsl = papPeriodic[scan - SCAN_1ST_PERIODIC];
if (ppsl)
addToList(precord, &ppsl->scan_list);
}
}
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 prio;
event_list *pel;
scan_list *psl = 0;
prio = precord->prio;
if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) {
recGblRecordError(-1, (void *)precord,
"scanDelete detected illegal PRIO field");
return;
}
pel = eventNameToHandle(precord->evnt);
if (pel && (psl = &pel->scan_list[prio]))
deleteFromList(precord, psl);
} else if (scan == menuScanI_O_Intr) {
ioscan_head *piosh = 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, &piosh)) return;
if (piosh == 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;
}
deleteFromList(precord, &piosh->iosl[prio].scan_list);
} else if (scan >= SCAN_1ST_PERIODIC) {
periodic_scan_list *ppsl = papPeriodic[scan - SCAN_1ST_PERIODIC];
if (ppsl)
deleteFromList(precord, &ppsl->scan_list);
}
}
double scanPeriod(int scan) {
periodic_scan_list *ppsl;
scan -= SCAN_1ST_PERIODIC;
if (scan < 0 || scan >= nPeriodic)
return 0.0;
ppsl = papPeriodic[scan];
return ppsl ? ppsl->period : 0.0;
}
int scanppl(double period) /* print periodic scan list(s) */
{
dbMenu *pmenu = dbFindMenu(pdbbase, "menuScan");
char message[80];
int i;
if (!pmenu || !papPeriodic) {
printf("scanppl: dbScan subsystem not initialized\n");
return -1;
}
for (i = 0; i < nPeriodic; i++) {
periodic_scan_list *ppsl = papPeriodic[i];
if (!ppsl) {
const char *choice = pmenu->papChoiceValue[i + SCAN_1ST_PERIODIC];
printf("Periodic scan list for SCAN = '%s' not initialized\n",
choice);
continue;
}
if (period > 0.0 &&
(fabs(period - ppsl->period) > 0.05))
continue;
sprintf(message, "Records with SCAN = '%s' (%lu over-runs):",
ppsl->name, ppsl->overruns);
printList(&ppsl->scan_list, message);
}
return 0;
}
int scanpel(const char* eventname) /* print event list */
{
char message[80];
int prio;
event_list *pel;
for (pel = pevent_list[0]; pel; pel = pel->next) {
if (!eventname || epicsStrGlobMatch(pel->eventname, eventname)) {
printf("Event \"%s\"\n", pel->eventname);
for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) {
if (ellCount(&pel->scan_list[prio].list) == 0) continue;
sprintf(message, " Priority %s", priorityName[prio]);
printList(&pel->scan_list[prio], message);
}
}
}
return 0;
}
int scanpiol(void) /* print pioscan_list */
{
ioscan_head *piosh;
ioscanInit();
epicsMutexMustLock(ioscan_lock);
piosh = pioscan_list;
while (piosh) {
int prio;
for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) {
io_scan_list *piosl = &piosh->iosl[prio];
char message[80];
sprintf(message, "IO Event %p: Priority %s",
piosh, priorityName[prio]);
printList(&piosl->scan_list, message);
}
piosh = piosh->next;
}
epicsMutexUnlock(ioscan_lock);
return 0;
}
static void eventCallback(epicsCallback *pcallback)
{
scan_list *psl;
callbackGetUser(psl, pcallback);
scanList(psl);
}
static void eventOnce(void *arg)
{
event_lock = epicsMutexMustCreate();
}
event_list *eventNameToHandle(const char *eventname)
{
int prio;
event_list *pel;
static epicsThreadOnceId onceId = EPICS_THREAD_ONCE_INIT;
double eventnumber = 0;
size_t namelength;
if (!eventname) return NULL;
while (isspace((int) eventname[0])) eventname++;
if (!eventname[0]) return NULL;
namelength = strlen(eventname);
while (isspace((int) eventname[namelength-1])) namelength--;
/* Backward compatibility with numeric events:
Treat any string that represents a double with an
integer part between 0 and 255 the same as the integer
because it is most probably a conversion from double
like from a calc record.
*/
if (epicsParseDouble(eventname, &eventnumber, NULL) == 0)
{
if (eventnumber >= 0 && eventnumber < 256)
{
if (eventnumber < 1)
return NULL; /* 0 is no event */
if ((pel = pevent_list[(int)eventnumber]) != NULL)
return pel;
}
else
eventnumber = 0; /* not a numeric event between 1 and 255 */
}
epicsThreadOnce(&onceId, eventOnce, NULL);
epicsMutexMustLock(event_lock);
for (pel = pevent_list[0]; pel; pel=pel->next) {
if (strncmp(pel->eventname, eventname, namelength) == 0
&& pel->eventname[namelength] == 0)
break;
}
if (pel == NULL) {
pel = calloc(1, sizeof(event_list) + namelength);
if (!pel)
goto done;
if (eventnumber > 0) {
/* backward compatibility: make all numeric events look like integers */
sprintf(pel->eventname, "%i", (int)eventnumber);
pevent_list[(int)eventnumber] = pel;
}
else
strncpy(pel->eventname, eventname, namelength);
for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) {
callbackSetUser(&pel->scan_list[prio], &pel->callback[prio]);
callbackSetPriority(prio, &pel->callback[prio]);
callbackSetCallback(eventCallback, &pel->callback[prio]);
pel->scan_list[prio].lock = epicsMutexMustCreate();
ellInit(&pel->scan_list[prio].list);
}
pel->next=pevent_list[0];
pevent_list[0]=pel;
}
done:
epicsMutexUnlock(event_lock);
return pel;
}
void postEvent(event_list *pel)
{
int prio;
if (scanCtl != ctlRun) return;
if (!pel) return;
for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) {
if (ellCount(&pel->scan_list[prio].list) >0)
callbackRequest(&pel->callback[prio]);
}
}
/* backward compatibility */
void post_event(int event)
{
if (event <= 0 || event > 255) return;
postEvent(pevent_list[event]);
}
static void ioscanOnce(void *arg)
{
ioscan_lock = epicsMutexMustCreate();
}
static void ioscanInit(void)
{
static epicsThreadOnceId onceId = EPICS_THREAD_ONCE_INIT;
epicsThreadOnce(&onceId, ioscanOnce, NULL);
}
static void ioscanDestroy(void)
{
ioscan_head *piosh;
ioscanInit();
epicsMutexMustLock(ioscan_lock);
piosh = pioscan_list;
pioscan_list = NULL;
epicsMutexUnlock(ioscan_lock);
while (piosh) {
ioscan_head *pnext = piosh->next;
int prio;
for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) {
epicsMutexDestroy(piosh->iosl[prio].scan_list.lock);
ellFree(&piosh->iosl[prio].scan_list.list);
}
free(piosh);
piosh = pnext;
}
}
void scanIoInit(IOSCANPVT *pioscanpvt)
{
ioscan_head *piosh = dbCalloc(1, sizeof(ioscan_head));
int prio;
ioscanInit();
for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) {
io_scan_list *piosl = &piosh->iosl[prio];
callbackSetCallback(ioscanCallback, &piosl->callback);
callbackSetPriority(prio, &piosl->callback);
callbackSetUser(piosh, &piosl->callback);
ellInit(&piosl->scan_list.list);
piosl->scan_list.lock = epicsMutexMustCreate();
}
epicsMutexMustLock(ioscan_lock);
piosh->next = pioscan_list;
pioscan_list = piosh;
epicsMutexUnlock(ioscan_lock);
*pioscanpvt = piosh;
}
/* Return a bit mask indicating each priority level
* in which a callback request was successfully queued.
*/
unsigned int scanIoRequest(IOSCANPVT piosh)
{
int prio;
unsigned int queued = 0;
if (scanCtl != ctlRun)
return 0;
for (prio = 0; prio < NUM_CALLBACK_PRIORITIES; prio++) {
io_scan_list *piosl = &piosh->iosl[prio];
if (ellCount(&piosl->scan_list.list) > 0)
if (!callbackRequest(&piosl->callback))
queued |= 1 << prio;
}
return queued;
}
unsigned int scanIoImmediate(IOSCANPVT piosh, int prio)
{
io_scan_list *piosl;
if (prio<0 || prio>=NUM_CALLBACK_PRIORITIES)
return S_db_errArg;
else if (scanCtl != ctlRun)
return 0;
piosl = &piosh->iosl[prio];
if (ellCount(&piosl->scan_list.list) == 0)
return 0;
scanList(&piosl->scan_list);
if (piosh->cb)
piosh->cb(piosh->arg, piosh, prio);
return 1 << prio;
}
/* May not be called while a scan request is queued or running */
void scanIoSetComplete(IOSCANPVT piosh, io_scan_complete cb, void *arg)
{
piosh->cb = cb;
piosh->arg = arg;
}
int scanOnce(struct dbCommon *precord) {
return scanOnceCallback(precord, NULL, NULL);
}
typedef struct {
struct dbCommon *prec;
once_complete cb;
void *usr;
} onceEntry;
int scanOnceCallback(struct dbCommon *precord, once_complete cb, void *usr)
{
static int newOverflow = TRUE;
onceEntry ent;
int pushOK;
ent.prec = precord;
ent.cb = cb;
ent.usr = usr;
pushOK = epicsRingBytesPut(onceQ, (void*)&ent, sizeof(ent));
if (!pushOK) {
if (newOverflow) errlogPrintf("scanOnce: Ring buffer overflow\n");
newOverflow = FALSE;
epicsAtomicIncrIntT(&onceQOverruns);
} else {
newOverflow = TRUE;
}
epicsEventSignal(onceSem);
return !pushOK;
}
static void onceTask(void *arg)
{
taskwdInsert(0, NULL, NULL);
epicsEventSignal(startStopEvent);
while (TRUE) {
epicsEventMustWait(onceSem);
while(1) {
onceEntry ent;
int bytes = epicsRingBytesGet(onceQ, (void*)&ent, sizeof(ent));
if(bytes==0)
break;
if(bytes!=sizeof(ent)) {
errlogPrintf("onceTask: received incomplete %d of %u\n",
bytes, (unsigned)sizeof(ent));
continue; /* what to do? */
} else if (ent.prec == (void*)&exitOnce) goto shutdown;
dbScanLock(ent.prec);
dbProcess(ent.prec);
dbScanUnlock(ent.prec);
if(ent.cb)
ent.cb(ent.usr, ent.prec);
}
}
shutdown:
taskwdRemove(0);
epicsEventSignal(startStopEvent);
}
int scanOnceSetQueueSize(int size)
{
onceQueueSize = size;
return 0;
}
int scanOnceQueueStatus(const int reset, scanOnceQueueStats *result)
{
int ret;
if (!onceQ) return -1;
if (result) {
result->size = epicsRingBytesSize(onceQ) / sizeof(onceEntry);
result->numUsed = epicsRingBytesUsedBytes(onceQ) / sizeof(onceEntry);
result->maxUsed = epicsRingBytesHighWaterMark(onceQ) / sizeof(onceEntry);
result->numOverflow = epicsAtomicGetIntT(&onceQOverruns);
ret = 0;
} else {
ret = -2;
}
if (reset) {
epicsRingBytesResetHighWaterMark(onceQ);
}
return ret;
}
void scanOnceQueueShow(const int reset)
{
scanOnceQueueStats stats;
if (scanOnceQueueStatus(reset, &stats) == -1) {
fprintf(stderr, "scanOnce system not initialized, yet. Please run "
"iocInit before using this command.\n");
} else {
double qusage = 100.0 * stats.numUsed / stats.size;
printf("PRIORITY HIGH-WATER MARK ITEMS IN Q Q SIZE %% USED Q OVERFLOWS\n");
printf("%8s %15d %10d %6d %6.1f %11d\n", "scanOnce", stats.maxUsed,
stats.numUsed, stats.size, qusage,
epicsAtomicGetIntT(&onceQOverruns));
}
}
static void initOnce(void)
{
if ((onceQ = epicsRingBytesLockedCreate(sizeof(onceEntry)*onceQueueSize)) == NULL) {
cantProceed("initOnce: Ring buffer create failed\n");
}
if(!onceSem)
onceSem = epicsEventMustCreate(epicsEventEmpty);
onceTaskId = epicsThreadCreate("scanOnce",
epicsThreadPriorityScanLow + nPeriodic,
epicsThreadGetStackSize(epicsThreadStackBig), onceTask, 0);
epicsEventWait(startStopEvent);
}
static void periodicTask(void *arg)
{
periodic_scan_list *ppsl = (periodic_scan_list *)arg;
epicsTimeStamp next, reported;
unsigned int overruns = 0;
double report_delay = OVERRUN_REPORT_DELAY;
double overtime = 0.0;
double over_min = 0.0;
double over_max = 0.0;
const double penalty = (ppsl->period >= 2) ? 1 : (ppsl->period / 2);
taskwdInsert(0, NULL, NULL);
epicsEventSignal(startStopEvent);
epicsTimeGetMonotonic(&next);
reported = next;
while (ppsl->scanCtl != ctlExit) {
double delay;
epicsTimeStamp now;
if (ppsl->scanCtl == ctlRun)
scanList(&ppsl->scan_list);
epicsTimeAddSeconds(&next, ppsl->period);
epicsTimeGetMonotonic(&now);
delay = epicsTimeDiffInSeconds(&next, &now);
if (delay <= 0.0) {
if (overtime == 0.0) {
overtime = over_min = over_max = -delay;
}
else {
overtime -= delay;
if (over_min + delay > 0)
over_min = -delay;
if (over_max + delay < 0)
over_max = -delay;
}
delay = penalty;
ppsl->overruns++;
next = now;
epicsTimeAddSeconds(&next, delay);
if (++overruns >= 10 &&
epicsTimeDiffInSeconds(&now, &reported) > report_delay) {
errlogPrintf("\ndbScan warning from '%s' scan thread:\n"
"\tScan processing averages %.3f seconds (%.3f .. %.3f).\n"
"\tOver-runs have now happened %u times in a row.\n"
"\tTo fix this, move some records to a slower scan rate.\n",
ppsl->name, ppsl->period + overtime / overruns,
ppsl->period + over_min, ppsl->period + over_max, overruns);
reported = now;
if (report_delay < (OVERRUN_REPORT_MAX / 2))
report_delay *= 2;
else
report_delay = OVERRUN_REPORT_MAX;
}
}
else {
overruns = 0;
report_delay = OVERRUN_REPORT_DELAY;
overtime = 0.0;
}
epicsEventWaitWithTimeout(ppsl->loopEvent, delay);
}
taskwdRemove(0);
epicsEventSignal(startStopEvent);
}
static void initPeriodic(void)
{
dbMenu *pmenu = dbFindMenu(pdbbase, "menuScan");
double quantum = epicsThreadSleepQuantum();
int i;
if (!pmenu) {
errlogPrintf("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++) {
periodic_scan_list *ppsl = dbCalloc(1, sizeof(periodic_scan_list));
const char *choice = pmenu->papChoiceValue[i + SCAN_1ST_PERIODIC];
double number;
char *unit;
int status = epicsParseDouble(choice, &number, &unit);
if (status || number <= 0) {
errlogPrintf("initPeriodic: Bad menuScan choice '%s'\n", choice);
}
else if (!*unit ||
!epicsStrCaseCmp(unit, "second") ||
!epicsStrCaseCmp(unit, "seconds")) {
ppsl->period = number;
}
else if (!epicsStrCaseCmp(unit, "minute") ||
!epicsStrCaseCmp(unit, "minutes")) {
ppsl->period = number * 60;
}
else if (!epicsStrCaseCmp(unit, "hour") ||
!epicsStrCaseCmp(unit, "hours")) {
ppsl->period = number * 60 * 60;
}
else if (!epicsStrCaseCmp(unit, "Hz") ||
!epicsStrCaseCmp(unit, "Hertz")) {
ppsl->period = 1 / number;
}
else {
errlogPrintf("initPeriodic: Bad menuScan choice '%s'\n", choice);
}
if (ppsl->period == 0) {
free(ppsl);
continue;
}
ppsl->scan_list.lock = epicsMutexMustCreate();
ellInit(&ppsl->scan_list.list);
ppsl->name = choice;
ppsl->scanCtl = ctlPause;
ppsl->loopEvent = epicsEventMustCreate(epicsEventEmpty);
number = ppsl->period / quantum;
if ((ppsl->period < 2 * quantum) ||
(number / floor(number) > 1.1)) {
errlogPrintf("initPeriodic: Scan rate '%s' is not achievable.\n",
choice);
}
papPeriodic[i] = ppsl;
}
}
static void deletePeriodic(void)
{
int i;
for (i = 0; i < nPeriodic; i++) {
periodic_scan_list *ppsl = papPeriodic[i];
if (!ppsl) continue;
ellFree(&ppsl->scan_list.list);
epicsEventDestroy(ppsl->loopEvent);
epicsMutexDestroy(ppsl->scan_list.lock);
free(ppsl);
}
free(papPeriodic);
papPeriodic = NULL;
}
static void spawnPeriodic(int ind)
{
periodic_scan_list *ppsl = papPeriodic[ind];
char taskName[20];
if (!ppsl) return;
sprintf(taskName, "scan-%g", ppsl->period);
periodicTaskId[ind] = epicsThreadCreate(
taskName, epicsThreadPriorityScanLow + ind,
epicsThreadGetStackSize(epicsThreadStackBig),
periodicTask, (void *)ppsl);
epicsEventWait(startStopEvent);
}
static void ioscanCallback(epicsCallback *pcallback)
{
ioscan_head *piosh;
int prio;
callbackGetUser(piosh, pcallback);
callbackGetPriority(prio, pcallback);
scanList(&piosh->iosl[prio].scan_list);
if (piosh->cb)
piosh->cb(piosh->arg, piosh, prio);
}
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)
return;
printf("%s\n", message);
while (pse) {
printf(" %-28s\n", pse->precord->name);
epicsMutexMustLock(psl->lock);
if (pse->pscan_list != psl) {
epicsMutexUnlock(psl->lock);
printf(" Scan list changed while printing, try again.\n");
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;
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 *)ellLast(&psl->list);
while (ptemp) {
if (ptemp->precord->phas <= precord->phas) {
ellInsert(&psl->list, &ptemp->node, &pse->node);
break;
}
ptemp = (scan_element *)ellPrevious(&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);
}