Comedi
Version
You can either find COMEDI in the kernel staging tree or download it from its own web site. Even though both code in both places claims to be version 0.7.76, the two copies are different. The website version is abandoned since 2008 when it was added to the kernel staging tree. Only the kernel version is updated.
One of the main improvements made in the kernel version is about the creation of the char devices; with the old web site version, the end user has the duty of the device files creation, either with mknod or make install. During the COMEDI reviewing to insert it in the kernel staging tree, developers added the automatic creation of the char device with udev.
Kernel version still supports old drivers written before COMEDI become part of the staging tree. Some features offered in the kernel versions are absent in the other repository, so low-level drivers written for the kernel version won't compile under the other one.
Documentation
The COMEDI documentation is available on the COMEDI website http://www.comedi.org/documentation.html and mainly refers to comedilib, which is not especially interesting.
Reading COMEDI source code may be the only way to understand how the
framework works. Unfortunately, the code is poorly commented and thus it
is difficult; the only commented code is the COMEDI driver example
(comedi/drivers/skel.c
) that tries to explains how to write a COMEDI
driver.
COMEDI parameters
The COMEDI core provides three parameters that can be set at module load
time: comedi_debug
, comedi_autoconfig
, comedi_num_legacy_minors
.
-
comedi_debug
is used to print some extra debug information. The parameter is only available if you enabled CONFIG_COMEDI_DEBUG=y at compile time. To enable debug information, load COMEDI module withcomedi_debug=1
, otherwise the default of 0 applies.
-
comedi_autoconfig
is used to enable/disable the automatic creation of char devices. Auto configuration
is enabled by default (comedi_autoconfig=1
) and permits to create automatically the char devices files/dev/comediN
. If auto configuration is disabled (comedi_autoconfig=0
) COMEDI creates 16 char devices.
-
comedi_num_legacy_minors
is used to provide supports to old drivers without auto configuration By setting this parameter COMEDI create automatically the required device files/dev/comediN
. This parameter supports only values between 0 and 48 (included).
Device organization
Comedi organizes hardware on three levels: devices, sub-devices and channels. Comedi describes its hierarchy in the following way:
- Channel is the lowest-level hardware component, that represents the properties of one single data channel; for example, an analog input, or a digital output.
- Sub-device is a set of functionally identical channels that are physically implemented on the same interface card. For example, a set of 16 identical analogue outputs.
- Device is a set of sub-devices that are physically implemented on the same interface card; in other words, the interface card itself.
COMEDI allocates 256 minor numbers and uses these for devices and sub-devices. The first 49 minors (from 0 to 48) are reserved for devices and other ones for sub-devices. A minor is assigned to a sub-device only if it supports the COMEDI command interface. The command interface is used to implement streaming mode, described later.
Minor management is still inefficient when auto configuration is
enabled. Device may be remote over a synchronized network, so the host
system may really to handle hundreds of them. For example a battery of
49 device with:
- 2 analog inputs, with stream support
- 2 analog outputs, with stream support
they require 245 minors (49 for devices and 196 for sub-devices). In essence, it is easy to run out of minors numbers when many devices or sub-devices with streaming support are used.
Auto configuration
Auto configuration is enabled by default and allows the automatic
creation of the device files /dev/comediN
on device connection. To use
auto configuration, the specific driver must support it, otherwise
setting comedi_autoconfig=1
won't have any effect. The COMEDI
framework provides two functions for auto configuration:
-
comedi_pci_auto_config()
, for PCI devices -
comedi_usb_auto_config()
, for USB devices
If a COMEDI driver doesn't support auto configuration you must set the
parameter comedi_num_legacy_minors
, so COMEDI creates the device files
/dev/comediN
for you.
You can set both the parameters comedi_num_legacy_minors=N
and
comedi_autoconfig=1
(auto configuration enabled); in this case COMEDI
reserves the first N minors for drivers without auto configuration, and
the other 49-N for drivers with auto configuration. The device files
created with
comedi_num_legacy_minors
are available only for drivers without auto
configuration support. For example if you set
comedi_num_legacy_minors=10
and comedi_autoconfig=1
you have minors
from 0 to 9 for drivers without auto configuration, and minors from 10
to 48 for drivers with auto configuration.
Bugs and Issues
While looking at the code in version 3.1 of the Linux kernel, I noticed
a few bugs and suboptimal points.
One issue is in comedi_read()
and comedi_write()
, functions used for
data stream. The problem appears in the following lines, part of
comedi/comedi_fops.c
:
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
schedule();
These lines are not completely correct because signal_pending()
should
be checked after sleeping and
not before. Checking for signals after schedule()
is one of the basic
rules of driver writing, so the code here is clearly wrong, most likely
because of its age. My patch for this issue is:
schedule();
if (signal_pending(current)) {
retval = -ERESTARTSYS;
break;
}
When the control comes back to COMEDI process, it checks for pending signals that were delivered while the process was sleeping.
COMEDI uses mmap_count
to count how many processes are using the VMA;
this counting operations is buggy.
COMEDI increments the counter when mmap()
is called, decrements it
when a process stops using the VMA.
The bug is between these two operations; if a process calls fork()
while holding a memory map on a comedi device, COMEDI does not increment
mmap_count
, which will later be decremented twice when each of the
processes unmaps its memory. If this use pattern happens, mmap_count
will remain negative (non-zero) and will prevent the module to be
unloaded like it was still in use.
A solution to the problem is to use mmap_count
in the open()
and
close()
functions of vm_operations_struct
: increment on open()
,
decrement on close()
. open()
is invoked any time a new reference to
the VMA is made, close()
is invoked when a process stops to use the
VMA. I made a patch with this solution, submitted it to COMEDI
maintainers and it was accepted upstream.
A serious design problem concerns file_operation
. COMEDI allows to
open()
a char device without a low-level driver attached to it (device
is unusable); the device must be attached at a later time by issuing a
COMEDI_DEVCONFIG
ioctl command. Because of this, any operation (and
system call) must check if a device is attached before start its work.
It is not correct approach: if there is not a device connected, then do
not permit any operations on it, in other words: forbid open()
until a
device driver is not attached into COMEDI.
COMEDI does not declare a list of supported systems, but if you need to
compile it with different architectures then x86 family, you can't. For
example, if you try to compile COMEDI on PowerPC or ARM you get a
compile error in
comedi/drivers.c:505
:
async->prealloc_buf = vmap(pages, n_pages, VM_MAP, PAGE_KERNEL_NOCACHE);
The error is on the page protection bits, because PAGE_KERNEL_NOCACHE
is defined only on some architectures, such as: x86, frv, sh, m32r,
mn10300.
A dirty work around for this issue is to add in comedi/drivers.c
the
following preprocessor lines:
#ifndef PAGE_KERNEL_NOCACHE
#define PAGE_KERNEL_NOCACHE PAGE_KERNEL
#endif
The solution is dirty because on some architectures it might work differently than expected.
In a recent patch by Ralf Baechle on June 23rd 2011 patch this problem was solved with these lines:
#ifdef PAGE_KERNEL_NOCACHE
vmap(pages, n_pages, VM_MAP, PAGE_KERNEL_NOCACHE);
#else
vmap(pages, n_pages, VM_MAP, PAGE_KERNEL);
#endif
Architectures which provide a specific bitmask for PAGE_KERNEL_NOCACHE
will use it, other platforms will fall back to the PAGE_KERNEL
value.
The patch is useless because, in the same day, he also patched
the Kconfig
file to forbid compilation of COMEDI under architectures
without PAGE_KERNEL_NOCACHE
definition.
COMEDI was designed more than 10 year ago. Today its code is old, some
choices show their age and don't respect current coding standards and
best practices; for example the large use of ioctl()
and the way
processes are put to sleep.
COMEDI from User Space
COMEDI is ioctl()
driven, each operations is done through ioctl()
,
except for data streaming. To use COMEDI from user space there are two
ways: you can write your raw sequence of ioctl()
or you can use
comedilib. You must know a few COMEDI implementation details before
writing your long sequence of ioctl commands; moreover the resulting
code is needlessly long. The role of comedilib is helping developers
by providing higher-level functions that invoke ioctl()
internally,
hiding some of the complexity.
The preferred choice between these two possibility is to use the comedilib, which can be downloaded from the COMEDI web site; it is out of scope for this discussion, which concentrates on kernel features.
Comedi Instruction and Instruction List
COMEDI is internally based on so-called "instructions". A COMEDI
instruction is a command to perform a specific task. Instructions are
synchronous, so the invoking process is blocked until the requested task
completes. An instruction is defined by the comedi_insn
structure:
struct COMEDI_insn {
unsigned int insn;
unsigned int n;
unsigned int __user *data;
unsigned int subdev;
unsigned int chanspec;
unsigned int unused[3];
};
Follow a rapid description of the structure, for details read the official documentation.
- insn: its value define the operation to perform, that can be one of the follow: INSN_READ, INSN_WRITE, INSN_BITS, INSN_GTOD (Get Time Of Day), INSN_WAIT, INSN_CONFIG, INSN_INTTRIG
- n: is the number of element in the data array
- data: buffer for read, write, configure operations. Its meaning depends from insn value; for example, for read and write operations data is a buffer of samples, for configuration is an array of parameters.
- subdev: number of sub-device where apply the instruction
- chanspec: channel specification use 32 bits to set channel number (index of the channel), channel range (voltage range) and channel reference (voltage reference).
- unused: to align memory.
User side, once an instruction is ready it can be submitted to COMEDI
with the function comedi_do_insn()
. If you need to perform a single
instruction, comedilib functions are preferred; they also hide
instructions complexity.
Instructions are useful when combined with instruction list, that is a
list of instructions. Instruction list is an array of instructions to be
executed in sequence, so when you need to perform a particular
sequence of operations you can build your own instruction list and push
it into COMEDI every time you need to perform that sequence. Instruction
list is just a vector of instruction so its structure is easy:
struct comedi_insnlist {
/* number of instruction */
unsigned int n_insns;
/* array of instruction */
struct comedi_insn __user *insns;
};
User side, instruction list can be submitted to COMEDI with the function
comedi_do_insnlist()
.
Instructions are synchronous, then used only to perform simple task such as: single acquisitions, single output and configuration; you can't do data streaming with instructions.
Driver side the implementation of the instructions is left to developer which can implements them if the device provides the functionality. COMEDI does nothing special with instruction, it route instructions from user space to the correct implement.
Instruction functions leave in struct comedi_sudevice
and are the
following:
int (*insn_read) (struct comedi_device *, struct comedi_subdevice *, struct comedi_insn *, unsigned int *);
int (*insn_write) (struct comedi_device *, struct comedi_subdevice *, struct comedi_insn *, unsigned int *);
int (*insn_bits) (struct comedi_device *, struct comedi_subdevice *, struct comedi_insn *, unsigned int *);
int (*insn_config) (struct comedi_device *, struct comedi_subdevice *, struct comedi_insn *, unsigned int *);
respectively: to read from sub-device, to write to sub-device, to read/write a set of bits of a digital sub-device, to configure a sub-device.
Comedi Data Streaming
For data streaming COMEDI provides command; it is a structure used to
configure target channels, and triggers which drive the acquisition. To
create a command the struct comedi_cmd
structure must be filled
(defined in comedi/comedi.h
).
The event fields are used to set up trigger(s) and coordinate the
acquisition. COMEDI command's events are not handled directly by COMEDI,
so their content depends from low-level drivers implementation; COMEDI
suggests some value to identify specific policies. The _src
fields
define the trigger policies and _arg
ones are related arguments for an
optional parameter; please check the COMEDI documentation for more
details on this.
Command permits to specify the sub-device of the streaming acquisition,
but the acquisition does not occur on all the sub-device channels but
only on the ones selected with the chanlist
vector.
data
is a pointer to a location memory where store data streaming on
input, or where device fetch data on output.
It is not possible to run another streaming operation on the same sub-device, even if it involves a different group of channels
Driver side, to perform streaming acquisition the developer must implements the following functions:
int (*do_cmd) (struct comedi_device *, struct comedi_subdevice *);
int (*do_cmdtest) (struct comedi_device *, struct comedi_subdevice *, struct comedi_cmd *);
The first function is used to perform the streaming, the second one is
used to test if streaming is possible. Typically do_cmdtest()
verifies
if the submitted command complies the device features, for example if
the chosen trigger is a valid trigger for the device.
Developing a Driver
To develop a COMEDI driver you must create and fill comedi_driver
for
your driver and register it into COMEDI with comedi_driver_register()
.
struct comedi_driver{
struct comedi_driver *next;
const char *driver_name;
struct module *module;
int (*attach) (struct comedi_device *,
struct comedi_devconfig *);
int (*detach) (struct comedi_device *);
/* number of elements in board_name and board_id arrays */
unsigned int num_names;
const char *const *board_name;
/* offset in bytes from one board name pointer to the next */
int offset;
};
Mandatory fields are driver_name
, module
, attach()
and detach()
.
The functions for input/output from/to device are specified for each sub-device but it is not required to implement all of them.
The attach()
function is used to initialize the device and sub-devices
of the board. The initialization is in-line, so each field of device and
sub-devices structure is filled in this function, also functions
assignment take place here. detach()
function it reverse what is done
in the attach()
one.
During the sub-devices initialization (attach()
) you have to set
device and sub-devices features and set the I/O functions for each
sub-device which are the
following:
int (*insn_read) (struct comedi_device *, struct comedi_subdevice *, struct comedi_insn *, unsigned int *);
int (*insn_write) (struct comedi_device *, struct comedi_subdevice *, struct comedi_insn *, unsigned int *);
int (*insn_bits) (struct comedi_device *, struct comedi_subdevice *, struct comedi_insn *, unsigned int *);
int (*insn_config) (struct comedi_device *, struct comedi_subdevice *, struct comedi_insn *, unsigned int *);
int (*do_cmd) (struct comedi_device *, struct comedi_subdevice *)
int (*do_cmdtest) (struct comedi_device *, struct comedi_subdevice *, struct comedi_cmd *);
int (*poll) (struct comedi_device *, struct comedi_subdevice *);
int (*cancel) (struct comedi_device *, struct comedi_subdevice *);
single sample read insn_read()
, single sample write insn_write()
,
streaming test do_cmdtest()
, streaming do_cmd()
, read/write a set of
bits insn_bits()
, poll poll()
and abort acquisition cancel()
.
There is no interfaces for buffers and triggers development; if you need your own buffer or trigger you can develop them outside COMEDI.
Compare with requirements
The following table shows how the Requirements list items are supported by COMEDI.
requirments | COMEDI | Note |
Digitial/Analog I/O | yes | |
TDC and DTC | no | While you can implement them using COMEDI instructions, there is no explicit support. |
One-shot, burst, and streaming support | yes | |
Layered structure | yes | |
No hard limits on the number of bits or channels | no | It supports at most 49 devices and 207 sub-devices. |
High-data rate, little storage overhead | yes | |
Easy and general configuration | no | There are a few configuration options, but they are mainly configured via ioctl |
Offset, gain, number of bits, ... | no | There is no support for attributes at all. |
Extensibility | no | No way to extend COMEDI within its core. |
Little code overhead | no | COMEDI low-level driver requires different implementations for different kind of acquisition (or output) and usually the implementation of triggers. Many drivers have thousand on line of code. |
Centralized semaphores | no | There are no interfaces for buffers and triggers. You must implement them by yourself, so all locking is left to the developer. |
Flexible buffer management | no | There is no support for specific buffers |
Device-driven data transfers | no | Triggers are left to low-level driver. |
Hardware time stamps | half | There is no support for time stamping, but COMEDI don't touch it if it is present within the samples. |