News Archive (1999-2012) | 2013-current at LinuxGizmos | Current Tech News Portal |    About   

The Linux “real time interrupt patch”

Jan 8, 2004 — by LinuxDevices Staff — from the LinuxDevices Archive — 39 views

The “real time interrupt patch” (rtirq-patch) described in this document enables the linux kernel for hard-real-time applications by adding priorities to interrupts and spinlocks.

With a measured worst case latency of five microseconds and with a typical jitter below one microsecond at an interrupt period of up to 100 kHz an rtirq-enhanced linux kernel may be usable for a broad range of hard real time control loop applications.

Interrupts are prioritized by enabling or disabling specific interrupts through the interrupt controllers. The current implementation provides two interrupt levels (low and high). The linux interrupt processing routines are modified to be re-entrant in order to allow higher priority interrupts to occur while low priority interrupt processing is in progress. Whenever cli(), sti(), local_irq_enable(), local_irq_disable(), save_flags(), save_flags_and_cli() or restore_flags() are called, then only low priority interrupts are enabled or disabled in the appropriate hardware interrupt controller and high priority interrupts can still occur. Additionaly, at the beginning of the interrupt service routine, it is ensured that the interrupt controllers are programmed to reflect the settings for current interrupt priority. This will allow high priority interrupts to occur while a low priority interrupt is still in progress.

Differentiation

Kernel preemption and the low-latency patch are improving the user space latencies and do not (heavily) affect interrupt latencies. It should be easly possible to mix low-latency and/or kernel preemption with rtirq to achive combined minimum interrupt and user-space latencies.

RTAI and RTLinux are achiving similar numbers for interrupt latencies as rtirq, but those variants are using a dual-kernel approach with interrupt defering (legacy RTAI and RTLinux) or interrupt priority queuing (RTAI with ADEOS). However, with a reasonable effort, it should be possible to modify the RTAI or RTLinux cores in a way that they are simply running as a high priority linux interrupt, allowing to make use of the existing and comfortable APIs of these sub-kernels.

Requirements

In this release, rtirq is only available for Intel Pentium and AMD Athlon CPUs that provide a local APIC.

Stabilty

This is the very first release of the linux interrupt priority scheme. It works pretty smooth and stable on my ASUS A7N8X Deluxe with an Athlon XP2400+. The system never crashed during the final testing period, even under heavy load (ping-flood and hardware accelerated OpenGL glxgears on an NVidia GeForce 4200) for hours.

General design considerations

High priority interrupts shall only be used in an application specific context and only when prooven to be unavoidable! No standard linux driver shall ever use high priority interrupts (maybe with the exception of non-buffered serial interfaces). Don't use high priority interrupts just because they are an available feature. The number of high priority interrupts per system should be reduced to a minimum and the user needs to make sure that the related interrupt service routines are determined.

Implementation notes

The current implementation provides three operating levels: “all interrupt enabled” (RTIRQ_LEVEL_ZERO), “low priority interrupts disabled” (RTIRQ_LEVEL_LOW) and “all interrupts disabled” (RTIRQ_LEVEL_HIGH). Interrupts 0 to 15 are routed through APIC on ExtINT and can be enabled or disabled with a single bit in some local APIC control register. The APIC timer interrupt has a sepererate enabling/disabling bit. In the current implementation, interrupts 0 to 15 are all low level interrupts and the APIC timer interrupt can be low or high priority. The basic trick is to allow ExtINT and APIC timer interrupt on level zero, disable ExtINT but enable APIC timer interrutp on level low and disable both on level high.

There does exist a seperate document (doc/patch-description.sxw) that walks through the patch and descibes it in detail.

Limitations

In the current implementation, only the local APIC timer can be a high priority interrupt source.

Feature candidate: high priority interrupts 0 to 15

In the current implementation, interrupts 0 to 15 are enabled or disabled as a set through the APIC ExtINT interrupt mask flag. This means it is not possible to have individual interrupts running in high priority. There are two ways on how to get arround that:

Interrupt priorization through IO-APIC:

When routing interrupts through an IO-APIC (rather than routing through ExtINT), it is possible to set individual interrupt priorities in some IO-APIC registers. In the local APIC, you can then decide through the PROCMASK register up to which level interrupts should be delivered to the CPU.

For systems without IO-APIC it is necessary to use another approach:

Mask low priority interrupts through i8259:

Interrupts could also be prioritized by enabling or disabling individual interrupt lines in the i8259 interrupt controller when changing the current interrupt level (rtirq_setlevel). But as accesses to the i8259 are pretty slow, system throughput performance may be reduced. On the other hand, this scheme does also allow to introduce interrupt priorization on CPUs without local APIC such as 80386 and 80486.

Feature candidate: more priority levels

