Chapter 11
Record Support

 11.1 Overview
 11.2 Overview of Record Processing
 11.3 Record Support and Device Support Entry Tables
 11.4 Example Record Support Module
  11.4.1 Declarations
  11.4.2 init_record
  11.4.3 process
  11.4.4 Miscellaneous Utility Routines
  11.4.5 Alarm Processing
  11.4.6 Raising Monitors
 11.5 Record Support Routines
  11.5.1 Generate Report of Each Field in Record
  11.5.2 Initialize Record Processing
  11.5.3 Initialize Specific Record
  11.5.4 Process Record
  11.5.5 Special Processing
  11.5.6 Get Value
  11.5.7 Convert dbAddr Definitions
  11.5.8 Get Array Information
  11.5.9 Put Array Information
  11.5.10 Get Units
  11.5.11 Get Precision
  11.5.12 Get Enumerated String
  11.5.13 Get Strings for Enumerated Field
  11.5.14 Put Enumerated String
  11.5.15 Get Graphic Double Information
  11.5.16 Get Control Double Information
  11.5.17 Get Alarm Double Information
 11.6 Global Record Support Routines
  11.6.1 Alarm Status and Severity
  11.6.2 Alarm Acknowledgment
  11.6.3 Generate Error: Process Variable Name, Caller, Message
  11.6.4 Generate Error: Status String, Record Name, Caller
  11.6.5 Generate Error: Record Name, Caller, Record Support Message
  11.6.6 Get Graphics Double
  11.6.7 Get Control Double
  11.6.8 Get Alarm Double
  11.6.9 Get Precision
  11.6.10 Get Time Stamp
  11.6.11 Forward link
  11.6.12 Initialize Constant Link
  11.6.13 Analog Value Deadband Check

11.1 Overview

The purpose of this chapter is to describe record support in sufficient detail such that a C programmer can write new record 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 previous chapters, 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 is used. 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 which are called by database access routines. These routines implement 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 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 its device support modules are the only source files that should include the record specific header files. Thus they will be the only routines that access record specific fields without going through database access.

11.2 Overview of Record Processing

The most important record support routine is process. This routine determines what record processing means. 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. Asynchronous records 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.

11.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 struct typed_rset data structure declared in recSup.h and defined by the specific record type. This use of a record support vector table isolates the iocCore software from the implementation details of each record type. Thus new record types can be defined without having to modify 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:

/⋆ record support entry table ⋆/ 
struct typed_rset { 
    long number; /⋆ number of support routines ⋆/ 
    long (⋆report)(void precord); 
    long (⋆init)(); 
    long (⋆init_record)(struct dbCommon precord, int pass); 
    long (⋆process)(struct dbCommon precord); 
    long (⋆special)(struct dbAddr paddr, int after); 
    long (⋆get_value)(void); /⋆ DEPRECATED set to NULL ⋆/ 
    long (⋆cvt_dbaddr)(struct dbAddr paddr); 
    long (⋆get_array_info)(struct dbAddr paddr, long no_elements, long offset); 
    long (⋆put_array_info)(struct dbAddr paddr, long nNew); 
    long (⋆get_units)(struct dbAddr paddr, char units); 
    long (⋆get_precision)(const struct dbAddr paddr, long precision); 
    long (⋆get_enum_str)(const struct dbAddr paddr, char pbuffer); 
    long (⋆get_enum_strs)(const struct dbAddr paddr, struct dbr_enumStrs p); 
    long (⋆put_enum_str)(const struct dbAddr paddr, const char pbuffer); 
    long (⋆get_graphic_double)(struct dbAddr paddr, struct dbr_grDouble p); 
    long (⋆get_control_double)(struct dbAddr paddr, struct dbr_ctrlDouble p); 
    long (⋆get_alarm_double)(struct dbAddr paddr, struct dbr_alDouble p); 
};

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 record instance⋆/ 
    DEVSUPFUN    get_ioint_info;   /⋆ get io interrupt info⋆/ 
    /⋆ 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 declared in dbCommon, contains the address of the DSET. It is given a value by iocInit.

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

11.4.1 Declarations

