GNUDD Logo lpirq 3.00


Next: , Previous: (dir), Up: (dir)

lpirq

This package is meant to acccess an electrical signal on the interrupt pin of the parallel port found on the PC – not on newer ones, unfortunately. It works with Linux-2.6.23 and later, older versions worked with earlier 2.6 releases and 2.4 as well. This requires 2.6.23 as the API to page fault handling changed and I chose to support the new API only, for simplicity (the old API was removed after 2.6.25).

I use it for two purposes: to synchonize data acquisition on several computers (by using a button connected to all of them in parallel) and to get a periodic timing source for quasi-real-time operations.

Although these uses may look obsolete by recent standards in both processing power and network capabilities, I still found them useful in my own environments, with slow computers and little resources.

The package is published in http://gnudd.com/pub/samplecode as I think it's still simple enough to be intersting code for beginners.

The core code is published according to the GNU GPL, version 2 or later, while sample client modules are released in the public domain.

I thank Paolo Costanzo, of cori.it for supporting this development.


Next: , Previous: Top, Up: Top

1 Package Overview

The package is made up of a few kernel modules and a few user-space tools. The main module creates a device node for user-space access (including mmap(2)) and exports a callback for other modules to use.

The user-space programs are my classic mapper and wmapper, to access mmap from the command line as well as examples specific to lpirq.

The package doesn't actually solve any problem by itself, it is rather a tool that supports problem-specific code to be designed.


Next: , Previous: Package Overview, Up: Top

2 Compiling and loading

To compile the package you need the kernel source or at least the kernel headers for the specific kernel version you are running. If you compiled your kernel by yourself you need the source tree; if you are running a distribution you need the linux-kernel-headers or equivalent package installed.

In order to tell the Makefile where to find the kernel, you can set LINUX in your environment before running make:

        export LINUX=/usr/src/kernels/linux-2.6.26
        make

As an alternative, you can specify LINUX= on the command line of make:

        make LINUX=/path/to/source/linux-2.6.24

If you run with the kernel of your distribution the default should work without setting LINUX=.

There is no make install rule as I use the toll where it is compiled, or copy to custom non-standard locations.

To load the main module, run insmod ad superuser:

        sudo insmod ./lpirq.ko

If the previous command fails with a device or resource busy error, you most likely have the parport driver loaded. Please remove it and try again, as this tool is low-level and conflicts with the higher level management offered by parport.

If you don't run udev, you'll need to create a device entry point. If (most likely) you run udev or another hotplug daemon, you won't need to do this. Unless you access the device as administrator, you need to change permissions or ownership on the device, if you run an hotplug daemon you might prefer fixing its configuration file instead.

        # mknod may not be needed
        sudo mknod /dev/lpirq c 10 47
        sudo chmod 666 /dev/lpirq

The other modules are loaded using insmod if you use them, but no device needs to be created any more.


Next: , Previous: Compiling and loading, Up: Top

3 Kernel Modules

The package is made up of a few kernel modules, they are explained one by one.


Next: , Previous: Kernel Modules, Up: Kernel Modules

3.1 lpirq.ko

The device /dev/lpirq supports read, poll (select), ioctl and mmap. The system calls have the following role:

read
The system call is used to wait for an interrupt. When an interrupt arrives it reports end-of-file, so a shell script can easily wait by running cat /dev/lpirq. Use from C code is explained in User Space Tools. Non-blocking read is not supported.
poll
You can wait for an interrupt using poll or select; the file descriptor will be reported as readable when an interrupt arrives. Please note that a subsequent read would wait for another interrupt; even using the irq count from ioctl doesn't make it free from race conditions. That's sample code, after all.
ioctl
The call is an alternative way to wait for the interrupt. See User Space Tools about how to use it. If the third argument is a valid pointer to integer, the module will report the number of interrupts received since load time. If the command is not 0, it waits for a new interrupt before reporting the number.
mmap
The driver implements a simple shared memory area. The first time you call mmap, the shared memory is allocated according to the size being mapped. It is only released when the device is unloaded. Resizing is not supported, and mmapping at non-zero offset is not supported, either. If your applications wants to exchange data with kernel space it can use this technique.

Each and every user mmap will access the same RAM memory. The programs mapper and wmapper can be used to test from the command line amd lpirq-shmem.ko can be used to test from kernel space.

In addition to the file operations and the shared memory, the module exports entry points to run an external task, as exemplified by the other modules.


Next: , Previous: lpirq.ko, Up: Kernel Modules

3.2 lpirq-shmem.ko

The module is a public-domain example of how to access the lpirq shared memory from kernel space. You can base your own module on that one.

When loaded, the module prints the size of the current shared memory and (if size is not zero) the first 16 bytes. Loading always return an error, so there is no need to rmmod before you can run it again.

