Interrupt System
[ English | 简体中文 ]
I. Implementing Chip Interrupt Debugging
When debugging the chip interrupt subsystem (bringup), the manufacturer needs to implement a series of architecture-related functions (arch functions) to complete the following tasks:
- Initialize interrupts
- Enable and disable interrupts
- Set interrupt priorities
The following content provides specific requirements and implementation examples.
1. Interrupt-Related Functions to be Implemented
The following are the interrupt-related functions that the vendor needs to implement and their functional descriptions.
1. Initialize the interrupt system, including disabling all interrupts, configuring the vector table location, setting the default priority, and enabling interrupts.
void up_irqinitialize(void)
{
// Disable all interrupts
// Set the NVIC vector location
// Set all interrupts (and exceptions) to the default priority
// Attach the SVCall and Hard Fault exception handlers
// enable interrupts
}
2. Enable a specified interrupt
void up_enable_irq(int irq)
{
//enable interrupt with irq
}
This function is used to enable the specified interrupt number.
3. Disable the specified interrupt number
void up_disable_irq(int irq)
{
//disable interrupt with irq
}
4. Set interrupt priority
If the CONFIG_ARCH_IRQPRIO configuration is enabled, the following function needs to be implemented:
#ifdef CONFIG_ARCH_IRQPRIO
int up_prioritize_irq(int irq, int priority)
{
// set irq priority
}
#endif
5. Manage interrupt status
-
Determine whether the
flagsare currently in the interrupt-disabled state.#define up_irq_is_disabled(flags) -
Save the current interrupt status and disable interrupts.
// Disable interrupts irqstate_t up_irq_save(void) { } -
Restore the specified interrupt status.
// Restore the interrupt status represented by flags void up_irq_restore(irqstate_t flags) { } -
Enable all interrupts.
// Enable all interrupts irqstate_t up_irq_enable(void) { } -
Obtain the current interrupt status.
// Obtain the current interrupt status irqstate_t irqstate(void) { }
6. Handle inter-processor interrupts
// Trigger an inter-processor interrupt
void up_trigger_irq(int irq, cpu_set_t cpuset)
7. Set the security attributes of interrupts
-
Set the security attributes of a specified interrupt.
// Set the security attributes of an interrupt void up_secure_irq(int irq, bool secure) -
Change the security attributes of all interrupts.
// Change the security attributes of all interrupts void up_secure_irq_all(bool secure)
2. Interrupt-Related Macros to be Defined
In addition to the above function implementations, the manufacturer also needs to define a series of interrupt-related macros to describe the configuration of the NVIC (Nested Vectored Interrupt Controller). These macros need to be defined in the chips/chip_name/include/irq.h file. For reference, see the RTL8720C example.
The following are the macros that must be implemented and their functional descriptions:
1. The first interrupt vector number
#define NVIC_IRQ_FIRST (16) /* Vector number of the first interrupt */
2. Number of interrupts
#define NR_IRQS (64)
3. NVIC priority levels
-
Minimum priority
#define NVIC_SYSH_PRIORITY_MIN 0xff /* All bits set in minimum priority */ -
Default priority
#define NVIC_SYSH_PRIORITY_DEFAULT 0x40 /* Midpoint is the default */ -
Maximum priority
#define NVIC_SYSH_PRIORITY_MAX 0x00 /* Zero is maximum priority */ -
Priority step
#define NVIC_SYSH_PRIORITY_STEP 0x40 /* Three bits priority used, bits[7-6] as group */ -
Subpriority step
#define NVIC_SYSH_PRIORITY_SUBSTEP 0x20 /* Three bits priority used, bit[5] as sub */
II. Interrupt Binding Handling Functions
During the interrupt handling process, handling functions can be bound in the following three ways. Each method is suitable for different scenarios and has its own advantages and disadvantages.
1. Using irq_attach
int irq_attach(int irq, xcpt_t isr, FAR void *arg)
1. Working mechanism
- When an interrupt is triggered,
isris called in the interrupt context. - The advantage of this method is high efficiency because interrupt handling is completed directly in the interrupt context.
- All interrupt responses are masked during the execution of
isr, which is not suitable for systems with high real-time requirements. - APIs that cause blocking (such as
sleep,wait, etc.) cannot be called inisr.
2. Unbinding
irq_detach(irq)
3. Advantages and disadvantages
- Advantage: High processing efficiency.
- Disadvantage: All interrupts are masked during interrupt handling, affecting system real-time performance.
2. Using irq_attach_thread
int irq_attach_thread(int irq, xcpt_t isr, xcpt_t isrthread, FAR void *arg, int priority, int stack_size)
1. Working mechanism
-
Users need to provide 1 or 2 handling functions:
isris called in the interrupt context, typically used to mask the current interrupt and quickly wake upisrthread.isrthreadis called in the thread context, used to handle remaining interrupt tasks.
-
If
isrisNULL,isrthreadwill be called directly.
2. Advantages
- The execution time of
isris shortened as much as possible, thereby improving system real-time performance. isrthreadruns as a thread, supports priority scheduling, and can be preempted by other high-priority tasks.
3. Disadvantages
- Consumes more memory (independent thread stack and interrupt thread structure).
- Increases a context switch, reducing efficiency.
- There will be a certain delay in the completion time of interrupt handling (about 5 microseconds).
4. Unbinding
-
Use the following method to unbind:
irq_detach_thread(irq)
3. Using irq_attach_wqueue
int irq_attach_wqueue(int irq, xcpt_t isr, xcpt_t isrwork, FAR void *arg, int priority)
1. Working mechanism
-
Users need to provide 1 or 2 handling functions:
isris called in the interrupt context.isrworkis called in the work queue context.
-
The difference from
irq_attach_threadis thatisrworkis executed in the work queue instead of an independent thread.
2. Advantages
- Multiple interrupts with the same priority can reuse the same work queue, thereby saving memory.
- High-priority work queues can preempt low-priority queues.
- If there are many interrupts, it saves more memory than
irq_attach_thread.
3. Disadvantages
- If there is only one interrupt, creating a work queue will bring additional overhead.
- In a multi-core system, the flexibility to control thread attributes and quantity is poor.
4. Unbinding
irq_detach_wqueue(irq)
Summary and comparison
| Binding method | Advantages | Disadvantages | Usage scenarios |
|---|---|---|---|
| irq_attach | High efficiency, direct handling in the interrupt context. | All interrupts are masked during interrupt handling, not suitable for systems with high real-time requirements. | Scenarios with simple processing logic and low real-time requirements. |
| irq_attach_thread | Improves real-time performance, supports priority scheduling. | Consumes more memory, increases context switching, and there is a certain delay in processing completion. | Scenarios with high real-time requirements. |
| irq_attach_wqueue | Saves memory, supports work queue reuse. | Low efficiency in single interrupt scenarios, insufficient flexibility in multi-core scenarios. | Scenarios with many interrupts and limited memory resources. |
III. Implementation Examples of Interrupt Thread/Work Queue
The following is sample code for binding interrupts and implementing interrupt threads or work queues.
1. Binding interrupts
Use the irq_attach_work function to bind the interrupt handling program:
irq_attach_work(IRQ, isrhandle, isrwork, arg, 253)
isrhandle: Interrupt handling function, executed in the interrupt context.isrwork: Interrupt thread or work queue handling function, executed in the thread context.arg: Parameters passed to the handling function.253: Priority setting.
2. Interrupt handling function example
In isrhandle, return IRQ_WAKE_THREAD to wake up the interrupt thread or work queue. If OK is returned, the interrupt thread will not be woken up. The sample code is as follows:
static int isrhandle(int irq, void *regs, void *arg)
{
up_disabled_irq(irq); // Mask the interrupt to ensure it will not be triggered again after exit
return IRQ_WAKE_THREAD; // Wake up the interrupt thread or work queue
}
3. Interrupt thread/work queue handling function example
isrwork is used to handle interrupt tasks and clear the interrupt status after completion. The sample code is as follows:
static int isrwork(int irq, void *regs, void *arg)
{
// Execute interrupt handling logic
// Clear the pending bit of the interrupt
up_enabled_irq(irq); // Re-enable the interrupt.
return OK;
}
4. Special case: One-shot interrupts
For some one-shot interrupts, isrhandle can be set to NULL, and isrwork is directly used to handle interrupt tasks.
IV. Optimization of Interrupt Structures
When using interrupts, the system usually defines a global interrupt structure array:
struct irq_info_s g_irqvector[NR_IRQS];
Where NR_IRQS represents the maximum interrupt number supported by the system, usually more than 200. However, the actual number of interrupts used is usually only a dozen, and these interrupt numbers are discretely distributed. This design will lead to the following problems:
- Memory waste: Even if only a few interrupts are used, memory needs to be allocated for all possible interrupt numbers to store
NR_IRQSstructures. - Inefficient resource utilization: Structures corresponding to most interrupt numbers are unused, causing resource waste.
1. Optimization strategy and implementation principle
To solve the above problems, the storage of interrupt structures can be optimized through the following dynamic mapping methods.
Mapping relation array
Define a mapping relation array to dynamically establish the mapping between interrupt numbers and interrupt structures:
irq_mapped_t g_irqmap[NR_IRQS]
- This array only occupies an additional
NR_IRQSbytes of memory. - The mapping relation is dynamically established when interrupts are used.
Streamline the interrupt structure array
Define g_irqvector as:
struct irq_info_s g_irqvector[CONFIG_ARCH_NUSER_INTERRUPTS];
CONFIG_ARCH_NUSER_INTERRUPTSrepresents the maximum number of interrupts that may be used in the system plus 1.- By limiting the array size, memory is only allocated for interrupts that may actually be used.
Interrupt usage statistics
Use g_irqmap_count to count the number of interrupts currently in use, which is convenient for monitoring and debugging.
2. Configuration examples
Enable optimization through the following macro configurations:
CONFIG_ARCH_MINIMAL_VECTORTABLE_DYNAMINC=y
CONFIG_ARCH_MINIMAL_VECTORTABLE=y
CONFIG_ARCH_NUSER_INTERRUPTS=24
CONFIG_ARCH_MINIMAL_VECTORTABLE_DYNAMIC: Enable dynamic mapping function.CONFIG_ARCH_MINIMAL_VECTORTABLE: Enable the streamlined interrupt vector table.CONFIG_ARCH_NUSER_INTERRUPTS: Set the maximum number of interrupts that may be used.
3. Optimization effects
- Memory saving: Only allocate storage space for actually used interrupts, avoiding memory waste for unused interrupt numbers.
- Improved flexibility: Support discretely distributed interrupt numbers through dynamic mapping relations.
- Scalability: Flexibly adjust the interrupt number limit through configuration macros.