/*************************************************************************\ * Copyright (c) 2009 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. * SPDX-License-Identifier: EPICS * EPICS BASE is distributed subject to a Software License Agreement found * in file LICENSE that is included with this distribution. \*************************************************************************/ /* * Original Author: Marty Kraimer * Date: 06-01-91 */ #include #include #include #include #include #include #include #include "dbDefs.h" #include "ellLib.h" #include "envDefs.h" #include "epicsExit.h" #include "epicsGeneralTime.h" #include "epicsPrint.h" #include "epicsSignal.h" #include "epicsThread.h" #include "errMdef.h" #include "iocsh.h" #include "taskwd.h" #include "caeventmask.h" #include "epicsExport.h" /* defines epicsExportSharedSymbols */ #include "alarm.h" #include "asDbLib.h" #include "callback.h" #include "dbAccess.h" #include "db_access_routines.h" #include "dbAddr.h" #include "dbBase.h" #include "dbBkpt.h" #include "dbCa.h" #include "dbChannel.h" #include "dbCommon.h" #include "dbFldTypes.h" #include "dbLock.h" #include "dbNotify.h" #include "dbScan.h" #include "dbServer.h" #include "dbStaticLib.h" #include "dbStaticPvt.h" #include "devSup.h" #include "drvSup.h" #include "epicsRelease.h" #include "initHooks.h" #include "iocInit.h" #include "link.h" #include "menuConvert.h" #include "menuPini.h" #include "recGbl.h" #include "recSup.h" #include "registryDeviceSupport.h" #include "registryDriverSupport.h" #include "registryJLinks.h" #include "registryRecordType.h" static enum iocStateEnum iocState = iocVoid; static enum { buildServers, buildIsolated } iocBuildMode; /* define forward references*/ static int checkDatabase(dbBase *pdbbase); static void checkGeneralTime(void); static void initDrvSup(void); static void initRecSup(void); static void initDevSup(void); static void finishDevSup(void); static void initDatabase(void); static void initialProcess(void); static void exitDatabase(void *dummy); /* * Iterate through all record instances (but not aliases), * calling a function for each one. */ typedef void (*recIterFunc)(dbRecordType *rtyp, dbCommon *prec, void *user); static void iterateRecords(recIterFunc func, void *user); int dbThreadRealtimeLock = 1; epicsExportAddress(int, dbThreadRealtimeLock); enum iocStateEnum getIocState(void) { return iocState; } /* * Initialize EPICS on the IOC. */ int iocInit(void) { return iocBuild() || iocRun(); } static int iocBuild_1(void) { if (iocState != iocVoid) { errlogPrintf("iocBuild: IOC can only be initialized from uninitialized or stopped state\n"); return -1; } errlogInit(0); initHookAnnounce(initHookAtIocBuild); if (!epicsThreadIsOkToBlock()) { epicsThreadSetOkToBlock(1); } errlogPrintf("Starting iocInit\n"); if (checkDatabase(pdbbase)) { errlogPrintf("iocBuild: Aborting, bad database definition (DBD)!\n"); return -1; } epicsSignalInstallSigHupIgnore(); initHookAnnounce(initHookAtBeginning); coreRelease(); iocState = iocBuilding; checkGeneralTime(); taskwdInit(); callbackInit(); initHookAnnounce(initHookAfterCallbackInit); return 0; } static void prepareLinks(dbRecordType *rtyp, dbCommon *prec, void *junk) { dbInitRecordLinks(rtyp, prec); } static int iocBuild_2(void) { initHookAnnounce(initHookAfterCaLinkInit); initDrvSup(); initHookAnnounce(initHookAfterInitDrvSup); initRecSup(); initHookAnnounce(initHookAfterInitRecSup); initDevSup(); initHookAnnounce(initHookAfterInitDevSup); /* used by autosave pass 0 */ iterateRecords(prepareLinks, NULL); dbLockInitRecords(pdbbase); initDatabase(); dbBkptInit(); initHookAnnounce(initHookAfterInitDatabase); /* used by autosave pass 1 */ finishDevSup(); initHookAnnounce(initHookAfterFinishDevSup); scanInit(); if (asInit()) { errlogPrintf("iocBuild: asInit Failed.\n"); return -1; } dbProcessNotifyInit(); epicsThreadSleep(.5); initHookAnnounce(initHookAfterScanInit); initialProcess(); initHookAnnounce(initHookAfterInitialProcess); return 0; } static int iocBuild_3(void) { initHookAnnounce(initHookAfterCaServerInit); iocState = iocBuilt; initHookAnnounce(initHookAfterIocBuilt); return 0; } int iocBuild(void) { int status; status = iocBuild_1(); if (status) return status; dbCaLinkInit(); status = iocBuild_2(); if (status) return status; dbInitServers(); status = iocBuild_3(); if (dbThreadRealtimeLock) epicsThreadRealtimeLock(); if (!status) iocBuildMode = buildServers; return status; } int iocBuildIsolated(void) { int status; status = iocBuild_1(); if (status) return status; dbCaLinkInitIsolated(); status = iocBuild_2(); if (status) return status; status = iocBuild_3(); if (!status) iocBuildMode = buildIsolated; return status; } int iocRun(void) { if (iocState != iocPaused && iocState != iocBuilt) { errlogPrintf("iocRun: IOC not paused\n"); return -1; } initHookAnnounce(initHookAtIocRun); /* Enable scan tasks and some driver support functions. */ scanRun(); dbCaRun(); initHookAnnounce(initHookAfterDatabaseRunning); if (iocState == iocBuilt) initHookAnnounce(initHookAfterInterruptAccept); if (iocBuildMode == buildServers) { dbRunServers(); initHookAnnounce(initHookAfterCaServerRunning); } if (iocState == iocBuilt) initHookAnnounce(initHookAtEnd); errlogPrintf("iocRun: %s\n", iocState == iocBuilt ? "All initialization complete" : "IOC restarted"); iocState = iocRunning; initHookAnnounce(initHookAfterIocRunning); return 0; } int iocPause(void) { if (iocState != iocRunning) { errlogPrintf("iocPause: IOC not running\n"); return -1; } initHookAnnounce(initHookAtIocPause); if (iocBuildMode == buildServers) { dbPauseServers(); initHookAnnounce(initHookAfterCaServerPaused); } dbCaPause(); scanPause(); initHookAnnounce(initHookAfterDatabasePaused); iocState = iocPaused; errlogPrintf("iocPause: IOC suspended\n"); initHookAnnounce(initHookAfterIocPaused); return 0; } /* * Database sanity checks * * This is not an attempt to sanity-check the whole .dbd file, only * two menus normally get modified by users: menuConvert and menuScan. * * The menuConvert checks were added to flag problems with IOCs * converted from 3.13.x, where the SLOPE choice didn't exist. * * The menuScan checks make sure the user didn't fiddle too much * when creating new periodic scan choices. */ static int checkDatabase(dbBase *pdbbase) { const dbMenu *pMenu; if (!pdbbase) { errlogPrintf("checkDatabase: No database definitions loaded.\n"); return -1; } pMenu = dbFindMenu(pdbbase, "menuConvert"); if (!pMenu) { errlogPrintf("checkDatabase: menuConvert not defined.\n"); return -1; } if (pMenu->nChoice <= menuConvertLINEAR) { errlogPrintf("checkDatabase: menuConvert has too few choices.\n"); return -1; } if (strcmp(pMenu->papChoiceName[menuConvertNO_CONVERSION], "menuConvertNO_CONVERSION")) { errlogPrintf("checkDatabase: menuConvertNO_CONVERSION doesn't match.\n"); return -1; } if (strcmp(pMenu->papChoiceName[menuConvertSLOPE], "menuConvertSLOPE")) { errlogPrintf("checkDatabase: menuConvertSLOPE doesn't match.\n"); return -1; } if (strcmp(pMenu->papChoiceName[menuConvertLINEAR], "menuConvertLINEAR")) { errlogPrintf("checkDatabase: menuConvertLINEAR doesn't match.\n"); return -1; } pMenu = dbFindMenu(pdbbase, "menuScan"); if (!pMenu) { errlogPrintf("checkDatabase: menuScan not defined.\n"); return -1; } if (pMenu->nChoice <= menuScanI_O_Intr) { errlogPrintf("checkDatabase: menuScan has too few choices.\n"); return -1; } if (strcmp(pMenu->papChoiceName[menuScanPassive], "menuScanPassive")) { errlogPrintf("checkDatabase: menuScanPassive doesn't match.\n"); return -1; } if (strcmp(pMenu->papChoiceName[menuScanEvent], "menuScanEvent")) { errlogPrintf("checkDatabase: menuScanEvent doesn't match.\n"); return -1; } if (strcmp(pMenu->papChoiceName[menuScanI_O_Intr], "menuScanI_O_Intr")) { errlogPrintf("checkDatabase: menuScanI_O_Intr doesn't match.\n"); return -1; } if (pMenu->nChoice <= SCAN_1ST_PERIODIC) { errlogPrintf("checkDatabase: menuScan has no periodic choices.\n"); return -1; } return 0; } static void checkGeneralTime(void) { epicsTimeStamp ts; epicsTimeGetCurrent(&ts); if (ts.secPastEpoch < 2*24*60*60) { static const char * const tsfmt = "%Y-%m-%d %H:%M:%S.%09f"; char buff[40]; epicsTimeToStrftime(buff, sizeof(buff), tsfmt, &ts); errlogPrintf("iocInit: Time provider has not yet synchronized.\n"); } epicsTimeGetEvent(&ts, 1); /* Prime gtPvt.lastEventProvider for ISRs */ } static void initDrvSup(void) /* Locate all driver support entry tables */ { drvSup *pdrvSup; for (pdrvSup = (drvSup *)ellFirst(&pdbbase->drvList); pdrvSup; pdrvSup = (drvSup *)ellNext(&pdrvSup->node)) { struct drvet *pdrvet = registryDriverSupportFind(pdrvSup->name); if (!pdrvet) { errlogPrintf("iocInit: driver %s not found\n", pdrvSup->name); continue; } pdrvSup->pdrvet = pdrvet; if (pdrvet->init) pdrvet->init(); } } static void initRecSup(void) { dbRecordType *pdbRecordType; for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { recordTypeLocation *precordTypeLocation = registryRecordTypeFind(pdbRecordType->name); rset *prset; if (!precordTypeLocation) { errlogPrintf("iocInit record support for %s not found\n", pdbRecordType->name); continue; } prset = precordTypeLocation->prset; pdbRecordType->prset = prset; if (prset->init) { prset->init(); } } } static void initDevSup(void) { dbRecordType *pdbRecordType; for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { devSup *pdevSup; for (pdevSup = (devSup *)ellFirst(&pdbRecordType->devList); pdevSup; pdevSup = (devSup *)ellNext(&pdevSup->node)) { dset *pdset = registryDeviceSupportFind(pdevSup->name); if (!pdset) { errlogPrintf("device support %s not found\n",pdevSup->name); continue; } dbInitDevSup(pdevSup, pdset); /* Calls pdset->init(0) */ } } } static void finishDevSup(void) { dbRecordType *pdbRecordType; for (pdbRecordType = (dbRecordType *)ellFirst(&pdbbase->recordTypeList); pdbRecordType; pdbRecordType = (dbRecordType *)ellNext(&pdbRecordType->node)) { devSup *pdevSup; for (pdevSup = (devSup *)ellFirst(&pdbRecordType->devList); pdevSup; pdevSup = (devSup *)ellNext(&pdevSup->node)) { dset *pdset = pdevSup->pdset; if (pdset && pdset->init) pdset->init(1); } } } static void iterateRecords(recIterFunc func, void *user) { 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; func(pdbRecordType, precord, user); } } return; } static void doInitRecord0(dbRecordType *pdbRecordType, dbCommon *precord, void *user) { rset *prset = pdbRecordType->prset; devSup *pdevSup; if (!prset) return; /* unlikely */ precord->rset = prset; precord->mlok = epicsMutexMustCreate(); ellInit(&precord->mlis); /* Reset the process active field */ precord->pact = FALSE; /* Initial UDF severity */ if (precord->udf && precord->stat == UDF_ALARM) precord->sevr = precord->udfs; /* Init DSET NOTE that result may be NULL */ pdevSup = dbDTYPtoDevSup(pdbRecordType, precord->dtyp); precord->dset = pdevSup ? pdevSup->pdset : NULL; if (prset->init_record) prset->init_record(precord, 0); } static void doResolveLinks(dbRecordType *pdbRecordType, dbCommon *precord, void *user) { dbFldDes **papFldDes = pdbRecordType->papFldDes; short *link_ind = pdbRecordType->link_ind; int j; /* For all the links in the record type... */ for (j = 0; j < pdbRecordType->no_links; j++) { dbFldDes *pdbFldDes = papFldDes[link_ind[j]]; DBLINK *plink = (DBLINK*)((char*)precord + pdbFldDes->offset); if (ellCount(&precord->rdes->devList) > 0 && pdbFldDes->isDevLink) { devSup *pdevSup = dbDTYPtoDevSup(pdbRecordType, precord->dtyp); if (pdevSup) { struct dsxt *pdsxt = pdevSup->pdsxt; if (pdsxt && pdsxt->add_record) { pdsxt->add_record(precord); } } } dbInitLink(plink, pdbFldDes->field_type); } } static void doInitRecord1(dbRecordType *pdbRecordType, dbCommon *precord, void *user) { rset *prset = pdbRecordType->prset; if (!prset) return; /* unlikely */ if (prset->init_record) prset->init_record(precord, 1); } static void initDatabase(void) { dbChannelInit(); iterateRecords(doInitRecord0, NULL); iterateRecords(doResolveLinks, NULL); iterateRecords(doInitRecord1, NULL); epicsAtExit(exitDatabase, NULL); return; } /* * Process database records at initialization ordered by phase * if their pini (process at init) field is set. */ typedef struct { int this; int next; epicsEnum16 pini; } phaseData_t; static void doRecordPini(dbRecordType *rtype, dbCommon *precord, void *user) { phaseData_t *pphase = (phaseData_t *)user; int phas; if (precord->pini != pphase->pini) return; phas = precord->phas; if (phas == pphase->this) { dbScanLock(precord); dbProcess(precord); dbScanUnlock(precord); } else if (phas > pphase->this && phas < pphase->next) pphase->next = phas; } static void piniProcess(int pini) { phaseData_t phase; phase.next = MIN_PHASE; phase.pini = pini; /* This scans through the whole database as many times as needed. * During the first pass it is unlikely to find any records with * PHAS = MIN_PHASE, but during each iteration it looks for the * phase value of the next pass to run. Note that PHAS fields can * be changed at runtime, so we have to look for the lowest value * of PHAS each time. */ do { phase.this = phase.next; phase.next = MAX_PHASE + 1; iterateRecords(doRecordPini, &phase); } while (phase.next != MAX_PHASE + 1); } static void piniProcessHook(initHookState state) { switch (state) { case initHookAtIocRun: piniProcess(menuPiniRUN); break; case initHookAfterIocRunning: piniProcess(menuPiniRUNNING); break; case initHookAtIocPause: piniProcess(menuPiniPAUSE); break; case initHookAfterIocPaused: piniProcess(menuPiniPAUSED); break; default: break; } } static void initialProcess(void) { initHookRegister(piniProcessHook); piniProcess(menuPiniYES); } /* * set DB_LINK and CA_LINK to PV_LINK * Delete record scans */ static void doCloseLinks(dbRecordType *pdbRecordType, dbCommon *precord, void *user) { devSup *pdevSup; struct dsxt *pdsxt; int j; int locked = 0; for (j = 0; j < pdbRecordType->no_links; j++) { dbFldDes *pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->link_ind[j]]; DBLINK *plink = (DBLINK *)((char *)precord + pdbFldDes->offset); if (plink->type == CA_LINK || plink->type == JSON_LINK || (plink->type == DB_LINK && iocBuildMode == buildIsolated)) { if (!locked) { dbScanLock(precord); locked = 1; } dbRemoveLink(NULL, plink); } } if (precord->dset && (pdevSup = dbDSETtoDevSup(pdbRecordType, precord->dset)) && (pdsxt = pdevSup->pdsxt) && pdsxt->del_record) { if (!locked) { dbScanLock(precord); locked = 1; } scanDelete(precord); /* Being consistent... */ pdsxt->del_record(precord); } if (locked) { precord->pact = TRUE; dbScanUnlock(precord); } } static void doFreeRecord(dbRecordType *pdbRecordType, dbCommon *precord, void *user) { int j; for (j = 0; j < pdbRecordType->no_links; j++) { dbFldDes *pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->link_ind[j]]; DBLINK *plink = (DBLINK *)((char *)precord + pdbFldDes->offset); dbFreeLinkContents(plink); } epicsMutexDestroy(precord->mlok); free(precord->ppnr); /* may be allocated in dbNotify.c */ } int iocShutdown(void) { if (iocState == iocVoid) return 0; initHookAnnounce(initHookAtShutdown); iterateRecords(doCloseLinks, NULL); initHookAnnounce(initHookAfterCloseLinks); /* stop and "join" threads */ scanStop(); initHookAnnounce(initHookAfterStopScan); callbackStop(); initHookAnnounce(initHookAfterStopCallback); if (iocBuildMode != buildIsolated) { dbStopServers(); } dbCaShutdown(); /* must be before dbFreeRecord and dbChannelExit */ initHookAnnounce(initHookAfterStopLinks); if (iocBuildMode == buildIsolated) { /* free resources */ initHookAnnounce(initHookBeforeFree); scanCleanup(); callbackCleanup(); iterateRecords(doFreeRecord, NULL); dbLockCleanupRecords(pdbbase); asShutdown(); dbChannelExit(); dbProcessNotifyExit(); iocshFree(); } iocState = iocVoid; iocBuildMode = buildServers; initHookAnnounce(initHookAfterShutdown); return 0; } static void exitDatabase(void *dummy) { iocShutdown(); }