Table of Contents Previous Chapter Chapter 8: Record And Device Support

Chapter 8: Record And Device Support

1. Overview

The purpose of this chapter is to describe record and device support in sufficient detail so that a C programmer can write new record and/or device support modules. Before attempting to write new support modules, you should carefully study a few of the existing support modules. If an existing support module is similar to the desired module most of the work will already be done.

From the previous discussion, it should be clear that many things happen as a result of record processing. The details of what happens are dependent on the record type. In order to allow new record types and new device types without impacting the core IOC system, the concept of record support and device support has been created. For each record type, a record support module exists. It is responsible for all record specific details. In order to allow a record support module to be independent of device specific details, the concept of device support has been created.

A record support module consists of a standard set of routines that can be called by database access routines. This set of routines implements record specific code. Each record type can define a standard set of device support routines specific to that record type.

By far the most important record support routine is process, which dbProcess calls when it wants to process a record. This routine is responsible for all the details of record processing. In many cases it calls a device support I/O routine. The next section gives an overview of what must be done in order to process a record. Next is a description of the entry tables that must be provided by record and device support modules. The remaining sections give example record and device support modules and describe some global routines useful to record support modules.

The record and device support modules are the only modules that are allowed to include the record specific include files as defined in epics/share/epicsH/rec. Thus they are the only routines that access record specific fields without going through database access.

2. Overview of Record Processing

The most important record support routine is process. This routine determines what record processing means. This section describes the overall model followed by record processing. Before the record specific "process" routine is called, the following has already been done:

The process routine, together with its associated device support, is responsible for the following tasks:

A complication of record processing is that some devices are intrinsically asynchronous. It is NEVER permissible to wait for a slow device to complete. The method to follow is to perform the following steps:

  1. Initiate the I/O operation and set pact TRUE
  2. Determine a method for again calling process when the operation completes
  3. Return immediately without completing record processing
  4. When process is called after the I/O operation complete record processing
  5. Set pact FALSE and return
The examples given below show how this can be done.

3. Record Support and Device Support Entry Tables

Each record type has an associated set of record support routines. These routines are located via the data structures defined in epics/share/epicsH/recSup.h. The concept of record support routines isolates the iocCore software from the details of each record type. Thus new records can be defined and supported without affecting the IOC core software.

Each record type also has zero or more sets of device support routines. Record types without associated hardware, e.g. calculation records, normally do not have any associated device support. Record types with associated hardware normally have a device support module for each device type. The concept of device support isolates IOC core software and even record support from device specific details.

Corresponding to each record type is a set of record support routines. The set of routines is the same for every record type. These routines are located via a Record Support Entry Table (RSET), which has the following structure

  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;   /* get value field */
    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 item */
    RECSUPFUN   get_enum_strs;   /* get all enum strings */
    RECSUPFUN   put_enum_str;   /* put enum item from string */
    RECSUPFUN   get_graphic_double;
    RECSUPFUN   get_control_double;
    RECSUPFUN   get_alarm_double;
  };
Each record support module must define its RSET. The external name must be of the form:

  <record_type>RSET
Any routines not needed for the particular record type should be initialized to the value NULL. Look at the example below for details.

Device support routines are located via a Device Support Entry Table (DSET), which has the following structure:

  struct dset {   /* device support entry table */
    long   number;   /* number of support routines */
    DEVSUPFUN   report;   /* print report */
    DEVSUPFUN   init;   /* init support */
    DEVSUPFUN   init_record;   /* init support for particular record */
    DEVSUPFUN   get_ioint_info;   /* get io interrupt information */
    /* other functions are record dependent*/
  };
Each device support module must define its associated DSET. The external name must be the same as the name which appears in devSup.ascii.

Any record support module which has associated device support must also include definitions for accessing its associated device support modules. The field "dset", which is located in dbCommon, contains the address of the DSET. It is given a value by iocInit.

4. Example Record Support Module

This section contains the skeleton of a record support package. The record type is xxx and the record has the following fields in addition to the dbCommon fields: VAL, PREC, EGU, HOPR, LOPR, HIHI, LOLO, HIGH, LOW, HHSV, LLSV, HSV, LSV, HYST, ADEL, MDEL, LALM, ALST, MLST. These fields will have the same meaning as they have for the ai record. Consult the Record Reference manual for a description.

