In addition to a record support module, each record type can have an arbitrary number of device support modules. The purpose of device support is to hide hardware specific details from record processing routines. Thus support can be developed for a new device without changing the record support routines.
A device support routine has knowledge of the record definition. It also knows how to talk to the hardware directly or how to call a device driver which interfaces to the hardware. Thus device support routines are the interface between hardware specific fields in a database record and device drivers or the hardware itself.
Release 3.14.8 introduced the concept of extended device support, which provides an optional interface that a device support can implement to obtain notification when a record's address is changed at runtime. This permits records to be reconnected to a different kind of I/O device, or just to a different signal on the same device. Extended device support is described in more detail in Section below.
Database common contains two device related fields:
The field DTYP
contains the index of the menu choice as defined by the device ASCII definitions. iocInit
uses this
field and the device support structures defined in devSup.h
to initialize the field DSET
. Thus record support can locate
its associated device support via the DSET
field.
Device support modules can be divided into two basic classes: synchronous and asynchronous. Synchronous device support is used for hardware that can be accessed without delays for I/O. Many register based devices are synchronous devices. Other devices, for example all GPIB devices, can only be accessed via I/O requests that may take large amounts of time to complete. Such devices must have associated asynchronous device support. Asynchronous device support makes it more difficult to create databases that have linked records.
If a device can be accessed with a delay of less then a few microseconds then synchronous device support is appropriate. If a device causes delays of greater than 100 microseconds then asynchronous device support is appropriate. If the delay is between these values your guess about what to do is as good as mine. Perhaps you should ask the hardware designer why such a device was created.
If a device takes a long time to accept requests there is another option than asynchronous device support. A driver can be created that periodically polls all its attached input devices. The device support just returns the latest polled value. For outputs, device support just notifies the driver that a new value must be written. the driver, during one of its polling phases, writes the new value. The EPICS Allen Bradley device/driver support is a good example.
/* Create the dset for devAiSoft */ long init_record(); long read_ai(); struct { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN read_ai; DEVSUPFUN special_linconv; }devAiSoft={ 6, NULL, NULL, init_record, NULL, read_ai, NULL }; epicsExportAddress(dset,devAiSoft); static long init_record(void *precord) { aiRecord *pai = (aiRecord *)precord; long status; /* ai.inp must be a CONSTANT, PV_LINK, DB_LINK or CA_LINK*/ switch (pai->inp.type) { case (CONSTANT) : if(recGblInitConstantLink(&pai->inp,DBF_DOUBLE,&pai->val)) pai->udf = FALSE; break; case (PV_LINK) : case (DB_LINK) : case (CA_LINK) : break; default : recGblRecordError(S_db_badField, (void *)pai, "devAiSoft (init_record) Illegal INP field"); return(S_db_badField); } /* Make sure record processing routine does not perform any conversion*/ pai->linr=0; return(0); } static long read_ai(void *precord) { aiRecord*pai =(aiRecord *)precord; long status; status=dbGetGetLink(&(pai->inp.value.db_link), (void *)pai,DBR_DOUBLE,&(pai->val),0,1); if (pai->inp.type!=CONSTANT && RTN_SUCCESS(status)) pai->udf = FALSE; return(2); /*don't convert*/ }
The example is devAiSoft
, which supports soft analog inputs. The INP
field can be a constant or a database link or a
channel access link. Only two routines are provided (the rest are declared NULL
). The init_record
routine first checks
that the link type is valid. If the link is a constant it initializes VAL
If the link is a Process Variable link it calls
dbCaGetLink
to turn it into a Channel Access link. The read_ai
routine obtains an input value if the link is a database
or Channel Access link, otherwise it doesn't have to do anything.
This example shows how to write an asynchronous device support routine. It does the following sequence of operations:
PACT
is FALSE
. It arranges for a callback (myCallback
) routine to be called after a number of
seconds specified by the DISV
field.
PACT
to TRUE
, and returns. The record processing routine
returns without completing processing.
myCallback
is called. It calls dbScanLock
to lock the record, calls process
,
and calls dbScanUnlock
to unlock the record. It calls the process entry of the record support module, which it
locates via the RSET
field in dbCommon
, directly rather than dbProcess
. dbProcess
would not call process
because PACT
is TRUE
.
process
executes, it again calls read_ai
. This time PACT
is TRUE
.
read_ai
prints a message stating that record processing is complete and returns a status of 2. Normally a value of
0 would be returned. The value 2 tells the record support routine not to attempt any conversions. This is a
convention (a bad convention!) used by the analog input record.
read_ai
returns the record processing routine completes record processing.
At this point the record has been completely processed. The next time process is called everything starts all over.
Note that this is somewhat of an artificial example since real code of this form would more likely use the callbackcallbackRequestProcessCallbackDelayed function to perform the required processing.
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); } static long init_record(struct aiRecord *pai) { CALLBACK *pcallback; switch (pai->inp.type) { case (CONSTANT) : pcallback = (CALLBACK *)(calloc(1,sizeof(CALLBACK))); callbackSetCallback(myCallback,pcallback); callbackSetUser(pai,pcallback); pai->dpvt = (void *)pcallback; break; default : recGblRecordError(S_db_badField,(void *)pai, "devAiTestAsyn (init_record) Illegal INP field"); return(S_db_badField); } return(0); } static long read_ai(struct aiRecord *pai) { CALLBACK *pcallback = (CALLBACK *)pai->dpvt; if(pai->pact) { pai->val += 0.1; /* Change VAL just to show we've done something. */ pai->udf = FALSE; /* We modify VAL so we are responsible for UDF too. */ printf("Completed asynchronous processing: %s\n",pai->name); return(2); /* don't convert*/ } printf("Starting asynchronous processing: %s\n",pai->name); pai->pact=TRUE; callbackRequestDelayed(pcallback,pai->disv); return(0); } /* Create the dset for devAiTestAsyn */ struct { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN read_ai; DEVSUPFUN special_linconv; }devAiTestAsyn={ 6, NULL, NULL, init_record, NULL, read_ai, NULL }; epicsExportAddress(dset,devAiTestAsyn);
This section describes the routines defined in the DSET. Any routine that does not apply to a specific record type must be
declared NULL
.
long report( int interest);
This routine is responsible for reporting all I/O cards it has found. If interest
is (0,1) then generate a (short, long)
report. If a device support module is using a driver, it normally does not have to implement this routine because the driver
generates the report.
long init( int after);
This routine is called twice at IOC initialization time. Any action is device specific. This routine is called twice: once
before any database records are initialized and once after all records are initialized but before the scan tasks are started.
after
has the value (0,1) (before, after) record initialization.
long init_record( void *precord); /* addr of record*/
The record support init_record
routine calls this routine.
long get_ioint_info( int cmd, struct dbCommon *precord, IOSCANPVT *ppvt);
This is called by the I/O interrupt scan task. If cmd
is (0,1) then this routine is being called when the associated record is
being (placed in, taken out of) an I/O scan list. See the chapter on scanning for details.
It should be noted that a previous type of I/O event scanning is still supported. It is not described in this document because, hopefully, it will go away in the near future. When it calls this routine the arguments have completely different meanings.
All other device support routines are record type specific.
This section describes the additional behaviour and routines required for a device support layer to support online changes to a record's hardware address.
In releases prior to R3.14.8 it is possible to change the value of the INP or OUT field of a record but (unless a soft device support is in use) this generally has no effect on the behaviour of the device support at all. Some device supports have been written that check this hardware address field for changes every time they process, but they are in the minority and in any case they do not provide any means to switch between different device support layers at runtime since no software is present that can lookup a new value for the DSET field after iocInit.
The extended device interface has been carefully designed to retain maximal backwards compatibility with existing device and record support layers, and as a result it cannot just introduce new routines into the DSET:
Since both basic and extended device support layers have to co-exist within the same IOC, some rules are enforced concerning whether the device address of a particular record is allowed to be changed:
add_record
and del_record
routines,
thus some devices may only allow one-way changes.
If an address change is refused, the change to the INP or OUT field will cause an error or exception to be passed to the software performing the change. If this was a Channel Access client the result is to generate an exception callback.
To switch to a different device support layer, it is necessary to change the DTYP field before the INP or OUT field. The change to the DTYP field has no effect until the latter field change takes place.
If a record is set to I/O Interrupt scan but the new layer does not support this, the scan will be changed to Passive.
Device support that implements the extended behaviour must provide an init
routine in the Device Support Entry Table
(see Section ).
In the first call to this routine (pass 0) it registers the address of its Device Support eXtension Table (DSXT) in a call to devExtend
.
The only exception to this registration requirement is when the device support uses a link type of CONSTANT. In this
circumstance the system will automatically register an empty DSXT for that particular support layer (both the
add_record
and del_record
routines pointed to by this DSXT do nothing and return zero). This exception allows
existing soft channel device support layers to continue to work without requiring any modification, since the iocCore
software already takes care of changes to PV_LINK addresses.
The following is an example of a DSXT and the initialization routine that registers it:
static struct dsxt myDsxt = { add_record, del_record }; static long init(int pass) { if (pass==0) devExtend(&myDsxt); return 0; }
A call to devExtend
can only be made during the first pass of the device support initialization process, and registers the
DSXT for that device support layer; if called at any other time it will log an error message and immediately return.
The full definition of struct dsxt
is found in devSup.h and currently looks like this:
typedef struct dsxt { long (*add_record)(struct dbCommon *precord); long (*del_record)(struct dbCommon *precord); } dsxt;
There may be future additions to this table to support additional functionality; such extensions may only be made by changing the devSup.h header file and rebuilding EPICS Base and all support modules, thus neither record types nor device support are permitted to make any private use of this table.
The two function pointers are the means by which the extended device support is notified about the record instances it is being given or that are being moved away from its control. In both cases the only parameter is a pointer to the record concerned, which the code will have to cast to the appropriate pointer for the record type. The return value from the routines should be zero for success, or an EPICS error status code.
long add_record( struct dbCommon *precord);
This function is called to offer a new record to the device support. It is also called during iocInit, in between the pass 0 and
pass 1 calls to the regular device support init_record
routine (described in Section above). When
converting an existing device support layer, this routine will usually be very similar to the old init_record
routine,
although in some cases it may be necessary to do a little more work depending on the particular record type involved. The
extra code required in these cases can generally be copied straight from the record type implementation itself. This is
necessary because the record type has no knowledge of the address change that is taking place, so the device support must
perform any bitmask generation and/or readback value conversions itself. This document does not attempt to describe all
the necessary processing for the various different standard record types, although the following (incomplete) list is
presented as an aid to device support authors:
If the add_record
routine discovers any errors, say in the link address, it should return a non-zero error status value to
reject the record. This will cause the record's PACT field to be set, preventing any further processing of this record until
some other address change to it gets accepted.
long del_record( struct dbCommon *precord);
This function is called to notify the device support of a request to change the hardware address of a record, and allow the device support to free up any resources it may have dedicated to this particular record.
Before this routine is called, the record will have had its SCAN field changed to Passive if it had been set to I/O Interrupt.
This ensures that the device support's get_ioint_info
routine is never called after the the call to del_record
has
returned successfully, although it may also lead to the possibility of missed interrupts if the address change is rejected by
the del_record
routine.
If the device support is unable to disconnect from the hardware for some reason, this routine should return a non-zero error status value, which will prevent the hardware address from being changed. In this event the SCAN field will be restored if it was originally set to I/O Interrupt.
After a successfull call to del_record
, the record's DPVT field is set to NULL and PACT is cleared, ready for use by
the new device support.
The init_record
routine from the DSET (see Section ) is called by the record type, and must still be
provided since the record type's per-record initialization is run some time after the initial call to the DSXT's
add_record
routine. Most record types perform some initialization of record fields at this point, and an extended device
support layer may have to fix anything that the record overwrites. The following (incomplete) list is presented as an aid to
device support authors: