INtime SDK Help
Interrupts
INtime SDK v6 > About INtime > INtime Kernel > Interrupts

Overview

Hardware events can cause an interrupt. An interrupt triggers an implicit call using an address supplied in the IDT. This directs control to an interrupt handler. The IDT is a system structure consisting of 256 entries, each of which can point to a different handler. The first 32 entries are reserved for special use by the CPU for events such as breakpoints and hardware faults. The remaining entries may be used to handle external interrupts. The index of a particular handle is called the interrupt vector.

If handling the interrupt takes little time and requires no system calls other than certain interrupt-related system calls, the interrupt handler can process the interrupt itself; the interrupt handler executes in the context of the thread running when the interrupt occurred. Otherwise, the handler should invoke an interrupt thread to finish processing the interrupt. Interrupt threads have their own context and are not dependent on the context of the thread that was interrupted.

After the interrupt is serviced by either the interrupt handler or the interrupt thread, control returns to the interrupted thread.

Interrupt sources and delivery methods

Most interrupts of interest to INtime programmers are generated by a device interface which is being programmed by an INtime application. Such devices generate two general classes of interrupt. When an interrupt is delivered to the CPU, the CPU must discover which interrupt have been delivered then look up its vector and invoke the appropriate interrupt handler. Over the years the method by which the CPU is informed of the interrupt vector has evolved, resulting in some programming complexity.

The original means of delivering an interrupt to the CPU was by means of a dedicated signal, called an interrupt request line, or IRQ. In order that multiple different devices can generate interrupts the IRQs are connected to an interrupt controller. Each input line on the controller is programmed to generate a different vector. The original Programmable Interrupt Controller (PIC) would assert the CPU's single interrupt signal, then the CPU would interrogate the PIC to discover the vector used by the particular IRQ. The PIC had to prioritize the IRQs to determine which one would be forwarded first in the event that two interrupts occurred simultaneously, and maintain the state of the other interrupts while one was being handled by the CPU. The handshake between the CPU and the PIC contributed to interrupt latency, which is the time between the signal being asserted, and the time at which the CPU starts to execute its interrupt handler. Each device had its own input on the controller, and the number of interrupts could be extended by cascading multiple controllers.

The next stage was to allow multiple devices to share an IRQ. Multiple devices could be connected to a single input pin on the PIC. Each device might have its own handler but they are all associated with the same vector. This was efficient in terms of hardware design but it means that every time that particular IRQ was asserted, all of the handlers associated with it had to be invoked to be sure that the interrupt was handled. It also added complexity to the interrupt handler because the code has to determine whether or not the interrupt came from its device before proceeding. This method of delivery first came about with the arrival of the PCI bus, where a greater number of devices could be installed in a system compared to the original ISA architecture.

With the arrival of multi-core CPUs a new interrupt controller was needed. Not only was it necessary to provide a vector when a given interrupt was asserted but a decision had to be made about which CPU core or cores were to be interrupted. The Advanced Programmable Interrupt Controller, or APIC, allowed for this, and it also made the delivery of the vector to the CPU core or cores more efficient by eliminating the handshake and just simply writes the vector to the CPU via a bus write cycle. Multiple APICs could be installed side by side without cascading, eliminating that source of additional latency also.

The current method of delivering interrupts from a device to the CPU cuts out the interrupt controller altogether. The device itself writes the vector directly to the CPU core or cores via a mechanism called a Message-Signaled Interrupt (MSI), which is a bus write cycle directly from the device to the CPU. This results in still less latency. MSI is the preferred method of delivering interrupts in the INtime kernel because of this minimal latency and because the delivery is independent of all other interrupt sources. Another extension of MSI is that it is possible for a device to deliver different vectors for different device statuses, allowing for simpler and more efficient programming of the interrupt handlers for the device.

Interrupt descriptor table (IDT)

The processor uses the IDT entry as a pointer to the interrupt handler to execute for the specific interrupt. Each IDT entry contains the physical address of the interrupt handler.

The hardware assigns a number to the cause of each interrupt and gives it an entry in the IDT. The INtime kernel does not use entries IDT 128-255. Because of the constraints of inter-operating with Windows, the entries assigned for external interrupts are different between the INtime for Windows and INtime Distributed RTOS kernels. This is one reason why interrupt levels are assigned to these interrupts, so that code is portable from one environment to the other. The entries are allocated as shown below.

See also: the user's guide for your microprocessor.

IDT Entry Description
0-31 Reserved for system events (faults, exceptions, breakpoints)
32-223 Reserved for internal kernel use
224–255 INtime interrupt entries

Interrupt controllers and interrupt lines

In the PC architecture, interrupts from hardware interfaces are commonly signaled to a PIC. This is configured in one of two ways. In legacy systems the controller resembles a pair of 8259A PIC devices cascade so that the slave PIC is connected to input #2 on the master PIC. In this mode there are 15 inputs available to connect interrupt lines (IRQs) to the controller. Of these up to 4 may be used for PCI devices. All PCI devices in the system must share these four lines. Certain inputs are generally assigned to specific devices in the PC architecture as in the following illustration:

Modern systems use the controller in APIC mode. In this mode the first 16 inputs are reserved for legacy device (ISA) interrupts leaving the rest available for handling PCI interrupts. The interrupt lines are connected to the I/O APIC (part of the chipset), which delivers the interrupts to the CPU via the Local APIC (part of the CPU).

An extension to the PCI specification and the APIC allows a PCI device to send an interrupt directly from the device to the PCI without using the I/O APIC. In this mode, called Message-Signaled Interrupts (MSI) the device sends a bus cycle to transfer the interrupt signal instead of activating the interrupt line. This feature is optional for PCI and PCI-X devices but is a requirement for PCI Express devices. This provides a significant advantage when dealing with shared interrupts.

Interrupt levels

The interrupt lines of the master and slave PICs are associated with interrupt levels. An interrupt level is a value defined by INtime which encodes both the interrupt input (IRQ) and its associated priority into a single value. All system calls which deal with interrupts use the interrupt level value to refer to an interrupt source.

In legacy mode the interrupt lines are assigned priority in order of the input on the 8259 PIC. IRQ0 (the system timer) has higher priority than IRQ5 on the mast PIC for example. All of the slave PIC interrupts connected to IRQ2 also have higher priority than IRQ5. If two interrupts occur simultaneously, the PIC informs the CPU of the higher-priority interrupt first.

Interrupt handlers and interrupt threads

Whether an interrupt handler services an interrupt level by itself or invokes an interrupt thread to service the interrupt depends on the system calls (these are limited) and the amount of time needed. An interrupt signal disables all interrupts; they remain disabled until the interrupt handler either services the interrupt and exits, or invokes an interrupt thread. Invoking an interrupt thread enables higher-priority interrupts (and in some cases, the same priority interrupts) to be accepted.

System calls and interrupt handlers

When writing an interrupt handler, follow these guidelines:

See also: Using INtime kernel calls in RT interrupt handlers

 

Interrupt system calls

This table lists common operations for interrupts and the system calls that perform the operations:

To . . . Use this system call . . .
Assign handler SetRtInterruptHandler
SetRtInterruptHandlerEx
Remove interrupt level ResetRtInterruptHandler
Send EOI SignalEndOfRtInterrupt
Invoke thread SignalRtInterruptThread
Put thread to sleep WaitForRtInterrupt
Enable level EnableRtInterrupt
Disable level DisableRtInterrupt
Get current level GetRtInterruptLevel
Set up segment EnterRtInterrupt
Get information about level GetRtInterruptInfo

This shows the order to make interrupt system calls and lists calls that interrupts frequently use:


Make these calls from the interrupt thread.

Make this call from the interrupt thread.

Make these calls from the interrupt handler.

Make this call from the interrupt thread or handler.

Make these calls from any thread.

Writing an interrupt handler

Interrupt handlers are generally written as C procedures, but they can be written in assembly language. You must save and restore all register values on entry and exit from the handler.

An interrupt handler uses the interrupted thread's stack.

If an interrupt handler services interrupts for a given level without invoking an interrupt thread, it must do these things:

  1. Save all register contents.
  2. Call EnterRtInterrupt to give access to the application data segment.
  3. Service the interrupt.
  4. Do one of these:
    • Call SignalRtInterruptThread. This signals the interrupt thread (if it exists) and sends an end-of-interrupt (EOI) signal to the hardware.
    • Call SignalEndOfRtInterrupt. This only sends an end-of-interrupt (EOI) signal to the hardware.
  5. Restore all register contents.
  6. Return using an IRETD instruction.
    Note:   Do not use a pointer to a value on the stack in your interrupt handler. This will cause a hardware fault.

