2. Core EPICS Device Functionality

The EPICS Device support module is used to create and manage a tight binding between EPICS PVs and a “driver” implementation. The so called “driver” layer implements the actual functionality of the IOC, while the “device” layer provided by this support module provides binding between the “driver” and EPICS.

The functionality described here is defined in the header file epics_device.h.

The design of this module implements a one-to-one correspondence between PVs and internally published PV names, and the builder support code is designed to help maintain this correspondence.

Using this module involves the following steps:

  1. The support module itself must be initialised by calling the function initialise_epics_device() before any PVs are declared.

  2. All PVs should be internally published using the PUBLISH() macros and numerous allies. This process establishes a set of valid internal PV names with associated processing actions: each PUBLISH() call binds one name to a processing action and possibly an initialisation action.

  3. If persistence is used then load_persistent_state() should now be called.

  4. Finally the IOC itself can be started. This involves at least the following steps:

    • Load the IOC .dbd file, which must include the epics_device.dbd file and complete database registration.

    • Load the database. This should contain record definitions corresponding to each internal PV name published above. The Database Builder Support chapter documents how to create this database.

    • Finally iocInit() can be called.

In this chapter we document the core PV publishing API.

2.1. Initialisation

One function is provided for initialisation.

error__t initialise_epics_device(void)

This function must be called at least once before publishing any records or calling any other function listed here. Repeated calls have no further effect.

This function can be called from the IOC shell, but in this case the return code is lost.

unsigned int check_unused_record_bindings(bool verbose)

This function is useful for checking for any mis-match between the published record bindings and the EPICS database. This should be called after iocInit() has been called, and will return the number of published bindings with no corresponding bound EPICS record. If verbose is set a message will be printed naming each missing binding.

pthread_mutex_t *set_default_epics_device_mutex(pthread_mutex_t *mutex)

Each record can associated with a mutex which is locked when the record is processed. The mutex can be specified by setting the .mutex attribute, or if this is not set then the default specified by this function is used. The previously set default is returned.

WITH_DEFAULT_MUTEX(mutex)

This macro can be used instead of calling set_default_epics_device_mutex() directly. Pass the mutex to be used as the mutex argument and follow this macro with a block of publishing code, for instance:

WITH_DEFAULT_MUTEX(mutex)
{
    PUBLISH(ao, 'AOUT', write)
}

This is equivalent to setting .mutex=mutex in every PUBLISH call, and the original mutex is restored on exit from the associated block.

Warning

Do not exit the guarded block with break or return.

2.2. PUBLISH Overview

The following record types are supported: ai, bi, longin, mbbi, stringin, ao, bo, longout, mbbo, stringout, waveform, together with aliases ulongin and ulongout. For the C datatype corresponding to each record type see TYPEOF().

Note that ulongin and ulongout are aliases for longin and longout, but supporting unsigned types internally. The data type over Channel Access is still signed, but internally the driver treats the values as unsigned.

EPICS records are published with the use of a variety of PUBLISH...() macros, defined below. Three classes of record are supported with slightly different macros and arguments. The table below summarises the options for record publishing.

IN records

Record types: [u]longin, ai, bi, stringin, mbbi

PUBLISH(record, name, read, .context, .io_intr, .set_time, .mutex)

PUBLISH_READ_VAR[_I](record, name, variable)

PUBLISH_READER[_I](record, name, reader)

PUBLISH_TRIGGER[_T](name)

OUT records

Record types: [u]longout, ao, bo, stringout, mbbo

PUBLISH(record, name, write, .init, .context, .persist, .mutex)

PUBLISH_WRITE_VAR[_P](record, name, variable)

PUBLISH_WRITER[_B][_P](record, name, writer)

PUBLISH_ACTION(name, action)

WAVEFORM records

Record type: waveform

Field types: char, short, int, float, double

PUBLISH_WAVEFORM(field_type, name, length, process, .init, .context, .persist, .io_intr, .mutex)

PUBLISH_WF_READ_VAR[_I](field_type, name, length, waveform)

PUBLISH_WF_READ_VAR_LEN[_I](field_type, name, max_len, len, waveform)

PUBLISH_WF_WRITE_VAR[_P](field_type, name, length, waveform)

PUBLISH_WF_WRITE_VAR_LEN[_P](field_type, name, max_len, len, waveform)

PUBLISH_WF_ACTION{,_I,_P}(field_type, name, length, action)

Suffixes:

_I

Sets .io_intr to enable I/O Intr scanning

_P

Sets .persist to enable persistent storage

_T

Sets .set_time to enable timestamp override

_B

Enables writer to return bool result

Throughout this document the dotted arguments are optional and should be specified using C99 named initialiser syntax, eg:

PUBLISH(longin, "RECORD", on_read, .context = read_context).

2.2.1. Common Datatypes

EPICS_STRING

This is a typedef:

typedef struct { char s[40]; } EPICS_STRING;

used for EPICS strings. This form of declaration allows strings to be passed by value and thus supports a more uniform interface to the EPICS Driver software. The helper function format_epics_string() should be used to modify values of this type.

bool format_epics_string(EPICS_STRING *s, const char *format, ...)

This helper function should be used for writing to a EPICS_STRING. This function wraps snprintf() to ensure that the string buffer does not overflow. false is returned if the formatted string is too long and has been truncated to fit.

struct epics_record

This is an opaque structure type used to represent the return value from calling a PUBLISH...() macro. The following functions can be called on values of this type depending on the underlying class of the defined record:

IN, WAVEFORM

trigger_record(), set_record_severity(), set_record_timestamp()

OUT

WRITE_OUT_RECORD

WAVEFORM

WRITE_OUT_RECORD_WF

IN, OUT

READ_RECORD_VALUE

WAVEFORM

READ_RECORD_VALUE_WF

2.3. PUBLISH API

All the PUBLISH...() macros in this section and the PUBLISH_WAVEFORM API section return values of type struct epics_record *.

TYPEOF(record)

record class record

Given one of the supported record type names listed in the table below, this macro computes the appropriate C datatype as shown:

In Record

Out Record

C Type

ai

ao

double

bi

bo

bool

longin

longout

int32_t

ulongin

ulongout

uint32_t

mbbi

mbbo

uint16_t

longin

longout

EPICS_STRING

Thus the list of valid identifiers for “record class” record is:

longin, ulongin, ai, bi, stringin, mbbi, longout, ulongout, ao, bo, stringout, mbbo

struct epics_record *PUBLISH(record, name, read, .context, .io_intr, .set_time, .mutex)
struct epics_record *PUBLISH(record, name, write, .init, .context, .persist, .mutex)

IN/OUT

record class record

const char *name

void *context

bool read(void *context, TYPEOF(record) *value)

IN

bool io_intr

IN

bool set_time

IN

bool write(void *context, TYPEOF(record) *value)

OUT

bool init(void *context, TYPEOF(record) *value)

OUT

bool persist

OUT

pthread_mutex_t *mutex

The PUBLISH macro is used to create a software binding for the appropriate record type to the given name. The corresponding read or write method will be called when the record processes, and the macro ensures proper type checking. Note that IN records and OUT records support different arguments, the first form is for IN records, the second for OUT records.

The macros documented below provide support for more specialised variants of these records with hard-wired implementations of the read and write methods.

Calling PUBLISH() returns a pointer to epics_record.

The arguments are as follows.

record

This identifies the record type, and must be one of longin, ulongin, ai, bi, stringin, mbbi for IN records or one of ulongout, longout, ao, bo, stringout, mbbo for OUT records. Using any other identifier will generate a cryptic compiler error.

name

This is the internal name for the PV and must be passed as a C string. The string will be copied before PUBLISH() returns, so dynamically generated strings can be used here. The same identifer should appear in the INP or OUT field of the record definition.

context

This is a void* pointer which can be used by the caller of PUBLISH() to bind the callbacks to any local context. This pointer is passed unchanged to the read, write, and init methods.

bool read(void *context, TYPEOF(record) *value)

For IN records this method will be called when the record is processed. If possible a valid value should be assigned to *value and true returned, otherwise false can be returned to indicate no value available, in which case the record will be marked as invalid.

bool write(void *context, TYPEOF(record) *value)

For OUT records this will be called on record processing with the value written to the record passed by reference. If the value is accepted then true should be return, otherwise if false is returned then value is treated as being rejected, the previous value of the record will be restored, and any associated Channel Access put will fail. Note that the value being written can be modified if required.