/⋆ Create RSET - Record Support Entry Table ⋆/ 
#define report NULL 
#define initialize NULL 
static long init_record(struct dbCommon ⋆, int); 
static long process(struct dbCommon ⋆); 
#define special NULL 
#define get_value NULL 
#define cvt_dbaddr NULL 
#define get_array_info NULL 
#define put_array_info NULL 
static long get_units(DBADDR ⋆, char ⋆); 
static long get_precision(const DBADDR ⋆, long ⋆); 
#define get_enum_str NULL 
#define get_enum_strs NULL 
#define put_enum_str NULL 
static long get_graphic_double(DBADDR ⋆, struct dbr_grDouble ⋆); 
static long get_control_double(DBADDR ⋆, struct dbr_ctrlDouble ⋆); 
static long get_alarm_double(DBADDR ⋆, struct dbr_alDouble ⋆); 
 
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 
}; 
epicsExportAddress(rset,xxxRSET); 
 
typedef struct xxxset { /⋆ xxx input dset ⋆/ 
    long number; 
    DEVSUPFUN dev_report; 
    DEVSUPFUN init; 
    DEVSUPFUN init_record; /⋆returns: (-1,0)=>(failure,success)⋆/ 
    DEVSUPFUN get_ioint_info; 
    DEVSUPFUN read_xxx; 
} xxxdset; 
 
static void checkAlarms(xxxRecord prec); 
static void monitor(xxxRecord prec);

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.

11.4.2 init_record

