Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

73
Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Transcript of Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Page 1: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS Drivers

(and Device Support)

Page 2: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Contents

■ Introduction■ A simple example driver■ Tips and tricks■ Example device support■Multi-threading■ I/O Intr

Page 3: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Introduction

Page 4: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Required knowledge about EPICS

■ Records and fields►Standard records (ai, ao, mbbi, stringout, …)►Probably specialized records (motor, …)

■ Record processing►Scan methods (periodic, I/O Intr, …)►Links that cause processing (PP links, CP links, forward links)

■ Channel Access clients (medm, caget, …)These are topics of a basic EPICS trainingSee also: IOC Application Developper's Guide.

Page 5: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Required knowledge about programming

■ C►Variable flavours (global, local, static, volatile)►struct, union, array [], typedef, enum►Memory allocation (malloc, calloc) and pointers►Pointers to functions►Macros (#define) and conditional compilation (#ifdef)

■ Data structures► Integer representations (2's complement, hex, byte order)►Bit fields (masking, shifting, …)►Linked lists

Page 6: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Important knowledge about hardware I/O

■ I/O registers►Side effects of reading and writing►Fifo registers

■ Busses►VME address spaces A16, A24, A32►Memory mapped access►Out-of-order execution / pipelining

■ Interrupts► Interrupt levels and vectors► Interrupt handlers

Page 7: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Driver and Device support

Page 8: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

What is an EPICS driver?

■ Software interface between EPICS records and hardware(or 3rd party software)■ Usually split in 2 parts for better maintainability►Device support● Interfaces to records●Does not care about hardware●Files: devXXX.c, XXX.dbd

►Driver ●Does low-level hardware (register) access●Does not care about records●Files: drvXXX.c, drvXXX.h, (sometimes XXX.dbd)

Page 9: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Layers: Record - Device Support – Driver - Hardware

Hardware

Driver

Device Support

Record

Register access

Driver API functions

Device supportfunction table

The "glue"

Does not know about records

Record specific

Driver specific

Hardware specific

devXXX.c

drvXXX.h

drvXXX.c

Records

s

Does not know about drivers

Page 10: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Splitting functionality: device support vs. driver

■ Device support is the "glue" between record and driver►Parses INP or OUT link►Reads / writes / initializes record fields►Calls driver API functions

■ Driver does the real work► Initializes the hardware►Creates work thread if necessary►Register access► Interrupt handling►Resource handling (semaphores, etc)

Page 11: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

A simple example driver

Page 12: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

In this chapter

■ Example hardware "myDac"■ Interface (API) in drvMyDac.h■ Implementation in drvMyDac.c►Configuration function►open() function► I/O funtions►Report function

■ Register to EPICS with drvMyDac.dbd►Registering variables►Registering functions

Page 13: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Example: 8 channel DAC

■ The card is a VME board in A16 address space■One DAC "card" has 8 "signals".■ Each signal is 16 bit (0x0000 = -10 V, 0xFFFF = +10 V)■ The 8 signals are registers at offsets 0x10, 0x12, 0x14, 0x16■ It is possible to read back the current register setting.■ The card does not use interrupts.■ The card has an identifier at offset 0x00►String: "MYDAC"+nullbyte

Page 14: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

How to start?

■ Decide what the driver should be able to do■ Define one function for each functionality of the hardware►Don't think about records at this time►Think of "cards" or "boards", "signals" and functionality

■Model a "card" as a structure► It contains hardware register address, internal states, etc…►Define a function which returns a pointer to this structure

(like open() for a file)►Other functions take this "handle" as an argument

■ Define a configuration function to initialize one "card"

Page 15: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

What to put into the header file

■ This file defines the interface to the driver►All public (API) driver functions►Maybe error codes if used as return values►Typedefs for used function parameters

■ This file does not contain implementation details►No card structure details►No internal functions (e.g. interrupt handler)►No macros etc. used only internally in driver►No register layout

Page 16: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Example drvMyDac.h file#ifndef drvMyDac_h#define drvMyDac_h#include <devLib.h>

/* Initialize the card (called from startup script) */int myDacConfigure (int cardnumber, unsigned int vmeAddress);

/* Define a type for the card handle */typedef struct myDacCard myDacCard;

/* Get card handle from card number */myDacCard* myDacOpen (int cardnumber);

/* Set and read back values */int myDacSet (myDacCard* card, int signal, epicsUInt16 value);int myDacGet (myDacCard* card, int signal, epicsUInt16* pvalue);

#endif

Configuration for one card

Type definition for card handle (no details)

Get handle for card

Public driver functions

What can the card do?

Page 17: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

What to avoid

■ Do not use void* for driver handle.►This allows the compiler to warn about wrong arguments.

■ Do not define the card handle structure in the header file.►This is an implementation detail and not of interest to the user.

■ Do not list internal functions in the header file.► Interrupt handlers etc. are implementation details.

■ Do not define the register layout in the header file.►You don't want anyone else but the driver to access registers.

Page 18: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Implementing the driver (part 1)

■ Define "card" structure.►Contains at least register base address.►May contain thread ID, semaphores, etc.►Build a container of "cards" (linked list).

■ Define macros for register access.►Mark all registers volatile.●This prevents the compiler from "optimizing" and reordering register access.

■ Avoid global variables whenever possible.►At least use static for any private global variable.►Prefix every non-static global variable with driver name.

Page 19: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Includes, card structure and macros in devMyDac.c#include <stdio.h>#include <string.h>#include <devLib.h>#include <drvSup.h>#include <epicsExport.h>#include "drvMyDac.h"

/* linked list of card structures */struct myDacCard { myDacCard *next; int cardnumber; int vmeAddress; char* baseaddress;};static myDacCard *firstCard = NULL;

/* 8 DACs (16 bit) at offset 0x10 and following */#define DAC(card,signal) ((volatile epicsUInt16*)(card->baseaddress+0x10))[signal]

/* Other internally used macros */#define VME_SIZE 0x100

/* Debugging switch */int myDacDebug = 0;epicsExportAddress (int, myDacDebug);

EPICS headers

Define card structure here (as linked list

element)

Macros for register access

Use compiler independent volatile type

Make private global variables static

Must be non-static and exported to EPICS.Prefix it properly.

Page 20: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

What to avoid

■ Do not use a structure to map registers.►Structures are compiler dependent.●Compiler may insert pad bytes to align elements.●Compiler may reorder elements to "optimize" access.

►Use macros to calculate address offset.

■ Do not use compiler dependent data types for registers.►Not all compilers use 2 bytes for unsigned short.►Use compiler independent types like epicsUInt16 instead.

■ Do not use global variables to store any card details.►You may have more than one card. Make a list of structures.

Page 21: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Implementing the driver (part 2)

■ Configuration function►Configure one and only one card per function call.►Give each card an individual id (number or string).►Do not use auto-numbering of cards.●This avoids problems when you have to remove a card (temporarily)

■ open() function►Return a card handle from a card id

■ Input/Output functions■ Report function■ (Interrupt handler)

Page 22: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

What to do in the configuration function

■ Check parameters for sanity.■ Calculate local (memory mapped) address of registers.■ Check if the hardware is installed.■ Create "card" structure.►Allocate memory.►Fill in values.►Put into linked list.

■Give good error messages if anything goes wrong.►Don't forget driver name and parameters in error message.►Write error messages to stderr, not to stdout.

Page 23: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Example configuration function in drvMyDac.c/* The configure function is called from the startup script */int myDacConfigure (int cardnumber, unsigned int vmeAddress){ long status; volatile void* baseaddress; char id[6]; myDacCard **pcard; /* Check card number for sanity */ if (cardnumber < 0) { fprintf (stderr, "myDacConfigure: cardnumber %d must be > 0\n", cardnumber); return S_dev_noDevice; }

/* Find end of card list and check for duplicates */ for (pcard = &firstCard; *pcard; pcard=&(*pcard)->next) { if ((*pcard)->cardnumber == cardnumber) { fprintf (stderr, "myDacConfigure: cardnumber %d already in use\n", cardnumber); return S_dev_identifyOverlap; } }

Write clear error messages

to stderr

Return error codes (from devLib.h or drvMyDac.h)

Check all parametes

Check for duplicate

configuration

Remember: now *pcard points to the end of the list

Page 24: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Configuration function continued /* Check vmeAddress for sanity */ if (vmeAddress % VME_SIZE != 0) { fprintf (stderr, "myDacConfigure: " "vmeAddress 0x%04x must be a multiple of 0x%x\n", vmeAddress, VME_SIZE); return S_dev_badA16; } /* Translate vme to local address and check for overlap */ status = devRegisterAddress ("myDac", atVMEA16, vmeAddress, VME_SIZE, &baseaddress); if (status) { fprintf (stderr, "myDacConfigure: " "cannot register vmeAddress 0x%04x\n", vmeAddress); return status; }

More parameter

checks

Use devRegisterAddress (from devLib.h) to register VME address and to translate to local memory

mapped address.

This fails if the address overlaps with another registered address.

Page 25: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Configuration function continued /* Check that correct hardware is installed */ status = (devReadProbe (2, (char*) baseaddress, id) || devReadProbe (2, (char*) baseaddress+2, id+2) || devReadProbe (2, (char*) baseaddress+4, id+4)); if (status) { fprintf (stderr, "myDacConfigure: " "no card found at vmeAddress 0x%04x (local:%p)\n", vmeAddress, baseaddress); return status; } if (strcmp(id, "MYDAC") != 0) { fprintf (stderr, "myDacConfigure: " "card at vmeAddress 0x%04x (local:%p) is not a MYDAC\n", vmeAddress, baseaddress); return S_dev_wrongDevice; }

Use devReadProbe (from devLib.h) to access

hardware safely.If hardware is not installed, it does not crash but just

fails.

Check that hardware is installed before

using it.

Check that you have really the hardware you

expect

Page 26: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Configuration function finished /* Create new card structure */ *pcard = malloc (sizeof (myDacCard)); if (!*pcard) { fprintf (stderr, "myDacConfigure: " "out of memory\n"); return S_dev_noMemory; } (*pcard)->next = NULL; (*pcard)->cardnumber = cardnumber; (*pcard)->baseaddress = baseaddress; return 0;}

Allocate card structure (malloc or calloc)

Success

Fill card structure with

values

Remember? *pcard points to end of list

Page 27: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

What to avoid

■ Do not use other parameter types than int or char*.►double does not work on vxWorks shell on PPC architecture►Other types are not supported by iocsh.

■ Do not use too many parameters.►vxWorks supports only 10 parameters for shell functions.

■ Do not crash if card is absent.►Check card before use.

■ Do not give meaningless messages like "driver init failed".►The user needs information. What failed where and why?►Provide driver/function name, failing parameter and error reason.

Page 28: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Hardware registers

■ A hardware register is not just a variable!►Write or read access may have side effects on the hardware.►Reading may get a different value than the last written one.►FIFO registers provide different values every time they are read, giving

sequential access to an array through a scalar.►Reading or writing in pieces (e.g. low and high 16bit word to a 32bit

register) may be invalid, may have unexpected effects, or may require a certain order.

■ Always use volatile to access registers►This tells the compiler not to try "optimization" on hardware registers.

Page 29: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

What to do in the API functions

■Open►Check card id (number or string)►Find card in list of configured cards.►Return pointer to card structure (handle) or NULL.

■ I/O functions►Check handle for validity.►Read or write registers.►Return error code on failure or 0 on success.►No need to print error messages here (device support should do that)►Put in switchable debug messages.

Page 30: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Example API functionsmyDacCard* myDacOpen (int cardnumber){ myDacCard* card; for (card = firstCard; card; card = card->next) if (card->cardnumber == cardnumber) return card; return NULL;}

int myDacSet (myDacCard* card, int signal, epicsUInt16 value){ if (!card) return S_dev_noDevice;

if (signal < 0 || signal > 7) return S_dev_badSignalNumber;

if (myDacDebug>0) printf("myDacSet card %d signal %d @%p = %x\n", card->cardnumber, signal, &DAC(card, signal), (unsigned int)value);

DAC (card, signal) = value; return 0;}

Translate card id to handle

Use handle in all other functions

Check handle for NULL

Check other parameters

Return error codes on failure.

Access registersSuccess

Switchable debug message

Page 31: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Example API functions continuedint myDacGet (myDacCard* card, int signal, epicsUInt16* pvalue){ if (!card) return S_dev_noDevice;

if (signal < 0 || signal > 7) return S_dev_badSignalNumber;

*pvalue = DAC(card, signal);

if (myDacDebug>1) printf ("myDacGet card %d signal %d @%p = %x\n", card->cardnumber, signal, &DAC(card, signal), (unsigned int)*pvalue);

return 0;}

Never forget the checks.Stability is more

important than speed.

Page 32: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

What to avoid

■ Do not translate card id to structure in each API function call.►Get card handle once and use it in all other API functions.

■ Do not use card when configuration failed.►When configuration fails return NULL from open() call.►Check for NULL in all other functions.

■ Do not assume anything about records.►Do not use function names like write_ao().►The driver only cares about the features of the hardware.►Records are the business of device support.

Page 33: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Reporting hardware and driver status

■Write a report function.►Print driver and register information to stdout.►Provide multiple levels of detail.● In lowest level (0) print only one line per card.● In higher levels print more details about configuration, registers, etc.

■ Register report function to EPICS.►Create a driver support structure.►Fill in a pointer to report function.►Export driver support structure to EPICS.

Page 34: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Example report functionlong myDacReport (int detail){ myDacCard *card; for (card = firstCard; card; card = card->next) { printf (" card %d at address %p\n", card->cardnumber, card->baseaddress); if (detail >= 1) { int signal; unsigned short value; for (signal = 0; signal < 4; signal++) { value = DAC (card, signal); printf(" DAC %d = 0x%04x = %+8.4f V\n", signal, value, value*20.0/0xffff-10.0); } } } return 0;}

drvet myDac = { 2, myDacReport, NULL};epicsExportAddress (drvet, myDac);

Print register contents only for higher detail level

Print card information in any detail level

Driver support structure contains

report function

Export driver support structure function to EPICS

Page 35: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Report function call

■ The dbior shell function calls driver report functions.■ Example:

► dbiorDriver: myDac card 1 @0xfbff1000

► dbior "myDac",1Driver: myDac card 1 @0xfbff1000 DAC 0 @0xfbff1010 = 0x0000 = -10.0000 V DAC 1 @0xfbff1012 = 0x0000 = -10.0000 V DAC 2 @0xfbff1014 = 0x0000 = -10.0000 V DAC 3 @0xfbff1016 = 0x0000 = -10.0000 V DAC 4 @0xfbff1018 = 0x0000 = -10.0000 V DAC 5 @0xfbff101a = 0x0000 = -10.0000 V DAC 6 @0xfbff101c = 0x0000 = -10.0000 V DAC 7 @0xfbff101e = 0x0000 = -10.0000 V

Page 36: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Exporting variables and functions to EPICS

■ VxWorks shell can access C functions and variables directly ■Other architectures must run iocsh►Shell must know about functions, variables, driver support►Export variables and driver support from C● epicsExportAddress (int, myDacDebug);● epicsExportAddress (drvet, myDac);

►Much more complicated for functions●Write parameter description●Write wrapper function●Write registrar function●Very ugly and error-prone

Page 37: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Wrapper and registrar for shell functions#include <iocsh.h>

static const iocshArg myDacConfigureArg0 = { "cardNumber", iocshArgInt };static const iocshArg myDacConfigureArg1 = { "vmeA16Address", iocshArgInt };

static const iocshArg * const myDacConfigureArgs[] = { &myDacConfigureArg0, &myDacConfigureArg1};

static const iocshFuncDef myDacConfigureDef = { "myDacConfigure", 2, myDacConfigureArgs };

static void myDacConfigureFunc (const iocshArgBuf *args){ myDacConfigure (args[0].ival, args[1].ival);}

static void myDacRegister (){ iocshRegister (&myDacConfigureDef, myDacConfigureFunc); /* iocshRegister (other shell functions) */}epicsExportRegistrar (myDacRegister);

Parameter description

Array of all parameters

Function description

Parameter count

Wrapper function

Real function callUnwrap parameters

Register functions to iocsh

Export registrar to EPICS

for e

ach

func

tion

Page 38: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Importing variables and functions to EPICS

■Make exported C variables and functions known to EPICS■Write MyDac.dbd file with references to exported entities►Driver support structure●driver(myDac)

►Variables●variable(myDacDebug, int)

►Registrar●registrar(myDacRegister)

►Coming soon: Device support●device(…)

Page 39: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Tips and Tricks

From Dirk's Code Kitchen

Page 40: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

A bit more safety / paranoia

■ Even if card != NULL, it may be invalid►E.g. user calls myDacGet() with cardnumber instead of handle.

■ Accessing wrong hardware address is bad.■ A cheap way to check the card handle is a "magic number"►Add magic number to card structure.► Insert magic number when card is configured.►Check magic number in every API function.

■ A good magic number is CRC checksum of driver name►echo -n myDac | cksum2191717791 5

Page 41: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Example usage of magic numbers#define MYMAGIC 2191717791U /* crc("myDac") */

struct myDacCard { epicsUInt32 magic; myDacCard *next; int cardnumber; int vmeAddress; volatile char* baseaddress;};

int myDacConfigure (int cardnumber, unsigned int vmeAddress){ … (*pcard)->magic = MYMAGIC; (*pcard)->next = NULL; (*pcard)->cardnumber = cardnumber; (*pcard)->baseaddress = baseaddress;

return 0;}

int myDacSet (myDac* card, int signal, epicsUInt16 value){ if (!card) return S_dev_noDevice; if (card->magic != MYMAGIC) return S_dev_wrongDevice;…

Store magic number in card structure.

Initialize magic number in configure function.

Check magic number in every call.

Page 42: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Simulation Mode

■ EPICS does not support VME on Unix (or Windows)►devRegisterAddress() fails on softioc►devReadProbe() fails on softioc

■ Implement "simulation mode" on Unix for driver test■Work on allocated memory instead of registers

#ifdef UNIX/* UNIX has no VME. Use a simulation for tests */#include <malloc.h>#define devRegisterAddress(name, type, addr, size, pbase) \ ((*(pbase)=memalign (0x10000, size))? \ strncpy ((void*)*(pbase), "MYDAC", size), 0 : S_dev_noMemory)#define devReadProbe (size, ptr, buff) \ (memcpy (buff, (void*)ptr, size), 0)#endif

Page 43: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Example (synchronous) device support

Page 44: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

In this chapter

■ Device support structure■ Record initialization►Parse INP/OUT link►Connect to driver►Fill record private data structure► Initialize record from hardware

■ Read or write (record processing)►Transfer data between record and driver

■ Linear scaling

Page 45: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

How to start?

■ Decide what record types to support►Write one device support for each record type►Find out what record expects in device support●See record reference manual / record source code●Unfortunately no header file defines the device support

■ Choose synchronous or asynchronous support► If driver never blocks: synchronous● e.g. register access

► If driver may block or driver has callback functions: asynchronous ● e.g. field bus access

■Maybe "I/O Intr" support if possible

Page 46: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

The device support structure

■One device support structure for each supported record type.■ Contains pointers to device support functions.■ Depends on record type.►Usually:

struct { long number; /* of functions below */ DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN read_or_write;}

►Additional functions for some record types(see record reference manual)

Page 47: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Typical device support functions

■ report (can be NULL)►Report function similar to driver report function, but per record type

■ init (can be NULL)► Initialization of device support per record type

■ init_record► Initialization of device support per record

■ get_ioint_info (can be NULL)►For records scanned in "I/O Intr" mode

■ read or write (depending on record type)►Actual I/O during record processing

Page 48: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Example: synchronous ao support for myDac■ Device support structure of ao:►Additional function special_linconv.

struct { long number; /* must be 6 */ DEVSUPFUN report; /* can be NULL */ DEVSUPFUN init; /* can be NULL */ DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; /* can be NULL */ DEVSUPFUN write; DEVSUPFUN special_linconv;}

■ Implement 3 functions► long myDacInitRecordAo(aoRecord *record)► long myDacWriteAo(aoRecord *record)► long myDacSpecialLinconvAo(aoRecord *record, int after)

■ Store record private data in record->dpvt.

Page 49: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Analog out device support (includes, private data)#include <stdio.h>#include <stdlib.h>#include <devSup.h>#include <recGbl.h>#include <alarm.h>#include <aoRecord.h>#include <epicsExport.h>#include "drvMyDac.h"

typedef struct { myDacCard* card; int signal;} myDacAoPrivate;

Include header(s) of supported record type(s)

Include driver header

Store record specific data (e.g. driver handle)

in private structure

Page 50: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Analog out device support (device support structure)long myDacInitRecordAo (aoRecord *record);long myDacWriteAo (aoRecord *record);long myDacSpecialLinconvAo (aoRecord *record, int after);

struct { long number; DEVSUPFUN report; DEVSUPFUN init; DEVSUPFUN init_record; DEVSUPFUN get_ioint_info; DEVSUPFUN write; DEVSUPFUN special_linconv;} myDacAo = { 6, NULL, NULL, myDacInitRecordAo, NULL, myDacWriteAo, myDacSpecialLinconvAo};epicsExportAddress(dset, myDacAo);

Implement at least 3 functions for ai/ao:

•init_record•read/write•special_linconv

Put functions into device support structure.

Use NULL for unimplemented

functions.

Export device support structure

to EPICS.

Page 51: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Importing device support to EPICS

■Make exported device supports known to EPICS■ Add one line for each supported record type to MyDac.dbd:device (ao, VME_IO, myDacAo, "MyDac")

record type

link type

device support

structure

Name used in DTYP field

of record

record (ao, "$(name)") {field (DTYP, "MyDac")field (OUT, "#C$(card) S$(signal)")…

Page 52: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Analog out device support (init_record part 1)long myDacInitRecordAo (aoRecord *record){ myDacAoPrivate *priv; epicsUInt16 initval; myDacCard* card; int signal; if (record->out.type != VME_IO) { errlogSevPrintf (errlogFatal, "myDacInitRecordAo %s: illegal OUT link type\n", record->name); return -1; }

card = myDacOpen (record->out.value.vmeio.card); if (!card) { errlogSevPrintf (errlogFatal, "myDacInitRecordAo %s: invalid card number %d\n", record->name, record->out.value.vmeio.card); return S_dev_noDevice; }

Check INP/OUT link type.

Get link parameters

Get handle to driver

Page 53: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Analog out device support (init_record part 2) signal = record->out.value.vmeio.signal; if (signal < 0 || signal >= 8) { errlogSevPrintf (errlogFatal, "myDacInitRecordAo %s: invalid signal number %d\n", record->name, signal); return S_dev_badSignalNumber; }

priv = (myDacAoPrivate*) malloc (sizeof (myDacAoPrivate)); if (!priv) { errlogSevPrintf (errlogFatal, "myDacInitRecordAo %s: out of memory\n", record->name); return S_dev_noMemory; } priv->card = card; priv->signal = signal; record->dpvt = priv;

myDacSpecialLinconvAo (record, TRUE); myDacGet (card, signal, &initval); record->rval = initval; return 0;}

Allocate private data

Get more link parameters

Fill private data and store in dpvt

Initialize record fields

Return 0 (OK) or error status

Page 54: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Analog out device support (write)long myDacWriteAo (aoRecord *record){ myDacAoPrivate *priv = (myDacAoPrivate*) record->dpvt; int status; if (!priv) { recGblSetSevr (record, UDF_ALARM, INVALID_ALARM); errlogSevPrintf (errlogFatal, "myDrvWriteAo %s: record not initialized correctly\n", record->name); return -1; }

status = myDacSet (priv->card, priv->signal, record->rval); if (status) { errlogSevPrintf (errlogFatal, "myDrvWriteAo %s: myDacSet failed: error code 0x%x\n", record->name, status); recGblSetSevr (record, WRITE_ALARM, INVALID_ALARM); } return status;}

Get private data back from dpvt

Check for proper initialization

Call driver function

Return 0 or error status

Page 55: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Analog out device support (special_linconv)

■Only required for ao and ai records■ ao record calculates RVAL = (VAL - EOFF) / ESLO

ai records calculates VAL = RVAL * ESLO + EOFF■ User provides:►EGUL (should map to minimal raw value, e.g. 0x0000)►EGUF (should map to maximal raw value, e.g. 0xFFFF)

long myDacSpecialLinconvAo (aoRecord *record, int after){ if (after) { record->eslo = (record->eguf - record->egul)/0xFFFF; record->eoff = record->egul; } return 0;}

Initialize linear scaling. This DAC uses range

0x0000 to 0xFFFF

Page 56: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

I/O Intr

Page 57: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

What is I/O Intr?

■ It is a record scanning mode.■ The record is scanned whenever the driver has new data.■ Its an easy way to implement fast (>10 Hz) or irregular

scanning.■ Can be triggered from driver thread or from interrupt level.

Page 58: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

How to set up I/O Intr scanning?

■ Create one IOSCANPVT structure (from dbScan.h) for each source of “new data events” (e.g. interrupt) of the driver.■ Implement get_ioint_info() in device support►The record calls this function whenever SCAN is set to “I/O Intr”.►The function should get the IOSCANPVT from the driver.► It calls scanIoInit(IOSCANPVT *) to register with the “new data

event”

■ The driver calls scanIoRequest(IOSCANPVT *) whenever it has new data.■ The record processes and reads the value.

Page 59: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Differences to normal scanning

■ Normally the record asks the driver to start I/O.■ Here the driver does I/O first, then processes the record.►The driver should store the data where the record can find it.

■Many record can be triggered from one event source.►E.g. 32 bi records bound to an digital I/O card.►This may increase performance when hardware access is costly.

Page 60: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Asynchronous Device Support

Page 61: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

About asynchronous support

■When to use?► If hardware access is slow or may block.►Examples: Fields busses (serial, GPIB, …)

■What is the problem?►Record processing must not block.

■ Solution: Asynchronous device support►Driver starts a “work thread” that can block.►Device support starts driver action with non-blocking function.►Driver calls back when I/O is complete.

Page 62: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Asynchronous read or write function in detail

■ The read or write function calls driver to start I/O.■ Then it sets the PACT (processing active) field and returns.■ The record now knows that I/O is still in progress and pauses.■ The IOC continues with other records.■ Driver thread requests to process record again when ready.■ Read or write function is called a second time with PACT=1.■ The function transfers values from driver to record and returns.■ Record processing completes (forward link, monitors, etc).

Page 63: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Example asynchronous ai read functionlong slowDeviceReadAi (aiRecord *record){ slowDeviceAiPrivate* priv = (slowDeviceAiPrivate*) record->dpvt;

if (!priv) { /* error handling: record not initialized */ return -1; }

if (record->pact == 0) { driverStartRead(priv->card, priv->signal, &slowDeviceFinishedAi, record); record->pact = 1; return 0; }

if (priv->status != 0) { errlogSevPrintf(errlogFatal, "myDriver %s: driver read failed: error code 0x%x\n", record->name, priv->status); recGblSetSevr(record, READ_ALARM, INVALID_ALARM); return priv->status; } record->rval = priv->value; return 0;}

Call non-blocking driver function to start I/O

Handle error status of driver

first call

Get private data back from dpvt

Tell record to wait and return

Get data from driver(see next slide)

second call

Driver will call back when

finished

Page 64: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Asynchronous finish functionvoid slowDeviceFinished (dbCommon* record, int status, epicsInt16 value) { slowDevicePrivate* priv = (slowDevicePrivate*) record->dpvt;

private->status = status; private->value = value;

callbackRequestProcessCallback (&priv->cb, record->prio, record);}

Store value and result where

record can find it.

Request record processing in one

of the three callback threads.

Page 65: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Multi-threading issues

Page 66: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Threads used in EPICS

■Many parts of the EPICS software work in parallel.(In vxWorks, threads are called tasks.)►E.g. each SCAN type runs in a separate thread●High priority thread for ".1 second" scanning● Low priority thread for "10 second" scanning● Lowest priority for "Passive" scanning as the result of a caput.

►Additional threads for callbacks, timeouts, channel access, …

■Many threads may execute the same function at the same time►E.g. two records with the same driver and different scan rates.

■ The CPU can switch from one thread to another at any time.

Page 67: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

The re-entrancy problem

■ Functions must be re-entrant.►Bad example:

char* numToString (int number){ static char buffer[20]; sprintf (buffer, "%d", number); return buffer;}

■What's bad?►Thread 1 calls numToString(12345).►Some time after sprintf() the CPU switches to thread 2.►Thread 2 calls numToString(42).►Some time after sprintf() the CPU switches to thread 1.►Thread 1 uses the function result and reads "42"

Page 68: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Making code re-entrant

■ Never return a pointer to static memory.■ Never call such a function.■ Nobody would do that? System functions that do:

► char *ether_ntoa (const struct ether_addr *addr)► char *asctime (const struct tm *tm)► struct tm *localtime (const time_t *timep)► char *strerror (int errnum)

■ Use functions where the caller provides the buffer► char *ether_ntoa_r (const struct ether_addr *addr, char *buf)► char *asctime_r (const struct tm *tm, char *buf)► struct tm *localtime_r (const time_t *timep, struct tm *result)► int strerror_r (int errnum, char *buf, size_t n)

Page 69: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

Non-atomic operations on global resources

■ Non-atomic operations may be interrupted by an other thread.■ If the other thread accesses the same global resource it may

get inconsistent data.■ Example:

timethread 1: paint_wall (green)

thread 2: look_at_wall()

thread 1 starts paining thread 1 is interrupted thread 1 has finished

Page 70: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

What are global resources?

■Global variables■ Static variables■ Heap objects■ Hardware registers■ Files■ Directories■ Sockets■ Anything for that you have only a pointer or handle

Page 71: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

What are non-atomic operations?

■ read-modify-write:► if (p == NULL) p = …► if (! file_exists(filename)) { fopen (filename, "w"); … }► flags |= 1;► reg &= mask;► counter++;

■ sequential read/write:► strcpy (globalstring, s);► globalstruct.a = a; globalstruct.b = b;► addressregister = adr; val = valueregister;► element->next = previous->next; previous->next = element;

Page 72: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

What is thread-safe?

■ Local variables►Everything on the stack is thread specific.

■ errno►Even though it looks like a global variable, it is thread specific.

■ Single threaded context►startup script► functions called from iocInit● driver and record initialization

■ Resources used only by one thread■ Resources used only in interrupt handler

Page 73: Dirk Zimoch, 2007 Writing EPICS Drivers (and Device Support)

Dirk Zimoch, 2007

Writing EPICS DriversWriting EPICS Drivers

How to make non-atomic operations safe?

■ Disable interrupts.►Only interrupts can cause unexpected thread switch.►This is very brute.►Do this only for VERY SHORT times.

■ Use mutual exclusion semaphores.►Use operating system independent wrapper (epicsMutex).►Be careful to prevent deadlocks:

Two different threads must never take two different semaphores in reverse order.

►Lock resources as short as possible.