ISR functions

Updated: May 06, 2022

The next issue we should tackle is the list of functions an ISR is allowed to call.

Let me digress just a little at this point. Historically, the reason that ISRs were so difficult to write (and still are in most other operating systems) is that the ISR runs in a special environment.

One particular thing that complicates writing ISRs is that the ISR isn't actually a “proper” thread as far as the kernel is concerned. It's this weird “hardware” thread, if you want to call it that. This means that the ISR isn't allowed to do any “thread-level” things, like messaging, synchronization, kernel calls, disk I/O, etc.

But doesn't that make it much harder to write ISR routines? Yes it does. The solution, therefore, is to do as little work as possible in the ISR, and do the rest of the work at thread-level, where you have access to all the services.

Your goals in the ISR should be:

This “architecture” hinges on the fact that QNX Neutrino has very fast context-switch times. You know that you can get into your ISR quickly to do the time-critical work. You also know that when the ISR returns an event to trigger thread-level work, that thread will start quickly as well. It's this “don't do anything in the ISR” philosophy that makes QNX Neutrino ISRs so simple!

So, what calls can you use in the ISR? Here's a summary (for the official list, see the Full Safety Information appendix in the QNX Neutrino C Library Reference):

Basically, the rule of thumb is, “Don't use anything that's going to take a huge amount of stack space or time, and don't use anything that issues kernel calls.” The stack space requirement stems from the fact that ISRs have very limited stacks.

The list of interrupt-safe functions makes sense—you might want to move some memory around, in which case the mem*() and str*() functions are a good choice. You'll most likely want to read data registers from the hardware (in order to save transitory data variables and/or clear the source of the interrupt), so you'll want to use the in*() and out*() functions.

What about the bewildering choice of Interrupt*() functions? Let's examine them in pairs:

InterruptMask() and InterruptUnmask()
These functions are responsible for masking the interrupt source at the PIC level; this keeps them from being passed on to the CPU. Generally, you'd use this if you want to perform further work in the thread and can't clear the source of the interrupt in the ISR itself. In this case, the ISR would issue InterruptMask(), and the thread would issue InterruptUnmask() when it had completed whatever operation it was invoked to do.

Keep in mind that InterruptMask() and InterruptUnmask() are counting—you must “unmask” the same number of times that you've “masked” in order for the interrupt source to be able to interrupt you again.

By the way, note that the InterruptAttachEvent() performs the InterruptMask() for you (in the kernel)—therefore you must call InterruptUnmask() from your interrupt-handling thread.

InterruptLock() and InterruptUnlock()
These functions are used to disable (InterruptLock()) and enable (InterruptUnlock()) interrupts on a single- or multiprocessor system. You'd want to disable interrupts if you needed to protect the thread from the ISR (or additionally, on an SMP system, the ISR from a thread). Once you've done your critical data manipulation, you'd then enable interrupts. Note that these functions are recommended over the “old” InterruptDisable() and InterruptEnable() functions as they will operate properly on an SMP system. There's an additional cost over the “old” functions to perform the check on an SMP system, but in a single-processor system it's negligible, which is why I'm recommending that you always use InterruptLock() and InterruptUnlock().
InterruptDisable() and InterruptEnable()
These functions shouldn't be used in new designs. Historically, they were used to invoke the x86 processor instructions cli and sti when QNX Neutrino was x86-only. They've since been upgraded to handle all supported processors, but you should use InterruptLock() and InterruptUnlock() (to keep SMP systems happy).

The one thing that bears repeating is that on an SMP system, it is possible to have both the interrupt service routine and another thread running at the same time.