1 2009: device support epics device support andy foster observatory sciences limited
TRANSCRIPT
12009: Device Support
EPICS
Device Support
Andy FosterObservatory Sciences Limited
22009: Device Support
EPICSOutline
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
32009: Device Support
EPICSWhat 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
RECORD SUPPORT
DEVICE SUPPORT
DRIVER SUPPORT
DATABASE ACCESS
CHANNEL ACCESS
42009: Device Support
EPICSWhy 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)
52009: Device Support
EPICSThe .dbd file entry
The IOC discovers what device supports are present from entries in the .dbd filedevice(recType,addrType,dset,"name")
addrType is one ofAB_IO BITBUS_IO BBGPIB_IOCAMAC_IO GPIB_IO INST_IORF_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 exampledevice(bi,INST_IO, devBiXy2440,“XYCOM-2440")
device(bo,VME_IO, devBoXy240, “XYCOM-240")
62009: Device Support
EPICSThe 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 startstruct 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
72009: Device Support
EPICSHow does a record find its
device support?Through .dbd ‘device’ statements:
82009: Device Support
EPICSDSET: 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.
92009: Device Support
EPICSDSET: 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
102009: Device Support
EPICSDSET: 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
112009: Device Support
EPICSDSET: 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
122009: Device Support
EPICSHardware Device Addresses
Device support .dbd entry wasdevice(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.
132009: Device Support
EPICSA 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;
}
142009: Device Support
EPICSA 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);
152009: Device Support
EPICSA simple example – device
support .dbd fileThe .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)")}
162009: Device Support
EPICSA 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")}
172009: Device Support
EPICSUseful 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
182009: Device Support
EPICSInterrupts
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 useHowever, not supported by some PPC BSP’s!
OS initialization takes two callsConnect interrupt handler to vector:devConnectInterruptVME(
unsigned vectorNumber,
void (*pFunction)(void *),
void *parameter);
Enable interrupt from VME level:devEnableInterruptLevelVME (unsigned level);
192009: Device Support
EPICSEPICS 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
202009: Device Support
EPICSEPICS 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 pointervoid 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 ...}
212009: Device Support
EPICSEPICS and Interrupts
scanIoInit – contdstruct 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.
222009: Device Support
EPICSEPICS and Interrupts
get_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);}
232009: Device Support
EPICSEPICS and Interrupts
get_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.
242009: Device Support
EPICSEPICS and Interrupts
scanIoRequest
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!
252009: Device Support
EPICSAsynchronous 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
262009: Device Support
EPICSAsynchronous 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
272009: Device Support
EPICSAsynchronous 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;
};
282009: Device Support
EPICSAsynchronous 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 */ }}
292009: Device Support
EPICSAsynchronous 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);
}
302009: Device Support
EPICSThe 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