# Presentation: 340398

## EPICSrecord/device/driverSupport

- kasemir@lanl.gov,many slides copied from johill@lanl.gov

## Interfacing Hardware

- Hardware

- “Driver”

- Software

- _General Idea_

- Hardware

- Device Support

- IOC Core: Db, CA, ...

- _EPICS_

- Driver Support

- Record Support

## 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?

## 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 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.

## 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

## DSET 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”
- 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

## 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