The current implementation has only two interrupt priority levels (low and high). When implementing the above suggested IO-APIC based interrupt priority scheme, more interrupt priority levels might be introduced to reflect the needs of more complex hard real time applications.

Feature candidate: SMP operation

It should be basically possible to have real time interrupts on SMP systems, but this variant has not yet been implemented. It would probably only take little adoptions (using prioritized interrupt though IO-APIC as suggested), but there is on problem left: when one cpu holds a low priority spinlock and the other cpu likes to aquire the lock during a high priority interrupt, then the interrupt is effectivly running in low priority mode, because it needs to wait until the lock is freed up on the first cpu. But here, the low-level locked processing may be interrupted again by higher level interrupts. It would be necessary to introduce a priority inversion avoidence mechanism (i.e priority inheritence) for spinlocks in this case. To avoid priority inversion (even on single CPU systems), it is recommended to only use high priority locks for high priority interrupts. (using low priority spinlocks in high priority interrupts is IMHO a bad design, anyway).

Portabilty

From experience with ARM, MIPS, M68K and PPC, it should be fairly easy to port the interrupt priority scheme to other architectures and platforms. In fact, the i386 port was probably the most complex one since it was necessary to take a lot of legacy and wired i386 stuff into account – so if linux interrupt priorization does works there, then it should be possible to make it working everwhere.

Interrupt priority API

There actually doesn't exist a real API for prioritized interrupts, because the basic idea is to use the same known interfaces as introduced for the standard kernel. However, there are certain constraints and additional functions the user need to take into account in order to properly use real timer interrupts:

void rtirq_mount_apic(unsigned int level);

Activate the real time interrupt timer system. The kernel will mostly behave just like a standard kernel until the real-time interrupt scheme is activated. You need to tell the priority the APIC timer interrupt should have. If choosing RTIRQ_LEVEL_LOW, then the interrupt will behave like a standard linux interrupt (means non-real time) and you can use any kernel function in the interrupt handler that is known to work safely in this context. If choosing RTIRQ_LEVEL_HIGH as parameter, the APIC timer will run in high priority interrupt mode and the only safe calls in within the associated interrupt handler are: rtirq_getlevel(), rtirq_setlevel(), wake_up_interruptible() and all spinlock operations (assuming the spinlocks used are high priority spinlocks, otherwise you will run into priority inversion problems).

void rtirq_umount_apic(void);

Put the system back into “bootstrap mode”. Mostly makes the kernel behave just like a standard kernel.

unsigned int rtirq_getlevel(void);

Get the currently set interrupt priority level. Valid values are RTIRQ_LEVEL_ZERO, RTIRQ_LEVEL_LOW and RTIRQ_LEVEL_HIGH.

unsigned int rtirq_setlevel(unsigned int level);

Set the interrupt priority level. Valid values are RTIRQ_LEVEL_ZERO, RTIRQ_LEVEL_LOW and RTIRQ_LEVEL_HIGH. The previsously set priority level will be returned.

request_irq() and free_irq()

These function will behave as usual. Please note the the currently only real time interrupt available is the local apic timer interrupt (number 32). When an high level interrupt is not of type SA_INTERRUPT, the the interrupt level will be set to low while processing the user definded interrupt handler. This will allow other high priority interrupts to occur but prevents low priority interrupt. Note, that (in future) it won't be possible to share interrupts amongst different priority levels.

wake_up_interruptible();

In an high priority interrupt handler, you can safely use wake_up_interruptible() to inform a waiting processes. Data that is i.e. shared between a high priority interrupt handler and a file operation routine should be protected by high priority spinlocks (when not being processed atomicaly in process context).

spinlock operations

Low priortiy spinlocks will behave “as usual”. All spinlocks are low level be default unless initialized with SPIN_LOCK_UNLOCKED_LEVEL(RTIRQ_LEVEL_HIGH) instead of the usual SPIN_LOCK_UNLOCKED. Please note that using a low priority spinlock in a high priority interrupt handler, is forbidden since it doesn't prevent the high priority interrupts to occur while spin_lock_irqsave() locked data is being processed.

Installation

  1. Download a vanilla 2.4.23 linux kernel
  2. Unpack the kernel tarball
  3. Apply the patch (patches/patch-2.4.23-rtirq)
  4. Configure the kernel to your needs. Please note that you need to activate:

    Processor type and features >
    [*] Local APIC support on uniprocessors
    [*] IO-APIC support on uniprocessors
    [*] Real-Time Interrupts

    Also, please make sure to disable power management or you will have much higher latencies due to APM BIOS calls.

  5. Compile and install the kernel and the modules.
  6. Reboot your machine with the new kernel. Disabling the APIC in BIOS may help for certain setups.
  7. Make sure that the IO-APIC is not in use: Do “cat /proc/interrupts”: interrupts 0 to 15 should be of type “XT-PIC”. If this is not the case, then your are probably out of luck and interrupt priorisation will not yet work on your system.
  8. Compile the example module (mod.c) and application (app.c) (to be found in the “example” sub-directory):

    gcc -I/lib/modules/`uname -r`/build/include -O2 -Wall -D__KERNEL__ -DMODULE -fomit-frame-pointer -c mod.c gcc -O2 -Wall -o app app.c

  9. Create a device node:

    mkdev c 254 0 /dev/apictimer

