LCLS Controls
SLC-Aware IOC Design
This is a work-in-progress!!
Debbie Rogind 11/22/04
Last Modified: 2/2/05
Quick Links
dbcheck.c – DBS helper routines
The following document describes the software design for the SLC-aware IOC Database Input/Output (I/O) Utilities. The utility routines described in this document are both publicly available routines that are useful to all SLC-aware threads, as well as “helper” routines available to the database service (DBS) threads. This document includes a description of each utility, the calling parameters, error message generated, and the include files required to use them in a thread.
The database I/O utilities are re-entrant routines which allow SLC-aware IOC threads to read and store SLC supertype data that has been downloaded from the SLC Control System. The routines closely mirror some of the I/O routines in the legacy micro software. One change from the legacy code is that all of the data access (dbl*) utilities require list arguments in order to enforce list generation in a job thread’s initial setup, possible when the thread uses these for periodic updates during operations.
See the Database
Service Design document by Debbie Rogind.
Refer to General
Purpose Utilities document by Diane Fairley for explanation of some common
utilities used, such as slcGetJobIndexByName(), cvtToVMS() and cvtFromVMS().
See the SLC-Aware IOC Functional Requirements document by Stephanie Allison.
The following section provides a quick overview of the public database access utilities available to all threads.
Threads include: dblist.h in order to gain access to routines which create and free lists containing pointers to desired data. If a thread does not dblistalloc() specifically, dblist() will. All threads must perform a dblistfree() on each list prior to exiting. Refer to Data – Lists for a list discussion.
vmsstat_t dblist (const char * const prim_a,
const char * const unit_a,
const char * const secn_a,
dbheader_ts ** const dblist_pps)
vmsstat_t dblistalloc (const unsigned short bytes_in_element,
const unsigned short num_elements,
dbheader_ts ** const dbdata_pps)
vmsstat_t dblistfree (const dbheader_ts * const dbdata_or_list_ps)
Threads include: dbl.h
in order to gain access to routines which read and write data of all types,
including listing all units for a given primary (dblunits()). A dblget() or
dblput() call must be preceeded with dblist().
vmsstat_t dblunits (const char * const prim_a,
dbheader_ts ** const dbdata_pps)
vmsstat_t dblget (const dbheader_ts * const dblist_ps ,
dbheader_ts **const dbdata_pps)
vmsstat_t dblput (const dbheader_ts * const dblist_ps,
const dbheader_ts * const dbdata_ps)
Threads include:
dbget.h in order to gain access to routines to get different attributes for
their data, once retrieved. Many of these routines will be called by the
vxWorks/Cexp shells.
vmsstat_t dbgetUnitAsString(const unsigned short unit,
char * const unit_a)
char dbgetFormat (const char * const prim_a,
const char * const secn_a)
int dbgetWidth (const char * const prim_a,
const char * const secn_a)
int dbgetCount (const char * const prim_a,
const char * const unit_a,
const char * const secn_a)
vmsstat_t dbgetMeta (const char * const prim_a,
const char * const unit_a,
const char * const secn_a,
char * format,
int * width,
int * count)
const char * const dbVersion_a dbgetVersion (void)
Threads include:
dbupdate.h and call dbupdate() in order to update the Alpha with data
previously stored via dblput(). Threads must be willing to wait in order to
complete this call (refer to Updates to Alpha).
vmsstat_t dbupdate(const char * const jobName_a)
All of the above utilities return a 4 byte unsigned VMS condition code, vmsstat_t, defined in util/dbdef.hc or in util/micrdef.hc
All device arguments (prim_a, unit_a, secn_a), as well as jobName_a, are 4 chars, left justified, with blank-padding to fill out to 4 chars if necessary. “ALL*” is allowed for unit_a in dblist() and jobName in dbupdate(). Dblunits() returns a list of 4 char non-null-terminated, left justified, blank-padded unit names.
The dblput() and dblget() routines block waiting for access to the database (both utilities wait on dbRWMutex).
The dblput() routine stores the upper and lower bounds of its offset address into a dbupdateHiLo array indexed by job so that accumulated updated data for that job (or “ALL*” jobs) may be sent to the Alpha at any time by calling dbupdate(). dbupdate() blocks until the update to the Alpha is complete. The blocking time is up to 15 seconds (per job) plus accumulated blocking times for other jobs ahead of it in the queue. dblput() will also block if the calling job’s updates are currently being sent to the Alpha, as it must wait on the semaphore protecting the dbhilo update list. A slcStop() request will unblock a dblput() and/ or a dbupdate() within a second (a second is the granularity at which dbSend, the thread sending updates to the Alpha, checks its stop flag while processing data).
The dblist(), and dblunits() routines wait at the DbdownloadEvent_ps forever if the database does not yet exist (!dbgetExists()). This causes job threads, during initial setup, to block on their dblist()/dblunits() calls until after the SLC database has been downloaded. If the database was not downloaded successfully, dbgetExists() will remain false. In this unsuccessful case, when and if the database service threads exit (via stop flag), dbHdlr, prior to termination, will release the dbdownloadEvent_ps if set, thereby unblocking all threads waiting upon it. In this case, an error is returned since dbgetExists() is still false and the threads should terminate.
Dblget() and dblput() check dbgetExists() and return an error when trying to access the db and it does not exist. As a precaution, dbupdate() does the same.
Dblist(), dblunits, dblget() and dblput() compare the dbVersion_a in the list header with the latest global dbVersion_a downloaded from the Alpha for compatibility, and log an error message if they are out of synch.
Device threads (“Device Services” in DBS Block Diagram) manage two different kinds of lists:
1) dblist- list comprised of pointers :
· Each pointer in list is of type dbhEntry_ts * (see dbs/dbhash.h) and points to dictionary hash table entry containing all the meta data for a particular PRIM:UNIT:SECN
· dblist list is an output argument from dblistalloc(), dblist()
· dblist is an input argument for dblget(), dblput
Note: in the legacy system, a dblist is comprised of primary/secondary/unit offsets (which, when added together with dbnodes[supertype], address the actual data in memory)
and
2) dbdata -list comprised of actual data:
· units (4 char unit names)
o output argument from dblunits(), dblget()
· typed data
o any type of data
o input argument for dblput()
Both lists (dblist and dbdata) have a header structure, dbheader_ts, which does the bookkeeping for the list, while the rest of the list is populated with the data itself. The first header field dbVersion_a is stored for later comparison against the global dbVersion_a of the database (downloaded by the ‘DXUP’ DBS message upon SLC IOC boot, IPL, or upon DBEX coming online) when accessing data. The second element of dbheader_ts, max, is the length in bytes of the array as allocated from the memory pool by dblistalloc(). The third field in dbheader_ts, cur, is set to the number of bytes of data already stored in the list (minus the dbheader size), is initialized to 0 by dblistalloc(), and is updated by the dblistAdd() helper utility as it appends each data item to the list. Dbl* utilities can be called one after another, with the same list, when it makes sense, in order to tack on data to the same list. If at any time the dbl* utility detects that the list is too short (when (cur + bytes_to_add + sizeof(dbheader_ts) ) > max), it calls dblistalloc() to replace it with the next largest list from its memory pool. A thread can also reset the cur field to 0 in order to over-write with new data after the data has been used.
The dbheader_ts structure is defined as:
Name |
Data type |
Represents |
Dbheader_ts |
|
DB I/O list header |
dbVersion_a[8] Max Cur dat[1] |
Char unsigned short unsigned short unsigned short |
Version “stamp” at the time the list was generated; 8 bytes ASCII Max word len of list; includes this header Current len of list Data items … |
In addition to the utilities dblistalloc() and dblistfree(), there are macros to help with list management, such as to clear a list, and both kinds of lists (dbdata and dblist) can use these. Relevant macros patterned after the legacy code ref_c_inc:dbgetc.h, will be adapted.
The API is flexible such that a SLC-aware IOC thread can call dblistalloc() prior to calling dblist() or dblunits(), or it can let the relevant dbl*() call take care of allocating space for a list (as long as it has a valid pointer to pointer input argument).
SLC-aware IOC threads
that call the public DB I/O list-based utilities are required to free:
The legacy dballoc() API, and current API for dblistalloc() specifies input arguments for a) the number of elements and b) the number of bytes in the element. In order to use memory management, (the API remains the same), dblistalloc() will allocate lists in discrete sizes – small and big, using the product ( a x b ) to choose the best fit pool size. The actual bytes allocated for the list from the associated memory pool will be returned in the max field of the list. If the product (a x b) is too large to use the big list, then memory will be allocated from the heap.
The epics freeList facility will be used to help manage the two discrete list memory pools. The dblistalloc() initially decides from which pool to allocate based on (bytes_in_element x num_elements). If a small list later exceeds the max small list size, a big list is allocated, the small list is memcpy’ed to the big list, and the little list is freed. If the big list is exceeded, then a callocMustSucceed() is done; the big list is copied to the alloc’ed memory, max is set at (bytes_in_element x num_elements), and the big list is freed.
The functional flow for each utility is very dependent upon the dictionary structure. Please reference the SLC IOC Database Service Design section on Dictionary. This section also introduces the db hash table utilities (such as dbhFind()), available by including dbhash.h.
Threads include: dblist.h in order to gain access to routines which create and free lists containing pointers to desired data. If a thread does not dblistalloc() specifically, dblist() will. All threads must perform a dblistfree() on each list prior to exiting. Refer to Data – Lists for a list discussion.
vmsstat_t dblist (const char * const prim_a,
const char * const unit_a,
const char * const secn_a,
dbheader_ts **const dblist_pps):
dbcheckWithWait(dblist_pps)
if (unit = ALL*)
dbcheckPrim(prim_a, dblist_pps, &ptr), return if error
progress linearly through units link list with dictU_ps
pointers
progress linearly through secns list with
dictS_ps pointers
if
secn's match:
dbh_ps
= dictS_ps->dictEntry
if
(!(dbh_ps) || (!(dbh_ps->userPvt)), return error
else
dblistAdd(dblist_pps,
4, 1, dbh_ps), return if error
if no secn's match for this unit:
log an error and return now
(don't
bother with the other units)
until secn list end
until unit list end
else
dbh_ps = dbhFind(name = “primunitsecn”)
if (!dbh_ps) || (!dbh_ps->userPvt) log error
and return
else
dblistAdd(dblist_pps, 4, 1, dbh_ps), return if error
This utility allocates and initializes a data list from a freelist memory pool. The list freelist memory pools (small and big) are initialized by dbHdlr. If a requested list size exceeds the big list size, memory is allocated from the heap.
vmsstat_t dblistalloc (const unsigned short bytes_in_element,
const unsigned short num_elements,
dbheader_ts **const dbdata_pps):
If (dbdata_pps = NULL) return error
If (*dbdata_pps =NULL)
dblistNew()
else
if dbVersion has changed, return error
if not enough space in list for (num_elements*bytes_in_element)
dblistEnlarge()
All slc-aware IOC threads are responsible for freeing dblist and dbdata lists using this call.
vmsstat_t dblistfree (const dbheader_ts *const dbdata_or_list_pps):
Free back to small list pool, big list pool, or heap,
depending on “max” field in header
In the file dblistUtil.c, the following helper routines assist in data list
management for the public utilities contained in dblist.c.
void dblistInitFreeLists (void):
Initialize the small and big free
lists using the freeListLib epics facility. This routine is called upon DBS
initialization.
void dblistDelFreeLists
(void):
Remove the small and big free lists
using the freeListLib epics facility. This routine is called upon DBS exit.
vmsstat_t dblistAdd( const
unsigned short bytes_in_elem,
const
unsigned short num_elems,
const void *addMe_p,
dbheader_ts
** const db_pps):
if (!(*db_pps)) dblistalloc(), return if error
if (! addMe_p), return error
bytes_to_add = bytes_in_elem *
num_elems
if (bytes to add > (max-
(cur+sizeof(dbheader_ts)))
dblistalloc(), return if error
end if
copy *addMe_p to *db_ps
cur += bytes_to_add
This utility is called
by dblistalloc().
vmsstat_t dblistNew
(const unsigned long bytes_to_allocate,
dbheader_ts * dbdata_p):
if bytes_to_allocate < small free list size (=DBL_SZSM)
freeListCalloc(small free list)
dbdata_p->max = DBL_SZSM
else if bytes_to_allocate < big free list size (=DBL_SZBIG)
freeListCalloc(big free list)
dbdata_p->max = DBL_SZBIG
else callocMustSucceed (1, bytes_to_allocate) (=get from heap)
dbdata_p->max = bytes_to_allocate
dbdata_p->dbVersion_a = dbgetVersion()
This utility is called
by dblistalloc().
vmsstat_t
dblistEnlarge (const unsigned long bytes_to_allocate,
dbheader_ts * dbdata_ps) :
Allocate next biggest list:
If small list – get big list – freeListFree(small list)
If big – callocMustSucceed(bytes_to_allocate)
If heap list – get bigger heap list
Memcpy smaller list to larger list; keep previous list’s dbVersion_a,
and cur; adjust max
Threads include: dbl.h
in order to gain access to routines which read and write data of all types,
including listing all units for a given primary (dblunits()). A dblget() or dblput() call must be preceeded
with dblist().
vmsstat_t dblunits (const char * const prim_a ,
dbheader_ts **const dbdata_pps):
dbcheckWithWait(dbdata_pps),
return if error
dbcheckPrim(prim_a, dbdata_pps, &dbhEntry_ps), return if error
progress linearly through units link list
for each unit in linked list,
dblistAdd(dbdata_pps, 4, 1, name_a),
return if error
until the end of all units
vmsstat_t dblget (const dbheader_ts * const dblist_ps ,
dbheader_ts **const dbdata_pps):
dbcheck(dbdata_pps),
return if error
dbcheckHeader(dblist_ps),
return if error
For each ptr in dblist:
Access
dbhEntry_ts * ptr from *dblist_ps
Copy meta data pointed to by dbhEntry_ts * into local var, including:
format, width, count, supertype num (supn), offset ptr (sptr)
Lock dbRWMutex
Memcpy data from sbnode[supn] + sptr into **dbdata_pps,
size= width x count
Unlock dbRWMutex
cvtFromVMS(format, width, count, dbdata_pps, dbdata_pps)
Advance dblist_ps ptr, dbdata_pps ptr, etc
until end of all ptrs.
vmsstat_t dblput (const dbheader_ts * const dblist_ps , const dbheader_ts * const
dbdata_ps):
dbcheckHeader(dbdata_pps),
return if error
dbcheckHeader(dblist_ps),
return if error
For each ptr in dblist:
Access
dbhEntry_ts * ptr from *dblist_ps
Log an error and return if
going beyond cur in dbdata_ps
Copy meta data pointed to by dbhEntry_ts * into local var, including:
format, width, count, supertype num (supn), offset ptr (sptr)
Form vms_ptr to ST data using sbnode[supn]
+ sptr
Lock dbRWMutex
Convert native data to VMS, using meta data above:
cvtToVMS(format, width, count, dbdata_ps, vms_ptr),
Unlock dbRWMutex
Lo_sptr = sptr; hi_sptr = lo_sptr + ( width x count )
Call dbupdateHiLo(job, supn, hi_sptr, lo_sptr)
to enter offset pair into job’s dbupdateHiLo array
until end of all ptrs.
Threads include:
dbget.h in order to gain access to routines to get different attributes for
their data, once retrieved.
vmsstat_t dbgetUnitAsString(const int2u int_unit, char * const unit_a):
Convert integer unit to its ASCII representation :
4-char, non null-terminated, blank padded (if necessary), left justified.
Return error if unit > 9999
char dbgetFormat (const char * const prim_a, const char * const secn_a):
if (SUCCESS (dbgetMeta(prim_a, ALL*, secn_a, format, width, count))
return ( format )
else return (blank)
int dbgetWidth (const char * const prim_a, const char * const secn_a):
if (SUCCESS (dbgetMeta(prim_a, ALL*, secn_a, format, width, count))
return ( width )
else return (-1)
int dbgetCount (const char * const prim_a, const char * const unit_a,
const char * const secn_a):
if (SUCCESS (dbgetMeta(prim_a, unit_a, secn_a, format, width, count))
return ( count )
else return (-1)
This routine returns meta
data about a certain prim/unit/secn (unit can be ALL*). This routine could be
expanded to return all meta data (supn, sptr) as well.
vmsstat_t dbgetMeta (const char * const prim_a, const char * const unit_a, const char *
const secn_a, char * format, int * width, int * count):
if (strncmp(unit_a, ALL*) == 0)
dbh_ps = dbhFind(name = “prim”)
if (!dbh_ps ) return error of invalid primary name
if (! dbh_ps->usrPvt) return error of no units
firstUnit_a = dbh_ps->userPvt->listUnits.next->name_a
dbgetCatPUSName(prim_a, firstUnit_a, secn_a,
namePUS_a)
else /* known unit number */
dbgetCatPUSName(prim_a, unit_a, secn_a,
namePUS_a)
dbh_ps = dbhFind(namePUS_a)
if (dbh_ps != NULL) || (dbh_ps->usrPvt != NULL)
format = dbh_ps->usrPvt->format
width = dbh_ps->usrPvt->width
count = -1 /* flag unknown, for ALL* case */
else return error of invalid secondary
const char * const dbVersion_a dbgetdbVersion (void):
Returns pointer to global value dbVersion_a
void
dbgetCatPUSName(const char * prim_a,
const char *
unit_a,
const
char * secn_a,
char *
primUnitSecn_a) :
Concatenate the 4 char
each input arguments prim_a, unit_a, secn_a, into 12 char string
primUnitSecn_a.
void dbgetBlkPad (char
* name_pa) :
Replace first null terminator
character found and all chars thereafter (to right) with blank pads.
Threads include:
dbupdate.h and call dbupdate() in order to update the Alpha with data
previously stored via dblput(). Threads must be willing to wait in order to
complete this call (refer to Updates to Alpha).
(Note: This section will not be updated until
March 2005)
vmsstat_t dbupdate(const char * const jobName_a):
if (jobName != "ALL*")
job_id = slcGetJobIndexByName(jobName_a);
if (job_id < 0 ) log
an error and return
status = dbupdateSend(job_id, 1=wait)
else
status = OK
for job_id = 0 to N_JOBS
if ( dbupdateSend(job_id, 0=no wait) is not
OK ) update status
end
return
status
dbupdateUtil.c is
comprised of helper routines which the database service utilities call.
(Note: This section will not be updated until
March 2005)
vmsstat_t dbupdateSend(int jobId, unsigned short
supn, epicsBoolean waitFlag):
if (dbupdate
msg.id = DB_UPDATE
msg.job = jobId
send msg to dbSend,
return if error
if (waitFlag)
wait on
dbAckEvent[jobId]
return
dbAckStatus[jobId]
vmsstat_t dbupdateHiLo(int jobId, unsigned short supn, unsigned long hiSptr,
unsigned long loSptr):
access dbupateHiLo[supn, jobId]
if hiSptr/loSptr update is near to
DBUP_MAXLOOK items (?), combine
if list is already full, dbudpateHiLoCompress(jobId, supn, 1)
(adapt algorithm in legacy rmx_dbs:
dbmain.c – dbhilo_upate() )
vmsstat_t dbupdateHiLoCompress (int jobId,
epicsBoolean combine):
use same algorithm in legacy
rmx_dbs: dbmain.c - microdbsendc()
Dbcheck.c is comprised
of helper routines which the database service utilities call.
This routine is called
by utilities dblist(), and dblunits() in order to wait for the database to be
downloaded, if necessary.
vmsstat_t dbcheckWithWait(const dbheader_ts
** const db_pps):
if (!slcGetDbExists())
{
epicsEventMustWait(
dbdownloadEvent_ps)}
iss= dbcheck(db_pps)
return iss
This routine is called
directly by the utilities (dblget(), dblput(), dbcheckWithWait()) who do not need
to wait on the database to be downloaded.
vmsstat_t dbcheck(const dbheader_ts
** const db_pps):
if (!db_pps) log a message and return an error
iss = dbcheckHeader(*db_pps), return if error
iss = dbcheckExists()
return iss
This routine is called
by dbcheck() to check a list’s header values, thereby checking the integrity of
the list itself.
vmsstat_t dbcheckHeader(const
dbheader_ts * const db_ps):
if (!db_ps) log a message
and return (iss) error
if (!dbcheckExists()) return (iss) an error
if (cur > max) log a
message and return (iss) an error
iss = dbcheckVersion( db_ps->dbVersion_a)
return iss
This routine is called to verify that a primary entry exists, and to
allocate space for a list, if it hasn’t been allocated already. This routine is
called by utilities (dblist() and dblunits()) whose output argument is a list.
vmsstat_t dbcheckPrim(const char
* const prim_a,
dbheader_ts **
const db_pps,
dbhEntry_ts ** dbhEntry_pps):
if (!db_pps) || (!dbhEntry_pps)
log error and return
*dbhEntry_pps = dbhFind(prim_a), log an error and return if ptr is zero
if ( !(*db_pps)) {
iss = dblistalloc(1, default byte size,
db_pps) ,}
return iss
Note: dblistalloc() will allocate a small list buffer from the small free
list to satisfy above parameters.
This utility returns
an error if the database does not exist.
vmsstat_t dbcheckExists (void):
if (!slcGetDbExists())
return (iss) an error = DB_NODATA?
This utility returns
an error (DB_BADMAJOR or DB_BADMINOR?) if the version contained in the list
header (input argument) does not match the current DBEX dbVersion global value.
vmsstat_t dbcheckVersion(const char * dbVersion_a):
Returns error if (strncmp( dbVersion_a, dbgetVersion(), 8) != 0 )
(Note: This section will not be updated until
March 2005)
All dbl* utilites will return an error if the db does not exist and log a message to indicate the get or put attempt when database did not exist.
The dbcheckVersion() utility logs a message prior to returning false, or 0, meaning current db version is not in synchronization with the version of db when the list was formed.
A dblist() of either non-existent primary, unit, or secondary returns an error, except if unit = ALL*. (Thus, a mis-spelled device argument will result in an error. A dbunits() of non-existent primary returns an error.)
An invalid list for dblist(), dblunits, dlput(), dblget() logs an error message.
TBD