%comedilib_entities; ]>
Writing a &comedi; driver This section explains the most important implementations aspects of the &comedi; device drivers. It tries to give the interested device driver writer an overview of the different steps required to write a new device driver. This section does not explain all implementation details of the &comedi; software itself: &comedi; has once and for all solved lots of boring but indispensable infrastructural things, such as: timers, management of which drivers are active, memory management for drivers and buffers, wrapping of RTOS-specific interfaces, interrupt handler management, general error handling, the /proc interface, etc. So, the device driver writers can concentrate on the interesting stuff: implementing their specific interface card's DAQ functionalities. In order to make a decent &comedi; device driver, you must know the answers to the following questions: How does the communication between user-space and kernel-space work? What functionality is provided by the generic kernel-space &comedi; functions, and what must be provided for each specific new driver? How to use DMA and interrupts? What are the addresses and meanings of all the card's registers? This information is to be found in the so-called register level manual of the card. Without it, coding a device driver is close to hopeless. It is also something that &comedi; (and hence also this handbook) cannot give any support or information for: board manufacturers all use their own design and nomenclature.
Communication user-space — kernel-space In user-space, you interact with the functions implemented in the Comedilib library. Most of the device driver core of the Comedilib library is found in lib subdirectory. All user-space &comedi; instructions and commands are transmitted to kernel space through a traditional ioctl system call. (See lib/ioctl.c in Comedilib.) The user-space information command is encoded as a number in the ioctl call, and decoded in the kernel-space library. There, they are executed by their kernel-space counterparts. This is done in the comedi_fops.c file in the Comedi sources: the comedi_unlocked_ioctl function processes the results of the ioctl system call, interprets its contents, and then calls the corresponding kernel-space do_…_ioctl function(s). For example, a &comedi; instruction is further processed by the do_insn_ioctl function. (Which, in turn, uses parse_insn for further detailed processing.) The data corresponding to instructions and commands is transmitted with the copy_from_user function; acquisition data captured by the interface card passes the kernel/user-space boundary with the help of a copy_to_user function.
Generic functionality The major include files of the kernel-space part of &comedi; are: include/linux/comedidev.h: the header file for kernel-only structures (device, subdevice, async (i.e., buffer/event/interrupt/callback functionality for asynchronous DAQ in a &comedi; command), driver, lrange), variables, inline functions and constants. include/linux/comedi_rt.h: all the real-time stuff, such as management of ISR in RTAI and RTLinux/Free, and spinlocks for atomic sections. include/linux/comedilib.h: the header file for the kernel library of &comedi; (kcomedilib module). From all the relevant &comedi; device driver code that is found in the comedi kernel module source directory, the generic functionality is contained in two parts: A couple of C files contain the infrastructural support. From these C files, it's especially the comedi_fops.c file that implements what makes &comedi; into what people want to use it for: a library that has solved 90% of the DAQ device driver efforts, once and for all. For real-time applications, the subdirectory kcomedilib implements an interface in the kernel that is similar to the &comedi; interface accessible through the user-space Comedi library. There are some differences in what is possible and/or needed in kernel-space and in user-space, so the functionalities offered in kcomedilib are not an exact copy of the user-space library. For example, locking, interrupt handling, real-time execution, callback handling, etc., are only available in kernel-space. Most drivers don't make use (yet) of these real-time functionalities.
Data structures This Section explains the generic data structures that a device driver interacts with: typedef struct comedi_lrange_struct comedi_lrange; typedef struct comedi_subdevice_struct comedi_subdevice; typedef struct comedi_device_struct comedi_device: typedef struct comedi_async_struct comedi_async typedef struct comedi_driver_struct comedi_driver; They can be found in include/linux/comedidev.h. Most of the fields are filled in by the &comedi; infrastructure, but there are still quite a handful that your driver must provide or use. As for the user-level &comedi;, each of the hierarchical layers has its own data structures: range (comedi_lrange), subdevice, and device. Note that these kernel-space data structures have similar names as their user-space equivalents, but they have a different (kernel-side) view on the DAQ problem and a different meaning: they encode the interaction with the hardware, not with the user. However, the comedi_insn and comedi_cmd data structures are shared between user-space and kernel-space: this should come as no surprise, since these data structures contain all information that the user-space program must transfer to the kernel-space driver for each acquisition. In addition to these data entities that are also known at the user level (device, sub-device, channel), the device driver level provides two more data structures which the application programmer doesn't get in touch with: the data structure comedi_driver that stores the device driver information that is relevant at the operating system level, and the data structure comedi_async that stores the information about all asynchronous activities (interrupts, callbacks and events).
<type>comedi_lrange</type> The channel information is simple, since it contains only the signal range information: struct comedi_lrange_struct{ int length; comedi_krange range[GCC_ZERO_LENGTH_ARRAY]; };
<type>comedi_subdevice</type> The subdevice is the smallest &comedi; entity that can be used for stand-alone DAQ, so it is no surprise that it is quite big: struct comedi_subdevice_struct{ int type; int n_chan; int subdev_flags; int len_chanlist; /* maximum length of channel/gain list */ void *private; comedi_async *async; void *lock; void *busy; unsigned int runflags; int io_bits; lsampl_t maxdata; /* if maxdata==0, use list */ lsampl_t *maxdata_list; /* list is channel specific */ unsigned int flags; unsigned int *flaglist; comedi_lrange *range_table; comedi_lrange **range_table_list; unsigned int *chanlist; /* driver-owned chanlist (not used) */ int (*insn_read)(comedi_device *,comedi_subdevice *,comedi_insn *,lsampl_t *); int (*insn_write)(comedi_device *,comedi_subdevice *,comedi_insn *,lsampl_t *); int (*insn_bits)(comedi_device *,comedi_subdevice *,comedi_insn *,lsampl_t *); int (*insn_config)(comedi_device *,comedi_subdevice *,comedi_insn *,lsampl_t *); int (*do_cmd)(comedi_device *,comedi_subdevice *); int (*do_cmdtest)(comedi_device *,comedi_subdevice *,comedi_cmd *); int (*poll)(comedi_device *,comedi_subdevice *); int (*cancel)(comedi_device *,comedi_subdevice *); int (*buf_change)(comedi_device *,comedi_subdevice *s,unsigned long new_size); void (*munge)(comedi_device *, comedi_subdevice *s, void *data, unsigned int num_bytes, unsigned int start_chan_index ); unsigned int state; }; The function pointers insn_readcancel . offer (pointers to) the standardized user-visible API that every subdevice should offer; every device driver has to fill in these functions with their board-specific implementations. (Functionality for which &comedi; provides generic functions will, by definition, not show up in the device driver data structures.) The buf_change and munge function pointers offer functionality that is not visible to the user and for which the device driver writer must provide a board-specific implementation: buf_change is called when a change in the data buffer requires handling; munge transforms different bit-representations of DAQ values, for example from unsigned to 2's complement.
<type>comedi_device</type> The last data structure stores the information at the device level: struct comedi_device_struct{ int use_count; comedi_driver *driver; void *private; kdev_t minor; char *board_name; const void *board_ptr; int attached; int rt; spinlock_t spinlock; int in_request_module; int n_subdevices; comedi_subdevice *subdevices; int options[COMEDI_NDEVCONFOPTS]; /* dumb */ int iobase; int irq; comedi_subdevice *read_subdev; wait_queue_head_t read_wait; comedi_subdevice *write_subdev; wait_queue_head_t write_wait; struct fasync_struct *async_queue; void (*open)(comedi_device *dev); void (*close)(comedi_device *dev); };
<type>comedi_async</type> The following data structure contains all relevant information: addresses and sizes of buffers, pointers to the actual data, and the information needed for event handling: struct comedi_async_struct{ void *prealloc_buf; /* pre-allocated buffer */ unsigned int prealloc_bufsz; /* buffer size, in bytes */ unsigned long *buf_page_list; /* physical address of each page */ unsigned int max_bufsize; /* maximum buffer size, bytes */ unsigned int mmap_count; /* current number of mmaps of prealloc_buf */ volatile unsigned int buf_write_count; /* byte count for writer (write completed) */ volatile unsigned int buf_write_alloc_count; /* byte count for writer (allocated for writing) */ volatile unsigned int buf_read_count; /* byte count for reader (read completed)*/ unsigned int buf_write_ptr; /* buffer marker for writer */ unsigned int buf_read_ptr; /* buffer marker for reader */ unsigned int cur_chan; /* useless channel marker for interrupt */ /* number of bytes that have been received for current scan */ unsigned int scan_progress; /* keeps track of where we are in chanlist as for munging */ unsigned int munge_chan; unsigned int events; /* events that have occurred */ comedi_cmd cmd; // callback stuff unsigned int cb_mask; int (*cb_func)(unsigned int flags,void *); void *cb_arg; int (*inttrig)(comedi_device *dev,comedi_subdevice *s,unsigned int x); };
<type>comedi_driver</type> struct comedi_driver_struct{ struct comedi_driver_struct *next; char *driver_name; struct module *module; int (*attach)(comedi_device *,comedi_devconfig *); int (*detach)(comedi_device *); /* number of elements in board_name and board_id arrays */ unsigned int num_names; void *board_name; /* offset in bytes from one board name pointer to the next */ int offset; };
Generic driver support functions The directory comedi contains a large set of support functions. Some of the most important ones are given below. From comedi/comedi_fops.c, functions to handle the hardware events (which also runs the registered callback function), to get data in and out of the software data buffer, and to parse the incoming functional requests: void comedi_event(comedi_device *dev,comedi_subdevice *s,unsigned int mask); int comedi_buf_put(comedi_async *async, sampl_t x); int comedi_buf_get(comedi_async *async, sampl_t *x); static int parse_insn(comedi_device *dev,comedi_insn *insn,lsampl_t *data,void *file); The file comedi/kcomedilib/kcomedilib_main.c provides functions to register a callback, to poll an ongoing data acquisition, and to print an error message: int comedi_register_callback(comedi_t *d,unsigned int subdevice, unsigned int mask,int (*cb)(unsigned int,void *),void *arg); int comedi_poll(comedi_t *d, unsigned int subdevice); void comedi_perror(const char *message); The file comedi/rt.c provides interrupt handling for real-time tasks (one interrupt per device!): int comedi_request_irq(unsigned irq,void (*handler)(int, void *,struct pt_regs *), unsigned long flags,const char *device,comedi_device *dev_id); void comedi_free_irq(unsigned int irq,comedi_device *dev_id)
Board-specific functionality The comedi/drivers subdirectory contains the board-specific device driver code. Each new card must get an entry in this directory. Or extend the functionality of an already existing driver file if the new card is quite similar to that implemented in an already existing driver. For example, many of the National Instruments DAQ cards use the same driver files. To help device driver writers, &comedi; provides the skeleton of a new device driver, in the comedi/drivers/skel.c file. Before starting to write a new driver, make sure you understand this file, and compare it to what you find in the other already available board-specific files in the same directory. The first thing you notice in skel.c is the documentation section: the &comedi; documentation is partially generated automatically, from the information that is given in this section. So, please comply with the structure and the keywords provided as &comedi; standards. The second part of the device driver contains board-specific static data structure and defines: addresses of hardware registers; defines and function prototypes for functionality that is only used inside of the device driver for this board; the encoding of the types and number of available channels; PCI information; etc. Each driver has to register two functions which are called when you load and unload your board's device driver (typically via a kernel module): mydriver_attach(); mydriver_detach(); In the attach function, memory is allocated for the necessary data structures, all properties of a device and its subdevices are defined, and filled in in the generic &comedi; data structures. As part of this, pointers to the low level instructions being supported by the subdevice have to be set, which define the basic functionality. In somewhat more detail, the mydriver_attach function must: check and request the I/O port region, IRQ, DMA, and other hardware resources. It is convenient here if you verify the existence of the hardware and the correctness of the other information given. Sometimes, unfortunately, this cannot be done. allocate memory for the private data structures. initialize the board registers and possible subdevices (timer, DMA, PCI, hardware FIFO, etc.). return 1, indicating success. If there were any errors along the way, you should return the appropriate (negative) error number. If an error is returned, the mydriver_detach function is called. The mydriver_detach function should check any resources that may have been allocated and release them as necessary. The &comedi; core frees dev->subdevices and dev->private, so this does not need to be done in mydriver_detach. If the driver has the possibility to offer asynchronous data acquisition, you have to code an interrupt service routine, event handling routines, and/or callback routines. Typically, you will be able to implement most of the above-mentioned functionality by cut-and-paste from already existing drivers. The mydriver_attach function needs most of your attention, because it must correctly define and allocate the (private and generic) data structures that are needed for this device. That is, each sub-device and each channel must get appropriate data fields, and an appropriate initialization. The good news, of course, is that &comedi; provides the data structures and the defines that fit very well with almost all DAQ functionalities found on interface cards. These can be found in the header files of the include/linux/ directory. Drivers with digital I/O subdevices should implement the following functions, setting the function pointers in the comedi_subdevice: insn_bits: drivers set this if they have a function that supports reading and writing multiple bits in a digital I/O subdevice at the same time. Most (if not all) of the drivers use this interface instead of insn_read and insn_write for DIO subdevices. insn_config: implements INSN_CONFIG instructions. Currently used for configuring the direction of digital I/O lines, although will eventually be used for generic configuration of drivers that is outside the scope of the currently defined &comedi; interface. Finally, the device driver writer must implement the insn_read and insn_write functions for the analog channels on the card: insn_read: acquire the inputs on the board and transfer them to the software buffer of the driver. insn_write: transfer data from the software buffer to the card, and execute the appropriate output conversions. In some drivers, you want to catch interrupts, and/or want to use the INSN_INTTRIG instruction. In this case, you must provide and register these callback functions. Implementation of all of the above-mentioned functions requires perfect knowledge about the hardware registers and addresses of the interface card. In general, you can find some inspiration in the already available device drivers, but don't trust that blind cut-and-paste will bring you far…
Callbacks, events and interrupts Continuous acquisition is tyically an asynchronous activity: the function call that has set the acquisition in motion has returned before the acquisition has finished (or even started). So, not only the acquired data must be sent back to the user's buffer in the background, but various types of asynchronous event handling can be needed during the acquisition: The hardware can generate some error or warning events. Normal functional interrupts are generated by the hardware, e.g., signalling the filling-up of the card's hardware buffer, or the end of an acquisition scan, etc. The device driver writer can register a driver-supplied callback function, that is called at the end of each hardware interrupt routine. Another driver-supplied callback function is executed when the user program launches an INSN_INTTRIG instruction. This event handling is executed synchronously with the execution of the triggering instruction. The interrupt handlers are registered through the functions mentioned before The event handling is done in the existing &comedi; drivers in statements such as this one: s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR It fills in the bits corresponding to particular events in the comedi_async data structure. The possible event bits are: COMEDI_CB_EOA: execute the callback at the End-Of-Acquisition (or End-Of-Output). COMEDI_CB_EOS: execute the callback at the End-Of-Scan. COMEDI_CB_OVERFLOW: execute the callback when a buffer overflow or underflow has occurred. COMEDI_CB_ERROR: execute the callback at the occurrence of an (undetermined) error.
Device driver caveats A few things to strive for when writing a new driver: Some DAQ cards consist of different layers of hardware, which can each be given their own device driver. Examples are: some of the National Instruments cards, that all share the same Mite PCI driver chip; the ubiquitous parallel port, that can be used for simple digital IO acquisitions. If your new card has such a multi-layer design too, please take the effort to provide drivers for each layer separately. Your hardware driver should be functional appropriate to the resources allocated. I.e., if the driver is fully functional when configured with an IRQ and DMA, it should still function moderately well with just an IRQ, or still do minor tasks without IRQ or DMA. Does your driver really require an IRQ to do digital I/O? Maybe someone will want to use your driver just to do digital I/O and has no interrupts available. Drivers are to have absolutely no global variables (apart from read-only, constant data, or data structures shared by all devices), mainly because the existence of global variables immediately negates any possibility of using the driver for two devices. The pointer dev->private should be used to point to a structure containing any additional variables needed by a driver/device combination. Drivers should report errors and warnings via the comedi_error function. (This is not the same function as the user-space comedi_perror function.)
Integrating the driver in the &comedi; library For integrating new drivers in the &comedi;'s source tree the following things have to be done: Choose a sensible name for the source code file. Let's assume here that you call it mydriver.c Put your new driver into comedi/drivers/mydriver.c. Edit comedi/drivers/Makefile.am and add mydriver.ko to the module_PROGRAMS list. Also add a line mydriver_ko_SOURCES = mydriver.c in the alphabetically appropriate place. Edit comedi/drivers/Kbuild and a line according to the type of driver: obj-$(COMEDI_CONFIG_PCI_MODULES) += mydriver.o # for a PCI driver obj-$(COMEDI_CONFIG_PCMCIA_MODULES) += mydriver.o # for a PCMCIA driver obj-$(COMEDI_CONFIG_USB_MODULES) += mydriver.o # for a USB driver obj-m += mydriver.o # for other driver types Run ./autogen.sh in the top-level comedi directory. You will need to have (a recent version of) autoconf and automake installed to successfully run autogen.sh. Afterwards, your driver will be built along with the rest of the drivers when you run make. If you want to have your driver included in the &comedi; distribution (you definitely want to :-) ) send it to the &comedi; mailing list for review and integration. See the top-level README for details of the &comedi; mailing list.) Note your work must be licensed under terms compatible with the GNU GPL to be distributed as a part of &comedi;.