Using SetRtInterruptHandler with a handler only

Before an interrupt handler can service an interrupt level, a thread must invoke SetRtInterruptHandler to bind the handler to the interrupt level. SetRtInterruptHandler places a pointer to the first instruction of the handler in the appropriate entry in the IDT.

These are the parameters you supply in SetRtInterruptHandler:

What the INtime kernel does with only a handler

  1. When an INtime application system starts running, all interrupt levels are disabled.
  2. When SetRtInterruptHandler binds an interrupt handler to a level, the INtime kernel enables the level immediately.
  3. When an interrupt occurs, the processor automatically transfers control to the handler. The handler executes in the context of the interrupted thread with all interrupts disabled.
  4. When the handler calls SignalEndOfRtInterrupt, this sends an end-of-interrupt (EOI) signal to the hardware. Control returns to the interrupted thread when the handler issues an IRETD instruction.

Use ResetRtInterruptHandler to cancel the assignment of a handler by clearing out the appropriate entry in the IDT. The call also disables the specified level.

Using an interrupt handler and an interrupt thread

If an interrupt handler invokes an interrupt thread, it must do these things:

  1. Save all register contents.
  2. Call EnterRtInterrupt.
  3. Possibly begin servicing the interrupt.
  4. Do one of these:
  5. Restore all register contents.
  6. Return using an IRETD instruction.

An interrupt thread must call these functions in the indicated order:

  1. Do any required thread initialization, such as pre-loading variables.
  2. Call SetRtInterruptHandler or SetRtInterruptHandlerEx.
  3. Enter a loop which:
    1. Calls WaitForRtInterrupt.
    2. Services the interrupt when notified by a SignalRtInterruptThread call from the handler.
    3. Returns to step a.
An interrupt thread, once initialized, is always in one of two modes: either servicing an interrupt or waiting for notification of an interrupt.

The interrupt thread has its own resources and runs in its own environment. The interrupt thread can use exception handlers, whereas the interrupt handler always handles exceptions inline.

Using SignalEndOfRtInterrupt with a handler and thread

While the interrupt thread is processing, the INtime kernel disables all lower interrupt levels. The associated interrupt level is either disabled or enabled, depending on the byMaxInt parameter passed to SetRtInterruptHandler.

If the number of pending interrupts is less than the interrupt limit specified, the associated interrupt level is enabled. All SignalRtInterruptThread calls that the handler makes (up to the limit specified) are counted.

If the associated interrupt level is disabled (the number of pending interrupts equals the pending interrupt limit) while the interrupt thread is running, the call to WaitForRtInterrupt enables that level.

Using WaitForRtInterrupt

You should call WaitForRtInterrupt from interrupt threads immediately after initializing and immediately after servicing interrupts. This call suspends the interrupt thread until the interrupt handler for the same level resumes it by invoking SignalRtInterruptThread.

If the number of pending interrupts is greater than 0 when the interrupt thread calls WaitForRtInterrupt the thread is not suspended. Instead, it continues processing the next WaitForRtInterrupt request.

Interrupt thread priorities

When a thread becomes an interrupt thread by calling SetRtInterruptHandler or SetRtInterruptHandlerEx, the INtime kernel assigns a priority to it according to the interrupt level to be serviced. The following table shows the relationship between the encoded level (the value used for the wLevel parameter of SetRtInterruptHandler), the Master and Slave interrupt levels, the IDT slot and the priorities of threads that service those levels.

Note:   If an interrupt thread's priority exceeds the maximum priority attribute of its process, the interrupt thread fails to set up and the INtime kernel returns an exceptional condition code. Prevent this by increasing the process's maximum thread priority using SetRtProcessMaxPriority.

Interrupt level and thread priority information — legacy interrupts