static long init_record(struct dbCommon pcommon, int pass) 
{ 
    xxxRecord prec = (xxxRecord ⋆) pcommon; 
    xxxdset pdset = (xxxdset ⋆) prec->dset; 
 
    if (pass == 0) 
        return 0; 
 
    if (!pdset) { 
        recGblRecordError(S_dev_noDSET, (void ⋆)prec, "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, (void ⋆)prec, "xxx:init_record"); 
        return S_dev_missingSup; 
    } 
 
    if (pdset->init_record) { 
        long status = pdset->init_record(prec); 
        if (status) 
            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).

11.4.3 process

static long process(struct dbCommon pcommon) 
{ 
    xxxRecord prec = (xxxRecord ⋆) pcommon; 
    xxxdset pdset = (xxxdset ⋆) prec->dset; 
    long status; 
    unsigned char pact = prec->pact; 
 
    if ((pdset==NULL) || (pdset->read_xxx==NULL)) { 
        prec->pact = TRUE; 
        recGblRecordError(S_dev_missingSup, (void ⋆)prec, "read_xxx"); 
        return S_dev_missingSup; 
    } 
 
    /⋆ pact must not be set until after calling device support ⋆/ 
    status = pdset->read_xxx(prec); 
 
    /⋆ check if device support set pact ⋆/ 
    if (!pact && prec->pact) return 0; 
    prec->pact = TRUE; 
 
    recGblGetTimeStamp(prec); 
 
    /⋆ check for alarms ⋆/ 
    checkAlarms(prec); 
 
    /⋆ check event list ⋆/ 
    monitor(prec); 
 
    /⋆ process the forward scan link record ⋆/ 
    recGblFwdLink(prec); 
 
    prec->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.

11.4.4 Miscellaneous Utility Routines

static long get_units(DBADDR paddr, char units) 
{ 
    xxxRecord prec = (xxxRecord ⋆) paddr->precord; 
 
    strncpy(units,prec->egu,DB_UNITS_SIZE); 
    return 0; 
} 
 
static long get_precision(const DBADDR paddr, long precision) 
{ 
    xxxRecord prec = (xxxRecord ⋆) paddr->precord; 
 
    precision = prec->prec; 
    if(paddr->pfield == (void ⋆)&prec->val) return(0); 
    recGblGetPrec(paddr,precision); 
    return 0; 
} 
 
static long get_graphic_double(DBADDR paddr,struct dbr_grDouble pgd) 
{ 
    xxxRecord prec = (xxxRecord ⋆) paddr->precord; 
    int fieldIndex = dbGetFieldIndex(paddr); 
 
    if(fieldIndex == xxxRecordVAL 
    || fieldIndex == xxxRecordHIHI 
    || fieldIndex == xxxRecordHIGH 
    || fieldIndex == xxxRecordLOW 
    || fieldIndex == xxxRecordLOLO 
    || fieldIndex == xxxRecordHOPR 
    || fieldIndex == xxxRecordLOPR) { 
        pgd->upper_disp_limit = prec->hopr; 
        pgd->lower_disp_limit = prec->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 the next section.

11.4.5 Alarm Processing

static void checkAlarms(xxxRecord prec) 
{ 
    double val; 
    float hyst, lalm, hihi, high, low, lolo; 
    unsigned short hhsv, llsv, hsv, lsv; 
 
    if (prec->udf) { 
        recGblSetSevr(prec, UDF_ALARM, prec->udfs); 
        return; 
    } 
    hihi = prec->hihi; lolo = prec->lolo; high = prec->high; low = prec->low; 
    hhsv = prec->hhsv; llsv = prec->llsv; hsv = prec->hsv; lsv = prec->lsv; 
    val = prec->val; hyst = prec->hyst; lalm = prec->lalm; 
 
    /⋆ alarm condition hihi ⋆/ 
    if (hhsv && (val >= hihi || ((lalm==hihi) && (val >= hihi-hyst)))){ 
        if (recGblSetSevr(prec, HIHI_ALARM, prec->hhsv)) 
            prec->lalm = hihi; 
        return; 
    } 
 
    /⋆ alarm condition lolo ⋆/ 
    if (llsv && (val <= lolo || ((lalm==lolo) && (val <= lolo+hyst)))){ 
        if (recGblSetSevr(prec, LOLO_ALARM, prec->llsv)) 
            prec->lalm = lolo; 
        return; 
    } 
 
    /⋆ alarm condition high ⋆/ 
    if (hsv && (val >= high || ((lalm==high) && (val >= high-hyst)))){ 
        if (recGblSetSevr(prec, HIGH_ALARM, prec->hsv)) 
            prec->lalm = high; 
        return; 
    } 
 
    /⋆ alarm condition low ⋆/ 
    if (lsv && (val <= low || ((lalm==low) && (val <= low+hyst)))){ 
        if (recGblSetSevr(prec, LOW_ALARM, prec->lsv)) 
            prec->lalm = low; 
        return; 
    } 
 
    /⋆ we get here only if val is out of alarm by at least hyst ⋆/ 
    prec->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. It honors the hysteresis only when the value is going to a lower alarm severity.

Note the test:

    if (prec->udf) { 
        recGblSetSevr(prec, UDF_ALARM, prec->udfs); 
        return; 
    }

Database common defines the field UDF, which should be set when the field VAL is undefined. The field UDFS controls the severity of the record in undefined state. The STAT and SEVR fields are initialized as if
recGblSetSevr(prec, UDF_ALARM, prec->udfs)was called. Thus if the record is never processed the record will be in an UNDEFINED alarm state with severity as set by the record’s UDFS field. Field UDF is initialized to the value 1 (true). Thus the above code will keep the record in the alarm state until UDF is reset to 0 (false).

The UDF field being non-zero means the record is undefined, i.e. that the contents of its VAL field don’t represent an actual value. When records are loaded into an ioc this is usually the initial state of the records. Whevever code sets the VAL field it should also set UDF, normally to false. UDF may be set to true for records whose VAL field is a DBF_FLOAT or DBF_DOUBLE when VAL gets set to a NaN (Not-a-number) value.

For input records device support is responsible for obtaining an input value. If no input value can be obtained neither record support nor device support sets UDF false. If device support reads a raw value it returns a value telling record support to perform a conversion. After the record support sets VAL equal to the converted value, it sets UDF false. If device support obtains a converted value that it writes to VAL, it sets UDF false.

For output records either something outside record/device support writes to the VAL field or else VAL is given a value because record support obtains a value via the OMSL field. In either case the code that writes to the VAL field sets UDF false.

Whenever database access writes to the VAL field it sets UDF false.

Routine recGblSetSevr is called to raise alarms. It can be called by iocCore, record support, or device support. The code that detects an alarm is responsible for raising the alarm.

11.4.6 Raising Monitors

static void monitor(xxxRecord prec) 
{ 
    unsigned short monitor_mask = recGblResetAlarms(prec); 
    double delta = prec->mlst - prec->val; 
 
    /⋆ check for value change ⋆/ 
    if (delta < 0.0) delta = -delta; 
    if (delta > prec->mdel) { 
        /⋆ post events for value change ⋆/ 
        monitor_mask |= DBE_VALUE; 
        /⋆ update last value monitored ⋆/ 
        prec->mlst = prec->val; 
    } 
 
    /⋆ check for archive change ⋆/ 
    delta = prec->alst - prec->val; 
    if (delta < 0.0) delta = -delta; 
    if (delta > prec->adel) { 
        /⋆ post events on value field for archive change ⋆/ 
        monitor_mask |= DBE_LOG; 
        /⋆ update last archive value monitored ⋆/ 
        prec->alst = prec->val; 
    } 
 
    /⋆ send out monitors connected to the value field ⋆/ 
    if (monitor_mask) { 
        db_post_events(prec, &prec->val, monitor_mask); 
    } 
}

All record types should call recGblResetAlarms as shown. 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.

Analog type records should also provide monitor and archive hysteresis fields as shown by this example.

db_post_events results in channel access issuing monitors for clients attached to the record and field. The call is

int db_post_events(void precord, void pfield, 
    unsigned int monitor_mask)

where:

precord - The address of the record
pfield - The address of the field
monitor_mask - A bit mask that can be any combinations of the following:
DBE_ALARM - A change of alarm state has occured. This is set by recGblResetAlarms.
DBE_LOG - Archived value update.
DBE_VAL - Value update.
DBE_PROPERTY - Value property update.

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.

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

11.5.1 Generate Report of Each Field in Record

long report(void precord);

This routine is not used by most record types. Any action is record type specific.

11.5.2 Initialize Record Processing

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

11.5.3 Initialize Specific Record

long (⋆init_record)(struct dbCommon precord, 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:

11.5.4 Process Record

long (⋆process)(struct dbCommon precord);

This routine must follow the guidelines specified previously.

11.5.5 Special Processing

long special(struct dbAddr paddr, int after);

This routine implements the record type specific special processing for the field referred to by dbAddr. It is called twice when a field is written to from outside the record, once with after=0 before any changes are made to the field, and again with after=1 after the change has been made. The routine can prevent any changes from being made by returning an error status from the first call (after=0). File special.h defines special types. This routine is only called for user special fields, i.e. fields with SPC_xxx >= 100. A field is declared special in the ASCII record definition file. New values should not by added to special.h, instead use SPC_MOD.

The database access routine, dbGetFieldIndex can be used to determine which field is being modified.

11.5.6 Get Value

This routine is no longer used. It should be left as a NULL procedure in the record support entry table.

11.5.7 Convert dbAddr Definitions

long 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, special,pfield, and dbr_type. 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.

The database access routine, dbGetFieldIndex can be used to determine which field is being modified.

NOTES:

11.5.8 Get Array Information

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

The database access routine, dbGetFieldIndex can be used to determine which field is being modified. It is permissible for get_array_info to change pfield. This feature can be used to implement double buffering.

When an array field is being written get_array_info is called before the field values are changed.

11.5.9 Put Array Information

long put_array_info(struct dbAddr paddr, long nNew);

This routine is called after new values have been placed in the specified array.

The database access routine, dbGetFieldIndex can be used to determine which field is being modified.

11.5.10 Get Units

long get_units(struct dbAddr paddr, char units);

This routine sets units equal to the engineering units for the field.

The database access routine, dbGetFieldIndex can be used to determine which field is being modified.

11.5.11 Get Precision

long get_precision(const 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. recGblGetPrec should be called for fields not directly related to the value field.

The database access routine, dbGetFieldIndex can be used to determine which field is being modified.

11.5.12 Get Enumerated String

long get_enum_str(const struct dbAddr paddr, char pbuffer);

This routine sets ⋆p equal to the ASCII string for the field value. The field must have type DBF_ENUM.

Look at the code for the bi or mbbi records for examples.

The database access routine, dbGetFieldIndex can be used to determine which field is being modified.

11.5.13 Get Strings for Enumerated Field

long get_enum_strs(const struct dbAddr paddr, struct dbr_enumStrs p);

This routine gives values to all fields of structure dbr_enumStrs.

Look at the code for the bi or mbbi records for examples.

The database access routine, dbGetFieldIndex can be used to determine which field is being modified.

11.5.14 Put Enumerated String

long put_enum_str(const struct dbAddr paddr, char pbuffer);

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.

Look at the code for the bi or mbbi records for examples.

The database access routine, dbGetFieldIndex can be used to determine which field is being modified.

11.5.15 Get Graphic Double Information

long get_graphic_double(struct dbAddr paddr, struct dbr_grDouble p);

This routine fills in the graphics related fields of structure dbr_grDouble. recGblGetGraphicDouble should be called for fields not directly related to the value field.

The database access routine, dbGetFieldIndex can be used to determine which field is being modified.

11.5.16 Get Control Double Information

long get_control_double(struct dbAddr paddr, struct dbr_ctrlDouble p);

This routine gives values to all fields of structure dbr_ctrlDouble. recGblGetControlDouble should be called for fields not directly related to the value field.

The database access routine, dbGetFieldIndex can be used to determine which field is being modified.

11.5.17 Get Alarm Double Information

long get_alarm_double(struct dbAddr paddr, struct dbr_alDouble p);

This routine gives values to all fields of structure dbr_alDouble.

The database access routine, dbGetFieldIndex can be used to determine which field is being modified.

11.6 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”. Code using these routines should

#include <recGbl.h>

11.6.1 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 for stat and sevr 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.

int recGblSetSevr(void precord, short nsta, short nsev);

Returns TRUE if it changed nsta and/or nsev, FALSE if it did not change them.

unsigned short recGblResetAlarms(void precord);

Returns: Initial value for monitor_mask

11.6.2 Alarm Acknowledgment

Database common contains two additional alarm related fields:

These fields are handled by iocCore and recGblResetAlarms and should not be used by record support. The alarm acknowledgement facility it provided for use by alarm handlers.

11.6.3 Generate Error: Process Variable Name, Caller, Message

SUGGESTION: use errlogPrintf instead of this for new code.

void recGblDbaddrError( 
    long   status, 
    struct dbAddr  paddr, 
    char   pcaller_name); /⋆ calling routine name ⋆/

This routine interfaces with the system wide error handling system to display the following information: Status information, process variable name, calling routine.

11.6.4 Generate Error: Status String, Record Name, Caller

SUGGESTION: use errlogPrintf instead of this for new code.

void recGblRecordError( 
    long   status, 
    void   precord, 
    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.

11.6.5 Generate Error: Record Name, Caller, Record Support Message

SUGGESTION: use errlogPrintf instead of this for new code.

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

11.6.6 Get Graphics Double

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

11.6.7 Get Control Double

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

11.6.8 Get Alarm Double

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

11.6.9 Get Precision

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

11.6.10 Get Time Stamp

void recGblGetTimeStamp(void precord)

This routine gets the current time stamp and puts it in the record It does the following:

11.6.11 Forward link

void recGblFwdLink(void precord);

This routine can be used by process to request processing of forward links.

11.6.12 Initialize Constant Link

int recGblInitConstantLink( 
    struct link  plink, 
    short  dbfType, 
    void   pdest);

Initialize a constant link. This routine is usually called by init_record (or by associated device support) to initialize the field associated with a constant link. It returns(FALSE, TRUE) if it (did not, did) modify the destination.

11.6.13 Analog Value Deadband Check

void recGblCheckDeadband( 
    epicsFloat64 poldval, 
    const epicsFloat64 newval, 
    const epicsFloat64 deadband, 
    unsigned monitor_mask, 
    const unsigned add_mask);

Check if analog (double) value is outside specified deadband, and set bits in monitor mask. This routine is usually called by an analog record’s monitor (as part of processing) to check if a value is outside a predefined deadband. It also set bits in a monitor mask according to the check result. If newval lies outside the specified deadband, newval is copied into ⋆poldval, and add_mask is OR’ed into monitor_mask.