Declarations

/* Create RSET - Record Support Entry Table*/
#define report NULL
#define initialize NULL
static long init_record();
static long process();
#define special NULL
static long get_value();
#define cvt_dbaddr NULL
#define get_array_info NULL
#define put_array_info NULL
static long get_units();
static long get_precision();
#define get_enum_str NULL
#define get_enum_strs NULL
#define put_enum_str NULL
static long get_graphic_double();
static long get_control_double();
static long get_alarm_double();
struct rset xxxRSET={
  RSETNUMBER,
  report,
  initialize,
  init_record,
  process,
  special,
  get_value,
  cvt_dbaddr,
  get_array_info,
  put_array_info,
  get_units,
  get_precision,
  get_enum_str,
  get_enum_strs,
  put_enum_str,
  get_graphic_double,
  get_control_double,
  get_alarm_double};
/* declarations for associated DSET */
struct xxxdset { /* analog input dset */
  long   number;
  DEVSUPFUN   dev_report;
  DEVSUPFUN   init;
  DEVSUPFUN   init_record; /* returns: (-1,0)=>(failure,success)*/
  DEVSUPFUN   get_ioint_info;
  DEVSUPFUN   read_xxx;
};
/* forward declaration for internal routines*/
static void alarm();
static void monitor();
The above declarations define the Record Support Entry Table (RSET), a template for the associated Device Support Entry Table (DSET), and forward declarations to private routines.

The RSET must be declared with an external name of xxxRSET. It defines the record support routines supplied for this record type. Note that forward declarations are given for all routines supported and a NULL declaration for any routine not supported.

The template for the DSET is declared for use by this module.

init_record

static long init_record(pxxx,pass)
  struct xxxRecord   *pxxx;
  int  pass;
{
  struct xxxdset *pdset;
  long  status;
  if(pass==0) return(0); 
  if((pdset = (struct xxxdset *)(pxxx->dset)) == NULL) {
    recGblRecordError(S_dev_noDSET,pxxx,"xxx: init_record");
    return(S_dev_noDSET);
  }
  /* must have read_xxx function defined */
  if( (pdset->number < 5) || (pdset->read_xxx == NULL) ) {
    recGblRecordError(S_dev_missingSup,pxxx,"xxx: init_record");
    return(S_dev_missingSup);
  }
  if( pdset->init_record ) {
    if((status=(*pdset->init_record)(pxxx))) return(status);
  }
  return(0);
}
This routine, which is called by iocInit twice for each record of type xxx, checks to see if it has a proper set of device support routines and, if present, calls the init_record entry of the DSET.

During the first call to init_record (pass=0) only initializations relating to this record can be performed. During the second call (pass=1) initializations that may refer to other records can be performed. Note also that during the second pass, other records may refer to fields within this record. A good example of where these rules are important is a waveform record. The VAL field of a waveform record actually refers to an array. The waveform record support module must allocate storage for the array. If another record has a database link referring to the waveform VAL field then the storage must be allocated before the link is resolved. This is accomplished by having the waveform record support allocate the array during the first pass (pass=0) and having the link reference resolved during the second pass (pass=1).

process

static long process(pxxx)
  struct xxxRecord   *pxxx;
{
  struct xxxdset   *pdset = (struct xxxdset *)(pxxx->dset);
  long   status;
  unsigned char   pact=pxxx->pact;
  if( (pdset==NULL) || (pdset->read_xxx==NULL) ) {
    /* leave pact true so that dbProcess doesnt keep calling*/
    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 */
  if(!pact && pxxx->pact) return(0); /* return if beginning of asynch processing*/
  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);
}
The record processing routines are the heart of the IOC software. The record specific process routine is called by dbProcess whenever it decides that a record should be processed. Process decides what record processing really means. The above is a good example of what should be done. In addition to being called by dbProcess the process routine may also be called by asynchronous record completion routines.

The above model supports both synchronous and asynchronous device support routines. For example, if read_xxx is an asynchronous routine, the following sequence of events will occur:

At this point the record has been completely processed. The next time process is called everything starts all over from the beginning.

