Interrupt System Adaptation Guide
[ English | 简体中文 ]
I. Implementing Chip Interrupt Debugging
When debugging the chip interrupt subsystem (bringup), vendors need to implement a series of architecture-related functions (arch functions) to accomplish the following tasks:
- Initialize interrupts
- Enable and disable interrupts
- Set interrupt priority
The following information provides specific requirements and implementation examples.
1. Required Interrupt-Related Functions
The following are the interrupt-related functions that vendors need to implement, along with their descriptions.
-
Initialize the interrupt system, including disabling all interrupts, configuring the vector table position, setting default priorities, 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 } -
Enable a specific interrupt:
void up_enable_irq(int irq) { // Enable interrupt with irq } -
Disable a specific interrupt:
void up_disable_irq(int irq) { // Disable interrupt with irq } -
Set interrupt priority: If the
CONFIG_ARCH_IRQPRIOconfiguration is enabled, the following function must be implemented:#ifdef CONFIG_ARCH_IRQPRIO int up_prioritize_irq(int irq, int priority) { // Set IRQ priority } #endif -
Manage interrupt states:
-
Check if
flagsindicate that interrupts are currently disabled:#define up_irq_is_disabled(flags) -
Save the current interrupt state and disable interrupts:
irqstate_t up_irq_save(void) { // Save current interrupt state and disable interrupts } -
Restore the specified interrupt state:
void up_irq_restore(irqstate_t flags) { // Restore the interrupt state indicated by flags } -
Enable all interrupts:
irqstate_t up_irq_enable(void) { // Enable all interrupts } -
Get the current interrupt state:
irqstate_t irqstate(void) { // Get the current interrupt state }
-
-
Handle inter-core interrupts:
// Trigger an inter-core interrupt void up_trigger_irq(int irq, cpu_set_t cpuset) -
Set the secure attributes of interrupts:
-
Set the secure attributes for a specific interrupt:
void up_secure_irq(int irq, bool secure) -
Modify the secure attributes for all interrupts:
void up_secure_irq_all(bool secure)
-
2. Required Interrupt-Related Macros
Alongside the above function implementations, vendors need to define a series of interrupt-related macros, which describe the configuration of the NVIC (Nested Vectored Interrupt Controller). These macros should be defined in the chips/chip_name/include/irq.h file. Refer to the RTL8720C example for guidance.
The required macros and their descriptions are as follows:
-
First interrupt vector number:
#define NVIC_IRQ_FIRST (16) /* Vector number of the first interrupt */ -
Number of interrupts:
#define NR_IRQS (64) -
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 size:
#define NVIC_SYSH_PRIORITY_STEP 0x40 /* Three bits priority used, bits[7-6] as group */ -
Sub-priority step size:
#define NVIC_SYSH_PRIORITY_SUBSTEP 0x20 /* Three bits priority used, bit[5] as sub */
-
II. Binding Interrupt Handlers
During interrupt processing, handlers can be bound using the following three methods. 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)
-
Mechanism:
- When the interrupt is triggered,
isris called in the interrupt context. - The advantage of this method is high efficiency since the interrupt handling is completed directly in the interrupt context.
- During the execution of
isr, all interrupts are masked, making it less suitable for systems with high real-time requirements. - Blocking APIs (e.g.,
sleep,wait) cannot be called withinisr.
- When the interrupt is triggered,
-
Unbinding:
irq_detach(irq) -
Advantages and Disadvantages:
- Advantages: High processing efficiency.
- Disadvantages: Interrupts are masked during processing, which affects the real-time performance of the system.
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)
-
Mechanism:
- Users need to provide one or two handler functions:
isris called in the interrupt context, typically to mask the current interrupt and quickly wake upisrthread.isrthreadis called in the thread context to handle the remaining interrupt tasks.
- If
isrisNULL,isrthreadis invoked directly.
- Users need to provide one or two handler functions:
-
Advantages:
- The execution time of
isris minimized, improving the system's real-time performance. isrthreadruns as a thread, supports priority scheduling, and can be preempted by other higher-priority tasks.
- The execution time of
-
Disadvantages:
- Consumes more memory (independent thread stacks and interrupt thread structures).
- Introduces an additional context switch, reducing execution efficiency.
- There may be a delay (approximately 5 microseconds) in completing interrupt handling.
-
Unbinding:
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)
-
Mechanism:
- Users need to provide one or two handler functions:
isris called in the interrupt context.isrworkis called in the work queue context.
- The key difference from
irq_attach_threadis thatisrworkis executed in the work queue instead of an independent thread.
- Users need to provide one or two handler functions:
-
Advantages:
- Interrupts with the same priority can share the same work queue, saving memory.
- High-priority work queues can preempt lower-priority queues.
- This method is more memory-efficient than
irq_attach_threadwhen dealing with many interrupts.
-
Disadvantages:
- If there is only one interrupt, creating a work queue introduces additional overhead.
- In multi-core systems, there is limited flexibility in controlling thread attributes and the number of threads.
-
Unbinding:
irq_detach_wqueue(irq)
Summary Comparison
| Binding Method | Advantages | Disadvantages | Use Cases |
|---|---|---|---|
| irq_attach | High efficiency, direct handling in the interrupt context. | Interrupts are masked during handling, unsuitable for systems with high real-time requirements. | Scenarios with simple processing and low real-time requirements. |
| irq_attach_thread | Improves real-time performance, supports priority scheduling. | Consumes more memory, introduces context switches, and has some delay in processing completion. | Scenarios with high real-time requirements. |
| irq_attach_wqueue | Saves memory, supports work queue reuse. | Low efficiency for single-interrupt scenarios, limited flexibility in multi-core systems. | Scenarios with many interrupts and limited memory resources. |
III. Implementation Example of Interrupt Thread/Work Queue
Below is an example of binding interrupts and implementing interrupt threads or work queues.
1. Binding Interrupts
Use the irq_attach_work function to bind an interrupt handler:
irq_attach_work(IRQ, isrhandle, isrwork, arg, 253)
isrhandle: The interrupt handler function, executed in the interrupt context.isrwork: The interrupt thread or work queue handler function, executed in the thread context.arg: The parameter passed to the handler functions.253: The priority setting.
2. Example of Interrupt Handler
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. Example code is as follows:
static int isrhandle(int irq, void *regs, void *arg)
{
up_disabled_irq(irq); // Mask the interrupt to ensure it does not trigger again after exit
return IRQ_WAKE_THREAD; // Wake up the interrupt thread or work queue
}
3. Example of Interrupt Thread/Work Queue Handler
isrwork is used to handle interrupt tasks and clear the interrupt state after completion. Example code is as follows:
static int isrwork(int irq, void *regs, void *arg)
{
// Execute the 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 Interrupt
For certain one-shot interrupts, isrhandle can be set to NULL, and isrwork can be used directly to handle interrupt tasks.
IV. Interrupt Structure Optimization
When using interrupts, the system typically defines a global interrupt structure array:
struct irq_info_s g_irqvector[NR_IRQS];
Here, NR_IRQS represents the maximum number of interrupts supported by the system, which is typically greater than 200. However, in practice, only a few interrupts are used, and these interrupt numbers are sparsely distributed. This design results in the following issues:
- Memory Waste: Even if only a small number of interrupts are used, memory must still be allocated for all possible interrupt numbers, requiring storage for
NR_IRQSstructures. - Inefficient Resource Utilization: The majority of interrupt numbers’ corresponding structures remain unused, leading to resource waste.
1. Optimization Strategy and Implementation Principle
To address the issues above, the storage of interrupt structures can be optimized using the following dynamic mapping method.
Mapping Relationship Array
Define a mapping relationship array to dynamically establish the mapping between interrupt numbers and interrupt structures:
irq_mapped_t g_irqmap[NR_IRQS]
- This array only requires
NR_IRQSbytes of additional memory. - The mapping relationship is dynamically established during interrupt usage.
Simplified 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 size of the array, memory is allocated only for interrupts that are actually likely to be used.
Interrupt Usage Statistics
Use g_irqmap_count to keep track of the number of interrupts currently in use, facilitating monitoring and debugging.
2. Configuration Example
Enable the optimization with the following macros:
CONFIG_ARCH_MINIMAL_VECTORTABLE_DYNAMINC=y
CONFIG_ARCH_MINIMAL_VECTORTABLE=y
CONFIG_ARCH_NUSER_INTERRUPTS=24
CONFIG_ARCH_MINIMAL_VECTORTABLE_DYNAMIC: Enables the dynamic mapping functionality.CONFIG_ARCH_MINIMAL_VECTORTABLE: Enables the minimal interrupt vector table.CONFIG_ARCH_NUSER_INTERRUPTS: Sets the maximum number of interrupts that may be used.
3. Optimization Effects
- Memory Saving: Allocates memory only for interrupts in use, avoiding waste for unused interrupt numbers.
- Flexibility Improvement: Supports sparsely distributed interrupt numbers through dynamic mapping.
- Scalability: Flexibly adjusts the interrupt limit via configuration macros.