Interrupts
Hardware events can generate interrupts on the CPU. Processing events asynchronically is more efficient and introduces less latency than constantly polling a hardware resource. Not only does the efficiency improvement provide more processing power to other tasks, it also greatly reduces power consumption on low power systems by entering sleep mode when the CPU is inactive.
Design
Usually, an interrupt service routing (ISR) has a specific function name replacing a weak symbol in the interrupt vector table at link time. As soon as the corresponding interrupt request (IRQ) is issued by the hardware, the CPU jumps to the ISR.
Figure: Overview of kernel interrupt handling.
As illustrated in the figure an IRQ triggers the ISR handler within the kernel. Then, the kernel calls the ISR registered by the user. The user can then pass a signal or data to a thread for further processing.
If an interrupt handler does not interact with the kernel or a thread, it can still be declared in the interrupt vector table. However, if the custom ISR exists in the vector table, the kernel ISR handler will not be called anymore.
Figure: Interrupt handler sequence diagram.
The diagram in the figure details the setup and the IRQ call sequence. At startup the user creates and registers a new ISR handler at kernel. When the IRQ occurs, the kernel ISR is called. With the IRQ number, the kernel selects which user ISRs to call. If there are multiple ISR handlers for one IRQ, they will be called in sequence. The user handler can then do some short work such as clearing flags or passing new data to a thread. The ISR is then exited and the kernel checks if a context switch is necessary. If a thread is awaiting an event, it can the process incoming data.
The goal is to keep interrupts short and to defer heavy work from ISRs to threads for multiple reasons. First, even the lowest interrupt priority is higher than any thread priority. A low priority interrupt might unnecessarily block a high priority thread. Secondly, interrupts run in kernel mode with the same privileges as the kernel. Fewer lines of code in kernel mode reduce the probability of a software defect. Also, keep in mind that the ISR is executed on the kernel stack.
\notebox{ Strict memory boundaries could be enforced if all interrupt handling was defferd to thread mode. An issue arises when an interrupt must be cleared in a microcontroller register the kernel does not know about. If the kernel were to exit the ISR without clearing the register, the handler would be called again resulting a deadlock. As a solution, the kernel could deactivate the interrupt until the user ISR processes it. This might lead to missed interrupts, which is to be avoided as well. }
Usage
Setting up an interrupt handler follows the concept of the thread builder.
static PROC: &Process = bern_kernel::new_process!(my_process, 8192);
#[entry]
fn main() -> ! {
// init
PROC.init(move |c| {
InterruptHandler::new(c)1
.stack(InterruptStack::Kernel)2
.connect_interrupt(stm32f4xx_hal::interrupt::EXTI15_10 as u16)3
.handler(move |_c| {4
// clear IRQ flags
// send events to threads
});
/*..*/
}).unwrap();
/*..*/
}
Listing: Creating a new interrupt handler.
Similar to spawning a thread, an interrupt handler is set up within a process context
Lastly, the handler code is placed in a closure
Note that the example excludes enabling the interrupt on microcontroller and interrupt controller level. It is up to the user to activate the interrupt. The kernel only handles interrupt calls.