Using InterruptAttachEvent()

Updated: April 19, 2023

If we were to recode the example above to use InterruptAttachEvent(), it would look like this:

/*
 * part of int2.c
*/

#include <stdio.h>
#include <sys/neutrino.h>

#define HW_SERIAL_IRQ      3
#define REG_RX             0
#define REG_II             2
#define REG_LS             5
#define REG_MS             6
#define IIR_MASK           0x07
#define IIR_MSR            0x00
#define IIR_THE            0x02
#define IIR_RX             0x04
#define IIR_LSR            0x06
#define IIR_MASK           0x07

static int base_reg = 0x2f8;

int
main (int argc, char **argv)
{
    int  intId;         // interrupt id
    int  iir;           // interrupt identification register
    int  serial_msr;    // saved contents of Modem Status Reg
    int  serial_rx;     // saved contents of RX register
    int  serial_lsr;    // saved contents of Line Status Reg
    struct sigevent event;

    // usual main() setup stuff...

    // set up the event
    intId = InterruptAttachEvent (HW_SERIAL_IRQ, &event, 0);

    for (;;) {

        // wait for an interrupt event (could use MsgReceive instead)
        InterruptWait (0, NULL);

        /*
         * determine the source of the interrupt (and clear it)
         * by reading the Interrupt Identification Register
        */

        iir = in8 (base_reg + REG_II) & IIR_MASK;

        // unmask the interrupt, so we can get the next event
        InterruptUnmask (HW_SERIAL_IRQ, intId);

        /* no interrupt? */
        if (iir & 1) {
            /* then wait again for next */
            continue;
        }

        /*
         * figure out which interrupt source caused the interrupt,
         * and determine if we need to do something about it
        */

        switch (iir) {
        case    IIR_MSR:
            serial_msr = in8 (base_reg + REG_MS);

            /*
             * perform whatever processing you would've done in
             * the other example...
            */
            break;

        case    IIR_THE:
            /* do nothing */
            break;

        case    IIR_RX:
            /* note the character */
            serial_rx = in8 (base_reg + REG_RX);
            break;

        case    IIR_LSR:
            /* note the line status reg. */
            serial_lsr = in8 (base_reg + REG_LS);
            break;
        }
    }

    /* You won't get here. */
    return (0);
}

Notice that the InterruptAttachEvent() function returns an interrupt identifier (a small integer). We've saved this into the variable intId so that we can use it later when we go to unmask the interrupt.

After we've attached the interrupt, we then need to wait for the interrupt to hit. Since we're using InterruptAttachEvent(), we'll get the event that we created earlier dropped on us for every interrupt. Contrast this with what happened when we used InterruptAttach()—in that case, our ISR determined whether or not to drop an event on us. With InterruptAttachEvent(), the kernel has no idea whether or not the hardware event that caused the interrupt was “significant” for us, so it drops the event on us every time it occurs, masks the interrupt, and lets us decide if the interrupt was significant or not.

We handled the decision in the code example for InterruptAttach() (above) by returning either a struct sigevent to indicate that something should happen, or by returning the constant NULL. Notice the changes that we did to our code when we modified it for InterruptAttachEvent():

Where you decide to clear the source of the interrupt depends on your hardware and the notification scheme you've chosen. With the combination of SIGEV_INTR and InterruptWait(), the kernel doesn't “queue” more than one notification; with SIGEV_PULSE and MsgReceive(), the kernel will queue all the notifications. If you're using signals (and SIGEV_SIGNAL, for example), you define whether the signals are queued or not. With some hardware schemes, you may need to clear the source of the interrupt before you can read more data out of the device; with other pieces of hardware, you don't have to and can read data while the interrupt is asserted.

Note: An ISR returning SIGEV_THREAD is one scenario that fills me with absolute fear! I'd recommend avoiding this “feature” if at all possible.

(QNX Neutrino 7.0.4 or later) In order to register or use an event of type SIGEV_THREAD, your process must have the PROCMGR_AID_SIGEV_THREAD ability enabled. For more information, see procmgr_ability().

In the serial port example above, we've decided to use InterruptWait(), which will queue one entry. The serial port hardware may assert another interrupt immediately after we've read the interrupt identification register, but that's fine, because at most one SIGEV_INTR will get queued. We'll pick up this notification on our next iteration of the for loop.