Running the example

  1. Insert the example kernel module (as root):

    insmod mod.o

    Alternativly, do

    insmod mod.o local_apic_timer_level=1

    to start the interrupt im low-priority mode.

  2. Start the example application (as root):

    ./app

  3. Put some interrupt load on your machine (ping flood, 3D-graphics, hard-disk operations, etc.)

Description of the example

The kernel module of the example application programs the local APIC timer to 1 KHz periodic interrupt. The user space part then collects interrupt latency information for one minute (you can stop it with CTRL+C if necessary). When attaching an oscilloscope to pin 2 of your parallel port (0x378), you can see a 500 Hz rectangle signal while the application is running. After the execution, you will see a new file named "results". The entries below the headline "Interrupt statistics" provides the following information:

Row 1: latency in microseconds
Row 2: number of interrupts occured with the latency in the first row
Row 3: accumulated percentage.

The following sample output means that 99.9% of all interrupts are handled in within 3.1 microseconds after the APIC timer has expired. The lowest latency measured was 0.4 microseconds (1333 occurences) and the worst case is 5.2 microseconds (one occurence out of the 60000 measured latencies during one minute).
interval: 1000 loops: 60000

Interrupt statistics
0.40 1333 (2.221667)
0.50 6159 (12.486667)
0.60 10996 (30.813333)
0.70 10042 (47.550000)
0.80 9228 (62.930000)
0.90 6398 (73.593333)
1.00 4902 (81.763333)
1.10 4191 (88.748333)
1.20 2988 (93.728333)
1.30 1527 (96.273333)
1.40 772 (97.560000)
1.50 443 (98.298333)
1.60 272 (98.751667)
1.70 137 (98.980000)
1.80 40 (99.046667)
1.90 18 (99.076667)
2.00 17 (99.105000)
2.10 38 (99.168333)
2.20 35 (99.226667)
2.30 44 (99.300000)
2.40 55 (99.391667)
2.50 52 (99.478333)
2.60 59 (99.576667)
2.70 63 (99.681667)
2.80 41 (99.750000)
2.90 38 (99.813333)
3.00 33 (99.868333)
3.10 29 (99.916667)
3.20 18 (99.946667)
3.30 17 (99.975000)
3.40 3 (99.980000)
3.50 3 (99.985000)
3.60 2 (99.988333)
3.70 2 (99.991667)
3.80 1 (99.993333)
4.40 1 (99.995000)
4.90 1 (99.996667)
5.10 1 (99.998333)
5.20 1 (100.000000)

This output has been sampled in high priority mode using an Athlon XP2400+ on an ASUS A7N8X (Rev 1.03) under ping-flood condition and with "glxgears" running on an Nvidia GeForce 4200 at the same time.

In low priority mode, 99.9% of all interrupts occur within 486.1 microseconds and the worst case is 500.8 microseconds.

If you like to experiment with a higher timer frequency, then just need to change the value for "INTERVAL" in def.h and re-compile the sample application and the kernel module. The default value is 1000 (1 kHz). A value of 10000 means 10 kHz. Depending on your system setup, you may try higher values, but be warned that your system may lock up in case the selected interval is too high for it!

(NO) Warranty

I certainly cannot provide any kind of warranty. Altough this document has been put together with best intensions, i cannot be held responsible for any kind of demage (physically or mentally) that may result by following the instructions given above.


About the author: Bernhard Kuhn lives in Munich, where he started hacking at the age of 12 on a Commodore PET2001. He switched from AmigaOS to Linux in 1994 after it was clear that there wouldn't be any new hardware available for the "best OS ever." After aborting a PhD (too boring) on real time computer systems, he spent almost two year at the German "Linux-Magazin" as test engineer and "stuff writer," playing arround with all sorts of brand new hardware. Working for Lineo and Metrowerks gave him the opportunity to get experienced with all sorts of embedded architectures such as ARM, MIPS, PPC, SH3 and Coldfire. He spends his rare free time with inline-skating and playing billards.

 
This article was originally published on LinuxDevices.com and has been donated to the open source community by QuinStreet Inc. Please visit LinuxToday.com for up-to-date news and articles about Linux and open source.



Comments are closed.