In this example burla% is the shell prompt:

        burla% sudo insmod lpirq.ko
        burla% sudo insmod lpirq-shmem.ko
        insmod: error inserting 'lpirq-shmem.ko': -1
                   Resource temporarily unavailable
        burla% dmesg | tail -3
        lpirq-3.0 loaded
        lpirq shmem is 0 (0x0) bytes
        printing first 16 bytes of 0 pages
     
        burla% echo abcdefg | user/wmapper /dev/lpirq 0 5000
        mapped "/dev/lpirq" from 0 to 5000 (0x0 to 0x2000)
        user/wmapper: short read
        burla% sudo insmod lpirq-shmem.ko
        insmod: error inserting 'lpirq-shmem.ko': -1
                   Resource temporarily unavailable
        lpirq shmem is 8192 (0x2000) bytes
        printing first 16 bytes of 2 pages
         61 62 63 64 65 66 67 0a 00 00 00 00 00 00 00 00
         00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00


Next: , Previous: lpirq-shmem.ko, Up: Kernel Modules

3.3 lpirq-client.ko

The module registers a kernel task to run every time the parallel port reports an interrupt. This is meant to work with a clock signal connected to pin 10 of the DB-25 connector.

The trivial module is released in the public domain so you can peruse it to load your code. Please note that the task is run at interrupt time, in atomic context with interrupts disabled on the current CPU.

As released, the module records its own activation time and prints statistics once per second using printk; you'll see the messages either on the console or in your log files (/var/log/kern.log, /var/log/messages or elsewhere, according to your /etc/syslogd.conf). You can also read the last chunk of kernel messages using the "dmesg" command. Try "dmesg | tail", for example. Finally, you can choose to kill klogd and run “cat /proc/kmsg” instead.

Please note that if you are running a serial console, the printk message once a second will just kill your performance, since the console is synchronous – so the whole message is printed at interrupt time, keeping the CPU busy for as long as your console takes to deliver the message.

To lower the console loglevel, please change the console log level:

        echo 1 > /proc/sys/kernel/printk

Then read your log file or /proc/kmsg, or run dmesg. Please note that the number of lost ticks is 0 by design as they would not be detected. This example is taken with an 8kHz clock connected to pin 10:

        <4>lpirq-client-3.0 loaded
        <4>kloops 5954 (lost   0); min  126 usec; avg  128 usec; max  130 usec
        <4>kloops 7812 (lost   0); min  113 usec; avg  128 usec; max  143 usec
        <4>kloops 7813 (lost   0); min  117 usec; avg  128 usec; max  137 usec
        <4>kloops 7813 (lost   0); min  119 usec; avg  128 usec; max  138 usec


Next: , Previous: lpirq-client.ko, Up: Kernel Modules

3.4 lpirq-tasklet.ko

The module registers a periodic talk, like lpirq-client, but the task runs in a tasklet (or bottom half handler), with interrupts enabled in the current CPU. The code is released in the public domain.

You can load lpirq-tasklet.ko instead of (or in addition to) lpirq-client.ko.

If you load both modules, you can tell messages apart: this prints "tloops" while lpirq-client prints "kloops". If you load the computer with interrupts (for example, through network activity), you'll see the tasklet has more jitter than the real interrupt handler.

This example is taken under ping -f -s 1024 and the usual square wave on the parallel port:

        <4>kloops 7813 (lost   0); min  124 usec; avg  128 usec; max  133 usec
        <4>tloops 7813 (lost   0); min  109 usec; avg  128 usec; max  148 usec
        <4>kloops 7813 (lost   0); min  105 usec; avg  128 usec; max  151 usec
        <4>tloops 7813 (lost   0); min  104 usec; avg  128 usec; max  153 usec
        <4>kloops 7813 (lost   0); min  109 usec; avg  128 usec; max  147 usec
        <4>tloops 7813 (lost   0); min  107 usec; avg  128 usec; max  149 usec
        <4>kloops 7812 (lost   0); min  108 usec; avg  128 usec; max  148 usec
        <4>tloops 7812 (lost   0); min  108 usec; avg  128 usec; max  149 usec

As you see, the task running at interrupt time is delayed less than the tasklet, although it is delayed as well if the interrupt event occurs while another interrupt is being serviced.


Previous: lpirq-tasklet.ko, Up: Kernel Modules

3.5 lpirq-busy.ko

This module is released according to the GNU GPL.

lpirq-busy reduces the activation jitter of its task by eating it in a busy loop that waits for the exact time when the task should be called. The module waits for a different amount each time, according to the real activation time; The duration of the loop is adapted over time to track the clock connected to the interrupt pin.

The code uses the get_cycles() function; which in turn uses the TSC register found on Pentium and newer x86 processors.

The busy loop is used to delay firing the client task until a known point in time. The chosen point in time is selected by estimating the period of the irq signal and by choosing the phase to minimize errors while keeping CPU load within the chosen bounds.

When loading the module you can specify a few parameters, but the default should work for you:

The module doesn't fire the client task for the first "avglen" seconds, as it uses them to estimate the interrupt period.

You shouldn't load this module unless you have a clock source connected to the parallel port interrupt pin, as the code might lock up the system (I didn't expect that situation nor did I try what the result it).