IRQ INtime Encoding Interrupt Thread Priority
0 0x0008 18
1 0x0018 34
3 0x0038 67
4 0x0048 83
5 0x0058 98
6 0x0068 114
7 0x0078 130
8 0x0020 36
9 0x0021 38
10 0x0022 40
11 0x0023 42
12 0x0024 44
13 0x0025 46
14 0x0026 48
15 0x0027 50
16 0x0030 52
17 0x0031 54
18 0x0032 56
19 0x0033 58
20 0x0034 60
21 0x0035 62
22 0x0036 64
23 0x0037 66
24 0x0040 68
25 0x0041 70
26 0x0042 72
27 0x0043 74
28 0x0044 76
29 0x0045 78
30 0x0046 80
31 0x0047 82

 Non-legacy interrupts

When a handler for an MSI or other non-legacy interrupt is installed, an unused entry starting at entry 16 is found and the thread priority for the entry used for the interrupt thread.

Using INtime kernel calls in RT interrupt handlers

The INtime kernel assigns priorities to INtime interrupt threads based on the handler's interrupt level. Lower-priority interrupts are disabled when an interrupt thread is running and will run later. If this is a problem for your application, use an ordinary thread instead of an interrupt threads. This enables you to control the thread's priority. You can use low-level INtime RT kernel calls to signal an ordinary or non-interrupt thread. When you use low-level INtime kernel calls in an INtime interrupt handler, you need to create the service thread, cause the service thread to perform specific functions, and cause the handler to perform specific functions.

Shared interrupts

The PCI bus architecture as used in the PC allows less flexibility to the system designer regarding how interrupts are routed and separated from other sources. While it may be possible on a system consisting of a PCI Local Bus architecture to separate PCI device interrupts from each other, as soon as a bridge is added, and more buses connected, then it is almost impossible to avoid the possibility that different devices may be sharing the same interrupt line.

INtime provides support to allow different devices to share the same interrupt line by providing generic interrupt handlers internally, which call out to user-supplied, device-specific handlers. When an interrupt occurs on such a line, all the handlers installed for that hardware interrupt level are called in sequence, starting with the first installed. This results in a little extra overhead over standard handlers, so use of this feature should be restricted to situations where you cannot guarantee exclusive access to an interrupt line.

Note:   It is not possible to share an interrupt line between INtime software's RT kernel and the Windows kernel.

Another consequence of interrupt sharing is that the interrupt signals are level-triggered. That is, the PIC is programmed to recognize an interrupt condition whenever the interrupt line is asserted. Non-PCI (non-shared) interrupts are edge-triggered; the PIC recognizes when the interrupt line changes state to active. The practical consequence of this level triggering is that if the device is not made to de-assert its interrupt line before the CPU exits from the handler, then the interrupt condition is still asserted, and the handler is immediately re-entered. Against that, remember that while the CPU is executing an interrupt handler, no other interrupt will occur until the CPU interrupt mask is cleared. Thus we want to minimize the time spent in each shared interrupt handler, but do sufficient work to remove the interrupt condition.

To install a shared interrupt handler, use SetRtInterruptHandlerEx, and specify that this is to be a shared interrupt handler by OR-ing the SHARED_LEVEL macro with the desired interrupt level. SetRtInterruptHandlerEx returns a modified interrupt level value which all subsequent interrupt system calls must use. Here is an example of a handler installed on the level associated with IRQ11:

wLevel = SetRtInterruptHandlerEx(
    SHARED_LEVEL | IRQ11_LEVEL, 
    255, SharedIntHandler, Param
 );

An INtime shared interrupt handler has a different interface when compared to a standard interrupt handler. The standard handler is invoked directly from the IDT when the interrupt occurs, whereas the shared interrupt handler is called from a common handler in INtime's RT kernel. The kernel thus can perform tasks normally required of the handler, such as setting the process context and finding the interrupt level value. An example of a shared handler looks like this:

__INTERRUPT void SharedIntHandler(WORD wRACS, WORD wLevel, LPVOID Param)
{
    // TODO: declare any local variables here
    __SHARED_INTERRUPT_PROLOG();
    // Interrupt-processing statements go here
    SignalRtInterruptThread(wLevel);
    __SHARED_INTERRUPT_RETURN();
}

The parameter lpParamPtr passed to SetRtInterruptHandlerEx is passed unmodified to the interrupt handler. The first parameter of the handler is not for use by the developer.

