Link Support
The future of Device Support & Soft Links?
Andrew Johnson
APS
18 May 1999
Executive summary
This proposal solves the long-term inflexibility of hardware
link addresses, allowing device support to define arbitrary structures
for address information. A new functional layer is introduced called link
support, which encapsulates the existing Channel Access and Database link
operations and allows new link types to be added in a similar manner to
adding new record and device types. The ability to automatically convert
existing database files to the new formats is taken as a prerequisite.
The DTYP field is removed from dbCommon. Device support entries in the
dbd file will take the form:
devicetype(rectype,linkname,dset,"description"){
field definitions ...
}
The new link support layers will declare an entry:
linktype(dbftype,linkname,lset,"description"){
field definitions ...
}
All link field addresses, whether referring to device or link support,
will be defined within a single string thus:
linkname(arg1,arg2,...)
Correct operation of updated soft channel device support layers requires
device support to be informed before and after a link address changes.
Introduction of an optional Device Support eXtension Table is proposed
which enhanced devices that wish to support online address changes must
register at initialization time.
The proposal retains more backwards compatibility than previously suggested
solutions, and allows device support developers to decide how much effort
to expend when converting existing software.
Problems this proposal addresses
-
Link types are defined in link.h and have to be specially coded in dbStaticLib.
-
The DTYP field is intimately connected with an INP or OUT field, requiring
more special code in dbStaticLib.
-
It is therefore too expensive to create new link types for new device support
as this would involve changes to the EPICS source code.
-
As a result, device support writers often use INST_IO links and parse the
string within their device support code, so additional parsing code has
to be written, debugged and loaded into the IOCs.
-
Database Configuration Tools (DCTs) have no knowledge of the sub-format
of these INST_IO address strings and can't aid the Database designer.
-
The syntax of these INST_IO address strings varies between different device
supports and has to be learned anew for each.
-
link.h contains private implementation details for PV links, which resolve
to either Database or Channel Access links.
-
If a spelling error occurs in the name of a Database link, it will probably
resolve to an unconnected Channel Access link which is not immediately
obvious at start-up. There is no way for a database designer to indicate
that a link must be found within this IOC, ensuring an error will be raised
instead.
-
The legality of a constant link value cannot be checked by a DCT because
only the record support code knows which field the link initializes and
hence the native type of the constant.
-
Most record link fields are conceptually different to a Device Support
I/O link, yet both are implemented using the same DBF_INLINK and DBF_OUTLINK
field types.
Requirements for new Link Support
-
Remove the hidden connection between DTYP and INP/OUT fields.
-
Addresses must be easy to define/parse in DCTs and iocCore (dbStaticLib
and runtime support).
-
Device Support must be able to define the format of the address information,
allowing the addition of new address types.
-
Addresses must be representable as a single short string, so a link address
can be changed in a single Channel Access put, using the same address syntax
as used in the .db file.
-
Existing databases must be convertible into the new format without human
intervention for each record instance.
-
Ideally this should also be independent of any knowledge about individual
record types, although this is acceptable if it is parameterized in a conversion
information file.
-
Automatic conversion of device addresses may be difficult if the old support
parses an INST_IO string itself but the new support does not.
-
Should re-use existing .dbd file syntax where possible.
-
The impact on existing record and device support code should be minimized,
although some changes are inevitable.
Proposal
-
Remove the DTYP field from dbCommon and its DBF_DEVICE field type.
-
Add a new link field type DBF_DEVLINK
-
Change the `controlling' INP/OUT field of all I/O record types to be DBF_DEVLINK.
-
At most one DBF_DEVLINK field may exist in each record type.
-
Reasoning: A record has only one DSET and one DPVT field, and these must
remain part of the record rather than the link for backwards compatibility
-- there are just too many references in the source code out there to change
this.
-
Anyone wanting a record type with multiple device support interfaces can
combine them in a single device support interface. The interface to these
devices would have to be very different from existing device support anyhow
(PACT, DPVT etc.).
-
Remove all hardware link types from link.h. Other changes will also be
needed here relating to the DBF_...LINK types.
-
Replace the .dbd device keyword with devicetype and add
a new keyword linktype
-
DBF_DEVLINK field addresses are selected from the list of devicetype
definitions for their record type.
-
DBF_INLINK, DBF_OUTLINK and DBF_FWDLINK field addresses are selected from
the list of linktype definitions for their field type.
-
The syntax for devicetype dbd entries is:
devicetype(rectype,linkname,dset,"description"){
field definitions ...
}
-
The syntax for linktype dbd entries is:
linktype(dbftype,linkname,lset,"description"){
field definitions ...
}
linkname should be fairly short and must follow the `C' rules for
identifier names.
-
A linkname may be reused with different rectype or dbftype
values.
lset is the symbol name of the Link Support Entry Table for this
link type.
-
A single LSET can support all three link support types.
-
See the Implementation section below for
the table contents.
rectype is a record type name (ai, mbbo etc.)
dbftype is one of
-
DBF_INLINK
-
DBF_OUTLINK
-
DBF_FWDLINK
field definitions use the same syntax as for recordtype
entries. Some keywords such as special and interest might
be ignored or disallowed by the implementation.
-
devicetype and linktype .dbd file entries will be converted
into a typedef struct declaration as recordtype entries
are.
-
For a devicetype entry, the type will be named rectype_linknameDev,
in the file rectype_linknameDev.h.
-
For a linktype entry, the type will be named iof_linknameLnk
where iof is in, out or fwd according
to the dbftype, in the file iof_linknameLnk.h.
Link Addresses
-
All link address instances will become a string in the format:
linkname(arg1,arg2,...)
-
The fields and field order specified in the devicetype or linktype
entry for linkname determine the number, type and meanings of arg1,
arg2
etc.
-
Spaces will be ignored when parsing this unless they appear inside a string
or choice item.
-
Integer arguments (char, short, long) may be given in decimal, octal or
hex using the strtol or strtoul formats.
-
Real arguments (float, double) are converted using strtod.
-
Menu choice arguments will be compared to the strings defined for the indicated
menu type, and the resulting index placed in the unsigned short which is
created as the structure member. A string which is not recognized will
be examined as an unsigned integer, and if that also fails the result will
be the value USHRT_MAX from limits.h (65535 on most systems).
-
The maximum length of an address string is dictated by the Channel Access
protocol to be 40 characters, thus authors are encouraged to be concise
in their menu strings.
-
String and choice menu arguments need only be quoted if they contain commas
or parentheses, or have spaces at either end of the string. Either double
or single (" or ') quotes may be used, but must be correctly paired.
-
Note that within a .db file the address string is already inside double
quotes in a field statement which complicates this issue.
-
Arguments and commas may be omitted from the end of the list, in which
case a default value for missing arguments will be used if one has been
given using the initial keyword within the relevant devicetype
or linktype field definition. An all zeros bit pattern will be
used if no other default is given.
-
Arguments which are empty will also default as described.
-
Routines to parse an address string will be provided for use by dbStaticLib
and the IOC CA server support code.
-
Within a DCT a link type may be selected from a menu of the devicetype
or linktype description strings defined for that particular record
of field type. Once a particular link type has been selected, a form can
be provided for its address arguments.
-
For a DBF_DEVLINK field the linkname is used to determine the record's
DSET value and hence the device support for the record.
-
For the other DBF_...LINK field types the linkname is used to determine
the LSET value which is stored as part of the link and gives pointers to
the link support routines which implement its methods.
-
Examples of device support link addresses:
xy240(0,3,)
camac(0,1,5,0x20,5,)
HP4396a(1,0x36,)
can(sector2,20,0x137,4,0x3ff)
Process Variable Links
-
Change the current process variable links to use a generic Link Support
interface, like Record, Device and Driver Support.
-
Process variable link addresses using the new syntax would look like:
db(sr02:VP23:PC.PRES,PP,MS)
ca(sr04:VP45:PS.VAL,CP,NMS)
pv(sr02:VP23:PS.SCAN,NPP,MS)
The db type allows "Not Found" errors to be flagged at boot time,
not just assumed to be unavailable CA links.
The ca type implements the 3.13 .CA flag.
The pv type supports existing behaviour, searching first for a
DB link but using CA if not found.
Implementation
-
All DBF_...LINK field types including DBF_DEVLINK will be implemented using
the same link field structure.
-
The link field structure is declared in link.h as:
typedef struct link {
struct dbLnkPvt *dbPvt;
struct lset *lset;
char *raw;
void *value;
} link;
link->dbPvt is for internal use by the runtime database routines.
DBF_INLINK, DBF_OUTLINK and DBF_FWDLINK functionality is implemented by
routines whose addresses are found in the Link Support Entry Table (LSET),
the symbol name of which is given in the linktype definition.
For these link types a pointer to the LSET is stored in link->lset.
The link->lset of a DBF_DEVLINK field will only be used when soft
channel device support is selected for the record. The Device Support Entry
Table address will be stored in the record's DSET field as at present.
The routines pointed to by a LSET are not compatible with those in a DSET
-- Link Support is very different to Device Support.
The same LSET can be used for DBF_INLINK, DBF_OUTLINK and DBF_FWDLINK types
if the relevant table entries are implemented.
An LSET has the following structure (open for discussion):
typedef struct lset {
void (*report) (link *plink, int level);
long (*init) (int after);
long (*add) (link *plink, short dbfType, dbCommon *precord);
long (*delete) (link *plink);
long (*get) (link *plink, short dbrType, void *pdest, long *nelems);
long (*put) (link *plink, short dbrType, void *psrce, long *nelems);
long (*scan) (link *plink);
} lset;
Where a link type does not implement particular routines, the pointers
must be NULL.
The above LSET structure is declared in lnkSup.h. All definitions (instances)
of LSETs must use this structure declaration to allow additional routines
to be added transparently in the future.
All link types must provide the routines add and delete.
Link types which support DBF_INLINK must provide a get routine.
Link types which support DBF_OUTLINK must provide a put routine.
Link types which support DBF_FWDLINK must provide a scan routine.
The report routine can be called with plink pointing
to a particular link (asking for a status report for that link), or containing
NULL (asking for a general status report on that link support layer). The
link support software may implement neither, either or (preferably) both
of these cases.
A new debugging command dblr will be provided to print link status
information by calling the report routines.
Changing link addresses at run-time
-
The lset->delete routine allows a link support to be told when
one of its links is being changed by a CA client or some other database
put method. The link support may cancel the change by returning an error
code.
-
The lset->add routine allows a link support to be offered a new
link address. It may refuse the link by returning an error code.
-
Address changes will be implemented in iocCore as follows:
-
Parse and check the new link address string .
-
Call lset->delete.
-
Free the old link value structure memory.
-
Find and set the new LSET address.
-
Allocate memory for and fill in the new link value structure.
-
Call lset->add.
-
Note that for the database link type at least, these calls to delete
and add may occur within the context of a call to put
on a different link (a link could potentially be asked to delete itself
if it pointed to itself, although this condition should probably be tested
for and rejected by the add routine).
-
During iocInit, the add routines for all link fields will be called
between the two passes of record support initialization.
-
Equivalent add and delete functionality is not yet available
for device support, but is essential for the correct implementation of
the soft channel device support routines.
-
The following proposed solution is compatible with existing device support
layers (for which it prohibits online address changes), and provides sufficient
functionality to properly implement link support. Additional functionality
may be added but falls outside the remit of this link support proposal.
Changing device support address at run-time
-
During the first pass of device support initialization (the dset->init(0)
call), a support layer that permits online link address changes must call
devExtend(&dset,&dsxt).
-
The arguments to this routine are pointers to the DSET and to a new Device
Support eXtension Table, which looks like this:
typedef struct dsxt {
long (*add) (struct dbCommon *precord);
long (*delete) (struct dbCommon *precord);
} dsxt;
This structure will be declared in devSup.h. Extensions are intended to
be independent of the record type, which should add new device routines
to the DSET if required.
Other routines may be added to the DSXT in the future. The devSup.h structure
declaration must be used for all definitions (instances) so future additions
can be made transparently.
Entries for the dsxt->add and dsxt->delete routines are
optional, and these will be called just as described for link support above.
It is recommended that either both or neither routine should be provided
-- having either one without the other is likely to cause confusion.
Records using a device support that has not registered a DSXT or where
the
dsxt->delete pointer is NULL cannot have their DBF_DEVLINK
field changed (the put will be rejected).
A DBF_DEVLINK address cannot be changed to a device support that has not
registered a DSXT or where the dsxt->add pointer is NULL.
-
All I/O record types use the second pass of record initialization to call
the
dset->init_record routine, therefore at iocInit the order
of device support initialization calls will be
-
dset->init(0) calls devExtend(dset,dsxt)
-
dsxt->add(precord) for all records
-
dset->init_record(precord) for all records
-
dset->init(1)
-
Extended device support may perform link-specific initialization in the
dsxt->add
routine (recommended), the dset->init_record routine, both or
neither.
-
Initialization use of the dset->init_record routine or the second
pass of the dset->init routine should be considered with care,
as no equivalent calls will be made after an online device address change.
-
A record that is I/O Interrupt scanned will be set to Passive before the
old link is deleted, and back to I/O Interrupt after the new link has been
added. This allows the new link type to reject the scan method if not supported,
or to create a new IOSCANPVT entry if it is.
-
A new routine scanIoFree will be provided which releases memory
associated with an IOSCANPVT pointer. This routine should be called from
dsxt->delete
if an interrupt source will no longer be relevant after this link deletion.
Changes to Record Support
-
The only changes needed to existing record support code involves removing
the test for and initialization of constant links.
-
See the section below on Constant links for
a discussion on how this functionality should be replaced or reimplemented.
Converting existing Device Support
-
The EPICS 3.13 device support definition for vxWorks Variable ai support
is:
device(ai,INST_IO,devAiSymb,"vxWorks Variable")
-
Its replacement may look like this:
devicetype(ai,symbol,devAiSymb,"vxWorks Variable"){
include "inst_io.dbd"
}
-
The file inst_io.dbd is provided for the convenience of existing device
support and backwards compatibility. It will contain:
field(string,DBF_STRING){
prompt("Parameter String")
size(40)
}
Similar files will be provided for all other existing hardware link types.
In practice it is expected that during conversion many devices will define
their own field definitions as this allows an appropriate prompt string
to be given for each field, and code which currently parses the parm field
can be removed.
The following declaration will be generated from the above devicetype
entry in the file ai_symbolDev.h:
typedef struct ai_symbolDev {
char *string;
} ai_symbolDev;
-
The members of this structure are the same as those in the existing inst_io
type.
-
Changes required to the device support code will be:
-
#include "ai_symbolDev.h"
-
Testing prec->inp.type is no longer necessary (link.type
doesn't exist)
-
prec->inp.value is now a void * pointer. To access the
structure members, this pointer must be cast to a (struct ai_symbolDev
*)
-
A record instance calling up this device support would be:
record(ai,"$(ioc):myVariable"){
field(INP,"symbol(myVariable)")
}
The vxWorks Variable device support layer would be a good example for a
new link support type (see New link types
below); the above discussion is to demonstrate the conversion process needed
for existing device support.
New Device Support
-
The string dnet(plc205,X,010) could address a device with this
.dbd file:
menu(devDirectNetADDRTYPE){
choice(devDirectNetADDRTYPEV,"V")
choice(devDirectNetADDRTYPEX,"X")
choice(devDirectNetADDRTYPEY,"Y")
choice(devDirectNetADDRTYPET,"T")
}
devicetype(mbbi,dnet,devMbbiDnet,"DirectNet PLC"){
field(plcName,DBF_STRING){
prompt("DirectNet PLC Name")
}
field(addrTyp,DBF_MENU){
prompt("PLC Address type")
menu(devDirectNetADDRTYPE)
}
field(address,DBF_ULONG){
prompt("Memory Address")
}
field(private,DBF_NOACCESS){
prompt("Link Private")
extra("struct dnRef *private")
}
}
-
More information may be needed for each field (promptgroup, initial etc.),
but this gives the general idea.
-
The ability to include hidden fields like this could, for some device support
layers at least, remove all need to allocate memory for device private
storage.
Link Support
-
Each link support layer defines address fields appropriate for its own
purposes.
-
Additional fields also be needed to hold information private to the link
type.
-
The standard link type entries for PV, DB and CA links will be:
linktype(DBF_INLINK,pv,lnkPvLSET,"Process Variable Link"){
include "pvIn.dbd"
}
linktype(DBF_OUTLINK,pv,lnkPvLSET,"Process Variable Link"){
include "pvOut.dbd"
}
linktype(DBF_FWDLINK,pv,lnkPvLSET,"Process Variable Link"){
include "pvFwd.dbd"
}
linktype(DBF_INLINK,db,lnkDbLSET,"Database Link"){
include "dbIn.dbd"
}
linktype(DBF_OUTLINK,db,lnkDbLSET,"Database Link"){
include "dbOut.dbd"
}
linktype(DBF_FWDLINK,db,lnkDbLSET,"Database Link"){
include "dbFwd.dbd"
}
linktype(DBF_INLINK,ca,lnkDbLSET,"Channel Access Link"){
include "caIn.dbd"
}
linktype(DBF_OUTLINK,ca,lnkCaLSET,"Channel Access Link"){
include "caOut.dbd"
}
linktype(DBF_FWDLINK,ca,lnkCaLSET,"Channel Access Link"){
include "caFwd.dbd"
}
Note that the design fields of PV and DB links are identical, so I have
not shown the pv*.dbd files below.
The following choice menu definitions will be required somewhere:
menu(menuProcessPassive){
choice(menuProcessPassiveNPP,"NPP")
choice(menuProcessPassivePP,"PP")
}
menu(menuMaximizeSeverity){
choice(menuMaximizeSeverityNMS,"NMS")
choice(menuMaximizeSeverityMS,"MS")
}
menu(menuProcessOnChange){
choice(menuProcessOnChangeNCP,"NCP")
choice(menuProcessOnChangeCP,"CP")
choice(menuProcessOnChangeCPP,"CPP")
}
The include files contain field definitions as follows:
-
dbIn.dbd and dbOut.dbd are identical:
field(name,DBF_STRING){
prompt("Process Variable Name")
size(29)
}
field(pp,DBF_MENU){
prompt("Process Passive")
menu(menuProcessPassive)
}
field(ms,DBF_MENU){
prompt("Maximize Severity")
menu(menuMaximizeSeverity)
}
field(name,DBF_STRING){
prompt("Process Variable Name")
size(29)
}
caIn.dbd:
field(name,DBF_STRING){
prompt("Channel Name")
size(29)
}
field(cp,DBF_MENU){
prompt("Process on Change")
menu(menuProcessOnChange)
}
field(ms,DBF_MENU){
prompt("Maximize Severity")
menu(menuMaximizeSeverity)
}
caOut.dbd and caFwd.dbd also happen to be identical:
field(name,DBF_STRING){
prompt("Channel Name")
size(29)
}
Link fields are filled in by parsing the link address value in the record
instance just as with device links.
Unlike device support, link support routines don't need to know anything
about the record type they are part of. At runtime they are told only the
addresses of the record and of their own field, and where to get/put their
value from/to.
New link types can be added by individual sites as required.
Soft Channel Device Support
-
The soft channel and raw soft channel device support layers will still
be needed for each record type.
-
These have to define separate device types for each link type, for example:
devicetype(ai,pv,devAiPvDSET,"Process Variable Soft Link"){
include "pvIn.dbd"
}
devicetype(ai,db,devAiDbDSET,"Database Soft Link"){
include "dbIn.dbd"
}
devicetype(ai,ca,devAiCaDSET,"Channel Access Soft Link"){
include "caIn.dbd"
}
The above demonstrates why link support should use an include.dbd file
to define its fields -- repeat for every record type where a soft channel
version of the relevant link support is desired.
Raw soft channel support (does anyone actually use this?) will need different
link names to distinguish it from normal soft channel support, e.g.:
devicetype(ai,rpv,devAiRpvDSET,"Process Variable Raw Soft Link"){
include "pvIn.dbd"
}
devicetype(ai,rdb,devAiRdbDSET,"Database Raw Soft Link"){
include "dbIn.dbd"
}
devicetype(ai,rca,devAiRcaDSET,"Channel Access Raw Soft Link"){
include "caIn.dbd"
}
This may seem like a lot of new code, but most of the soft channel device
support routines for a single record type can be reused for all link types,
so the additional DSETs will not require much new code to be written.
-
The DBF_DEVLINK field contains a link->lset member which these
soft channel device support routines use.
-
The dset->init routine registers a DSXT during its first pass.
-
The dsxt->add routine copies the address of the relevant LSET
into the link->lset pointer of the record's DBF_DEVLINK field,
then calls the lset->add routine.
-
The dset->readWrite function can be generic to all link types
for this record type. It just calls the lset->get or lset->put
routines using the same link API as record support.
-
Any calls of dsxt->delete are passed onto lset->delete
and if accepted the link->lset is cleared. This action is also
generic for all link types associated with this record type.
It may be possible to provide a shortcut to implementing these soft channel
device support layers for each link type by using macros.
Constant links
-
Constant links are implemented as just another type of soft link:
linktype(DBF_INLINK,const,lnkConstLSET,"Constant"){
field(value,DBF_STRING) {
prompt("Value")
size(40)
}
}
const(5.3E-3)
const(IOC Rebooted)
-
The string inside the parentheses is stored as the link `address'.
-
When
getting a value from any link, the record code requests a
particular DBF_XXX type of result and provides the address of the destination
field. On the first get request to a constant link, the software
will attempt to convert the `address' string to that type. Subsequent
gets
do nothing, just return Ok.
-
This may produce slightly different behaviour to the existing implementation
in some cases, although it is not easy to see how or when.
-
This proposal still does not enable DCTs to check the legality of the constant's
value.
-
A better solution is to allow the target fields of all links to be set
by the DCTs, which does permit full checking.
-
Sites using CapFast might have to convert each constant link manually
if this solution were imposed. The constant link proposal allows etdb to
create const links, thus supporting existing schematics without change.
-
Constant links implemented this way are just another link type with their
own LSET and linktype entry. Sites can choose whether they wish to use
them or change all databases to setting the target field value instead.
New link type examples
-
Link support can provide the same functionality as the vxWorks symbol device
support without needing a separate record:
linktype(DBF_INLINK,sym,lnkSymLSET,"C variable"){
field(name,DBF_STRING){
prompt("Variable name")
size(40)
}
field(ftyp,DBF_MENU){
prompt("Variable type")
menu(menuFtype)
}
}
linktype(DBF_OUTLINK,sym,lnkSymLSET,"C variable"){
field(name,DBF_STRING){
prompt("Variable name")
size(40)
}
field(ftyp,DBF_MENU){
prompt("Variable type")
menu(menuFtype)
}
}
Why stop at variable names though? Links can call subroutines too:
linktype(DBF_INLINK,sub,lnkSubLSET,"C Subroutine"){
field(rtyp,DBF_MENU){
prompt("Return type")
menu(menuFtype)
}
field(name,DBF_STRING){
prompt("Subroutine name")
size(40)
}
field(private,DBF_NOACCESS){
prompt("Private pointer")
extra("void *private")
}
}
linktype(DBF_OUTLINK,sub,lnkSubLSET,"C Subroutine"){
field(name,DBF_STRING){
prompt("Subroutine name")
size(40)
}
field(atyp,DBF_MENU){
prompt("Value Argument type")
menu(menuFtype)
}
field(private,DBF_NOACCESS){
prompt("Private pointer")
extra("void *private")
}
}
linktype(DBF_FWDLINK,sub,lnkSubLSET,"C Subroutine"){
field(name,DBF_STRING){
prompt("Subroutine name")
size(40)
}
field(private,DBF_NOACCESS){
prompt("Private pointer")
extra("void *private")
}
}
Link support could also allow
Alternative hardware I/O models
Communication with IOCs running a different Channel Access server protocol
Connections to host databases (CORBA IIOP?)
Interoperation with other control system products