Skip to content

  • Projects
  • Groups
  • Snippets
  • Help
    • Loading...
  • Sign in
Z
ZIO
  • Project
    • Project
    • Details
    • Activity
    • Cycle Analytics
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Charts
  • Issues 0
    • Issues 0
    • List
    • Board
    • Labels
    • Milestones
  • Merge Requests 1
    • Merge Requests 1
  • Wiki
    • Wiki
  • image/svg+xml
    Discourse
    • Discourse
  • Members
    • Members
  • Collapse sidebar
  • Activity
  • Graph
  • Charts
  • Create a new issue
  • Commits
  • Issue Boards
  • Projects
  • ZIO
  • Wiki
  • Comedi

Comedi

Last edited by OHWR Gitlab support Mar 27, 2019
Page history

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 with comedi_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.
Clone repository
  • Documents
  • Home
  • News
  • Comedi
  • Iio
  • Performance
  • Readme
  • Requirements
  • Todo
  • Types
  • Documents
    • Project attachments
    • Slides
    • Thesis
    • User manual
    • Zio proposals
More Pages

New Wiki Page

Tip: You can specify the full path for the new file. We will automatically create any missing directories.