bool init(void *context, TYPEOF(record) *value)

For OUT records if this function is specified it will be called record initialisation to assign an initial value to the record unless a persistent initial value can be found. false can be returned to indicate failure. If persist is set and a value is successfully read from storage then this method will be ignored.

io_intr

If it is desired to operate an IN record with self generated triggering, i.e. with SCAN='I/O Intr' then this optional boolean flag must be set to true. If this is done record processing can then be triggered at any time by calling trigger_record(). The _I macro variants automatically set this flag.

Note that I/O Intr processing of OUT records is deliberately not supported.

set_time

It is possible for the driver software to specify the timestamp of IN records. This is done by setting TSE=-2 and setting this optional boolean flag to true. In this case set_record_timestamp() must be used to explicitly set the record timestamp each time it processes. The _T macro variant automatically sets this flag.

Again, this facility is deliberately not supported for OUT records.

persist

OUT records can be marked for “persistence” by setting this optional boolean flag to true. If this is set then during record initialisation (during iocInit()) the persistence store will be checked for an initial value which will be loaded into the record instead of calling its init function.

mutex

If a pthread mutex is specified here or is set by set_default_epics_device_mutex() then this mutex will be locked while calling the associated read, write, or process method.

The following macros provide shortcuts when setting the context and persist attributes of a record definition:

struct epics_record *PUBLISH_C(record, name, process, context, )

This macro publishes a record with the .context field set to context.

struct epics_record *PUBLISH_P(record, name, process, )

This macro publishes a persistent record with .persist set to true.

struct epics_record *PUBLISH_C_P(record, name, process, context, )

This macro combines the actions of PUBLISH_C and PUBLISH_P.

The following macros provide specialisation for specific types of record. See the descriptions for PUBLISH() above for descriptions of arguments not described below.

struct epics_record *PUBLISH_READ_VAR(record, name, variable, )
struct epics_record *PUBLISH_READ_VAR_I(record, name, variable, )

record class record

const char *name

TYPEOF(record) variable

The given variable will be read each time the record is processed. The variable must be of type TYPEOF(record) and should be passed by name to this macro.

struct epics_record *PUBLISH_READER(record, name, reader, )
struct epics_record *PUBLISH_READER_I(record, name, reader, )

record class record

const char *name

TYPEOF(record) reader(void)

This will be called each time the record processes and should return the value to be used to update the record.

struct epics_record *PUBLISH_TRIGGER(name, )
struct epics_record *PUBLISH_TRIGGER_T(name, )

const char *name

This record is useful for generating triggers into the database. The record type is set to bi and the io_intr flag is set. Call trigger_record() to make this record process, use FLNK in the database to build a useful processing chain.

The _T option is available for generating triggers with time specified by set_record_timestamp() before calling trigger_record().

struct epics_record *PUBLISH_WRITE_VAR(record, name, variable, )
struct epics_record *PUBLISH_WRITE_VAR_P(record, name, variable, )

record class record

const char *name

TYPEOF(record) variable

The variable is written each time the record is processed and is read on startup to initialise the associated EPICS record. The variable must be of type TYPEOF(record).

struct epics_record *PUBLISH_WRITER(record, name, writer, )
struct epics_record *PUBLISH_WRITER_P(record, name, writer, )

record class record

const char *name

void writer(TYPEOF(record) value)

This method will be called each time the record processes with the current value of the record.

struct epics_record *PUBLISH_WRITER_B(record, name, writer, )
struct epics_record *PUBLISH_WRITER_B_P(record, name, writer, )

record class record

const char *name

bool writer(TYPEOF(record) value)

This method will be called each time the record processes. The writer can return a boolean to optionally reject the write, otherwise void is returned and the write is unconditional.

struct epics_record *PUBLISH_ACTION(name, action, )

const char *name

void action(void)

This method is called when the record processes.

2.4. PUBLISH_WAVEFORM API

struct epics_record *PUBLISH_WAVEFORM(field_type, name, max_length, process, .init, .context, .persist, .io_intr)

type name field_type

const char *name

unsigned int max_length

void process(void *context, field_type array[max_length], unsigned int *length)

void init(void *context, field_type array[max_length], unsigned int *length)

void *context

bool persist

bool io_intr

pthread_mutex_t *mutex

This macro creates the software binding for waveform records with data of the specified type. The process method will be called each time the record processes – the process method can choose whether to implement reading or writing as the primitive operation. Again, a pointer to epics_record is returned which can be used for triggering and access.

EPICS waveform record support manages a buffer of length max_length. A pointer to this buffer is passed into the process and init functions defined here during record processing and initialisation (respectively); it’s up to the driver implementation to decide on the appropriate action to take.

The arguments are as follows:

field_type

This specifies the type of values in the waveforms handled by this record. One of the following identifiers must be used, otherwise a cryptic compiler error message will be generated, and the corresponding string must be written into the FTVL field:

C type

FTVL setting

char

'CHAR'

short

'SHORT'

int

'LONG'

float

'FLOAT'

double

'DOUBLE'

EPICS_STRING

'STRING'

Note that the int type is anomalous – although EPICS uses the description 'LONG' this must in fact be a 32-bit type. The current implementation of EPICS Device assumes sizeof(int) == sizeof(int32_t) and will fail on other targets. Clearly this can be fixed if necessary.

max_length

This specifies the number of points in the waveform and must match the value specified in the NELM field of the record.

name, context, io_intr, persist, mutex

As documented above for PUBLISH(). Note that as WAVEFORM records can act as either IN or OUT records, both types of functionality are supported.

void process(void *context, field_type array[max_length], unsigned int *length)

This is called during record processing with *length initialised with the current waveform length, as set in the NORD field of the the record. The array can be read or written as required and *length (and thus NORD) can be updated as appropriate if the data length changes (though of course max_length must not be exceeded).

void init(void *context, field_type array[max_length], unsigned int *length)

This optional function may be called during initialisation to initialise the waveform if a persistent value is not specified.

The following macros provide shortcuts when setting the context and persist attributes of a waveform definition:

struct epics_record *PUBLISH_WAVEFORM_C(field_type, name, length, process, context, )

This macro publishes a waveform record with the .context field set to context.

struct epics_record *PUBLISH_WAVEFORM_P(field_type, name, length, process, )

This macro publishes a persistent waveform record with .persist set to true.

struct epics_record *PUBLISH_WAVEFORM_C_P(field_type, name, length, process, context, )

This macro combines the actions of PUBLISH_WAVEFORM_C and PUBLISH_WAVEFORM_P.

struct epics_record *PUBLISH_WF_READ_VAR(field_type, name, max_length, waveform, )
struct epics_record *PUBLISH_WF_READ_VAR_I(field_type, name, max_length, waveform, )

type name field_type

const char *name

unsigned int max_length

field_type waveform[max_length]

waveform will be copied into the record buffer each time this record processes. This is useful for publishing internally generated waveforms.

struct epics_record *PUBLISH_WF_READ_VAR_LEN(field_type, name, max_length, length, waveform, )
struct epics_record *PUBLISH_WF_READ_VAR_LEN_I(field_type, name, max_length, length, waveform, )

type name field_type

const char *name

unsigned int max_length

const unsigned int *length

field_type waveform[max_length]

waveform will be copied into the record buffer each time this record processes. This is useful for publishing internally generated waveforms. length will be read and used to update the length of the waveform.

struct epics_record *PUBLISH_WF_WRITE_VAR(field_type, name, max_length, waveform, )
struct epics_record *PUBLISH_WF_WRITE_VAR_P(field_type, name, max_length, waveform, )

type name field_type

const char *name

unsigned int max_length

field_type waveform[max_length]

waveform will updated from the record each time the record processes.

struct epics_record *PUBLISH_WF_WRITE_VAR_LEN(field_type, name, max_length, length, waveform, )
struct epics_record *PUBLISH_WF_WRITE_VAR_LEN_P(field_type, name, max_length, length, waveform, )

type name field_type

const char *name

unsigned int max_length

unsigned int *length

field_type waveform[max_length]

waveform will updated from the record each time the record processes and length will be updated with the new length of waveform.

struct epics_record *PUBLISH_WF_ACTION(field_type, name, max_length, action, )
struct epics_record *PUBLISH_WF_ACTION_I(field_type, name, max_length, action, )
struct epics_record *PUBLISH_WF_ACTION_P(field_type, name, max_length, action, )

