6.2.  Generic functionality

6.2.1. Data structures
6.2.2. Generic driver support functions

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 comedi kernel module source directory, the generic functionality is contained in two parts:

6.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 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).  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];
};  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_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.  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);
};  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);
};  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;

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