
Add a step to the instructions for integrating a new driver into comedi. The Kbuild file needs to be edited.
923 lines
31 KiB
XML
923 lines
31 KiB
XML
<?xml version="1.0" encoding="utf-8"?>
|
|
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
|
|
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" [
|
|
<!ENTITY % comedilib_entities SYSTEM "comedilib.ent">
|
|
%comedilib_entities;
|
|
]>
|
|
|
|
<section id="driverwriting">
|
|
<title>
|
|
Writing a &comedi; driver
|
|
</title>
|
|
|
|
<para>
|
|
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.
|
|
</para>
|
|
<para>
|
|
This section does <emphasis>not</emphasis> 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 <filename role="directory">/proc</filename>
|
|
interface, etc. So,
|
|
the device driver writers can concentrate on the interesting stuff:
|
|
implementing their specific interface card's DAQ functionalities.
|
|
</para>
|
|
<para>
|
|
In order to make a decent &comedi; device driver, you must
|
|
know the answers to the following questions:
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
<para>
|
|
How does the
|
|
<link linkend="userkernelhow">communication</link> between user-space
|
|
and kernel-space work?
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
What functionality is provided by the
|
|
<link linkend="comedikernelgeneric">generic</link> kernel-space
|
|
&comedi; functions, and what must be provided for each
|
|
<link linkend="boardspecific">specific new driver</link>?
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
How to use <link linkend="drivercallbacks">DMA and interrupts</link>?
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
What are the addresses and meanings of all the card's registers?
|
|
</para>
|
|
<para>
|
|
This information is to be found in the so-called <quote>register level
|
|
manual</quote> 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.
|
|
</para>
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
</para>
|
|
|
|
<section id="userkernelhow">
|
|
<title>
|
|
Communication user-space — kernel-space
|
|
</title>
|
|
|
|
<para>
|
|
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
|
|
<filename role="directory">lib</filename> subdirectory.
|
|
</para>
|
|
<para>
|
|
All user-space &comedi;
|
|
<link linkend="instructions">instructions</link> and
|
|
<link linkend="commandsstreaming">commands</link>
|
|
are transmitted to kernel space through a traditional
|
|
<function>ioctl</function> system call.
|
|
(See <filename>lib/ioctl.c</filename> in Comedilib.)
|
|
The user-space information command is <emphasis>encoded</emphasis> as
|
|
a number in the <function>ioctl</function> call, and decoded in the
|
|
kernel-space library. There, they are executed by their kernel-space
|
|
counterparts. This is done in the
|
|
<filename>comedi_fops.c</filename> file in the Comedi sources: the
|
|
<function>comedi_unlocked_ioctl</function> function processes the results of
|
|
the <function>ioctl</function> system call, interprets its contents,
|
|
and then calls the corresponding kernel-space
|
|
<function>do_…_ioctl</function> function(s).
|
|
For example, a &comedi;
|
|
<link linkend="instructions">instruction</link> is further processed
|
|
by the <function>do_insn_ioctl</function> function. (Which, in turn,
|
|
uses <function>parse_insn</function> for further detailed processing.)
|
|
</para>
|
|
<para>
|
|
The data corresponding to instructions and commands is transmitted
|
|
with the <function>copy_from_user</function> function;
|
|
acquisition data captured by the interface card passes the
|
|
kernel/user-space boundary with the help of a <function>copy_to_user</function>
|
|
function.
|
|
</para>
|
|
|
|
</section>
|
|
|
|
<section id="comedikernelgeneric">
|
|
<title>
|
|
Generic functionality
|
|
</title>
|
|
|
|
<para>
|
|
The major include files of the kernel-space part of &comedi; are:
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
<para>
|
|
<filename>include/linux/comedidev.h</filename>: 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.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<filename>include/linux/comedi_rt.h</filename>:
|
|
all the real-time stuff, such as management of ISR in RTAI and
|
|
RTLinux/Free, and spinlocks for atomic sections.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<filename>include/linux/comedilib.h</filename>: the header file for
|
|
the kernel library of &comedi; (<systemitem>kcomedilib</systemitem> module).
|
|
</para>
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
</para>
|
|
<para>
|
|
From all the relevant &comedi; device driver code that is found in the
|
|
<filename role="directory">comedi</filename> kernel module source directory,
|
|
the <emphasis role="strong">generic</emphasis> functionality is
|
|
contained in two parts:
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
<para>
|
|
A couple of <filename>C</filename> files contain the <emphasis
|
|
role="strong">infrastructural support</emphasis>.
|
|
From these <filename>C</filename> files, it's especially the
|
|
<filename>comedi_fops.c</filename> 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.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
For <emphasis role="strong">real-time</emphasis> applications,
|
|
the subdirectory <filename role="directory">kcomedilib</filename>
|
|
implements an interface in the kernel that is similar to the &comedi;
|
|
interface accessible through the
|
|
<link linkend="functionreference">user-space Comedi library</link>.
|
|
</para>
|
|
<para>
|
|
There are some differences in what is possible and/or needed
|
|
in kernel-space and in user-space, so the functionalities offered in
|
|
<filename role="directory">kcomedilib</filename> 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.
|
|
</para>
|
|
<para>
|
|
Most drivers don't make use (yet) of these real-time functionalities.
|
|
</para>
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
</para>
|
|
|
|
|
|
<section id="driverdatastructures">
|
|
<title>
|
|
Data structures
|
|
</title>
|
|
|
|
<para>
|
|
This Section explains the generic data structures that a device driver
|
|
interacts with:
|
|
<programlisting>
|
|
typedef struct comedi_lrange_struct <link linkend="comedilrange">comedi_lrange</link>;
|
|
typedef struct comedi_subdevice_struct <link linkend="comedisubdevice">comedi_subdevice</link>;
|
|
typedef struct comedi_device_struct <link linkend="comedidevice">comedi_device</link>:
|
|
typedef struct comedi_async_struct <link linkend="comediasync">comedi_async</link>
|
|
typedef struct comedi_driver_struct <link linkend="comedidriver">comedi_driver</link>;
|
|
</programlisting>
|
|
They can be found in
|
|
<filename>include/linux/comedidev.h</filename>.
|
|
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 (<type>comedi_lrange</type>),
|
|
subdevice, and device.
|
|
</para>
|
|
<para>
|
|
Note that these kernel-space data structures have similar names as
|
|
their
|
|
<link linkend="datatypesstructures">user-space equivalents</link>, but
|
|
they have a different (kernel-side) view on the DAQ problem and a
|
|
different meaning: they encode the interaction with the
|
|
<emphasis>hardware</emphasis>, not with the <emphasis>user</emphasis>.
|
|
</para>
|
|
<para>
|
|
However, the <type><link linkend="ref-type-comedi-insn">comedi_insn</link></type>
|
|
and <type><link linkend="ref-type-comedi-cmd">comedi_cmd</link></type>
|
|
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.
|
|
</para>
|
|
<para>
|
|
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
|
|
<type><link linkend="comedidriver">comedi_driver</link></type>
|
|
that stores the device driver information that is relevant at the
|
|
operating system level, and the data structure
|
|
<type><link linkend="comediasync">comedi_async</link></type> that stores the
|
|
information about all <emphasis>asynchronous</emphasis> activities
|
|
(interrupts, callbacks and events).
|
|
</para>
|
|
|
|
<section id="comedilrange">
|
|
<title>
|
|
<type>comedi_lrange</type>
|
|
</title>
|
|
<para>
|
|
The channel information is simple, since it contains only the signal
|
|
range information:
|
|
<programlisting>
|
|
struct comedi_lrange_struct{
|
|
int length;
|
|
<link linkend="ref-type-comedi-krange">comedi_krange</link> range[GCC_ZERO_LENGTH_ARRAY];
|
|
};
|
|
</programlisting>
|
|
</para>
|
|
|
|
</section>
|
|
|
|
|
|
<section id="comedisubdevice">
|
|
<title>
|
|
<type>comedi_subdevice</type>
|
|
</title>
|
|
<para>
|
|
The subdevice is the smallest &comedi; entity that can be used for
|
|
<quote>stand-alone</quote> DAQ, so it is no surprise that it is
|
|
quite big:
|
|
<programlisting>
|
|
struct comedi_subdevice_struct{
|
|
int type;
|
|
int n_chan;
|
|
int subdev_flags;
|
|
int len_chanlist; /* maximum length of channel/gain list */
|
|
|
|
void *private;
|
|
|
|
<link linkend="comediasync">comedi_async</link> *async;
|
|
|
|
void *lock;
|
|
void *busy;
|
|
unsigned int runflags;
|
|
|
|
int io_bits;
|
|
|
|
<link linkend="ref-type-lsampl-t">lsampl_t</link> maxdata; /* if maxdata==0, use list */
|
|
<link linkend="ref-type-lsampl-t">lsampl_t</link> *maxdata_list; /* list is channel specific */
|
|
|
|
unsigned int flags;
|
|
unsigned int *flaglist;
|
|
|
|
<link linkend="comedilrange">comedi_lrange</link> *range_table;
|
|
<link linkend="comedilrange">comedi_lrange</link> **range_table_list;
|
|
|
|
unsigned int *chanlist; /* driver-owned chanlist (not used) */
|
|
|
|
int (*insn_read)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *,<link linkend="ref-type-comedi-insn">comedi_insn</link> *,<link linkend="ref-type-lsampl-t">lsampl_t</link> *);
|
|
int (*insn_write)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *,<link linkend="ref-type-comedi-insn">comedi_insn</link> *,<link linkend="ref-type-lsampl-t">lsampl_t</link> *);
|
|
int (*insn_bits)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *,<link linkend="ref-type-comedi-insn">comedi_insn</link> *,<link linkend="ref-type-lsampl-t">lsampl_t</link> *);
|
|
int (*insn_config)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *,<link linkend="ref-type-comedi-insn">comedi_insn</link> *,<link linkend="ref-type-lsampl-t">lsampl_t</link> *);
|
|
|
|
int (*do_cmd)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *);
|
|
int (*do_cmdtest)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *,<link linkend="ref-type-comedi-cmd">comedi_cmd</link> *);
|
|
int (*poll)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *);
|
|
int (*cancel)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *);
|
|
|
|
int (*buf_change)(<link linkend="comedidevice">comedi_device</link> *,<link linkend="comedisubdevice">comedi_subdevice</link> *s,unsigned long new_size);
|
|
void (*munge)(<link linkend="comedidevice">comedi_device</link> *, <link linkend="comedisubdevice">comedi_subdevice</link> *s, void *data, unsigned int num_bytes, unsigned int start_chan_index );
|
|
|
|
unsigned int state;
|
|
};
|
|
</programlisting>
|
|
The function pointers <structfield>insn_read</structfield> …
|
|
<structfield>cancel</structfield> .
|
|
offer (pointers to) the standardized
|
|
<link linkend="functionreference">user-visible API</link>
|
|
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.)
|
|
</para>
|
|
<para>
|
|
The <structfield>buf_change</structfield> and <structfield>munge</structfield>
|
|
function pointers offer functionality that is not visible to the user and for
|
|
which the device driver writer must provide a board-specific
|
|
implementation:
|
|
<function>buf_change</function> is called when a change in the
|
|
data buffer requires handling; <function>munge</function> transforms
|
|
different bit-representations of DAQ values, for example from
|
|
<emphasis>unsigned</emphasis> to <emphasis>2's complement</emphasis>.
|
|
</para>
|
|
|
|
</section>
|
|
|
|
<section id="comedidevice">
|
|
<title>
|
|
<type>comedi_device</type>
|
|
</title>
|
|
|
|
<para>
|
|
The last data structure stores the information at the
|
|
<emphasis>device</emphasis> level:
|
|
<programlisting>
|
|
struct comedi_device_struct{
|
|
int use_count;
|
|
<link linkend="comedidriver">comedi_driver</link> *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;
|
|
<link linkend="comedisubdevice">comedi_subdevice</link> *subdevices;
|
|
int options[COMEDI_NDEVCONFOPTS];
|
|
|
|
/* dumb */
|
|
int iobase;
|
|
int irq;
|
|
|
|
<link linkend="comedisubdevice">comedi_subdevice</link> *read_subdev;
|
|
wait_queue_head_t read_wait;
|
|
|
|
<link linkend="comedisubdevice">comedi_subdevice</link> *write_subdev;
|
|
wait_queue_head_t write_wait;
|
|
|
|
struct fasync_struct *async_queue;
|
|
|
|
void (*open)(<link linkend="comedidevice">comedi_device</link> *dev);
|
|
void (*close)(<link linkend="comedidevice">comedi_device</link> *dev);
|
|
};
|
|
</programlisting>
|
|
</para>
|
|
|
|
</section>
|
|
|
|
<section id="comediasync">
|
|
<title>
|
|
<type>comedi_async</type>
|
|
</title>
|
|
|
|
<para>
|
|
The following data structure contains all relevant information:
|
|
addresses and sizes of buffers, pointers to the actual data, and the
|
|
information needed for
|
|
<link linkend="drivercallbacks">event handling</link>:
|
|
<programlisting>
|
|
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 */
|
|
|
|
<link linkend="ref-type-comedi-cmd">comedi_cmd</link> cmd;
|
|
|
|
// callback stuff
|
|
unsigned int cb_mask;
|
|
int (*cb_func)(unsigned int flags,void *);
|
|
void *cb_arg;
|
|
|
|
int (*inttrig)(<link linkend="comedidevice">comedi_device</link> *dev,<link linkend="comedisubdevice">comedi_subdevice</link> *s,unsigned int x);
|
|
};
|
|
</programlisting>
|
|
</para>
|
|
|
|
</section>
|
|
|
|
<section id="comedidriver">
|
|
<title>
|
|
<type>comedi_driver</type>
|
|
</title>
|
|
|
|
<para>
|
|
<programlisting>
|
|
struct comedi_driver_struct{
|
|
struct comedi_driver_struct *next;
|
|
|
|
char *driver_name;
|
|
struct module *module;
|
|
int (*attach)(<link linkend="comedidevice">comedi_device</link> *,comedi_devconfig *);
|
|
int (*detach)(<link linkend="comedidevice">comedi_device</link> *);
|
|
|
|
/* 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;
|
|
};
|
|
</programlisting>
|
|
</para>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
|
|
<section id="driversupportfunctions">
|
|
<title>
|
|
Generic driver support functions
|
|
</title>
|
|
|
|
<para>
|
|
The directory
|
|
<filename role="directory">comedi</filename> contains a large set of
|
|
support functions. Some of the most important ones are given below.
|
|
</para>
|
|
<para>
|
|
From <filename>comedi/comedi_fops.c</filename>, 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:
|
|
<programlisting>
|
|
void comedi_event(<link linkend="comedidevice">comedi_device</link> *dev,<link linkend="comedisubdevice">comedi_subdevice</link> *s,unsigned int mask);
|
|
|
|
int comedi_buf_put(<link linkend="comediasync">comedi_async</link> *async, <link linkend="ref-type-sampl-t">sampl_t</link> x);
|
|
int comedi_buf_get(<link linkend="comediasync">comedi_async</link> *async, <link linkend="ref-type-sampl-t">sampl_t</link> *x);
|
|
|
|
static int parse_insn(<link linkend="comedidevice">comedi_device</link> *dev,<link linkend="ref-type-comedi-insn">comedi_insn</link> *insn,<link linkend="ref-type-lsampl-t">lsampl_t</link> *data,void *file);
|
|
</programlisting>
|
|
The file <filename>comedi/kcomedilib/kcomedilib_main.c</filename> provides
|
|
functions to register a callback, to poll an ongoing data acquisition,
|
|
and to print an error message:
|
|
<programlisting>
|
|
int comedi_register_callback(<link linkend="ref-type-comedi-t">comedi_t</link> *d,unsigned int subdevice, unsigned int mask,int (*cb)(unsigned int,void *),void *arg);
|
|
|
|
int comedi_poll(<link linkend="ref-type-comedi-t">comedi_t</link> *d, unsigned int subdevice);
|
|
|
|
void comedi_perror(const char *message);
|
|
</programlisting>
|
|
The file <filename>comedi/rt.c</filename> provides interrupt handling
|
|
for real-time tasks (one interrupt per <emphasis>device</emphasis>!):
|
|
<programlisting>
|
|
int comedi_request_irq(unsigned irq,void (*handler)(int, void *,struct pt_regs *), unsigned long flags,const char *device,<link linkend="comedidevice">comedi_device</link> *dev_id);
|
|
void comedi_free_irq(unsigned int irq,<link linkend="comedidevice">comedi_device</link> *dev_id)
|
|
</programlisting>
|
|
</para>
|
|
|
|
</section>
|
|
|
|
</section>
|
|
|
|
|
|
<section id="boardspecific">
|
|
<title>
|
|
Board-specific functionality
|
|
</title>
|
|
|
|
<para>
|
|
The <filename role="directory">comedi/drivers</filename>
|
|
subdirectory contains
|
|
the <emphasis role="strong">board-specific</emphasis> device driver
|
|
code. Each new card must get an entry in this directory.
|
|
<emphasis role="strong">Or</emphasis>
|
|
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.
|
|
</para>
|
|
<para>
|
|
To help device driver writers,
|
|
&comedi; provides the <quote>skeleton</quote> of a new device driver,
|
|
in the <filename>comedi/drivers/skel.c</filename> 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.
|
|
</para>
|
|
<para>
|
|
The first thing you notice in <filename>skel.c</filename> 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.
|
|
</para>
|
|
<para>
|
|
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.
|
|
</para>
|
|
<para>
|
|
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):
|
|
<programlisting>
|
|
mydriver_attach();
|
|
mydriver_detach();
|
|
</programlisting>
|
|
In the <quote>attach</quote> function, memory is allocated for the
|
|
necessary <link linkend="driverdatastructures">data structures</link>,
|
|
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 <function>mydriver_attach</function> function must:
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
<para>
|
|
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.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
allocate memory for the private data structures.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
initialize the board registers and possible subdevices (timer, DMA, PCI,
|
|
hardware FIFO, etc.).
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
return <literal>1</literal>, indicating success. If there were any errors along the way,
|
|
you should return the appropriate (negative) error number. If an error is
|
|
returned, the <function>mydriver_detach</function> function is
|
|
called. The <function>mydriver_detach</function> function should
|
|
check any resources that may have been allocated and release them as
|
|
necessary. The &comedi; core frees
|
|
<structfield>dev->subdevices</structfield> and
|
|
<structfield>dev->private</structfield>, so this does not need to be done in
|
|
<function>mydriver_detach</function>.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
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.
|
|
</para>
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
Typically, you will be able to implement most of
|
|
the above-mentioned functionality by
|
|
<emphasis>cut-and-paste</emphasis> from already existing drivers. The
|
|
<function>mydriver_attach</function> 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
|
|
<link linkend="comedikernelgeneric">header files</link> of the
|
|
<filename role="directory">include/linux/</filename>
|
|
directory.
|
|
</para>
|
|
<para>
|
|
Drivers with digital I/O subdevices should implement the following functions,
|
|
setting the function pointers in the <type>comedi_subdevice</type>:
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
<para>
|
|
<function>insn_bits</function>: 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.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<function>insn_config</function>: implements <constant>INSN_CONFIG</constant>
|
|
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.
|
|
</para>
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
Finally, the device driver writer must implement the
|
|
<function>insn_read</function> and <function>insn_write</function> functions for
|
|
the analog channels on the card:
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
<para>
|
|
<function>insn_read</function>: acquire the inputs on the board and
|
|
transfer them to the software buffer of the driver.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<function>insn_write</function>: transfer data from the software
|
|
buffer to the card, and execute the appropriate output conversions.
|
|
</para>
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
In some drivers, you want to catch interrupts, and/or want to use the
|
|
<constant><link linkend="insn-inttrig">INSN_INTTRIG</link></constant>
|
|
instruction. In this
|
|
case, you must provide and register these
|
|
<link linkend="drivercallbacks">callback</link> functions.
|
|
</para>
|
|
<para>
|
|
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
|
|
<emphasis>some</emphasis> inspiration in the already available device
|
|
drivers, but don't trust that blind
|
|
<emphasis>cut-and-paste</emphasis> will bring you far…
|
|
</para>
|
|
|
|
</section>
|
|
|
|
<section id="drivercallbacks">
|
|
<title>
|
|
Callbacks, events and interrupts
|
|
</title>
|
|
|
|
<para>
|
|
Continuous acquisition is tyically an
|
|
<emphasis>asynchronous</emphasis> 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 <quote>in the background</quote>, but
|
|
various types of asynchronous <emphasis>event handling</emphasis> can
|
|
be needed during the acquisition:
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
<para>
|
|
The <emphasis>hardware</emphasis> can generate some error or
|
|
warning events.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
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 <link linkend="scan">scan</link>, etc.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
The device driver writer can register a driver-supplied
|
|
<quote>callback</quote> function, that is called at the end of each
|
|
hardware interrupt routine.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
Another driver-supplied callback function is executed when the user
|
|
program launches an <constant><link linkend="insn-inttrig">INSN_INTTRIG</link></constant>
|
|
instruction. This event handling is executed
|
|
<emphasis>synchronously</emphasis> with the execution of the
|
|
triggering instruction.
|
|
</para>
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
</para>
|
|
<para>
|
|
The interrupt handlers are registered through the functions mentioned
|
|
<link linkend="driversupportfunctions">before</link>
|
|
The event handling is done in the existing &comedi; drivers in
|
|
statements such as this one:
|
|
<programlisting>
|
|
<anchor id="async-events"/>
|
|
s->async->events |= COMEDI_CB_EOA | COMEDI_CB_ERROR
|
|
</programlisting>
|
|
It fills in the bits corresponding to particular events in the
|
|
<type><link linkend="comediasync">comedi_async</link></type> data structure.
|
|
The possible event bits are:
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
<para>
|
|
<anchor id="comedi-cb-eoa"/>
|
|
<constant>COMEDI_CB_EOA</constant>: execute the callback at the
|
|
<quote>End-Of-Acquisition</quote> (or <quote>End-Of-Output</quote>).
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<anchor id="comedi-cb-eos"/>
|
|
<constant>COMEDI_CB_EOS</constant>: execute the callback at the
|
|
<quote>End-Of-Scan</quote>.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<anchor id="comedi-cb-overflow"/>
|
|
<constant>COMEDI_CB_OVERFLOW</constant>: execute the callback when a
|
|
buffer overflow or underflow has occurred.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
<anchor id="comedi-cb-error"/>
|
|
<constant>COMEDI_CB_ERROR</constant>: execute the callback at the
|
|
occurrence of an (undetermined) error.
|
|
</para>
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
</para>
|
|
|
|
</section>
|
|
|
|
|
|
<section id="drivercaveats">
|
|
<title>
|
|
Device driver caveats
|
|
</title>
|
|
|
|
<para>
|
|
A few things to strive for when writing a new driver:
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
<para>
|
|
Some DAQ cards consist of different <quote>layers</quote> of hardware,
|
|
which can each be given their own device driver. Examples are:
|
|
some of the National Instruments cards, that all share the same
|
|
<emphasis>Mite</emphasis> 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.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
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 <emphasis>just</emphasis> to do digital I/O
|
|
and has no interrupts available.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
Drivers are to have absolutely <emphasis role="strong">no</emphasis>
|
|
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 <structfield>dev->private</structfield> should be used
|
|
to point to a structure containing any additional variables needed by
|
|
a driver/device combination.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
Drivers should report errors and warnings via the
|
|
<function>comedi_error</function> function.
|
|
(This is <emphasis>not</emphasis> the same function as the user-space
|
|
<function><link linkend="func-ref-comedi-perror">comedi_perror</link></function> function.)
|
|
|
|
</para>
|
|
</listitem>
|
|
|
|
</itemizedlist>
|
|
</para>
|
|
</section>
|
|
|
|
<section id="integratingdriver">
|
|
<title>
|
|
Integrating the driver in the &comedi; library
|
|
</title>
|
|
|
|
<para>
|
|
For integrating new drivers in the &comedi;'s source tree the following
|
|
things have to be done:
|
|
<itemizedlist>
|
|
|
|
<listitem>
|
|
<para>
|
|
Choose a sensible name for the source code file. Let's assume here
|
|
that you call it <quote>mydriver.c</quote>
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
Put your new driver into <filename>comedi/drivers/mydriver.c</filename>.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
Edit <filename>comedi/drivers/Makefile.am</filename> and add <literal>mydriver.ko</literal>
|
|
to the <literal>module_PROGRAMS</literal> list. Also add a line
|
|
<programlisting>
|
|
mydriver_ko_SOURCES = mydriver.c
|
|
</programlisting>
|
|
in the alphabetically appropriate place.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
Edit <filename>comedi/drivers/Kbuild</filename> and a line according to
|
|
the type of driver:
|
|
<programlisting>
|
|
obj-$(COMEDI_CONFIG_PCI_MODULES) += mydriver.o # for a PCI driver
|
|
</programlisting>
|
|
<programlisting>
|
|
obj-$(COMEDI_CONFIG_PCMCIA_MODULES) += mydriver.o # for a PCMCIA driver
|
|
</programlisting>
|
|
<programlisting>
|
|
obj-$(COMEDI_CONFIG_USB_MODULES) += mydriver.o # for a USB driver
|
|
</programlisting>
|
|
<programlisting>
|
|
obj-m += mydriver.o # for other driver types
|
|
</programlisting>
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
Run <command>./autogen.sh</command> in the top-level comedi directory. You will
|
|
need to have (a recent version of) autoconf and automake
|
|
installed to successfully run <command>autogen.sh</command>. Afterwards, your driver will
|
|
be built along with the rest of the drivers when you run <command>make</command>.
|
|
</para>
|
|
</listitem>
|
|
|
|
<listitem>
|
|
<para>
|
|
If you want to have your driver included in the &comedi; distribution
|
|
(you <emphasis>definitely</emphasis> want to :-) ) send it to
|
|
the &comedi; mailing list
|
|
for review and integration. See the top-level <filename>README</filename>
|
|
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;.
|
|
</para>
|
|
</listitem>
|
|
</itemizedlist>
|
|
</para>
|
|
|
|
</section>
|
|
|
|
</section>
|