type name field_type

const char *name

unsigned int max_length

void action(field_type value[max_length])

This is called each time the record processes. It is up to the implementation of action to determine whether this is a read or a write action. PUBLISH_WF_ACTION()

2.5. Auxiliary API

A handful of auxiliary functions and macros allow some further processing of records.

void push_record_name_prefix(const char *prefix)
void pop_record_name_prefix(void)
void set_record_name_separator(const char *separator)

These two functions can be used to manage a string prefixed to the name of each record published by any of the PUBLISH macros. The list of pushed prefixes is prepended to the record name generated, and prefixes are deleted in reverse order. Each prefix is followed by the separator, which defaults to ":" at startup, but can be changed.

Note that when set_record_name_separator() is used to change the record name separator, the change only affects subsequent calls to push_record_name_prefix(), any existing prefix is unchanged.

WITH_NAME_PREFIX(prefix)

This macro is designed to be used instead of push_record_name_prefix() and pop_record_name_prefix(). Specify the record name prefix as the only argument, and follow with a block of record publishing code, for example:

WITH_NAME_PREFIX("prefix")
{
    PUBLISH(ai, "AIN", read_ain);
}

Warning

Do not exit the guarded block with break or return.

enum epics_alarm_severity

This is a copy of the base EPICS severity type with the following possible values:

enum name

Value

Meaning

epics_sev_none

0

Normal status, no alarm

epics_sev_minor

1

Minor alarm status

epics_sev_major

2

Major alarm status

epics_sev_invalid

3

PV value is invalid

void set_record_severity(struct epics_record *epics_record, enum epics_alarm_severity severity)

Can be used to update the reported record severity for any IN or WAVEFORM epics_record.

void set_record_timestamp(struct epics_record *epics_record, const struct timespec *timestamp)

If epics_record was published with set_time set then this function should be called before or as part of record processing to set the timestamp.

void trigger_record(struct epics_record *epics_record)

If epics_record was published with io_intr set then calling this function will trigger record processing.

struct epics_record *get_current_epics_record(void)

During record processing this will return the record being processed. At other times NULL is returned.

struct epics_record *LOOKUP_RECORD(record, name)

record class record

const char *name

returns struct epics_record*

If a record of the specified record class has been published with the given name this function returns a pointer to the epics_record structure for the record, otherwise NULL is returned.

bool WRITE_OUT_RECORD(record, epics_record, value, process)
bool WRITE_NAMED_RECORD(record, name, value)

record class record

struct epics_record *epics_record

const char *name

TYPEOF(record) value

bool process

The given value is written directly to the EPICS record associated with epics_record. process can be set to false to suppress normal record processing, otherwise normal record processing will occur and the driver’s write method will be called. false is returned if writing to the record fails, typically if the update is rejected during record processing.

The WRITE_NAMED_RECORD() variant includes an unchecked call to LOOKUP_RECORD() to translate a record name to the appropriate struct epics_record* value.

bool WRITE_OUT_RECORD_WF(field_type, epics_record, value, length, process)
bool WRITE_NAMED_RECORD_WF(field_type, name, value, length)

type name field_type

struct epics_record *epics_record

const char *name

const field_type value[length]

unsigned int length

bool process

As for WRITE_OUT_RECORD(), and WRITE_NAMED_RECORD() but for waveform records. The EPICS copy of the waveform is updated, and the record is processed or not as appropriate.

TYPEOF(record) READ_RECORD_VALUE(record, epics_record)
TYPEOF(record) READ_NAMED_RECORD(record, name)

record class record

struct epics_record *epics_record

const char *name

returns TYPEOF(record)

Returns the current value of any scalar record. Can be called with either epics_record or name which is subject to an unchecked lookup.

READ_RECORD_VALUE_WF(field_type, epics_record, value, length)
READ_NAMED_RECORD_WF(field_type, name, value, length)

type name field_type

struct epics_record *epics_record

const char *name

field_type value[length]

unsigned int length

Reads the current waveform value of a waveform record.

2.6. Utility Functions

Documentation for miscellaenous auxiliary functions.

void dump_epics_device_db(FILE *output)

This prints all currently published entries in the database to the given output object.