Miscellaneous Utility Routines

static long get_value(pxxx,pvdes)
  struct xxxRecord  *pxxx;
  struct valueDes  *pvdes;
{
  pvdes->field_type = DBF_FLOAT;
  pvdes->no_elements=1;
  (float *)(pvdes->pvalue) = &pxxx->val;
  return(0);
}
static long get_units(paddr,units)
  struct dbAddr *paddr;
  char   *units;
{
  struct xxxRecord  *pxxx=(struct xxxRecord *)paddr->precord;
  strncpy(units,pxxx->egu,sizeof(pxxx->egu));
  return(0);
}
static long get_graphic_double(paddr,pgd)
  struct dbAddr  *paddr;
  struct dbr_grDouble  *pgd;
{
  struct xxxRecord  *pxxx=(struct xxxRecord *)paddr->precord;
  if(paddr->pfield == (void *)(&pxxx->val)) {
    pgd->upper_disp_limit = pxxx->hopr;
    pgd->lower_disp_limit = pxxx->lopr;
  } else recGblGetGraphicDouble(paddr,pgd);
  return(0);
}
/* similar routines would be provided for get_control_double and get_alarm_double*/
These are a few examples of various routines supplied by a typical record support package. The functions that must be performed by the remaining routines are described in Section 5 on page 72.

Alarm Processing

static void alarm(pxxx)
  struct xxxRecord   *pxxx;
{
  double  val;
  float   hyst,lalm,hihi,high,low,lolo;
  unsigned short   hhsv,llsv,hsv,lsv;
  if(pxxx->udf == TRUE ){
    recGblSetSevr(pxxx,UDF_ALARM,VALID_ALARM);
    return;
  }
  
  hihi=pxxx->hihi; lolo=pxxx->lolo; high=pxxx->high; low=pxxx->low;
  hhsv=pxxx->hhsv; llsv=pxxx->llsv; hsv=pxxx->hsv; lsv=pxxx->lsv;
  val=pxxx->val; hyst=pxxx->hyst; lalm=pxxx->lalm;
  /* alarm condition hihi */
  if (hhsv && (val >= hihi || ((lalm==hihi) && (val >= hihi-hyst)))) {
    if(recGblSetSevr(pxxx,HIHI_ALARM,pxxx->hhsv)) pxxx->lalm = hihi;
    return;
  }
  /* alarm condition lolo */
  if (llsv && (val <= lolo || ((lalm==lolo) && (val <= lolo+hyst)))) {
    if(recGblSetSevr(pxxx,LOLO_ALARM,pxxx->llsv)) pxxx->lalm = lolo;
    return;
  }
  /* alarm condition high */
  if (hsv && (val >= high || ((lalm==high) && (val >= high-hyst)))) {
    if(recGblSetSevr(pxxx,HIGH_ALARM,pxxx->hsv)) pxxx->lalm = high;
    return;
  }
  /* alarm condition low */
  if (lsv && (val <= low || (lalm==low) && (val <= low+hyst)))) {
    if(recGblSetSevr(pxxx,LOW_ALARM,pxxx->lsv)) pxxx->lalm = low;
    return;
  }
  /*we get here only if val is out of alarm by at least hyst*/
  pxxx->lalm=val;
  return;
}
This is a typical set of code for checking alarms conditions for an analog type record. The actual set of code can be very record specific. Note also that other parts of the system can raise alarms. The algorithm is to always maximize alarm severity, i.e. the highest severity outstanding alarm will be reported.

The above algorithm also honors a hysteresis factor for the alarm. This is to prevent alarm storms from occurring in the event that the current value is very near an alarm limit and noise makes it continually cross the limit. The above algorithm ensures that the alarm being reported will not change unless the value changes by the hysteresis value.

Raising Monitors

