Xenomai: Hard Real Time Driver Example Tutorial with MMAP using the RTDM (Real Time Driver Model)
Here we have a template/skeleton of a hard real time driver with MMAP, utilizing the RTDM (Real Time Driver Model)
on Xenomai 2.1.
UPDATE: With the help of Jan Kiszka I'm working on version 2.0 of the standard driver template.
Basically the changes from V1.0 to V2.0 will be in regard of generalizing the driver, but
also issues regarding RT/non-RT and calling the driver from another kernel module will be addressed.
The user space demo application writes a string to the driver, then tries to read from it. The driver read operation
is blocking until the interrupt service routine in the driver is signalling to unblock the read operation.
This can be used to signal when data is ready. Then the user space app is doing some MMAP stuff (if
#define USEMMAP is enabled in the include file inc.h) and finally some ioctl operation.
All this user space stuff is done in a hard real time task created with rt_task_create and started with rt_task_start.
If your read operation does not block, you can use rt_task_wait_period() in the task. But if you use a blocking
read (e.g. read unblocks only when data is ready), you can't use rt_task_wait_period(), otherwise you get
ETIMEDOUT 110 = Connection timed out after the next rt_task_wait_period (see comments in user.c).
If you use the parallel port interrupt by commenting #define TIMERINT, you need to
make a "null-modem" for the parallel port - connect any output pin (pin 2-9) with the IRQ pin (pin 10 = ACK).
If #define TIMERINT is present, the linux timer interrupt is used - only every 100th interrupt is used to
unblock the driver read operation. The timer interrupt is the default setting.
So far so good. That was the user space demo application. Now lets dive into the hard real time driver.
The driver is a kernel module, so it has some init and cleanup functions:
/**********************************************************/
/* INIT DRIVER */
/**********************************************************/
int init_module(void)
{
return rtdm_dev_register(&demo_device);
}
/**********************************************************/
/* CLEANUP DRIVER */
/**********************************************************/
void cleanup_module(void)
{
rtdm_dev_unregister(&demo_device, 1000);
}
Next we need to define some operations which are supported by the driver (e.g. read, write, ioctl...).
The important thing with RTDM is that you can define RT (real time) and NRT (non real time) operations,
so it's possible to use the RT driver for NRT stuff aswell.
/**********************************************************/
/* DRIVER OPERATIONS */
/**********************************************************/
static struct rtdm_device demo_device = {
struct_version: RTDM_DEVICE_STRUCT_VER,
device_flags: RTDM_NAMED_DEVICE,
context_size: sizeof(struct demodrv_context),
device_name: DEV_FILE,
open_rt: demo_open_rt,
open_nrt: demo_open_rt,
ops: {
close_rt: demo_close_rt,
close_nrt: demo_close_rt,
ioctl_rt: demo_ioctl_rt,
ioctl_nrt: demo_ioctl_rt,
read_rt: demo_read_rt,
read_nrt: NULL,
write_rt: demo_write_rt,
write_nrt: NULL,
recvmsg_rt: NULL,
recvmsg_nrt: NULL,
sendmsg_rt: NULL,
sendmsg_nrt: NULL,
},
device_class: RTDM_CLASS_EXPERIMENTAL,
device_sub_class: 222,
driver_name: DRV_NAME,
peripheral_name: DEV_FILE_NAME,
provider_name: "-",
proc_name: demo_device.device_name,
};
Well, what else is important:
There is the driver context struct:
struct demodrv_context {
[...]
};
It is used for storing various information about the driver and it's status.
It is accessible via dev_private from the driver functions:
int demo_ioctl_rt(struct rtdm_dev_context *context,
rtdm_user_info_t *user_info,
int request,
void *arg)
{
struct demodrv_context *my_context;
[...]
my_context = (struct demodrv_context *)context->dev_private;
I guess now we're at a stage where the source code tells more than 1000 words ;-)
So here we go:
Real Time Driver Source Code realtimedriver.c
Real Time Driver User Space Application user.c
Real Time Driver extra files: include file inc.h and Makefile
Compile everything with the new standard Makefile for kernel and userspace applications for Xenomai.
Insert the kernel module with either
# insmod ./realtimedriver.o
-or-
# insmod ./realtimedriver.ko
depending on your kernel version.
Start the user-space application with
./user
and you'll see an output like this:
# ./user
PRESS CTRL-C to EXIT
timer started
demodev0 opened
my_task created
starting my_task
ioctl = 0
*p = 1234, p = 0xb7df1000
WRITE: written=17 sz=17
READ: read=CAPTAIN 0
MMAP: *((int *)mmappointer + 10) = 1001
IOCTL: readbackcounter=1
WRITE: written=17 sz=17
READ: read=CAPTAIN 1
MMAP: *((int *)mmappointer + 10) = 1002
IOCTL: readbackcounter=2
WRITE: written=17 sz=17
READ: read=CAPTAIN 2
demodev0 (write) -> invalid fd or context
exit
demodev0 (user) -> closed
delete my_task
stop timer
exit
You see that the timer is started, the real time driver is opened (don't try to find "demodev0" in /dev/ - it's not there -
this is an RTDM internal identifier). The hard real time task is created, started and off the fun goes.
At the beginning of the real time task, the pointer for the MMAP'ed region is fetched via ioctl. Then the
task starts to write to the driver - the string is read back from the driver as soon as the interrupt service
routine (ISR) unblocks the read operation when an interrupt (IRQ) occurs.
Furthermore some MMAP operation is performed and some ioctl write and read.
Press CTRL-C to end the userspace application.
Remove the module with:
# rmmod realtimedriver
I've put unusually many comments in the source code this time, so figuring out the rest
should be easy :-)
Developed and tested on Xenomai 2.1-rc3 with kernel 2.4.32 and 2.6.15 with the ADEOS IPIPE patches
adeos-ipipe-2.4.32-i386-1.1-03.patch and adeos-ipipe-2.6.15-i386-1.2-01.patch.
Last-Modified: Tue, 07 Mar 2006 16:51:18 GMT