Note that the PROLOG and RETURN macros differ from those used in the standard interrupt handler. The return statement should not be used in the handler; use the __SHARED_INTERRUPT_RETURN() macro instead.

Other than the differences illustrated above, and taking into account the restrictions imposed on a shared interrupt handler, the same techniques can be applied as to a standard interrupt handler. Some interrupt system calls will have no effect. For example, SignalEndOfRtInterrupt is ignored for shared handlers, since this action is taken automatically after all handlers have been called.

Shared interrupt handlers should not call GetRtInterruptLevel since the return value is meaningless in shared handler context. The encoded interrupt level is supplied as a parameter to the handler. Shared handlers should not call EnterRtInterrupt since the process context is already set up on shared handler entry.

Message-signaled Interrupt (MSI)

If your device supports MSI then you can tell the RT kernel to use this feature to invoke your interrupt handler. MSI handlers are never shared, since each MSI event generates a different interrupt vector. This has two advantages; the performance is slightly better than a shared handler because only one is called, and the device is isolated from any other interrupt source.

Generally the same rules should be followed when writing an MSI handler as when writing a shared handler. Keep the handler short but perform sufficient operations to remove the interrupt condition at the device and signal a thread to perform the rest of the work of handling the interrupt condition.

To install an MSI handler, first set up a structure identifying the PCI device and any user parameter, then use SetRtInterruptHandlerEx and specify that this is to be an MSI handler by specifying the predefined value MSI_LEVEL for the level parameter. SetRtInterruptHandlerEx returns a modified interrupt level value which all subsequent interrupt system calls must use. Here is an example of a handler installed for a PCI device described in a PCIDEV structure:

MSI_PARAM msi;
msi.PciAddress = MKPCIADDR(&pci);
msi.ReservedZero = 0;
msi.Param = Param;
wLevel = SetRtInterruptHandlerEx(MSI_LEVEL, 255, MsiHandler, &msi);

Note that the PCI structure is of type PCIDEV and is usually populated by a call to PciFindDevice. The MKPCIADDR macros encodes an identifier for the device which the kernel uses to program the device to generate an MSI. The Param value is the user parameter which is passed unmodified to the interrupt handler.

An INtime MSI handler has a similar interface to a shared interrupt handler. The MSI handler is called from a common handler in INtime software's RT kernel. The kernel thus can perform tasks normally required of the handler, such as setting the process context and finding the interrupt level value. An example of an MSI handler looks like this:

__INTERRUPT void MsiHandler(WORD wRACS, WORD wLevel, MSI_PARAM *msi)
{
    LPVOID *Param = msi->Param;      // user parameter

        // TODO: declare any local variables here

        __SHARED_INTERRUPT_PROLOG();

        // Interrupt-processing statements go here

        SignalRtInterruptThread(wLevel);

        __SHARED_INTERRUPT_RETURN();
}

The parameter of type MSI_PARAM or MSI_PARAM_EX passed to SetRtInterruptHandlerEx is passed unmodified to the interrupt handler. The first parameter of the handler is not for use by the developer.

Other than the differences illustrated above, and taking into account the restrictions imposed on a shared interrupt handler, the same techniques can be applied as to standard interrupt handler. It is not required to call SignalEndOfRtInterrupt for MSI handlers, since this action is taken automatically by the kernel after the handler returns.

Like shared handlers, MSI handlers should not call GetRtInterruptLevel since the return value is meaningless in MSI handler context. The encoded interrupt level is supplied as a parameter to the handler. Calling EnterRtInterrupt is also superfluous since the process context is already set up on MSI handler entry.

Multiple MSI support

The MSI specification allows a device to generate interrupts on multiple vectors. Each might be used to signal a different condition or else to signal a different CPU in the system. Multiple MSI support is present in the INtime kernel and requires special programming. Each interrupt vector generated by the device has its own handler and thread.

Multiple MSI support is not available in INtime for Windows Shared Mode, due to the limited number of interrupt vectors available to INtime in this mode.

To find out if a device is capable of generating multiple MSIs, call PciGetMsiCount to determine the number of vectors supported by the device. If the number is greater than 1, then the device supports multiple MSIs. In this case, create an interrupt object for the number of vectors reported, using AllocateRtInterrupts. The interrupt object reserves and encapsulates a range of interrupt vectors which meets the alignment requirements of the MSI specification.