static void monitor(pxxx)
  struct xxxRecord   *pxxx;
{
  unsigned short   monitor_mask;
  float   delta;
  monitor_mask = recGblResetAlarms(pxxx);
  /* check for value change */
  delta = pxxx->mlst - pxxx->val;
  if(delta<0.0) delta = -delta;
  if (delta > pxxx->mdel) {
    /* post events for value change */
    monitor_mask |= DBE_VALUE;
    /* update last value monitored */
    pxxx->mlst = pxxx->val;
  }
  /* check for archive change */
  delta = pxxx->alst - pxxx->val;
  if(delta<0.0) delta = 0.0;
  if (delta > pxxx->adel) {
    /* post events on value field for archive change */
    monitor_mask |= DBE_LOG;
    /* update last archive value monitored */
    pxxx->alst = pxxx->val;
  }
  /* send out monitors connected to the value field */
  if (monitor_mask){
    db_post_events(pxxx,&pxxx->val,monitor_mask);
  }
  return;
}
The first part of the code will be common to most record types. Note that nsta and nsev will have the value 0 after this routine completes. This is necessary to ensure that alarm checking starts fresh after processing completes. The code also takes care of raising alarm monitors when a record changes from an alarm state to the no alarm state. It is essential that record support routines follow the above model or else alarm processing will not follow the rules.

IMPORTANT: The record support module is responsible for calling db_post_event for any fields that change as a result of record processing. Also it should NOT call db_post_event for fields that do not change.

5. Global Record Support Routines

A number of global record support routines are available. These routines are intended for use by the record specific processing routines but can be called by any routine that wishes to use their services.

The name of each of these routines begins with "recGbl".

Alarm Status and Severity

Alarms may be raised in many different places during the course of record processing. The algorithm is to maximize the alarm severity, i.e. the highest severity outstanding alarm is raised. If more than one alarm of the same severity is found then the first one is reported. This means that whenever a code fragment wants to raise an alarm, it does so only if the alarm severity it will declare is greater then that already existing. Four fields (in database common) are used to implement alarms: sevr, stat, nsev, and nsta. The first two are the status and severity after the record is completely processed. The last two fields (nsta and nsev) are the status and severity values to set during record processing. Two routines are used for handling alarms. Whenever a routine wants to raise an alarm it calls recGblSetSevr. This routine will only change nsta and nsev if it will result in the alarm severity being increased. At the end of processing, the record support module must call recGblResetAlarms. This routine sets stat=nsta, sevr=nsev, nsta=0, and nsev=0. If stat or sevr has changed value since the last call it calls db_post_event and returns a value of DBE_ALARM. If no change occured it returns 0. Thus after calling recGblResetAlarms everything is ready for raising alarms the next time the record is processed. The example record support module presented above shows how these macros are used.

  recGblSetSevr(
    void   *precord,
    short   nsta,
    short   nsevr);
Returns: (TRUE, FALSE) if (did, did not) change nsta and nsev.

  unsigned short recGblResetAlarms(void  *precord);
Returns: Initial value for monitor_mask

Alarm Acknowledgment

Database common contains two additional alarm related fields: acks (Highest severity unacknowledged alarm) and ackt (does transient alarm need to be acknowledged). These field are handled by iocCore and recGblResetAlarms and are not the responsibility of record support. These fields are intended for use by the alarm handler at some future time.

Generate Error: Process Variable Name, Caller, Message

  recGblDbaddrError(
    long   status,
    struct dbAddr  *paddr,
    char   *pcaller_name); /* calling routine name */
This routine can be called whenever an error is returned from a call to dbNameToAddr, dbGetxxx, or dbPutxxx. It interfaces with the system wide error handling system to display the following information: Status information, process variable name, calling routine.

Generate Error: Status String, Record Name, Caller

  recGblRecordError(
    long   status,
    void   *precord,   /* addr of record  */
    char   *pcaller_name);   /* calling routine name */
This routine interfaces with the system wide error handling system to display the following information: Status information, record name, calling routine.

Generate Error: Record Name, Caller, Record Support Message

  recGblRecsupError(
    long   status,
    struct   dbAddr   *paddr,
    char   *pcaller_name,   /* calling routine name */
    char   *psupport_name);   /* support routine name*/
This routine interfaces with the system wide error handling system to display the following information: Status information, record name, calling routine, record support entry name.

Get Graphics Double

  recGblGetGraphicDouble(
    struct dbAddr  *paddr,
    struct dbr_grDouble  *pgd);
This routine can be used by the get_graphic_double record support routine to obtain graphics values for fields that it doesn't know how to set.

