| lpirq 3.00 |
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.
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.
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.
The package is made up of a few kernel modules, they are explained one by one.
The device /dev/lpirq supports read, poll (select),
ioctl and mmap. The system calls have the following
role:
cat /dev/lpirq. Use from C
code is explained in User Space Tools. Non-blocking read
is not supported.
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.
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
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
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.
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%
The subdirectory user/ includes a few programs that can be used to interact with the module from user space.
mapperwmapperwaitirqwaitirq-ioctlVERBOSE exists, the count is
printed to stdout.
getcounttimelystdout 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.