Using this interrupt object, an interrupt handler may be installed for each of the available vectors using SetRtInterruptHandlerEx and a MSI_PARAM_EX structure:


MSI_PARAM_EX msi;
msi.PciAddress = MKPCIADDR(&pci);

msi.MsiIndex = 0; // which MSI source is to be programmed?
msi.MsiVectors = AllocateRtInterrupts(nVectors);
msi.ReservedZero = 0;
msi.Param = Param;
wLevel = SetRtInterruptHandlerEx(MSI_LEVEL, 1, MsiHandler, &msi);

Further instances of the MSI_PARAM_EX structure may be created with different values of MsiIndex in order to install further handlers for the device.

The Visual Studio INtime Application Wizard generates generic code which detects if multiple MSIs are available and will create a handler for the first vector (MsiIndex = 0) for the device.

Creating the service thread

You need to create a thread to handle the interrupt. When creating the thread, set the thread priority so that it does not disable lower-level interrupts.

Things to do from the service thread

  1. Use knCreateRtSemaphore or knCreateRtMailbox to create a low-level semaphore or mailbox. Store the handle in your application's global memory.
  2. Call SetRtInterruptHandler with byMaxInt set to 0. This indicates there is no associated RT interrupt thread.
  3. Enter an infinite loop in which you wait at the low-level semaphore or mailbox for notification of an interrupt. Process the interrupt then wait again, and so on.

Things to do from the interrupt handler

  1. Call EnterRtInterrupt.
  2. Obtain a scheduling lock by using knStopRtScheduler prior to signaling the thread. This prevents a thread switch from immediately occurring as a result of the signaling call. If a thread is waiting, it will be made ready but will not run immediately.
  3. After doing the required handler-level processing use knReleaseRtSemaphore or knSendRtData to signal the thread that handles the interrupt.
  4. Send an End of Interrupt (EOI) to the interrupt controller by using SignalEndOfRtInterrupt.
  5. Release the scheduling lock by using knStartRtScheduler. This call resumes normal scheduling. Under normal scheduling the highest priority ready thread runs. If knStartRtScheduler causes an immediate thread switch, the rest of the handler code will not execute until the originally interrupted thread gets to run again. For this reason, you should place the knStartRtScheduler call just prior to restoring the registers and returning from the handler.

Example using low-level INtime kernel calls in RT interrupt handlers

The following code shows how you can use INtime kernel calls in RT interrupt handlers:

__INTERRUPT void IntHdlr(void)
{
    WORD IntLevel;

    __INTERRUPT_PROLOG();

    IntLevel = GetRtInterruptLevel(IntLevel);
    EnterRtInterrupt (IntLevel);
    */
    * perform any handler level interrupt processing here 
    */
    /* 
    * The handler will now signal an ordinary INtime thread which is waiting at
    * a Kernel semaphore.
    * Get scheduling lock prior to making the signaling call. 
    */
    knStopRtScheduler();
    /*
    * The knStopRtScheduler call prevents a thread switch from immediately
    * occurring as a result of knReleaseRtSemaphore.
    * If a thread is waiting at knsemaphore, it will be made ready but will
    * not run immediately.
    */
    knReleaseRtSemaphore(knsemaphore);   /* signal ordinary thread */
    /*
    * The SignalEndOfRtInterrupt call sends an End of Interrupt (EOI) to the
    * interrupt controller.
    */
    SignalEndOfRtInterrupt(IntLevel);
    /* 
    * Release the scheduling lock and resume normal scheduling.
    * At this point the highest priority ready thread will run, possibly 
    * even before the return from knStartRtScheduler. 
    *
    * If knStartRtScheduler causes an immediate thread switch, the rest 
    * of the handler code will not be executed until the *interrupted* 
    * thread gets to run again. For this reason, the knStartRtScheduler 
    * call should be the very last call in the handler, just prior to the 
    * return.
    */
    knStartRtScheduler();

    __INTERRUPT_RETURN();
}

Interrupt servicing patterns

The next figure illustrates the relationships between the servicing patterns of interrupt handlers and interrupt threads.

The handler performs the simple, less time-consuming functions; it signals the interrupt thread to perform more complicated functions. The handler sends information to the thread in data buffers. The number of pending interrupts influences when and how interrupts are disabled.

