08 Interrupts
So in our life, there are so many times we get interrupted, like when we are playing games and our mom calls us to turn off the radio for her.
As I am working with microcontrollers, an interrupt is a signal sent from hardware to the CPU that needs to be handled. I believe it is the same in a Linux system. The service that handles interrupts is called an interrupt handler or interrupt service routine (ISR). We know interrupts as async events.
What will happen when the interrupt comes?
- Upon receiving an interrupt, the interrupt controller sends a signal to the processor. (In the arm cortex m3, it uses the interrupt controller called NVIC to handle).
- The processor detects this signal and interrupts its current execution to handle the interrupt.
- The processor can then notify the operating system that an interrupt has occurred and go back to the previous execution flow.
Interrupts can come anytime.
Interrupts can come together.
CPU can not handle interrupt while it is in critical sections, so keep critical sections as short and few as possible.
Interrupts and Exceptions
Exceptions are often discussed at the same time as interrupts. But exceptions are generated by CPU itself, some exceptions can be named are "Division by zero", "Page fault", …
So the difference is:
Interrupts– asynchronous interrupts generated by hardware.Exceptions– synchronous interrupts generated by the processor.
There is a further classification of interrupts and exceptions.
Interrupts
maskable- can be ignored
- signaled via INT pin
non-maskable- cannot be ignored
- signaled via NMI pin
Most interrupts are maskable, which means we can temporarily postpone running the interrupt handler when we disable the interrupt until interrupt is re-enabled. However, there are a few critical interrupts that can not be disabled/postponed (NMI, …).
Exceptions
Falts– Like Divide by zero, Page Fault, Segmentation Fault.Traps– Reported immediately following the execution of the trapping instruction. Like Breakpoints.Aborts– Aborts are used to report severe errors, such as hardware failures and invalid or inconsistent values in system tables.
Interrupt Handling
For a device’s each interrupt, its device driver must register an interrupt handler.
Interrupt handler is a funtion that is called by kernel when interrupt occurs.
- Each device that generates interrupts has an associated interrupt handler.
- The interrupt handler for a device is part of the device’s driver (the kernel code that manages the device).
The hardware has to execute interrupt immediately, and programmer has to keep interrupt handler as short as possible.
Process Context and Interrupt Context
The context is space where kernel do its job The Linux kernel uses these types of context:
- Process contexts to handle system calls and manage user processes, allowing for blocking operations and scheduling. In contrast
- Interrupt contexts are used to handle hardware interrupts asynchronously, requiring quick, non-blocking execution.
So there restrictions on what can be done in interrupt, Code executing from interrupt context cannot do the following:
- Go to sleep or relinquish the processor
- Acquire a mutex
- Perform time-consuming tasks
- Access user space virtual memory
And one more important thing: sometimes, when processing an interrupt job, that job can be lengthy and undeterministic. That is why a method called deferred interrupt is used in many RTOS platforms. But in Linux it has difference, the processing of interrupts is split into two parts or halves:
- Top halves
- Bottom halves ( This part, i think it is similar to deffered interrupt)
If the interrupt handler function could process and acknowledge interrupts within a few microseconds consistently, then absolutely there is no need for top half/bottom half delegation.
Top halves and Bottom halves
Top halves
The top halves is:
- It truely is interrupt handler
- Runs immediately on interrupt requests
- Performs time-critical tasks, less comsuming tasks
These are jobs that usually in top halves:
- Acknowledging the Interrupt
- Reading Status Registers
- Updating Device State
- Handling Simple Events
- …
Bottom halves
The bottom halves are used to processing what top halves wants it to complete the interrupt process. It is bassically task, that is “deffered” from top halves(interrup).
Interrupts are enabled when a bottom half runs. The interrupt can be disabled if necessary, but generally, this should be avoided as this goes against the basic purpose of having a bottom half – processing data while listening to new interrupts. The bottom half runs in the future, at a more convenient time, with all interrupts enabled.
There are 4 bottom half mechanisms are available in Linux:
- Workqueue
- Threaded IRQs
- Softirq
- Tasklets (deprecated)
So these parts are for future, now we will working with how to handle interrupt with short action, therefor we currently dont need bottom halves.
Interrupt APIs
Include <linux/interrupt.h> for using interrupts in linux driver.
request_irq
This api adds a handler for an interrupt line. This call allocates an interrupt and establishes a handler.
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev_id);
irq: IRQ number to allocate.handler: Interrupt handler function.This function will be invoked whenever the operating system receives the interrupt. The data type of return isirq_handler_t:- If its return value is
IRQ_HANDLED, it indicates that the processing is completed successfully, - If the return value is
IRQ_NONE, the processing fails.
- If its return value is
flags: can be either zero or a bit mask of one or more of the flags defined in<linux/interrupt.h>. The most important of these flags are (will be explained more):IRQF_DISABLEDIRQF_SAMPLE_RANDOMIRQF_SHAREDIRQF_TIMER
name: Used to identify the device name using this IRQ, for example,cat /proc/interruptswill list the IRQ number and device name.dev_id: device using this interrupt. IRQ shared by many devices and this param allows the registration of an interrupt handler with a pointer to device-specific data. This enables the handler to access relevant information about the device that triggered the interrupt.Return value: returns zero on success and nonzero value indicates an error. A typical value is -EBUSY which means that the interrupt was already requested by another device driver.
request_irq() cannot be called from interrupt context
free_irq
Parallel with request_irq, free_irq releases an IRQ registered
void *free_irq(unsigned int irq, void *dev_id);
irq: IRQ number to free.dev_id: The last parameter of request_irq.
If the specified interrupt line is not shared, this function removes the handler and disables the line.
If the interrupt line is shared, the handler identified via dev_id is removed, but the interrupt line is disabled only when the last handler is removed. With shared interrupt lines, a unique cookie is required to differentiate between the multiple handlers that can exist on a single line and enable free_irq() to remove only the correct handler.
In either case (shared or unshared), if dev_id is non-NULL, it must match the desired handler. A call to free_irq() must be made from process context.
enable_irq
Re-enable interrupt disabled by disable_irq or disable_irq_nosync.
void enable_irq(unsigned int irq);
disable_irq
Disable an IRQ from issuing an interrupt.
void disable_irq(unsigned int irq);
disable_irq_nosync
Disable an IRQ from issuing an interrupt, but wait until there is an interrupt handler being executed.
void disable_irq_nosync(unsigned int irq);
in_irq
Returns a non-zero value (true) if the current context is an interrupt context, and it returns zero (false) if it is not.
#define in_irq() (hardirq_count())
in_interrupt
Returns a non-zero value (true) if the current context is an interrupt context or in process of bottom halves, and it returns zero (false) if it is not.
#define in_interrupt() (irq_count())
Interrupts Flags
These flags are passed to function request_irq() as second param, to specify behavior of interrupt handler.
IRQF_DISABLED: The interrupt handler is called with interrupts disabled.IRQF_SHARED: This flag specifies that the interrupt line can be shared among multiple interrupt handlers. Each handler registered on a given line must specify this flag. If this flag is not set, then if there is already a handler associated with the requested interrupt, the request for interrupt will fail because only one handler can exist per line.IRQF_TIMER: This flag specifies that this handler process interrupts the system timer.IRQF_ONESHOT: Interrupt will be reactivated after running the process context routine; Without this flag, the interrupt will be reactivated after running the handler routine in the context of the interrupt.
IRQ line
The available IRQ lines depend on the architecture and the interrupt controller being used.
The board im using has SoC Rockchip RV1106 on it. According to document:
RV1106 provides a general interrupt controller (GIC) for CPU, which has 128 SPI (shared peripheral interrupts) interrupt sources. The triggered type for each SPI interrupt is high level sensitive, not programmable.
Among those 128 IRQ lines. It may be interesting to see which specific interrupt inside GIC here at page 14. And check what /proc/interrupts shows:
[root@luckfox root]# cat /proc/interrupts | head -5
CPU0
17: 1343380 GIC-0 29 Level arch_timer
18: 0 GIC-0 30 Level arch_timer
20: 0 GIC-0 157 Edge debug-signal
21: 0 GIC-0 118 Level rknpor_powergood
First column is IRQ line(the lower, the higher prio), next is CPU core(RV1106 has 1 core), third is name of interrupt controller GIC, fourth is internal interrupt number of interrupt controller, fifth is name of driver.
We will stop coding for a while and go next to GPIO part, to look at how to control GPIO basically like output, input and with input we can play with interrupt.