EPICS record/device/driver Support ,
Download
Report
Transcript EPICS record/device/driver Support ,
EPICS
record/device/driver
Support
[email protected],
many slides copied from
[email protected]
Interfacing Hardware
General Idea
Software
EPICS
IOC Core: Db, CA, …
Record Support
“Driver”
Hardware
Device Support
Driver Support
Hardware
Where to extend…
Common Case: New Hardware (I/O Board,..)
Sometimes: Specialized Record
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
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
CAMAC_IO
RF_IO
dset is the ‘C’ symbol name for the Device Support Entry
Table (DSET)
BITBUS_IO
BBGPIB_IO
GPIB_IO INST_IO
VME_IO VXI_IO
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")
Record/Device/Driver
Tek4320 HPXXX
AI
AO
LI
devAITe …
k4320
devAOTe …
k4302
…
…
Driv GP-IB
er
PS
contr.
…
AdVME Td4V
1521
…
…
…
…
…
…
…
…
ARCne VME
t
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
Device Support for AI Record
Common
report
initialization
initialize instance
attach to device
interrupt
AI-Specific
read ai device value
linear conversion
(RVAL->VAL)
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;
Advanced: Interrupts
Device supports interrupts, want to use SCAN=“I/O
Intr”
Difficulty
higher scan rate
scan synchronized with device
Interrupts: interrupt level, most OS routines prohibited
Record processing: task level
IOSCANPVT
EPICS Core Helper for processing records in response to
interrupt
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