Title: Writing C code for IOCs
1Writing C code for IOCs
2Contents
- vxWorks intro
- Major differences between vxWorks and Unix/Linux
- Using the vxWorks shell
- Programming techniques
- Calling C code from EPICS
- Subroutine records sub and genSub
- Soft device support
- State notation language
- Compiling C code for IOCs
- Using driver.makefile and require
3Major differences between vxWorks and Unix/Linux
- vxWorks has no programs but many threads (called
"tasks"). - 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. - vxWorks has no users.
- 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, - If you want something save you must make it save.
4Consequences of the "one program" concept
- All functions exist in 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 dont start programs from the shell, you call
functions. - When name clash happens, you might call the wrong
function.
5Consequences of multi threading
- Any problem in one thread affects the whole IOC.
- System resources are global to the whole IOC.
- Memory
- File handles
- Semaphores
- Ending a thread does not clean up system
resources. - The programmer (that's you!) must close files,
free memory, etc. - Global data needs protection against concurrent
access. - Global variables
- VME access
6Boon 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!
7Contents
- vxWorks intro
- Major differences between vxWorks and Unix/Linux
- Using the vxWorks shell
- Programming techniques
- Calling C code from EPICS
- Subroutine records sub and genSub
- Soft device support
- State notation language
- Compiling C code for IOCs
- Using driver.makefile and require
8Accessing 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. - Serial line settings 9600 baud, 8N1, no hardware
handshake - Windows hyperterm.exe (buggy) or Terminal.exe
9vxWorks help
- Online help http//vxworks.web.psi.ch
- Important for beginnersVxWorks Programmer's
Guide, Chapter 2 - All about tasks, semaphores, watchdog timers,
interrupts - Always helpfulvxWorks Reference Manual
- All vxWorks system functions
- Run-time help Type help on the vxWorks shell.
- Separate talk on vxWorks debugging?
10XTEST-VME-ID1 gt help help
Print this list ioHelp
Print I/O utilities help info dbgHelp
Print debugger help info nfsHelp
Print nfs help info netHelp
Print network help
info spyHelp Print task
histogrammer help info timexHelp
Print execution timer help info h n
Print (or set) shell history i
task Summary of tasks'
TCBs ti task Complete info
on TCB for task sp adr,args...
Spawn a task, pri100, opt0x19,
stk20000 taskSpawn name,pri,opt,stk,adr,args...
Spawn a task td task
Delete a task ts task
Suspend a task tr task
Resume a task d adr,nunits,width
Display memory m adr,width
Modify memory mRegs reg,task
Modify a task's registers interactively pc
task Return task's program
counter Type ltCRgt to continue, QltCRgt to stop
11Calling functions from the vxWorks shell
- Never call your main function main!
- Use a specific name, the name you would give a
program on Linux. - The shell can pass up to 10 integer or string
arguments. - float 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, ). - The shell can call functions in a separate task
- sp function, arg1,
- repeatedly repeat n, function, arg1,
- periodically period seconds, function, arg1,
12Examples
- 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
13Contents
- vxWorks intro
- Major differences between vxWorks and Unix/Linux
- Using the vxWorks shell
- Programming techniques
- Calling C code from EPICS
- Subroutine records sub and genSub
- Soft device support
- State notation language
- Compiling C code for IOCs
- Using driver.makefile and require
14Why 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. - Solution 2 Prefix global variable name with
module name.
Wrong
Right
/ internal variable / int card_count /
external variable / int debug0
/ internal variable / static int
card_count / external variable / int
drvXyDebug0
15Why 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 firstNULL
16Debug 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 drvXyDebug0
- message if (drvXyDebuggt2) fprintf(stderr, )
17Be paranoid!
- Error checking is the key to a stable system.
- Stability is limited by the weakest point!
- Check arguments to API functions (esp. shell
functions) - Never trust a user! Not even yourself.
- Always check pointer arguments for validity.
- 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. - Check for "impossible" values (e.g. in case
constructs)
18Contents
- vxWorks intro
- Major differences between vxWorks and Unix/Linux
- Using the vxWorks shell
- Programming techniques
- Calling C code from EPICS
- Subroutine records sub and genSub
- Soft device support
- State notation language
- Compiling C code for IOCs
- Using driver.makefile and require
19Subroutine 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
20Subroutine record user function
- Inputs are in fields a l, output goes to val
(all double) - Example accumulate AB to VAL
- include ltsubRecord.hgt
- int subAccu (struct subRecord record)
record-gtval record-gtval record-gta
record-gtb return 0 - Specify name of function in SNAM field of record.
- record (sub, "(NAME)") field (SNAM,
"subAccu") field (INPA, "(INPUT)") field
(INPB, "(SCALE)")
21Subroutine record initialization
- Optional init function
- int subAccuInit (subRecord record)
record-gtval 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.
22Advanced Asynchronous subroutine record
- If function takes long time to complete...
- Run calculation in separate work thread with low
priority. - Setup thread in init function.
- Store data for inter-thread communication in dpvt
field. - Trigger work thread from record function.
- Return 1 from record function to signal
calculation not yet complete. - Re-process record when calculation completes.
- Use callbackRequestProcessCallback.
- pact field is 0 in first run and 1 in second run.
- Return 0 from record function to signal
calculation complete. - Return other value (e.g. ERROR or errno) to
signal failure.
23Asynchronous subroutine stub
- include ltsubRecord.hgtinclude
ltcallback.hgtinclude lttaskLib.hgtinclude
ltsemLib.hgtinclude lterrno.hgt - / 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)
24Asynchronous subroutine work thread
- void myAsyncSubThread(struct subRecord record)
- asyncSubPriv priv record-gtdpvt / get
private data / - while (1) / loop forever /
semTake(priv-gttrigger, WAIT_FOREVER) / wait / - / do calculations / / leave result in
priv-gtval / / leave error status in
priv-gtstatus / - / re-process record /
callbackRequestProcessCallback( priv-gtcb,
record-gtprio, record)
25Asynchronous subroutine user function
- int myAsyncSub(struct subRecord record)
asyncSubPriv priv record-gtdpvt / get private
data / - if (priv NULL) return ERROR / INAM
missing / - if (record-gtpact 0) / first run /
semGive(priv-gttrigger) / trigger work thread
/ return 1 / signal not yet done / - /second run / if (priv-gtstatus) / error
in work thread / return priv-gtstatus - record-gtval priv-gtval / update record /
return 0 / signal done /
add error messages here
26Asynchronous subroutine init function
- int myAsyncSubInit(struct subRecord record)
int tid SEM_ID trigger - asyncSubPriv priv malloc(sizeof(asyncSubPriv
)) if (priv NULL) return errno - priv-gttrigger semBCreate(SEM_Q_FIFO,
SEM_EMPTY) if (priv-gttrigger 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-gtdpvt priv return 0
add error messages here
27General subroutine record genSub (compared to sub)
- All inputs and outputs are arrays of user defined
type. - Input links INPA INPU and fields A U
- Output fields VALA VALU and links OUTA OUTU
- Input/output data types FTA FTU, FTVA FTVU
- One of CHAR, SHORT, LONG, ULONG, FLOAT, DOUBLE,
- Input/output element count NOA NOU, NOVA NOVU
- Always set FT and NO fields of all used inputs
and outputs! - SNAM and INAM fields similar to sub record.
- Asynchronous user function is not supported.
- The genSub record must be loaded require
"SynApps"
28General 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. - Check only once in init function (when IOC
boots)! - Do not process record after check failed!
29General subroutine record init function
- Check all data types and element counts.
- Field types are one of menuFtypeSHORT,
menuFtypeDOUBLE, - Print descriptive error message if check fails!
- Initialize any other private data (buffers, etc)
- Assign structure to dpvt field only if all checks
succeed. - 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.
30Contents
- vxWorks intro
- Major differences between vxWorks and Unix/Linux
- Using the vxWorks shell
- Programming techniques
- Calling C code from EPICS
- Subroutine records sub and genSub
- Soft device support
- State notation language
- Compiling C code for IOCs
- Using driver.makefile and require
31Soft device support
- Available for "standard" I/O records
- ai, bi, mbbi, waveform,
- Makes a new DTYP choice available
- 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)
32Writing 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 - Write dbd file to make function table known
- device(waveform, CONSTANT, devIntegrateWaveform,
"integrate")
the two essential functions
device support table name
Link type, CONSTANT means "constant or link to
record"
record type
DTYP string
33Example soft device support Integrate waveform
- include ltrecGbl.hgtinclude ltdevSup.hgtinclude
ltalarm.hgtinclude ltdbAccess.hgtinclude
ltwaveformRecord.hgt - long devIntegrateWaveformInit(waveformRecord
record) switch (record-gtinp.type) case
(PV_LINK) case (DB_LINK) case
(CA_LINK) break - default recGblRecordError(S_db_badFi
eld, record, "devIntegrateWaveform
(init_record) Illegal INP field") return
S_db_badField return 0
34Example soft device support Integrate waveform
- long devIntegrateWaveformRead(waveformRecord
record) long status, n, i - n record-gtnelm status
dbGetLink(record-gtinp, record-gtftvl,
record-gtbptr, 0, n) if (status)
recGblSetSevr(record, READ_ALARM,
INVALID_ALARM) return status - record-gtnord n switch (record-gtftvl)
case DBF_DOUBLE double sum 0.0
for (i0 iltn i) sum
((double)(record-gtbptr))i
((double)(record-gtbptr))i sum
break - / case ... / return 0
35Example soft device support Integrate waveform
struct long number DEVSUPFUN
report DEVSUPFUN init DEVSUPFUN
init_record DEVSUPFUN get_ioint_info
DEVSUPFUN read devIntegrateWaveform 5,
NULL, NULL, devIntegrateWaveformInit,
NULL, devIntegrateWaveformRead
36Contents
- vxWorks intro
- Major differences between vxWorks and Unix/Linux
- Using the vxWorks shell
- Programming techniques
- Calling C code from EPICS
- Subroutine records sub and genSub
- Soft device support
- State notation language
- Compiling C code for IOCs
- Using driver.makefile and require
37State Notation Language
- State machine implementation for EPICS
- Do something when event happens
- "Events" are CA monitors (record changes) or
timeout - "Do something" can be any C-code
- C-like syntax
- Understands many C functions and statements
- Escapes to "real" C-code for special occasions
- Easy to use CA interface
- pvPut, pvGet, monitor
- Any number of input and output records
38Using SNL
- program coolingswitch
- int coolingassign cooling to "DEVCOOLING"
- double tempassign temp to "DEVTEMP"monitor
temp - ss coolingswitch
- state cold when (tempgt25.3)
cooling 1 pvPut(cooling) state
hot - state hot when (templt22.0)
cooling 0 pvPut(cooling) state
cold
start
state "cold"
tempgt25.3 / cooling on
templt22 / cooling off
state "hot"
39Including 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 ltmath.hgt
- void myCalc( double i1, double i2,
double o1, double o2) o1
sin(i1 i2) o2 cos(i1 i2)
40"Abusing" SNLfor calculations
- double in1double in2double out1double
out2 - assign in1 to "DEVINPUT1"assign in2 to
"DEVINPUT2"assign out1 to "DEVOUTPUT1"as
sign out2 to "DEVOUTPUT2" - monitor in1monitor in2
- evflag newInputsync in1 to newInputsync in2
to newInput - ss calculator
- state idle when (efTestAndClear(newInput)
) myCalc(in1, in2, out1, out2)
pvPut(out1) pvPut(out2) state idle
start
idle
newInput / myCalc()
41Contents
- vxWorks intro
- Major differences between vxWorks and Unix/Linux
- Using the vxWorks shell
- Programming techniques
- Calling C code from EPICS
- Subroutine records sub and genSub
- Soft device support
- State notation language
- Compiling C code for IOCs
- Using driver.makefile and require
42The problem of compiling EPICS code
- We have many different EPICS versions in use.
- 3.13.2, 3.13.9, 3.13.10, 3.14.8, (3.14.10)
- We have different operating systems
- 2 versions of vxWorks, 3 versions of Linux,
Windows - We have 2 different VME boards types in use.
- MVME2300, MVME5100
- Other systems
- Embedded Linux on Virtex 4, Cosylab microIOC,
- We want to run "the same" code on all systems.
43Differences 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.)
44The solution driver.makefile
- It is a sophisticated Makefile that builds the
same code - for all EPICS versions (3.13 and 3.14)
- for all operating system versions (except Windows
at the moment) - It builds a module that can be loaded with
require - In many cases, it finds out what to do
automatically - 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.
45The two flavours of modules
- Global module
- Commonly used code (device driver, record type,
) - Installed into global driver pool with make
install - Can be used by everyone
- Gets version number from CVS tag
- CVS location G/DRV/
- Can be loaded with require
- Local module
- 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
46Example 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 "hytec","1"
- or require "hytec","1.4"
- or require "hytec","1.4.2"
- or require "hytec","test"
47Example 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 Vbuild clean
make -C src _at_ - Install to IOC with swit together with other
project files. - Can be loaded with require "GAP"
48Using 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
- If this is too much magic, you can configure it!
49Configuring driver.makefile
- Source code list, dbd file list
- Default is all files in current directory
- OverwriteSOURCES file1.ccSOURCES
subdir/file2.cDBDS xxx.dbd - Why overwriting?
- Files are not all in the same directory as the
Makefile. - Not all files should be used.
50Configuring 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
51Configuring 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 versionsEXCLUDE_VERSIONS
3.14EXCLUDE_VERSIONS 3.13.2 - Why excluding versions?
- Code does not compile for all EPICS versions.
- Code is not necessary for all EPICS versions.
52Configuring 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
53Configuring 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
54Make targets
- make, make build
- Builds a loadable module for each EPICS/OS
combination - make 3.14, make 3.13.2
- Builds only for certain EPICS versions
- make install, make install.3.13
- Installs global module to driver pool
- make uninstall
- Cleanly (!) removes global module from driver
pool - make help
- Prints some help
55Loading 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
56Using 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_registerRecordDevice
Driver - 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"
- Loads test (untagged) version of library and dbd
file
57Driver pool location
- Location of global module libraries
- INSTBASE/iocBoot/ltEPICS_VERSIONgt/ltARCHgt
- /work/iocBoot/R3.13.10/T2-ppc604
- /psi-xfel/prod/iocBoot/R3.14.8/SL5-x86
- Location of dbd files
- INSTBASE/iocBoot/ltEPICS_VERSIONgt/dbd
- Location of headers
- INSTBASE/iocBoot/ltEPICS_VERSIONgt/include
58Example of link system in driver pool
- motorLib -gt motorLib-6.2.5motorLib-4 -gt
motorLib-4.963.0motorLib-4.74 -gt
motorLib-4.74.1motorLib-4.74.1motorLib-4.96 -gt
motorLib-4.96.1motorLib-4.96.1motorLib-4.962 -gt
motorLib-4.962.0motorLib-4.962.0motorLib-4.963
-gt motorLib-4.963.0motorLib-4.963.0motorLib-6
-gt motorLib-6.2.5motorLib-6.2 -gt
motorLib-6.2.5motorLib-6.2.5motorLib-test