Get Control Double

  recGblGetControlDouble(
    struct dbAddr  *paddr,
    struct dbr_ctrlDouble   *pcd);
This routine can be used by the get_control_double record support routine to obtain control values for fields that it doesn't know how to set.

Get Alarm Double

  recGblGetAlarmDouble(
    struct dbAddr  *paddr,
    struct dbr_alDouble  *pcd);
This routine can be used by the get_alarm_double record support routine to obtain control values for fields that it doesn't know how to set.

Get Precision

  recGblGetPrec(
    struct dbAddr  *paddr,
    long  *pprecision);
This routine can be used by the get_precision record support routine to obtain the precision for fields that it doesn't know how to set the precision.

Get Time Stamp

  recGblGetTimeStamp(void *precord)
This routine gets the current time stamp.

Forward link

  recGblFwdLink(
    void *precord);
This routine can be used by process to request processing of forward links.

Get Input Link

  recGblGetLinkValue(
    struct link  *plink,
    void   *precord,
    short  dbrType,
    void   *pdest,
    long   *poptions,
    long   *pnRequest);
This routine gets a value from an input link. If the link is a constant this call amounts to a NOP.

Put Output Link

  recGblPutLinkValue(
    struct link  *plink,
    void   *precord,
    short  dbrType,
    void   *pdest,
    long   *pnRequest);
This routine writes a value to an output link. If the link is a constant this call amounts to a NOP.

Initialize Fast Input Link

  recGblInitFastInLink(
    struct link  *plink,
    void   *precord,
    short   dbrType,
    char   *ca_string);
Initialize a fast input link. This routine should be used if scalar data with options is desired. If the link is not a channel access link or a database link this amounts to a NOP. ca_string is the uppercase name of the field that Channel Access is to get a value from.

Initialize Fast Output Link

  recGblInitFastOutLink(
    struct link  *plink,
    void  *precord,
    short  dbrType,
    char  *ca_string);
Initialize a fast output link. This routine should be used if scalar data is to be written via the link. If the link is not a channel access link or a database link this amounts to a NOP. "ca_string" is the uppercase name of the field that Channel Access will take its value from.

Get Fast Input Link

  recGblGetFastLink(
    struct link  *plink,
    void  *precord,
    void  *pdest);
Gets a value from a fast input link. This routine can only be used if the link was initialized via recGblInitFastInLink. If the link is a constant link this amounts to a NOP.

Put Fast Output Link

  recGblPutFastLink(
    struct link  *plink,
    void  *precord,
    void   *psource);
Puts a value to a fast output link. This routine can only be used if the link was initialized via recGblInitFastOutLink. If the link is a constant link this amounts to a NOP.

6. Record Support Routines

This section describes the routines defined in the RSET. Any routine that does not apply to a specific record type must be declared NULL.

Generate Report of Each Field in Record

  report(void *precord);   /* addr of record*/
This routine is not used by most record types. Any action is record type specific.

Initialize Record Processing

  init(void);
This routine is called once at IOC initialization time. Any action is record type specific. Most record types do not need this routine.

Initialize Specific Record

  init_record(
    void *precord,   /* addr of record*/
    int     pass);
iocInit calls this routine twice (pass=0 and pass=1) for each database record of the type handled by this routine. It must perform the following functions:

Process Record

  process(void *precord);   /* addr of record*/
This routine must follow the guidelines specified previously.

Special Processing

  special(
    struct dbAddr   *paddr,
    int   after);   /*(FALSE,TRUE)=>(Before,After)Processing*/
This routine implements the record type specific special processing for the field referred to by dbAddr. Note that it is called twice. Once before any changes are made to the associated field and once after. File special.h defines special types. This routine is only called for user special fields. A field is defined to be user special in the ASCII record definition.

Get Value

  get_value(
    void   *precord,   /* addr of record*/
    struct valueDes   *p);   /*addr of value description struct*/
This routine returns a description of the VAL field of the record. The structure valueDes, which is defined in recSup.h, is defined as follows:

  struct valueDes {
    int    field_type,
    long   no_elements,
    void   *pvalue}

Convert dbAddr Definitions

  cvt_dbaddr(struct dbAddr *paddr);
