Using InterruptAttach()

The InterruptAttach() function associates the IRQ vector (intr) with your ISR handler (handler), passing it a communications area (area).

The size and flags arguments aren't germane to our discussion here (they're described in the C Library Reference for the InterruptAttach() function).

For the ISR, the handler() function takes a void * pointer and an int identification parameter; it returns a const struct sigevent * pointer. The void * area parameter is the value given to the InterruptAttach() function—any value you put in the area parameter to InterruptAttach() is passed to your handler() function. (This is simply a convenient way of coupling the interrupt handler ISR to some data structure. You're certainly free to pass in a NULL value if you wish.)

After it has read some registers from the hardware or done whatever processing is required for servicing, the ISR may or may not decide to schedule a thread to actually do the work. In order to schedule a thread, the ISR simply returns a pointer to a const struct sigevent structure—the kernel looks at the structure and delivers the event to the destination. (See the QNX Neutrino C Library Reference under sigevent for a discussion of event types that can be returned.) If the ISR decides not to schedule a thread, it simply returns a NULL value.

As mentioned in the documentation for sigevent, the event returned can be a signal or a pulse. You may find that a signal or a pulse is satisfactory, especially if you already have a signal or pulse handler for some other reason.

Note, however, that for ISRs we can also return a SIGEV_INTR event. This is a special event that really has meaning only for an ISR and its associated controlling thread.

A very simple, elegant, and fast way of servicing interrupts from the thread level is to have a thread dedicated to interrupt processing. The thread attaches the interrupt (via InterruptAttach()) and then the thread blocks, waiting for the ISR to tell it to do something. Blocking is achieved via the InterruptWait() call. This call blocks until the ISR returns a SIGEV_INTR event:

struct sigevent event;

main ()
{
    // perform initializations, etc.
    …
    // start up a thread that is dedicated to interrupt processing
    pthread_create (NULL, NULL, int_thread, NULL);
    …
    // perform other processing, as appropriate
    …
}

// this thread is dedicated to handling and managing interrupts
void *
int_thread (void *arg)
{
    /* Enable the INTERRUPT ability */
    procmgr_ability(0,
        PROCMGR_ADN_ROOT|PROCMGR_AOP_ALLOW|PROCMGR_AID_INTERRUPT,
        PROCMGR_AID_EOL);
    …
    // initialize the hardware, etc.
    …
    // initialize the event as type SIGEV_INTR
    SIGEV_INTR_INIT( &event );

    // attach the ISR to IRQ 3
    InterruptAttach (IRQ3, isr_handler, NULL, 0, 0);
    …
    // perhaps boost this thread's priority here
    …
    // now service the hardware when the ISR says to
    while (1)
    {
        InterruptWait (NULL, NULL);
        // at this point, when InterruptWait unblocks,
        // the ISR has returned a SIGEV_INTR, indicating
        // that some form of work needs to be done.

        …
        // do the work
        …
        // if the isr_handler did an InterruptMask, then
        // this thread should do an InterruptUnmask to
        // allow interrupts from the hardware
    }
}

// this is the ISR
const struct sigevent *
isr_handler (void *arg, int id)
{
    // look at the hardware to see if it caused the interrupt
    // if not, simply return (NULL);
    …
    // in a level-sensitive environment, clear the cause of
    // the interrupt, or at least issue InterruptMask to
    // disable the PIC from reinterrupting the kernel
    …
    // return a pointer to an event structure (preinitialized
    // by int_thread()) that contains SIGEV_INTR as its notification type.
    // This causes the InterruptWait() in int_thread() to unblock.
    return (&event);
}

In the above code sample, we see a typical way of handling interrupts. The main thread creates a special interrupt-handling thread (int_thread()). The sole job of that thread is to service the interrupts at the thread level. The interrupt-handling thread attaches an ISR to the interrupt (isr_handler()), and then waits for the ISR to tell it to do something. The ISR informs (unblocks) the thread by returning an event structure with the notification type set to SIGEV_INTR.

This approach has a number of advantages over using an event notification type of SIGEV_SIGNAL or SIGEV_PULSE:

The only caveat to be noted when using InterruptWait() is that the thread that attached the interrupt is the one that must wait for the SIGEV_INTR.