An interrupt handler might signal an interrupt thread sometimes, but not every time. For example, an interrupt handler may put characters entered at a terminal into a buffer. If the character is an end-of-line character, or if the character count maintained by the interrupt handler indicates the buffer is full, the interrupt handler calls SignalRtInterruptThread to activate the interrupt thread to process the contents of the buffer. Otherwise, the interrupt handler calls SignalEndOfRtInterrupt and then returns control to the application thread.


Single Buffer Example

An interrupt handler that reads data from an external device, character by character, and places the characters into a buffer is an example of a single-buffer interrupt handler. When the buffer fills, the handler calls SignalRtInterruptThread to signal an interrupt thread to further process the data. There is only one buffer for the data, so the interrupt level associated with the interrupt thread must be disabled while the thread is processing.

Because the thread called SetRtInterruptHandler with byMaxInt equal to 1, the INtime kernel automatically disables the interrupt level when the handler invokes SignalRtInterruptThread.

This prevents the interrupt handler from destroying the contents of the buffer by continuing to place data into an already full buffer. This figure illustrates single buffering:


  1. The handler places data into the buffer.
  2. When the buffer is full, the handler calls SignalRtInterruptThread to start the thread.
  3. Upon completion, the thread calls WaitForRtInterrupt.

If you require only single buffering in interrupt servicing, specify 1 for the byMaxInt parameter in SetRtInterruptHandler.

Multiple buffer example

In this example, the interrupt handler and the interrupt thread provide the same functions as in the previous example, but they use multiple buffers. In this case, the interrupt level associated with the thread need not always be disabled while the thread runs. Instead, the thread can process a full buffer while the handler continues to accept interrupts. When the handler fills a buffer, it calls SignalRtInterruptThread to start the interrupt thread, as in the first example. However, because the byMaxInt is greater than 1, the interrupt level is not disabled. Instead, the handler continues to accept interrupts, placing the data into the next empty buffer.

While this occurs, the interrupt thread processes the full buffer. When the thread completes the processing, it calls WaitForRtInterrupt to indicate that it is ready to accept another SignalRtInterruptThread request (another full buffer),and to indicate that the buffer it just finished processing is available for re-use by the handler.

Because the handler and the thread run somewhat independently, the handler may fill a buffer and call SignalRtInterruptThread before the thread finishes processing the previous buffer. To prevent the SignalRtInterruptThread request from becoming lost, the INtime kernel maintains a count of pending interrupt requests. Each time the handler calls SignalRtInterruptThread , the count of pending interrupts is incremented by one. Each time the thread calls WaitForRtInterrupt, the count of pending interrupts decrements by one. You can use the SDM vt command to view an interrupt thread and the count of pending interrupts.

If the count of pending interrupts is still greater than 0 after the interrupt thread calls WaitForRtInterrupt, the thread does not wait for the next SignalRtInterruptThread to occur before resuming execution. Instead, it immediately starts processing the next full buffer. Neither the interrupt thread nor the interrupt handler has to wait for the other. The interrupt handler can continually respond to interrupts without having the thread disable the interrupt level. The interrupt thread can continually process full buffers of data without waiting for the handler to call SignalRtInterruptThread.

This illustrates the multiple buffering handler:



  1. The interrupt handler starts filling the empty buffer.
  2. The handler calls SignalRtInterruptThread to start the thread on processing the full buffer.
  3. The interrupt thread processes the full buffer, then calls WaitForRtInterrupt to wait for the next full buffer.
  4. The handler keeps filling buffers.
  5. The thread keeps processing them and calling WaitForRtInterrupt.

The following table describes the actions of the handler and the thread. The table is divided into three parts:

  1. Actions of the interrupt handler
  2. Actions of the interrupt thread
  3. The count of pending interrupts

The byMaxInt parameter of SetRtInterruptHandler is set to three. The table shows the actions of both the handler and the thread through time, and the change in value of the count.

