370 likes | 624 Views
EPICS record/device/driver Support. kasemir@lanl.gov, many slides copied from johill@lanl.gov. Interfacing Hardware. General Idea. EPICS. Software. IOC Core: Db, CA, …. Record Support. “Driver”. Device Support. Hardware. Driver Support. Hardware. Where to extend….
E N D
EPICSrecord/device/driverSupport kasemir@lanl.gov,many slides copied from johill@lanl.gov
Interfacing Hardware General Idea EPICS Software IOC Core: Db, CA, … Record Support “Driver” Device Support Hardware Driver Support Hardware
Where to extend… • Common Case: New Hardware (I/O Board,..) • Driver: Any low-level code to talk to the hardware.Might not have any knowledge of EPICS • Device: EPICS-specific glue code between driver and (subset of) records • Sometimes: Specialized Record • Copy of existing record w/ slight change • Seldom: New Record Type • Can task be handled by combination of existing records, maybe w/ help of SNL? • But sometime it is fun to introduce a new record(NY).
Driver/Device/Record Support • Read the “IOC Application Developer's Guide” before even thinking about doing this! • Common Idea: • Describe the new driver/device/record via DBD (Database Description File, ASCII) • Implement the functionality, providing a function table(DRVET,DSET or RSET) specific to driver/device/record support (init(), report(), do_something(), …) • Link/load the binaries which export a function table • During startup, iocCore will parse the DBD, locate the function table and invoke the appropriate functions • Well-defined interfaces for adding new support, minimal recompilation.
The .dbd file entry • .dbd file includes • Record type definition recordtype(xxx){include "dbCommon.dbd" field(VAL,DBF_DOUBLE) { prompt("Current EGU Value") asl(ASL0) pp(TRUE) } … } name of RSET for the record xxx have to be xxxRSET • Device support link to a record type device(recType,addrType,dset,"name") • Driver declaration driver(drvet)
The .dbd file entry • The IOC discovers what device supports are present from entries in the .dbd file device(recType,addrType,dset,"name") • addrType is one of AB_IO BITBUS_IO BBGPIB_IO CAMAC_IO GPIB_IO INST_IO RF_IO VME_IO VXI_IO • dset is the ‘C’ symbol name for the Device Support Entry Table (DSET) • By convention the dset name indicates the record type and hardware interface • This is how record support and the database code call device support routines • For example device(ai,INST_IO,devAiSymb,"vxWorks variable") device(bo,VME_IO,devBoXy240,"Xycom XY240")
Driver Support • Drivers can be complicated: • Bus-level access, critical timing, interrupts, semaphores, threads, deadlocks, fail-safe, OS-specific (vxWorks, Linux, Win32), … • Typical collection of routines:Check, report, init, read, write, setup_trigger(callback),… • “EPICS part”: Optional & Trivial!
Driver Support Entry Table /* EPICS Base include file <drvSup.h> */ typedef long (*DRVSUPFUN) (); struct drvet { long number; /*number of support routines*/DRVSUPFUN report; /*print report*/DRVSUPFUN init; /*init the driver */ }; • Any routine pointer can be NULL
DRVET Example /* xy.c */ #include<drvSup.h> static long xy_report() { printf(“XY Driver Info:\n); … } static long xy_init() { if (xy_check()) { … } struct drvet drvXy = {2, xy_report, xy_init };
Registering Driver w/ EPICS • EPICS DBD File Entry • driver(drvXy) • Results: • EPICS iocInit will locate “drvXy” in symbol table, call the registered “init” routine (unless NULL) • EPICS dbior will invoke “report” routine (unless NULL)
Good Practice • Wrong: Assume e.g. two XY board, one at base address 0x1234 and one at 0x4567, all hard-coded in driver. • Best: Provide “configure” routine, callable from e.g. vxWorks startup file before invoking iocInit: # EPICS records that refer to XY #0 will # access board at base addr. 0xfe12 # in mode 15 xy_config(0, 0xfe12, 15)
Device Support • Glue between record and driver is highly specific to the resp. record: • AI record, DTYP=“XY”, INP=“#C0 S5”:Device support has to call driver for “XY” card #0 and get signal #5 into the record’s RVAL field • Dev.sup routines common to all records: • Report: Show info • Init: Called once • Init_Record: Called for each record • Get I/O Interrupt Info: Used w/ SCAN=“I/O Intr” Most are optional, but check specific record type.
Device Support Entry Table /* Defined in devSup.h */ struct dset { long number; /* number of support routines */ DEVSUPFUN report; /* print report*/ DEVSUPFUN init; /* init support*/ DEVSUPFUN init_record; /* init particular record */ DEVSUPFUN get_ioint_info; /* get I/O Intr. Info */ /* Rest specific to record, e.g. BI: DEVSUPFUN read_bi; Result: (0,2,error) 0 -> raw value stored in RVAL, convert to VAL 2 -> value already stored in VAL, don’t convert */ }
Registering Device Support • EPICS DBD File: • device(ai,INST_IO,devAiXX,“My XX") • Result: iocCore … • now allows DTYP=“My XX” for ai records • will locate DSET “devAiXX” in symbol table,call init(), then init_record() for each record, record will call read() whenever record gets processed, …
Before Implementing Dev.Sup • Understand how to use the driver • Read • “Application Developer Guide” • Source for specific record type XX, understand how record calls its device support • Examples in EPICS base sources: base/src/dev/softDev/devXXSoft.c
Common report initialization initialize instance attach to device interrupt AI-Specific read ai device value linear conversion(RVAL->VAL) Device Support for AI Record
AI Device Support Type Initialization long aiDevInit (unsigned pass) • common to all record types • device specific initialization • pass = 0, prior to initializing each record during "iocInit()“ • Check hardware, …but note that records are not ready to handle data • pass = 1, after initializing each record during "iocInit()“ • Activate triggers, …
AI Device Report long aiDevReport (struct aiRecord * pai, int level); • common to all records, but gets passed pointer to specific record type • called once for every record instance when the user types "dbior <level>" • device status to "stdout" from this routine • Idea: detail increases with increasing "level"
AI Device Initialization for Record long aiDevInitInstance(struct aiRecord *pai) • Called from within “iocInit()” once for each record attached to device • Typical • Parse & check device address (pai->inp) • Store device-data, e.g. the parsed signal #, driver handles, … in DPVT: pvt = (X *) calloc(1, sizeof(X); pvt->signal = signal_this_record_wants; pvt->drv = magic_handle_we_got_from_driver; pai->dpvt = (void *) pvt;
Read Signal Value long aiDevRead_(struct aiRecord * pai){ long rval; if (device OK){ rval=pDevMemoryMap-> aiRegister[pai->dpvt->signal]; pai->rval = rval;} else recGblSetSevr(pai, READ_ALARM, INVALID_ALARM); }
AI Linear Conversion long aiDevLinearConv ( struct aiRecord *pai, int after); • Setup the slope and offset for the conversion to engineering units if (!after) return S_XXXX_OK; /* A 12 bit DAC is assumed here */ pai->eslo = (pai->eguf - pai->egul)/0x0FFF; pai->roff = 0;/* roff could be different for device w/ e.g. +-5V, half scale = 0V */
From convert() in aiRecord.c double val; val = pai->rval + pai->roff; /* * adjust with slope/offset * if linear convert is used */ if ( pai->aslo != 0.0 ) val *= pai->aslo; if( pai->aoff != 0.0 ) val+= pai->aoff; if(pai->linr == menuConvertLINEAR) val = (val * pai->eslo) + pai->eoff;
Device supports interrupts, want to use SCAN=“I/O Intr” higher scan rate scan synchronized with device Difficulty Interrupts: interrupt level, most OS routines prohibited Record processing: task level IOSCANPVT EPICS Core Helper for processing records in response to interrupt Advanced: Interrupts
IOSCANPVT • Initialize & keep one IOSCANPVT per distinct interrupt that the hardware can generate /* Record’s DPVT points to struct X* which contains IOCSCANPVT ioscanpvt */X = (X *) rec->dpvt;scanIoInit(&X->ioscanpvt); • Each Interrupt Occurrence (ISR): scanIoRequest(X->ioscanpvt); • safe to call from ISR, but don’t call scanIoRequest() until after database init (“iocInit()”) completes(extern volatile int interruptAccept;)
Provide IO Interrupt Info long aiDevGetIoIntInfo ( int cmd, struct aiRecord *pai, IOSCANPVT *ppvt); • associates interrupt source with record *ppvt = X->ioscanpvt; • cmd==0 - insert into IO interrupt scan • cmd==1 - remove from IO Interrupt scan
Asynchronous Devices • read/write routine sets “PACT” true and returns zero for success. • asynchronous IO completion callback completes record processing • don’t process a record from within an ISR
Example Asynchronous Read long devXxxRead (struct aiRecord *pai) { if (pai->pact) return S_devXxx_OK; /* zero */ pai->pact = TRUE devXxxBeginAsyncIO(pai->dpvt); return S_devXxx_OK; }
Example Asynchronous Read Completion void devXxxAsyncIOCompletion(struct aiRecord *pai, long ioStatus) { struct rset *prset = (struct rset *) pai->rset; dbScanLock(pai); if (ioStatus != S_devXxx_OK) { recGblSetSevr(pai, READ_ALARM, INVALID_ALARM); } (*prset->process)(pai); dbScanUnlock(pai); }
Record Support • Similar to device & driver support: • Certain routines need to be implemented • Binary exports a “Record support entry table” which contains those routines • DBD File to describe record and each field (name, data type, maybe menu of enumerated values, …)
Record DBD File • Need to read “IOC Application Developer's Guide” to understand full DBD syntax! • Use xxxRecord created by makeBaseApp as example recordtype(xxx){ # Each record needs to start w/ the common fields! include "dbCommon.dbd" field(VAL,DBF_DOUBLE) { prompt("Current EGU Value") asl(ASL0) pp(TRUE) } … }
Record support entry table • Initialization:General and per-record instance • Process routine:Implements functionality of record. Often • calls device support specific to this record type • checks for alarms • posts monitors • processes forward links • Utility routines that allow iocCore to properly read, write & display fields of this record
Record support entry table… • Record Implementation must export struct rset <xxxRecord>RSET; struct rset /* record support entry table */ { long number; /* number of support routine */ RECSUPFUN report; /* print report */ RECSUPFUN init; /* init support */ RECSUPFUN init_record; /* init record */ RECSUPFUN process; /* process record */ RECSUPFUN special; /* special processing */ RECSUPFUN get_value; /* OBSOLETE: Just leave NULL */ RECSUPFUN cvt_dbaddr; /* cvt dbAddr */ RECSUPFUN get_array_info; RECSUPFUN put_array_info; RECSUPFUN get_units; RECSUPFUN get_precision; RECSUPFUN get_enum_str; /* get string from enum */ RECSUPFUN get_enum_strs; /* get all enum strings */ RECSUPFUN put_enum_str; /* put enum from string */ RECSUPFUN get_graphic_double; RECSUPFUN get_control_double; RECSUPFUN get_alarm_double; };
Initialization • init() • Called once during IOC startup • init_record(void *precord, int pass) • Called twice per record instance. Second pass can affect other records.
Processing • Usually follows this example: static long process(void *precord) { xxxRecord*pxxx = (xxxRecord *)precord; xxxdset *pdset = (xxxdset *)pxxx->dset; long status; unsigned char pact=pxxx->pact; if( (pdset==NULL) || (pdset->read_xxx==NULL) ) { /* leave pact true so that dbProcess doesnt call again*/ pxxx->pact=TRUE; recGblRecordError(S_dev_missingSup, pxxx, ”read_xxx”); return (S_dev_missingSup); } /* pact must not be set true until read_xxx completes*/ status=(*pdset->read_xxx)(pxxx); /* read the new value */ /* return if beginning of asynch processing*/ if(!pact && pxxx->pact) return(0); pxxx->pact = TRUE; recGblGetTimeStamp(pxxx); /* check for alarms */ alarm(pxxx); /* check event list */ monitor(pxxx); /* process the forward scan link record */ recGblFwdLink(pxxx); pxxx->pact=FALSE; return(status); }
Utility Routines • Allow to define • how the record responds when fields are read or written • the units, precision, limits;not only of the VAL field!
Utility Routines… • special(DBADDR *addr, int after) • Allows us to react before/after somebody else accesses field ref’ed by addr. • get_units(DBADDR *addr, char *units),get_precision(DBADDR *addr, long *prec),get_array_info(…) • Can simply copy contents of EGU or PREC fields,can also provide information specific to field ref’ed by addr