Files
pcas/src/db/dbScan.c
Andrew Johnson c9bc70bd6a Added iocBuild, iocPause and iocRun commands.
These are mainly for redundant IOCs, but may be useful elsewhere.
iocBuild prepares the IOC and freezes it just before it goes live.
A subsequent iocRun kicks it into life, or restarts it when paused.
iocPause freezes all scan tasks and disconnects the server.
2008-07-09 21:31:56 +00:00

753 lines
21 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) 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 <epicsStdlib.h>
#include <stddef.h>
#include <stdio.h>
#include <string.h>
#include <limits.h>
#include <math.h>
#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;
scanOnce((dbCommon *)&exitOnce);
epicsEventWait(startStopEvent);
for (i = 0; i < nPeriodic; i++) {
papPeriodic[i]->scanCtl = ctlExit;
epicsEventSignal(papPeriodic[i]->loopEvent);
epicsEventWait(startStopEvent);
}
}
long scanInit()
{
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;
scanCtl = ctlRun;
for (i = 0; i < nPeriodic; i++)
papPeriodic[i]->scanCtl = ctlRun;
}
void scanPause(void)
{
int i;
scanCtl = ctlPause;
for (i = 0; i < nPeriodic; i++)
papPeriodic[i]->scanCtl = ctlPause;
}
void scanAdd(struct dbCommon *precord)
{
int scan;
/* get the list on which this record belongs */
scan = precord->scan;
if (scan == SCAN_PASSIVE) return;
if (scan < 0 || scan >= nPeriodic + SCAN_1ST_PERIODIC) {
recGblRecordError(-1, (void *)precord,
"scanAdd detected illegal SCAN value");
} else if (scan == SCAN_EVENT) {
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 = SCAN_PASSIVE;
return;
}
prio = precord->prio;
if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) {
recGblRecordError(-1, (void *)precord,
"scanAdd: illegal prio field");
precord->scan = SCAN_PASSIVE;
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 == SCAN_IO_EVENT) {
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 = SCAN_PASSIVE;
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 = SCAN_PASSIVE;
return;
}
if (get_ioint_info(0, precord, &piosl)) {
precord->scan = SCAN_PASSIVE;
return;
}
if (piosl == NULL) {
recGblRecordError(-1, (void *)precord,
"scanAdd: I/O Intr not valid");
precord->scan = SCAN_PASSIVE;
return;
}
prio = precord->prio;
if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) {
recGblRecordError(-1, (void *)precord,
"scanAdd: illegal prio field");
precord->scan = SCAN_PASSIVE;
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 == SCAN_PASSIVE) return;
if (scan < 0 || scan >= nPeriodic + SCAN_1ST_PERIODIC) {
recGblRecordError(-1, (void *)precord,
"scanDelete detected illegal SCAN value");
} else if (scan == SCAN_EVENT) {
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 = SCAN_PASSIVE;
return;
}
prio = precord->prio;
if (prio < 0 || prio >= NUM_CALLBACK_PRIORITIES) {
recGblRecordError(-1, (void *)precord,
"scanAdd: illegal prio field");
precord->scan = SCAN_PASSIVE;
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 == SCAN_IO_EVENT) {
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 && evnt<event_number) continue;
if (event_number && evnt>event_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() /* 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; prio<NUM_CALLBACK_PRIORITIES; prio++) {
pesl = pevent_list[prio][event];
if (!pesl) continue;
if (ellCount(&pesl->scan_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()
{
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] == 0) 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);
if (precord->spvt==NULL) {
epicsMutexUnlock(psl->lock);
return;
}
pse = precord->spvt;
if (pse == NULL || pse->pscan_list != psl) {
epicsMutexUnlock(psl->lock);
errlogPrintf("deleteFromList failed");
return;
}
pse->pscan_list = NULL;
ellDelete(&psl->list, (void *)pse);
psl->modified = TRUE;
epicsMutexUnlock(psl->lock);
}