This example has been taken with the usual 8kHz clock:

        <4>lpirq-busy: estimating cycles_t pace
        <4>lpirq-busy: 145669165 cycles in 99307 usec = 1466857 cpm
        <4>lpirq-busy-3.0 loaded
        <4>lpirq-busy: period estimated: 187747 cycles (min 169265, max 206707)
        <4>lpirq-busy: period estimated: 127.992 usec
        <4>lpirq-busy: period is updated over 23438 cycles
        <7>bloops  250; min  128 usec; avg  127 usec; max  128 usec; delta 420 nsec
        <7>bloops 7813; min  128 usec; avg  128 usec; max  128 usec; delta 142 nsec
        <7>bloops 7813; min  128 usec; avg  128 usec; max  146 usec; delta 18011 nsec
        <7>bloops 7813; min  128 usec; avg  128 usec; max  128 usec; delta 27 nsec
        <7>bloops 7813; min  128 usec; avg  128 usec; max  128 usec; delta 31 nsec
        <7>bloops 7813; min  128 usec; avg  128 usec; max  128 usec; delta 37 nsec

This example shows verbose reporting (verbose=1), so the choices being made for the loop time are shown (the example shows both loop expansion because of errors and loop reduction to fit in the 20% constraint):

        <4>lpirq-busy: period estimated: 187747 cycles (min 154947, max 217823)
        <4>lpirq-busy: period estimated: 127.993 usec
        <4>lpirq-busy: period is updated over 23438 cycles
        <4>lpirq-busy: reducing maxload from 33% to 20%
        <7>bloops 7094; min  128 usec; avg  128 usec; max  130 usec; delta 1955 nsec
        <7>lpirq-busy: update: adjusted phase by 2238 for errors
        <7>bloops 7813; min  128 usec; avg  128 usec; max  146 usec; delta 17675 nsec
        <7>lpirq-busy: update: adjusted avg by 1 (now 187748)
        <7>lpirq-busy: update: adjusted phase by 25812 for errors
        <7>lpirq-busy: busy time was 16%
        <7>lpirq-busy: cal time was 16%
        <7>bloops 7813; min  128 usec; avg  128 usec; max  128 usec; delta 18 nsec
        <7>lpirq-busy: busy time was 16%
        <7>lpirq-busy: cal time was 16%
        <7>bloops 7813; min  128 usec; avg  128 usec; max  128 usec; delta 31 nsec
        <7>lpirq-busy: busy time was 16%
        <7>lpirq-busy: cal time was 16%
        <7>bloops 7813; min  128 usec; avg  128 usec; max  128 usec; delta 24 nsec
        <7>lpirq-busy: busy time was 16%
        <7>lpirq-busy: cal time was 17%
        [...]
        <7>bloops 7813; min  128 usec; avg  128 usec; max  128 usec; delta 24 nsec
        <7>lpirq-busy: busy time was 19%
        <7>lpirq-busy: cal time was 19%
        <7>bloops 7813; min  128 usec; avg  128 usec; max  128 usec; delta 36 nsec
        <7>lpirq-busy: update: adjusted phase by 8 - too busy
        <7>lpirq-busy: busy time was 19%
        <7>lpirq-busy: cal time was 20%
        <7>bloops 7813; min  128 usec; avg  128 usec; max  128 usec; delta 45 nsec
        <7>lpirq-busy: update: adjusted phase by 228 - too busy
        <7>lpirq-busy: busy time was 20%
        <7>lpirq-busy: cal time was 20%


Previous: Kernel Modules, Up: Top

4 User Space Tools

The subdirectory user/ includes a few programs that can be used to interact with the module from user space.

mapper
wmapper
These programs access a file or a device using mmap. Both take three arguments: filename, offset and size. mapper reads the file using mmap and writes to stdout while wmapper reads stdin and writes the file with mmap.
waitirq
The trivial program uses read to wait for an interrupt.
waitirq-ioctl
This shows use of ioctl to wait for an interrupt and get the total number of interrupts received since module load time. If the enviroment variable VERBOSE exists, the count is printed to stdout.
getcount
The program runs for 5 seconds and prints the number of interrupts received by the module once per second.
timely
The program runs a timely task each time an interrupt is reported. It prints statistics to stdout in the same format as the kernel modules.

This is an example of how the tools look like:

        burla% user/getcount
         27424275
         27432089 (delta  7814)
         27439902 (delta  7813)
         27447716 (delta  7814)
         27455529 (delta  7813)
        burla% export VERBOSE=1
        burla% user/waitirq-ioctl
        27605149
        burla% user/timely
        user/timely: setscheduler: Operation not permitted
        loops 1104 (lost   0); min  125 usec; avg  128 usec; max  131 usec
        loops 7813 (lost   0); min  124 usec; avg  128 usec; max  132 usec
        loops 7813 (lost   0); min  125 usec; avg  128 usec; max  132 usec
        loops 7812 (lost   0); min  125 usec; avg  128 usec; max  132 usec

The sescheduler error message above is reported since the program tries to get soft-real-time scheduling policy, but only the administrator succeeds.