ISR functions
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:
- Fetch information that is transitory.
- Clear the source of the ISR.
- Optionally dispatch a thread to get the
real
work done.
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):
- atomic_*() functions (such as atomic_set())
- mem*() functions (such as memcpy())
- most str*() functions (such as strcmp()). Beware, though, that not all these are safe, such as strdup()—it calls malloc(), which uses a mutex, and that's not allowed. For the string functions, you should really consult the individual entries in the QNX Neutrino C Library Reference before using.
- InterruptMask()
- InterruptUnmask()
- InterruptLock()
- InterruptUnlock()
- InterruptDisable()
- InterruptEnable()
- in*() and out*()
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'vemasked
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 theold
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
cliandstiwhen 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.