This routine is called by dbNameToAddr if the field has special set equal to SPC_DBADDR. A typical use is when a field refers to an array. This routine can change any combination of the dbAddr fields: no_elements, field_type, field_size, and special. For example if the VAL field of a waveform record is passed to dbNameToAddr, cvt_dbaddr would change dbAddr so that it refers to the actual array rather then VAL.

Get Array Information

  get_array_info(
    struct dbAddr  *paddr,
    long   *no_elements,
    long   *offset);
This routine returns the current number of elements and the offset of the first value of the specified array. The offset field is meaningful if the array is actually a circular buffer.

Put Array Information

  put_array_info(
    struct dbAddr   *paddr,
    long   old_offset,
    long   nNew);
This routine is called after new values have been placed in the specified array.

Get Units

  get_units(
    struct dbAddr  *paddr,
    char  *punits);
This routine sets units equal to the engineering units for the field.

Get Precision

  get_precision(
    struct dbAddr  *paddr,
    long   *precision);
This routine gets the precision, i.e. number of decimal places, which should be used to convert the field value to an ASCII string. Note that recGblGetPrec should be called for fields not directly related to the value field.

Get Enumerated String

  get_enum_str(
    struct dbAddr  *paddr,
    char  *p);
This routine sets *p equal to the ASCII string for the field value. The field must have type DBF_ENUM.

Get Strings for Enumerated Field

  get_enum_strs(
    struct dbAddr   *paddr,
    struct dbr_enumStrs  *p);
This routine gives values to all fields of structure dbr_enumStrs.

Put Enumerated String

  put_enum_str(
    struct dbAddr  *paddr,
    char  *p);
Given an ASCII string, this routine updates the database field. It compares the string with the string values associated with each enumerated value and if it finds a match sets the database field equal to the index of the string which matched.

Get Graphic Double Information

  get_graphic_double(
    struct dbAddr  *paddr,
    struct dbr_grDouble  *p);   /* addr of return info*/
This routine fills in the graphics related fields of structure dbr_grDouble. Note that recGblGetGraphicDouble should be called for fields not directly related to the value field.

Get Control Double Information

  get_control_double(
    struct dbAddr  *paddr,
    struct dbr_ctrlDouble  *p);  /* addr of return info*/
This routine gives values to all fields of structure dbr_ctrlDouble. Note that recGblGetControlDouble should be called for fields not directly related to the value field.

Get Alarm Double Information

  get_alarm_double(
    struct dbAddr  *paddr,
    struct dbr_alDouble  *p);  /* addr of return info*/
This routine gives values to all fields of structure dbr_alDouble.

7. Example Device Support Modules

In addition to a record support module, each record type has an arbitrary number of device support modules. The purpose of device support is to hide device specifics 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 the device support routines are the interface between hardware specific fields in a database record and device drivers or the hardware itself.

The common portion of every database record contains two device related fields:

The field dtyp is filled in by DCT. It contains the index of the menu choice as defined in devSup.ascii. iocInit uses this field and the device support structures defined in devSup.h to initialize the field dset.

Synchronous Device Support Module

