300 likes | 510 Views
Device Support. Andy Foster Observatory Sciences Limited. Outline. What is device support? The .dbd file entry The Device Support Entry Table DSET – report DSET – init DSET – init_record DSET – read_write Hardware device addresses Useful EPICS Facilities Interrupts
E N D
Device Support Andy Foster Observatory Sciences Limited
Outline • What is device support? • The .dbd file entry • The Device Support Entry Table • DSET – report • DSET – init • DSET – init_record • DSET – read_write • Hardware device addresses • Useful EPICS Facilities • Interrupts • EPICS and Interrupts • The callback system • scanIoInit • DSET – get_ioint_info • scanIoRequest • Asynchronous I/O • The optional driver layer
What is Device Support? • Interface between record and hardware • Provides an API for record support to call • Record type determines routines needed • Performs record I/O on request • Determines whether a record is to be synchronous or asynchronous • Provides I/O Interrupt support • Determines whether I/O Interrupts are allowed CHANNEL ACCESS DATABASE ACCESS RECORD SUPPORT DEVICE SUPPORT DRIVER SUPPORT
Why use it • Why not make record types for each hardware interface, with fields to allow full control over the facilities it provides? • Users don't have to learn about a new record type for each I/O board they want to use • Changing the I/O hardware is much easier than writing new records! • Record types provided are sufficient for most hardware interfacing tasks • Device support is simpler than record support • Device support isolates the hardware interface code from changes to the record API • Bug-fixes or enhancements to record support only need to happen in one place • Modularity (good software engineering practice)
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(bi,INST_IO, devBiXy2440,“XYCOM-2440") device(bo,VME_IO, devBoXy240, “XYCOM-240")
The Device Support Entry Table • … is a ‘C’ struct containing function pointers, the content of which can vary by record type • Each device support layer defines a named DSET with pointers to its own routines • All DSET structure declarations start struct xxxdset { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN read_write; }; • number gives the number of function pointers in the DSET, usually 5 for standard types • A NULL pointer is given when an optional routine is not implemented
How does a record find its device support? Through .dbd ‘device’ statements:
DSET: report long report(int type); • Optional function. Called by dbior shell command • Normally omitted because the driver also has a report function which prints out information about the current state of the hardware. • The value of type defines how much information to print.
DSET: init long init(int pass); • Initializes the device support layer • Used for one-time startup operations, e.g. • Starting background tasks • Creating semaphores • Optional routine, not always needed • Routine called twice by iocInit: • pass=0 — Before record initialization • At this stage we don’t know what I/O is to be used by the database, therefore, do not access hardware. • pass=1 — After all record initialization • Can be used as a final startup step, all devices addressed by database are known by this point
DSET: init_record long init_record( struct xxxRecord *prec ); • Called from xxxRecord’s init_record routine once for each record. This occurs at iocInit. • Important routine, normally required. • init_record should: • Parse the hardware address contained in the INP or OUT field • Check if addressed hardware is present • Allocate any private storage required • Every record type has a void *dpvt field for device support to use as it wishes • Program device registers • Set record-specific fields needed for conversion to/from engineering units
DSET: read_write long read_write( struct xxxRecord *prec ); • Important routine. • Called from xxxRecord’s process routine and implements the I/O operation (or calls the driver to do this) which occurs when the record is processed. • Precise action depends on the record type and whether synchronous or asynchronous processing. • Generally, synchronous input support • Reads hardware value into prec->rval • Returns 0 • and synchronous output support • Copies value in prec->rval to hardware • Returns 0
Hardware Device Addresses • Device support .dbd entry was device(recType,addrType,dset,"name") • addrType tells database software what type to use for the address link device(bo,VME_IO,devBoXy240,"XYCOM-240”) implies that: • pbo->out.type = VME_IO • If we look in base/include/link.h we see that struct vmeio { short card; short signal; char *parm; } • We can obtain card, signal, parm as follows: pvmeio = (struct vmeio *)&(pbo->out.value) • The most flexible link type is INST_IO which provides a string the user can parse.
A simple example #include <recGbl.h> #include <devSup.h> #include <devLib.h> #include <biRecord.h> #include <epicsExport.h> long init_record(biRecord *prec) { char *pbyte, dummy; prec->pact = 1; if ((prec->inp.type != VME_IO) || (prec->inp.value.vmeio.signal < 0) || (prec->inp.value.vmeio.signal > 7)) { recGblRecordError(S_dev_badInpType, (void *)prec, "devBiFirst: Bad INP address"); return S_dev_badInpType; } if( devRegisterAddress("devBiFirst", atVMEA16, prec->inp.value.vmeio.card, 0x1, &pbyte) != 0) { recGblRecordError(S_dev_badCard, (void *)prec, "devBiFirst: Bad VME address"); return -1; } if( devReadProbe(1, pbyte, &dummy) < 0 ) { recGblRecordError(S_dev_badCard, (void *)prec, "devBiFirst: Nothing there!"); return -1; } prec->dpvt = pbyte; prec->pact = 0; return OK; }
A simple example long read_bi(biRecord *prec) { char *pbyte = (char *)prec->dpvt; prec->rval = *pbyte; return OK; } struct { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN read_write; } devBiFirst = { 5, NULL, NULL, init_record, NULL, read_bi }; epicsExportAddress(dset, devBiFirst);
A simple example – device support .dbd file The .dbd file for the device support routines shown on the preceding pages might be: device(bi, VME_IO, devBiFirst, “simpleInput”) An application .db file using the device support routines shown might contain: record(bi, "$(P):statusBit") { field(DESC, "Simple example binary input") field(DTYP, "simpleInput") field(INP, "#C$(C) S$(S)") }
A simple example – application startup script An application startup script (st.cmd) using the device support routines shown on the preceding pages might contain: dbLoadRecords("db/example.db", "P=test,C=0x1E0,S=0") which would expand the .db file into record(bi, "test:statusBit") { field(DESC, "Simple example binary input") field(DTYP, "simpleInput") field(INP, "#C0x1E0 S0") }
Useful facilities • ANSI C routines (EPICS headers fill in vendor holes) • epicsStdio.h – printf, sscanf, epicsSnprintf • epicsString.h – strcpy, memcpy, epicsStrDup • epicsStdlib.h – getenv, abs, epicsScanDouble • OS-independent hardware access (devLib.h) • Bus address Local address conversion • Interrupt control • Bus probing • EPICS routines • epicsEvent.h – process synchronization semaphore • epicsMutex.h – mutual-exclusion semaphore • epicsThread.h – multithreading support • recGbl.h – record error and alarm reporting
Interrupts • vxWorks ISRs can be written in ‘C’ • On VME two parameters are needed: • Interrupt Level — prioritized 1-7 • Often set with I/O board DIP switches or jumpers • Level 7 is non-maskable and can crash vxWorks • Interrupt Number • 0-255 has to be unique within this CPU • Interrupt numbers < 64 are reserved by MC680x0 • Use EPICS veclist on IOC to see vectors in use However, not supported by some PPC BSP’s! • OS initialization takes two calls Connect interrupt handler to vector: devConnectInterruptVME( unsigned vectorNumber, void (*pFunction)(void *), void *parameter); Enable interrupt from VME level: devEnableInterruptLevelVME (unsigned level);
EPICS and Interrupts The callback system • How do you make an EPICS record process when an interrupt occurs? • Use the SCAN setting of “I/O Intr” in the record • Since an ISR cannot call a record’s process routine directly, I/O interrupt scanning is implemented using the EPICS callback system. • The callback system uses ring buffers to queue callback requests made from interrupt level. • One of 3 prioritized callback tasks executes the registered callback function which in turn calls the record’s process routine. • See callback.h for a definition of the EPICS callback structure. • The choice of callback task is determined by the PRIO field in the record (LOW, MEDIUM, HIGH). • From the device/driver layer, we support I/O interrupt scanning by using the following routines: • scanIoInit • get_ioint_info • scanIoRequest
EPICS and Interrupts scanIoInit • scanIoInit must be called for each possible interrupt “source”. Device support can be written to allow any number of “sources”. Ultimately, the granularity of interrupts depends on the hardware. • One “source” per card • One “source” per bit • scanIoInit is typically called from the driver initialize routine to initialize a pointer void scanIoInit(IOSCANPVT *ppvt) int xy2440Initialise( void ) { struct config2440 *plist = ptrXy2440First; #ifndef NO_EPICS { int i; for( i=0; i<MAXPORTS*MAXBITS; i++ ) { scanIoInit( &plist->biScan[i] ); scanIoInit( &plist->mbbiScan[i] ); } } #endif ... }
EPICS and Interrupts scanIoInit – contd struct config2440 { ... #ifndef NO_EPICS IOSCANPVT biScan[MAXPORTS*MAXBITS]; IOSCANPVT mbbiScan[MAXPORTS*MAXBITS]; #endif } • scanIoInit allocates an array of 3 “scan lists”, one for each callback priority (PRIO field). IOSCANPVT will hold the address of this array.
EPICS and Interruptsget_ioint_info • The DSET must have a get_ioint_info routine. This is called at iocInit for all records with “SCAN = I/O Intr”. • Device support copies the IOSCANPVT value for this record (determined by which bit) into *ppvt typedef struct { int port; int bit; } xipIo_t; static long init_bi( struct biRecord *pbi ) { xipIo_t *pxip; switch(pbi->inp.type) { case(INST_IO): pxip = (xipIo_t *)malloc(sizeof(xipIo_t)); status = xipIoParse(pbi->inp.value.instio.string, pxip); pbi->dpvt = pxip; ret = 0; break;default: printf("Suitable error message\n"); ret = 1; break; } return(ret); }
EPICS and Interruptsget_ioint_info – contd static long bi_ioinfo( int cmd, struct biRecord *pbi, IOSCANPVT *ppvt ) { struct config2440 *plist = ptrXy2440First; xipIo_t *pxip; pxip = (xipIo_t *)pbi->dpvt; /* "channel" is calculated from pxip->port, pxip->bit */ *ppvt = plist->biScan[channel]; return(0); } • Return 0 if OK, else non-zero when scan will be reset to Passive • Can usually ignore the cmd value: • At iocInit this routine is called with cmd=0 for all records with SCAN=I/O Intr . • If SCAN is later changed by a caPut or dbPut, this routine will be called again with cmd=1.We do not have to worry about this case. • get_ioint_info adds this record to the appropriate scan list, depending on PRIO.
EPICS and InterruptsscanIoRequest • In the ISR, determine which channel on the card caused the interrupt and then call:scanIoRequest(plist->biScan[channel]); • This causes all records in all 3 scan lists associated with “plist->biScan[channel]” to process!
Asynchronous I/O • It is never permissible for Device Support to wait for slow I/O • If hardware read/write operations take “a long time,” (>100µsec) use asynchronous record processing • If the device does not provide a suitable completion interrupt, a background task can poll it periodically • An asynchronous read_write routine looks at precord->pact, and if FALSE, it: • Starts the I/O operation • Sets precord->pact = TRUE and returns
Asynchronous I/Ocontinued • When the operation completes, Device Support must run the following code at task level, not in the ISR: struct rset *prset = (struct rset *)precord->rset; dbScanLock(precord); (*prset->process)(precord); dbScanUnlock(precord); • The record’s process routine will call the Device Support read_write routine again. Now pact is TRUE, the read_write routine will: • Complete the I/O by setting the rval field etc. • Return 0
Asynchronous example #include <vxWorks.h> #include <stdlib.h> #include <stdio.h> #include <wdLib.h> #include <recSup.h> #include <devSup.h> #include <aiRecord.h> static long init_record(); static long read_ai(); static void myCallback( CALLBACK * ); typedef struct {long number;DEVSUPFUN report;DEVSUPFUN init;DEVSUPFUN init_record;DEVSUPFUN get_ioint_info;DEVSUPFUN read_ai;DEVSUPFUN special_linconv; } ANALOGDSET; ANALOGDSET devAiTestAsyn = { 6, NULL, NULL, init_record, NULL, read_ai, NULL }; /* Device Private Structure */ struct devPrivate {CALLBACK callback; /* EPICS CALLBACK structure */WDOG_ID wdId; };
Asynchronous example - continued static long init_record( struct aiRecord *pai ) { struct devPrivate *pdevp; pai->udf = FALSE; pdevp = (struct devPrivate *)(calloc(1,sizeof(struct devPrivate))); pai->dpvt = (void *)pdevp; callbackSetCallback( myCallback, &pdevp->callback ); callbackSetPriority( pai->prio, &pdevp->callback ); callbackSetUser( pai, &pdevp->callback ); pdevp->wdId = wdCreate(); return(0); } static long read_ai(struct aiRecord *pai) { struct devPrivate *pdevp = (struct devPrivate *)(pai->dpvt); int waitTime; if( pai->pact == FALSE ) { waitTime = (int)(pai->val * vxTicksPerSecond); printf("(%s) Starting Async. Processing...\n", pai->name);wdStart( pdevp->wdId, waitTime, (FUNCPTR)callbackRequest, (int)&pdevp->callback );pai->pact = TRUE;return(0); } else { printf("(%s) Completed Async. Processing...\n", pai->name);return(2); /* No linear conversion in record */ } }
Asynchronous example - continued static void myCallback( CALLBACK *pCallback ) { struct dbCommon *precord; struct rset *prset; callbackGetUser( precord, pCallback ); prset = (struct rset *)precord->rset; dbScanLock(precord); (*prset->process)(precord); dbScanUnlock(precord); }
The optional Driver layer • Some reasons to include a driver layer: • Share code between similar device support layers for different record types • Most digital I/O interfaces support record types bi, bo, mbbi, mbbo, mbbiDirect and mbboDirect • Initialize a software module before individual device layers using it are initialized • The order in which device support initialization is called is not guaranteed • Provide a common interface to an I/O bus such as GPIB, Bitbus, Allen-Bradley etc. • I/O devices placed on this bus need their own device support, but share the interface hardware • Provide a wrapper around commercial device drivers or other software libraries