Writing C code for IOCs
Contents
vxWorks intro − − −
Calling C code from EPICS − − −
Major differences between vxWorks and Unix/Linux Using the vxWorks shell Programming techniques Subroutine records sub and genSub Soft device support State notation language
Compiling C code for IOCs −
Using driver.makefile and require
Major differences between vxWorks and Unix/Linux vxWorks has no programs but many threads (called "tasks"). − − −
vxWorks has no users. − −
The whole IOC is one "program". Parts of the IOC (modules, libraries, threads) are not independent. If any part of the "program" crashes, the whole IOC does. Everything runs as "root". (To be exact: in kernel space) Everybody can do everything.
vxWorks is optimized for speed – not for safety. −
You can overwrite memory, stack, interrupt tables, …
Consequences of the "one program" All functions exist inconcept the same "space". − −
Name clashes may appear between different modules (libraries). Use unique names (with prefix) for global functions!
−
Wrong: config, test, read_bi Right: drvXyConfig, fooTest, devAbc_read_bi
Or make functions static.
vxWorks has no main function. Every function (including the shell) can call any other function. −
You don’t start programs from the shell, you call functions.
Consequences of multi threading
Any problem in one thread affects the whole IOC. System resources are global to the whole IOC. − − −
Ending a thread does not clean up system resources. −
Memory File handles Semaphores The programmer (that's you!) must close files, free memory, etc.
Global data needs protection against concurrent access. −
Global variables
Boon and bane of unlimited memory access
Pro: Functions and threads can easily … − −
exchange large amounts of data by reference (pointers). access any hardware register (e.g. VME bus).
Con: Functions and threads can easily … − − − −
overrun allocated memory or stack size (esp. with arrays) overwrite system tables. (e.g. interrupt handler table at NULL) overwrite program code. modify global variables of other modules (e.g. drivers).
Global variables are EVIL!
Contents
vxWorks intro − − −
Calling C code from EPICS − − −
Major differences between vxWorks and Unix/Linux Using the vxWorks shell Programming techniques Subroutine records sub and genSub Soft device support State notation language
Compiling C code for IOCs −
Using driver.makefile and require
Accessing the vxWorks shell at SLS
Type rmc iocname, e.g. rmc MTEST-VME-T1. − − − −
The SLS specific command rmc stands for "remote minicom". It does ssh to a central server. It starts minicom on the server. The server is connected to the DEBUG port of the IOC.
You must be on the same network as the IOC. You may need your AFS password or the slsop password. If the IOC is connected to local Linux PC, use minicom.
vxWorks help
Online help: http://vxworks.web.psi.ch −
Important for beginners: VxWorks Programmer's Guide, Chapter 2
−
Always helpful: vxWorks Reference Manual
All about tasks, semaphores, watchdog timers, interrupts
All vxWorks system functions
Run-time help: Type help on the vxWorks shell. Separate talk on vxWorks debugging?
XTEST-VME-ID1 > help help ioHelp dbgHelp nfsHelp netHelp spyHelp timexHelp h i ti sp taskSpawn td ts tr d m mRegs pc
Print this list Print I/O utilities help info Print debugger help info Print nfs help info Print network help info Print task histogrammer help info Print execution timer help info [n] Print (or set) shell history [task] Summary of tasks' TCBs task Complete info on TCB for task adr,args... Spawn a task, pri=100, opt=0x19, stk=20000 name,pri,opt,stk,adr,args... Spawn a task task Delete a task task Suspend a task task Resume a task [adr[,nunits[,width]]] Display memory adr[,width] Modify memory [reg[,task]] Modify a task's registers interactively [task] Return task's program counter
Type <CR> to continue, Q<CR> to stop:
Calling functions from the vxWorks shell
Never call your main function main! −
The shell can pass up to 10 integer or string arguments. −
− −
Use a specific name, the name you would give a program on Linux.
or double shell arguments don't work on PPC architectures. No check is done by the shell. Check all arguments for sanity (numeric ranges, NULL strings, …). float
The shell can call functions in a separate task
Examples
Setting or creating global variables drvXyDebug = 1 str = "This is a string"
Calling functions printf (“String: %s, number: %d\n”, str, drvXyDebug) −
Note: Outermost parentheses are optional
Things that do not work − − −
C constructs (non-functions) like: if, switch, for, while, … Floating point: printf “%g\n”, 3.1415 More than 10 parameters
Contents
vxWorks intro − − −
Calling C code from EPICS − − −
Major differences between vxWorks and Unix/Linux Using the vxWorks shell Programming techniques Subroutine records sub and genSub Soft device support State notation language
Compiling C code for IOCs −
Using driver.makefile and require
Why global variables are evil (1)
Global variables with the same name in different modules are the same piece of memory. Problem: Two different modules may mutually overwrite their values. − Solution 1: Make variable local to one source file with static. Wrong Right − Solution 2: Prefix global variable name with module name. internal variable */ /* internal variable */ −
/* int card_count;
static int card_count;
/* external variable */ int debug=0;
/* external variable */ int drvXyDebug=0;
Why global variables are evil (2)
All instances (of threads, drivers, SNL programs …) share the same global variable. − −
Problem: Two instances mutually overwrite their values. Solution: Wrap variables in a struct, allocate one struct per instance. Wrong Right
/* values for one card */ static char* addr; static int ivec;
/* linked list */ struct drvPriv { struct drvPriv *next; char* addr; int ivec; } drvPriv; static drvPriv *first=NULL;
Debug and error messages are vital
Fail early and loud! Make messages descriptive. −
What happened where under which circumstances?
Bad: "error read" Good: "drvXyReadInteger
card 4 signal 2: read timeout after 5000
msec"
Write error and debug messages to stderr. Make debug messages switchable. (perhaps multiple levels) − −
global switch: int drvXyDebug=0; message: if (drvXyDebug>=2) fprintf(stderr,
…);
Be paranoid!
Error checking is the key to a stable system. −
Check arguments to API functions (esp. shell functions) −
Never trust a user! Not even yourself.
Always check pointer arguments for validity. −
Stability is limited by the weakest point!
Writing to NULL overwrites the interrupt handler table!
Check results of system functions (malloc, fopen, …) − −
System functions may fail and return NULL or ERROR. Using these values unchecked may crash the system much later.
Contents
vxWorks intro − − −
Calling C code from EPICS − − −
Major differences between vxWorks and Unix/Linux Using the vxWorks shell Programming techniques Subroutine records sub and genSub Soft device support State notation language
Compiling C code for IOCs −
Using driver.makefile and require
Subroutine record sub
12 input links INPA … INPL, 12 input fields A … L − − −
Record copies from links to fields before calling user function. Either use input link or write directly to input field. Input fields are all of type double.
User function can use A … L and writes result to VAL. SNAM field contains name of user function. INAM field contains name of optional init function. Functions get pointer to record and have access to all fields. −
Field names are lower case: a … l, val
Subroutine record user function
Inputs are in fields a … l, output goes to val (all double) Example: accumulate A*B to VAL #include <subRecord.h> int subAccu (struct subRecord* record) { record->val = record->val + record->a * record->b; return 0; }
Specify name of function in SNAM field of record. record (sub, "$(NAME)") { field (SNAM, "subAccu") field (INPA, "$(INPUT)") field (INPB, "$(SCALE)") }
Subroutine record initialization
Optional init function int subAccuInit (subRecord* record) { record->val = 1.0; return 0; }
Specify init function name in INAM field. record (sub, "$(NAME)") { field (SNAM, "subAccu") field (INAM, "subAccuInit") ... }
Init function runs only once at boot time.
Advanced: Asynchronous subroutine record
If function takes long time to complete... −
Run calculation in separate work thread with low priority.
− − −
Trigger work thread from record function. Return 1 from record function to signal: calculation not yet complete. Re-process record when calculation completes.
−
Setup thread in init function. Store data for inter-thread communication in dpvt field.
Use callbackRequestProcessCallback. pact field is 0 in first run and 1 in second run.
Return 0 from record function to signal: calculation complete.
Asynchronous subroutine stub #include #include #include #include #include
<subRecord.h> <callback.h> <taskLib.h> <semLib.h> <errno.h>
/* private data for record (stored in dpvt field) */ typedef struct { int status; /* error status */ double val; /* result */ SEM_ID trigger; /* trigger for work thread */ CALLBACK cb; /* callback for re-processing */ } asyncSubPriv; void myAsyncSubThread(struct subRecord* record); int myAsyncSub(struct subRecord* record); int myAsyncSubInit(struct subRecord* record);
Asynchronous subroutine work thread void myAsyncSubThread(struct subRecord* record) { asyncSubPriv* priv = record->dpvt; /* get private data */ while (1) { /* loop forever */ semTake(priv->trigger, WAIT_FOREVER); /* wait */ /* do calculations */ /* leave result in priv->val */ /* leave error status in priv->status */ /* re-process record */ callbackRequestProcessCallback( &priv->cb, record->prio, record); } }
Asynchronous subroutine user function int myAsyncSub(struct subRecord* record) { asyncSubPriv* priv = record->dpvt; /* get private data */ if (priv == NULL) { return ERROR; } /* INAM missing */ if (record->pact == 0) { /* first run */ semGive(priv->trigger); /* trigger work thread */ return 1; /* signal: not yet done */ } /*second run */ if (priv->status) { /* error in work thread */ return priv->status; } record->val = priv->val; /* update record */ add error messages return 0; /* signal: done */ }
here
Asynchronous subroutine init function int myAsyncSubInit(struct subRecord* record) { int tid; SEM_ID trigger; asyncSubPriv* priv = malloc(sizeof(asyncSubPriv)); if (priv == NULL) { return errno; } priv->trigger = semBCreate(SEM_Q_FIFO, SEM_EMPTY); if (priv->trigger == NULL) { return errno; } tid = taskSpawn("asyncSub", 200, VX_FP_TASK, 10000, (FUNCPTR) myAsyncSubThread, (int) record, 0, 0, 0, 0, 0, 0, 0, 0, 0); if (tid == ERROR) { return errno; } record->dpvt = priv; return 0; }
add error messages here
General subroutine record genSub (compared
All inputs and outputsto aresub) arrays of user defined type. − −
Input/output data types FTA … FTU, FTVA … FTVU −
One of CHAR, SHORT, LONG, ULONG, FLOAT, DOUBLE, …
Input/output element count NOA … NOU, NOVA … NOVU −
Input links INPA … INPU and fields A … U Output fields VALA … VALU and links OUTA … OUTU
Always set FT* and NO* fields of all used inputs and outputs!
SNAM and INAM fields similar to sub record.
General subroutine record user function
Input and output fields a … u, vala … valu are void*. − −
Fields are pointers to arrays, even if element count is 1. Cast void* to correct pointer type.
This easily crashes the IOC if ft* and no* fields are wrong! Always check field type and size! Do not process if type or size is wrong. Exit with error message.
Danger of crashing IOC is much higher than with sub record! Checking every time the record processes is expensive.
General subroutine record init function
Check all data types and element counts. − −
Initialize any other private data (buffers, etc…) Assign structure to dpvt field only if all checks succeed. −
Field types are one of menuFtypeSHORT, menuFtypeDOUBLE, … Print descriptive error message if check fails!
If no private data is needed, set dpvt to a dummy value.
Check dpvt field at start of user function. Do not process if dpvt is not set.
Contents
vxWorks intro − − −
Calling C code from EPICS − − −
Major differences between vxWorks and Unix/Linux Using the vxWorks shell Programming techniques Subroutine records sub and genSub Soft device support State notation language
Compiling C code for IOCs −
Using driver.makefile and require
Soft device support
Available for "standard" I/O records −
Makes a new DTYP choice available −
ai, bi, mbbi, waveform, … Just like "Soft Channel" and "Raw Soft Channel"
Only one input (INP) and one output (VAL) Examples: − − − −
Timestamp for stringin (INP contains format string) File read for waveform (INP contains file name) FFT for waveform (INP points to other waveform) Integration for waveform (INP points to other waveform)
Writing device support
Write record init and read function Define a global device support function table struct { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN read; } devIntegrateWaveform = …
the two essential functions device support table name
Write dbd file to make function table known
device(waveform, CONSTANT, devIntegrateWaveform , "integrate")
record type
Link type, CONSTANT means: "constant or link to record"
DTYP string
Example soft device support: Integrate #include <recGbl.h> waveform #include <devSup.h> #include <alarm.h> #include <dbAccess.h> #include <waveformRecord.h> long devIntegrateWaveformInit(waveformRecord *record) { switch (record->inp.type) { case (PV_LINK): case (DB_LINK): case (CA_LINK): break; default: recGblRecordError(S_db_badField, record, "devIntegrateWaveform (init_record) Illegal INP field"); return S_db_badField; } return 0; }
Example soft device support: Integrate long devIntegrateWaveformRead(waveformRecord *record) waveform { long status, n, i;
n = record->nelm; status = dbGetLink(&record->inp, record->ftvl, record->bptr, 0, &n); if (status) { recGblSetSevr(record, READ_ALARM, INVALID_ALARM); return status; } record->nord = n; switch (record->ftvl) { case DBF_DOUBLE: { double sum = 0.0; for (i=0; i<n; i++) { sum += ((double*)(record->bptr))[i]; ((double*)(record->bptr))[i] = sum; } break; } /* case ... */ } return 0; }
Example soft device support: Integrate struct { waveform long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN read; } devIntegrateWaveform = { 5, NULL, NULL, devIntegrateWaveformInit , NULL, devIntegrateWaveformRead };
Contents
vxWorks intro − − −
Calling C code from EPICS − − −
Major differences between vxWorks and Unix/Linux Using the vxWorks shell Programming techniques Subroutine records sub and genSub Soft device support State notation language
Compiling C code for IOCs −
Using driver.makefile and require
State Notation Language
State machine implementation for EPICS − − −
C-like syntax − −
Understands many C functions and statements Escapes to "real" C-code for special occasions
Easy to use CA interface −
Do something when event happens "Events" are CA monitors (record changes) or timeout "Do something" can be any C-code
pvPut, pvGet, monitor
Any number of input and output records
program coolingswitch int cooling; assign cooling to "{DEV}:COOLING"; double temp; assign temp to "{DEV}:TEMP"; monitor temp; ss coolingswitch { state cold { when (temp>25.3) { cooling = 1; pvPut(cooling); } state hot } state hot { when (temp<22.0) { cooling = 0; pvPut(cooling); } state cold } }
Using SNL start state "cold"
[temp>25.3째] / cooling on
[temp<22째] / cooling off
state "hot"
Including C-code into SNL
Escape single line with % %... −
especially #include
Escape block with % {...}% Avoid accessing "global" SNL variables from within escaped C code. −
Implementation depends on "+r" flag
program calculator %%#include <math.h> %{ void myCalc( double i1, double i2, double* o1, double* o2) { *o1 = sin(i1 + i2); *o2 = cos(i1 – i2); } }%
"Abusing" SNL for calculations start idle [newInput] / myCalc()
double in1; double in2; double out1; double out2; assign in1 to "{DEV}:INPUT1"; assign in2 to "{DEV}:INPUT2"; assign out1 to "{DEV}:OUTPUT1"; assign out2 to "{DEV}:OUTPUT2"; monitor in1; monitor in2; evflag newInput; sync in1 to newInput; sync in2 to newInput; ss calculator { state idle { when (efTestAndClear(newInput)) { myCalc(in1, in2, &out1, &out2); pvPut(out1); pvPut(out2); } state idle } }
Contents
vxWorks intro − − −
Calling C code from EPICS − − −
Major differences between vxWorks and Unix/Linux Using the vxWorks shell Programming techniques Subroutine records sub and genSub Soft device support State notation language
Compiling C code for IOCs −
Using driver.makefile and require
The problem of compiling EPICS code
We have many different EPICS versions in use. −
We have different operating systems −
MVME2300, MVME5100
Other systems −
2 versions of vxWorks, 3 versions of Linux, Windows
We have 2 different VME boards types in use. −
3.13.2, 3.13.9, 3.13.10, 3.14.8, (3.14.10)
Embedded Linux on Virtex 4, Cosylab microIOC, …
We want to run "the same" code on all systems.
Differences between EPICS 3.13 and 3.14
EPICS 3.14 is designed to be independent of vxWorks − − −
Use OSI functions instead of vxWorks functions. Use EPICS registry instead of vxWorks symbol table. More complex build mechanism to build "host"-IOCs.
Incompatibilities between 3.13 and 3.14 − −
New OSI functions are not available for 3.13. Registry calls must be added all over the place.
−
for device support, sub/genSub functions, snl state machines
Makefile uses different variable names and mechanisms. (And make is a very complicated programming language.)
The solution: driver.makefile
It is a sophisticated Makefile that builds the same code − −
It builds a module that can be loaded with require In many cases, it finds out what to do automatically − −
for all EPICS versions (3.13 and 3.14) for all operating system versions (except Windows at the moment)
No need to write a complex Makefile In special cases, it can be configured
Originally designed for drivers, device support and new record types, it can be used for sub/genSub and SNL code, too.
The two flavours of modules
Global module −
−
Commonly used code (device driver, record type, …) Installed into global driver pool with make install
− −
Local module − −
Can be used by everyone Gets version number from CVS tag
CVS location: G/DRV/ Can be loaded with require
Locally used code (SNL, sub/genSub functions, …) Installed to IOC boot directory with swit
− −
No interference with other modules Does not need version numbers
CVS location: A, X, F, P Can be loaded with require
Example global module: hytec drivers
CVS location: G/DRV/hytec Needs CVS tags with versions like hytec_1_4_2 −
Untagged test versions are also supported.
Install to driver pool with make install Can be loaded with require "hytec" − − − −
or require or require or require or require
"hytec","1" "hytec","1.4" "hytec","1.4.2" "hytec","test"
Example local module: genSub function
CVS location: X/ID/GAP −
genSub function file: X/ID/GAP/src/e_to_gap.c
Tell project GNUmakefile to build the code in src/ ioc: build swit –V build clean: make -C src $@
Install to IOC with swit together with other project files. Can be loaded with require "GAP"
Using driver.makefile
In your source directory, create a one line Makefile include /ioc/tools/driver.makefile
Running make "automagically" builds a loadable module. − − − − − −
Detects all .c, .cc, .C, .cpp, .st, .stt, and .dbd files in directory. Generates module name from directory name. Builds for all EPICS versions. Builds only for vxWorks by default (but can build for Linux, too). Finds dependencies on other modules (drivers). Levels out many differences between 3.13 and 3.14
Configuring driver.makefile
Source code list, dbd file list − −
Default is all files in current directory Overwrite: SOURCES += file1.cc SOURCES += subdir/file2.c DBDS += xxx.dbd
Why overwriting? − −
Files are not all in the same directory as the Makefile. Not all files should be used.
Configuring driver.makefile
Project (module) name − − −
Default name is directory name If directory name is src or snl, default is name of parent directory Overwrite: PROJECT = othername
Why overwriting? − −
I don't like directory name. Directory name contains illegal characters like – or space.
only alphanumeric plus underscore allowed
Configuring driver.makefile
EPICS versions to build −
Default is (3.13.2), 3.13.9, 3.13.10, 3.14.8, (3.14.10)
−
May change over time and depends on machine
Exclude versions: EXCLUDE_VERSIONS = 3.14 EXCLUDE_VERSIONS = 3.13.2
Why excluding versions? − −
Code does not compile for all EPICS versions. Code is not necessary for all EPICS versions.
Configuring driver.makefile
Operating system class −
Default is vxWorks only (all versions)
−
Reason: backward compatibility with old vxWorks depended drivers.
Overwrite: BUILDCLASSES = Linux (only for Linux) BUILDCLASSES += Linux (for vxWorks and Linux)
Why overwriting? −
Module should be used on other OS than vxWorks.
genSub or SNL code for Linux IOCs
Configuring driver.makefile
Header file installation −
Default is not to install any headers
−
Exception: generated header files for new record types
Overwrite: HEADERS += file.h
Why overwriting? −
Module is a lower level driver on that other drivers may depend
−
For example asyn or ipac
Only important for global modules
Local modules are not installed for use by others
Make targets
make, make build −
make 3.14, make 3.13.2 −
Installs global module to driver pool
make uninstall −
Builds only for certain EPICS versions
make install, make install.3.13 −
Builds a loadable module for each EPICS/OS combination
Cleanly (!) removes global module from driver pool
make help −
Prints some help
Loading modules: require
require is an SLS extension. It is used in the IOC startup script to load modules. It checks if a module is already loaded. − −
Already loaded with compatible version is OK. Already loaded with incompatible version stops booting.
It recursively solves dependencies on other modules. It loads the library and dbd file and initializes the module. − −
Initialize means: make functions etc, available to the shell Uses ld and dbLoadDatabase to load the files
Using require
require "xxx" −
Loads latest version of library
− −
xxxLib, xxxLib.munch, libxxx.so, or xxx.dll
Loads dbd file xxx.dbd 3.14: Calls init function xxx_registerRecordDeviceDriver
require "xxx","2.1" −
Loads version 2.1 of library and dbd file (only for global modules)
xxxLib-2.1, xxxLib-2.1.munch, libxxx-2.1.so, or xxx-2.1.dll
require "xxx","test"
Driver pool location
Location of global module libraries: − − −
Location of dbd files: −
$INSTBASE/iocBoot/<EPICS_VERSION>/<ARCH> /work/iocBoot/R3.13.10/T2-ppc604 /psi-xfel/prod/iocBoot/R3.14.8/SL5-x86
$INSTBASE/iocBoot/<EPICS_VERSION>/dbd
Location of headers: −
$INSTBASE/iocBoot/<EPICS_VERSION>/include
Example of link system in driver pool motorLib -> motorLib-6.2.5 motorLib-4 -> motorLib-4.963.0 motorLib-4.74 -> motorLib-4.74.1 motorLib-4.74.1 motorLib-4.96 -> motorLib-4.96.1 motorLib-4.96.1 motorLib-4.962 -> motorLib-4.962.0 motorLib-4.962.0 motorLib-4.963 -> motorLib-4.963.0 motorLib-4.963.0 motorLib-6 -> motorLib-6.2.5 motorLib-6.2 -> motorLib-6.2.5 motorLib-6.2.5 motorLib-test