/* 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};
static long init_record(pai)
  struct aiRecord  *pai;
{
  long status;
  /* ai.inp must be a CONSTANT or a PV_LINK or a DB_LINK or a CA_LINK*/
  switch (pai->inp.type) {
    case (CONSTANT) :
      pai->val = pai->inp.value.value;  break;
case (PV_LINK) : status = dbCaAddInLink(&(pai->inp), (void *)pai,"VAL"); if(status) return(status); break; case (DB_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(pai) struct aiRecord *pai; { long status; long options=0; long nRequest=1; status=recGblGetLinkValue(&(pai->inp.value.db_link),(void *)pai,DBR_DOUBLE, &(pai->val),&options,&nRequest); } if(status) return(status); 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.

Asynchronous Device Support Module

This example shows how to write an asynchronous device support routine. It does the following sequence of operations:

  1. When first called pact is FALSE. It arranges for a callback (myCallback) routine to be called after a number of seconds specified by the VAL field. callbackRequest is an EPICS supplied routine. The watchdog timer routines are supplied by vxWorks.
  2. It prints a message stating that processing has started, sets pact TRUE, and returns. The record processing routine returns without completing processing.
  3. When the specified time elapses myCallback is called. It locks the record, calls process, and unlocks 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.
  4. When process executes, it again calls read_ai. This time pact is TRUE.
  5. 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.
  6. When 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.

/* Create the dset for devAiTestAsyn */
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;
} devAiTestAsyn={
  6,
  NULL,
  NULL,
  init_record,
  NULL,
  read_ai,
  NULL};
/* control block for callback*/
struct callback {
  CALLBACK   callback;
  sruct dbCommon   *precord;
  WDOG_ID   wd_id;
};
static void myCallback(pcallback)
  struct callback *pcallback;
{
  struct dbCommon   *precord=pcallback->precord;
  struct rset   *prset=(struct rset *)(precord->rset);
  dbScanLock(precord);
  *(prset->process)(precord);
  dbScanUnlock(precord);
}
static long init_record(pai)
    struct aiRecord  *pai;
{
   struct callback *pcallback;
    /* ai.inp must be a CONSTANT*/
  switch (pai->inp.type) {
  case (CONSTANT) :
    pcallback = (struct callback *)(calloc(1,sizeof(struct callback)));
    pai->dpvt = (void *)pcallback;
    callbackSetCallback(myCallback,pcallback);
    pcallback->precord = (struct dbCommon *)pai;
    pcallback->wd_id = wdCreate();
    pai->val = pai->inp.value.value;
    pai->udf = FALSE;
    break;
  default :
    recGblRecordError(S_db_badField, (void *)pai,
      "devAiTestAsyn (init_record) Illegal INP field");
    return(S_db_badField);
  }
  return(0);
}
static long read_ai(pai)
  struct aiRecord   *pai;
{
  struct callback *pcallback=(struct callback *)(pai->dpvt);
  int   wait_time;
  /* ai.inp must be a CONSTANT*/
  switch (pai->inp.type) {
  case (CONSTANT) :
    if(pai->pact) {
      printf("%s Completed\n",pai->name);
      return(2); /* don`t convert*/
    } else {
      wait_time = (int)(pai->val * vxTicksPerSecond);
      if(wait_time<=0) return(0);
      callbackSetPriority(pai->prio,pcallback);
      printf("%s Starting asynchronous processing\n",pai->name);
      wdStart(pcallback->wd_id,wait_time,callbackRequest,(int)pcallback);
      pai->pact = TRUE;
      return(0);
    }
  default :
    if(recGblSetSevr(pai,SOFT_ALARM,VALID_ALARM)) {
      if(pai->stat!=SOFT_ALARM) {
        recGblRecordError(S_db_badField, (void *)pai,
          "devAiTestAsyn (read_ai) Illegal INP field");
      }
    }
  }
  return(0);
}

8. Device Support Routines

This section describes the routines defined in the DSET. Any routine that does not apply to a specific record type must be declared NULL.

Generate Device Report

  report(
    FILE   fp,   /* file pointer*/
    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.

Initialize Record Processing

  init(
    int   after);
This routine is called twice at IOC initialization time. Any action is device specific. This routine is called twice: once before the database records are initialized and once after. after has the value (0,1) (before, after) record initialization.

Initialize Specific Record

  init_record(
    void *precord);   /* addr of record*/
The record support init_record routine calls this routine.

Get I/O Interrupt Information

  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.

Other Device Support Routines

All other device support routines are record type specific.

9. Device Drivers

Device drivers are modules that interface directly with the hardware. They are provided to isolate device support routines from details of how to interface to the hardware. Device drivers have no knowledge of the internals of database records. Thus there is no necessary correspondence between record types and device drivers. For example the Allen Bradley driver provides support for many different types of signals including analog inputs, analog outputs, binary inputs, and binary outputs.

In general only device support routines know how to call device drivers. Since device support varies widely from device to device, the set of routines provided by a device driver is almost completely driver dependent. The only requirement is that routines report and init must be provided. Device support routines must, of course, know what routines are provided by a particular device driver.

File drvSup.h describes the format of a driver support entry table. File drvSup.ascii defines the supported device drivers.

 
Table of Contents Next Chapter