Time Interrupt handler Interrupt thread Count
Thread A calls SetRtInterruptHandler to set handler and thread for level, setting byMaxInt to 3. 0
A calls WaitForRtInterrupt to wait for first request from handler. 0
Intrpt Handler processes interrupt, starts filling first buffer.
Intrpt Process interrupt. Buffer is full. Call SignalRtInterruptThread.
A starts processing 1st full buffer. 1
Intrpt Process interrupt. Start filling next buffer.
Intrpt Process interrupt. Buffer is full. Call SignalRtInterruptThread. 2
Intrpt Process interrupt.Start filling next buffer. 2
Intrpt Process interrupt. Buffer is full. Call SignalRtInterruptThread. Count is 3. Interrupt level is disabled. 3
Call WaitForRtInterrupt. Start processing next buffer. 2
Intrpt Process interrupt. Buffer is full. Call SignalRtInterruptThread. 3
Call WaitForRtInterrupt. Start processing next full buffer. 2

The interrupt thread, when it initially calls SetRtInterruptHandler, specifies the maximum number of pending interrupt requests in byMaxInt. When the interrupt handler calls SignalRtInterruptThread, causing the number of pending interrupts to be incremented to the maximum:

Always set byMaxInt equal to the number of buffers that the thread and handler use. If the thread sets byMaxInt larger than the number of buffers, the handler will accept interrupts when no buffers are available and data will be lost. If the thread sets byMaxInt smaller than the number of buffers, there will always be empty buffers and space will be wasted.

For example, if you need one buffer, set byMaxInt to 1. In this case, the INtime kernel disables the interrupt level while the thread is processing the buffer. If you need two buffers, set byMaxInt to 2. Then, the handler can fill one buffer while the thread is processing the other. Additional buffers require correspondingly higher limits. However, if the thread sets the limit to 0, the interrupt handler operates without an interrupt thread.

Disabling interrupts

The INtime kernel may mask less important interrupts automatically while the interrupt thread is running. Occasionally you may want to prevent interrupt signals from causing an immediate interrupt at the thread's own level. For example, in a device driver finish procedure, you may want to disable interrupts from the device before deleting resources an interrupt handler or thread would require. You can disable each interrupt level except the system clock. You disable a level by using the DisableRtInterrupt system call.

If the level is disabled, the interrupt signal is blocked until the level is enabled, at which time the signal is recognized by the CPU. However, if the signal is no longer emanating from its source, it is not recognized and the interrupt is not handled.

Warning: The APIC and MSI hardware implementation mean that masking one of these interrupt sources may cause interrupts to be lost. These devices do not latch the interrupt state and it is not recommended to call DisableRtInterrupt during runtime for an IRQ connected to the APIC or an MSI. If you need to temporarily disable an interrupt source you must do this in the device itself, which requires knowledge of how the device works.

An interrupt level can be disabled in these ways:

Note: the kernel does NOT disable the interrupt level for shared interrupts and never disables the level for MSI sources, because of the possibility of losing interrupt state while the level is masked.

Enabling interrupt levels from within a thread

Sometimes, an interrupt thread may finish with a buffer of data before it finishes its processing. An example of this is a thread that processes a buffer and then waits at a mailbox, possibly for a message from a user terminal, before calling WaitForRtInterrupt. If other buffers of data are available to the handler (the number of pending interrupts has not reached the limit), this does not present a problem. The handler can continue accepting interrupts and filling empty buffers. However, if the interrupt thread is processing the last available buffer (i.e., the limit has been reached), the interrupt handler will not receive further interrupts because the interrupt level is disabled. This may be an undesirable situation if the interrupt thread takes a long time before calling WaitForRtInterrupt.

To prevent this situation, the interrupt thread can call EnableRtInterrupt immediately after it processes the buffer, enabling its associated interrupt level. This means that while the thread engages in its time-consuming activities, the interrupt handler can accept further interrupts and place the data into the buffer just released by the thread. You can use this technique whenever the limit is 1, whether or not you use a buffer.

However, if the interrupt handler fills the buffer and calls SignalRtInterruptThread before the thread calls WaitForRtInterrupt, these events occur:

If the interrupt thread calls EnableRtInterrupt when the count is below the limit, the level is enabled and no exception code is returned. However, if the interrupt thread tries to enable the interrupt level when the count is greater than the limit, the EnableRtInterrupt system call returns the E_CONTEXT condition code.

If a thread other than an interrupt thread tries to enable the level, one of these events may occur:

See Also

Semaphores

Mailboxes

Scheduler