devBusMapped - Device Support for Accessing Memory-Mapped Registers =================================================================== Introduction ------------ Simple, memory-mapped devices are quite common. In many cases, this kind of device features a few quad-, word- or byte sized registers and the user wants to read and or write several bitfields or entire register contents. Writing EPICS devices support, e.g., for modifying a single bit in a register - albeit straightforward - can be cumbersome due to the amount of 'glue' that has to be provided: defining a 'dset' along with all methods ('init', 'init-record', 'read/write', 'get_ioint_info'), entries in the 'dbd' and 'Makefile's along with a database have to be created. 'devBusMapped' aims at simplifying the task for the user so that most the work can be done in the database directly. Modifying a bit in a register using 'devBusMapped' is as simple as providing a proper 'bo' record declaration ('.db' file). Setting the 'DTYP' field to "Bus Address" selects 'devBusMapped' device support. The targeted bit is addressed with the 'MASK' field and the register is specified as part of the 'OUT' link e.g.,: field(OUT, "#C0S0@my_device+0x30,le32") field(MASK, "0x200") This example would address bit 9 in a 32-bit, little-endian register at an offset 0x30 from the symbolic base address 'my_device'. The base address 'my_device' has to be registered from a startup script or from driver C-code. Using 'devBusMapped' -------------------- The 'bus mapped' device support is designed for providing simple and direct access to device registers (or plain memory locations) via (epics) database INP or OUT links. The INP/OUT link field syntax - - - - - - - - - - - - - - - The supported record types (ai, ao, bi, bo, li, lo, mbbi and mbbo) are given a link field of the format #C S @+, 'theBase' is a symbolic name representing the base address of the device (later I explain how that is set up). A device may support multiple identical 'instances' (e.g., 'channels' or similar). The register address is then computed by devBusMapped as theBase + (instance << shift) + offset For example, assume you have something with 4 channels whose register blocks are spaced by 0x10000. A CSR shall be located at offset 0x30 in each channel's block. The memory map looks like this: base-> 0: (channel 0) 0x30: CSR1 ... 0x10000: (channel 1) 0x10030: CSR2 ... 0x20000: (channel 2) 0x20030: CSR3 ... etc. ... To address the CSR of channel 2 (started enumerating at 0, i.e., the device has channels 0..3) you would write field(INP, "#C2 S16 @my_device+0x30,") devBusMapped then uses '' to access the specified register for reading/writing the raw value (e.g., setting a bit with bo in a 32-bit register involves a read and then write access). The Access Methods - - - - - - - - - - There are a bunch of predefined access methods: 'be8, be16, be32, le16, le32, m8, m16, m32' they access 8, 16, or 32-bit wide registers that may be big- ['be' methods] or little-endian ['le' methods]. The 'm' methods use the native endianness and are designed for accessing ordinary memory (variables). The methods are portable and do the proper byte swapping as necessary. They also make sure that proper execution ordering is enforced (that's the difference between 'be8' and 'm8' - for device access always use 'be8') etc. All less-than-32-bit wide methods may have a 's' appended to specify that the item is to be treated as a 'signed' entity. E.g., if you want to read a signed 16-bit little-endian register then you would use 'le16s' (this is to make sure the 16-bit number is properly sign-extended when reading it e.g., into a longin record). Let us now assume you want to read bit 3 in the aforementioned CSR with a bi record and control bits 4-5 which in this example may control the device speed (00 off, 10 low, 01 hi). The device shall be a PCI device (-> little-endian) with 32-bit registers: record(bi, XX_STAT) { field(DTYP,"Bus Address") field(INP, "#C2S16@my_device+0x30,le32") field(MASK,"0x08") field(ZNAM,"BLAH_OK") field(ONAM,"BLAH_BAD") field(ZSV, "MAJOR") } record(mbbo,YY_SPEED) { field(DTYP, "Bus Address") field(OUT, "#C2S16@my_device+0x30,le32") field(SHFT, "4") field(NOBT, "2") field(ZRVL, "0") field(ONVL, "2") field(TWVL, "1") field(ZRST, "SPEED_OFF") field(ONST, "SPEED_LOW") field(TWST, "SPEED_HI") } -> various records may access the same register. devBusMapped takes care of 'mutexing'. Registering the Base Address - - - - - - - - - - - - - - - Finally, the device 'base' address needs to be registered with 'devBusMapped' (which uses the EPICS 'registry' under the hood). From C-code or an initialization script [just realize that I haven't bothered adding iocsh support yet but that can be done easily; you can call this from the RTEMS shell though] call devBusMappedRegister("my_device",) (In our example that would have to be done from some driver init code that reads the BAR from PCI config space) Note that it is not strictly necessary to register the base address if it is static and known outside of the run-time environment. A integer number (usual C-style '0x' or '0' prefixes allowed) may be used in the link field instead of a symbolic name. Example with Memory Variable - - - - - - - - - - - - - - - Sometimes you want to attach an EPICS record to a normal 'C-code' variable for monitoring or control. This is where the 'm' methods come in. They always use native endianness which is appropriate for this kind of data. Let's say you want to be able to control a 'debug' variable through an EPICS record. The value of the 'debug' variable ranges from 0 to 2 (debugging messages off, few messages, verbose messages). Your code already looks like this static char debug = 0; ... if ( debug > 1 ) printf("My verbose message\n"); ... Simply add a line somewhere during initialization: devBusMappedRegister("var1",&debug); And add a record to the EPICS db: record(longout,"DEBUG") { field(DTYP, "BusAddress") field(OUT, "#C0S0@var1,m8") } That's it -- now you are able to control the amount of messages printed via EPICS. Adding devBusMapped Support to an IOC Application - - - - - - - - - - - - - - - - - - - - - - - - - In the IOC application's 'dbd' file add: include "devBusMapped.dbd" and in the Makefile: xxx_LIBS += devBusMapped Extended Features ----------------- I/O Intr Scanning - - - - - - - - - You can have any record that is using 'devBusMapped' device support added to the IO scan list of your choice: - your driver declares and initializes a scan list - your driver calls int devBusMappedRegisterIOScan(char *name, IOSCANPVT scan); to give the scan list a name (all the names registered with devBusMapped should be short due to length limitations of the INP/OUT fields). - in your record declaration, append a comma ',' followed by the registered name of your scanlist to the access method. The 'OUT' field in the example above would then look like: field(OUT, "#C2S16@my_device+0x30,le32,my_io") - set SCAN of the record to 'I/O Intr' as desired. - your driver requests the list to be scanned at apropriate times (e.g., from your driver's ISR). User Defined Access Methods - - - - - - - - - - - - - - In some cases the predefined access methods are not enough (e.g., if a bitfield is spread over multiple registers). In such cases, the user may provide his/her own low-level register access routines. Consult 'devBusMapped.h' for prototypes of those routines. A pointer to a small struct holding the user's read/write function pointers is to be registered with 'devBusMapped' using the int devBusMappedRegisterIO(char *name, DevBusMappedAccess accessMethods); routine which associates a symbolic name with the user-defined methods. This name is then used in the INP/OUT field specification instead of one of the predefined methods (such as 'le32').