5. 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:

5.1. Communication user space-kernel space

In user space, you interact with the functions implemented in the /usr/src/comedilib directory. 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 /usr/src/comedilib/lib/ioctl.c.) 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 /usr/src/comedi/comedi/comedi_fops.c file: the comedi_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() system call; acquisition data captured by the interface card passes the kernel-user space boundary with the help of a copy_to_user() system call.

5.2. Generic functionality

The major include files of the kernel-space part of Comedi are:

From all the relevant Comedi device driver code that is found in the /usr/src/comedi/comedi directory (if the Comedi source has been installed in its normal /usr/src/comedi location), the generic functionality is contained in two parts:

5.2.1. 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 /usr/src/comedi/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: channel (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).

5.2.1.1. comedi_lrange

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];
};

5.2.1.2. comedi_subdevice

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_read) ... (*cancel) . 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() functions 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.

5.2.1.3. comedi_device

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);
};

5.2.1.4. comedi_async

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); 
};

5.2.1.5. comedi_driver

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;
};

5.2.2. 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)

5.3. Board-specific functionality

The /usr/src/comedi/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:

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 /usr/src/comedi/include/linux/ directory.

Drivers for digital IOs should implement the following functions:

Finally, the device driver writer must implement the read and write functions for the analog channels on the card:

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...

5.4. 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 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:

5.5. Device driver caveats

A few things to strive for when writing a new driver:

5.6. Integrating the driver in the Comedi library

For integrating new drivers in the Comedi's source tree the following things have to be done: