transferred from: http://blog.csdn.net/droidphone/article/details/74897561. Brief introduction of medium-cut control layer
In earlier versions of the kernel, almost all interrupts were handled by the __DO_IRQ function, but because of the different electrical characteristics of the various interrupt requests, or the characteristics of the interrupt controller, this would result in a difference in the following processing:
- When to send an ACK response to the interrupt controller;
- Treatment of MASK_IRQ and UNMASK_IRQ;
- Does the interrupt controller require a EOI response?
- When is the local IRQ interrupt for the CPU turned on? To allow the nesting of IRQ;
- Interrupt the synchronization and protection of data structure;
/*****************************************************************************************************/
Statement: The content of this blog by Http://blog.csdn.net/droidphone Original, reproduced please indicate the source, thank you!
/*****************************************************************************************************/
For this reason, the general interrupt subsystem abstracts several commonly used flow control types and implements the corresponding standard functions for them, so we simply select the corresponding function and assign the value to the Handle_irq field of the IRQ_DESC structure corresponding to the IRQ. These standard callback functions are irq_flow_handler_t types:
[CPP]View Plaincopy
- typedef VOID (*irq_flow_handler_t) (unsigned int IRQ,
- struct Irq_desc *desc);
The current general interrupt subsystem implements these standard flow-control callback functions, which are defined in the following: Kernel/irq/chip.c,
- HANDLE_SIMPLE_IRQ for simple flow control treatment;
- HANDLE_LEVEL_IRQ is used for the level triggering interrupt flow control processing;
- HANDLE_EDGE_IRQ for edge-triggered interrupt flow control;
- HANDLE_FASTEOI_IRQ for interrupt controllers that need to respond to EOI;
- Handle_percpu_irq for interrupts that are only used in a single CPU response;
- HANDLE_NESTED_IRQ is used to handle nested interrupts using threads;
The driver and board-level code can set the IRQ's flow control function through several APIs:
- Irq_set_handler ();
- Irq_set_chip_and_handler ();
- Irq_set_chip_and_handler_name ();
The following sequence diagram shows the interrupt response process for the entire general interrupt subsystem, which is the life cycle of the Flow_handle control layer:
Figure 1.1 Interrupt response process for a general interrupt subsystem
2. Handle_simple_irq
This function does not implement any substantial flow control operation, after locking the IRQ_DESC structure, call Handle_irq_event to handle the action linked list in Irq_desc, which is usually used for multiplexing (similar to interrupt Controller cascade) in the sub-interrupts, Called by the parent interrupt in the flow control callback. Or for interrupts that do not require hardware control. The following is its simplified code:
[CPP]View Plaincopy
- void
- HANDLE_SIMPLE_IRQ (unsigned int IRQ, struct Irq_desc *desc)
- {
- Raw_spin_lock (&desc->lock);
- ......
- Handle_irq_event (DESC);
- Out_unlock:
- Raw_spin_unlock (&desc->lock);
- }
3. Handle_level_irq This function is used to handle the flow control operation of level interruption. The feature of level interruption is that as long as the interrupt request pin (middle line) of the device is kept in the pre-trigger level, the interrupt is always requested, so in order to avoid repeated response of the same interrupt, the mask IRQ must be put on before the interrupt is processed, and then an IRQ is given to reset the interrupt request pin of the device. Unmask the IRQ after the response is complete. The actual situation is slightly more complicated, after the mask and ACK, but also to determine the irq_inprogress flag bit, if the flag has been set, then exit directly, no longer do substantive processing, Irq_inprogress flag at the beginning of the Handle_irq_event , clear at the end of the handle_irq_event, if the monitor to Irq_inprogress is set, indicating that the IRQ is being processed by another CPU, so the direct exit, the level of interrupt is the correct way to handle. But I think this is not going to happen in an arm system, because the interrupt controller does not receive an ACK notification until the HANDLE_LEVEL_IRQ is entered, and it does not send an interrupt request to the second CPU again, and when the program enters HANDLE_LEVEL_IRQ, The first action is the mask IRQ, and then the ACK IRQ (usually joined together: MASK_ACK_IRQ), this time even if the device again issued an interrupt request, also at the end of Handle_irq_event, unmask IRQ, then irq_ The INPROGRESS flag has been cleared. I don't know if other systems like X86 have different behaviors, please let me know if you have any friends. Here's the simplified code for HANDLE_LEVEL_IRQ:
[CPP]View Plaincopy
- void
- HANDLE_LEVEL_IRQ (unsigned int IRQ, struct Irq_desc *desc)
- {
- Raw_spin_lock (&desc->lock);
- MASK_ACK_IRQ (DESC);
- if (Unlikely (Irqd_irq_inprogress (&desc->irq_data)))
- Goto Out_unlock;
- ......
- if (Unlikely (!desc->action | | irqd_irq_disabled (&DESC->IRQ_DATA)))
- Goto Out_unlock;
- Handle_irq_event (DESC);
- if (!irqd_irq_disabled (&desc->irq_data) &&! ( Desc->istate & Irqs_oneshot))
- UNMASK_IRQ (DESC);
- Out_unlock:
- Raw_spin_unlock (&desc->lock);
- }
Although the HANDLE_LEVEL_IRQ is required to handle the level interrupt flow control, because of the characteristics of the level interrupt: As long as there is no ACK IRQ, the disconnection will always be valid, so we do not miss a interrupt request, but if the driver's developers understand the process is not thorough, It is particularly prone to situations where an interruption is handled more than once. In particular, interrupt threads (ACTION->THREAD_FN) are used to respond to interrupts: usually MASK_ACK_IRQ only clears the pending state of the interrupt controller. Many slow devices (such as devices that are controlled via i²c or SPI) need to clear the pending state of the interrupt line in the interrupt thread, but the HANDLE_LEVEL_IRQ has returned long before it is dispatched, and the UNMASK_IRQ has been executed. The interrupt line pending of the device is in a valid state, and the interrupt controller sends the interrupt request again, resulting in an interrupt request from the device, resulting in two interrupt responses. To avoid this situation, the best way is not to use the interrupt process alone, but to implement the second parameter of REQUEST_THREADED_IRQ () Irq_handler_t:handler, using DISABLE_IRQ () in handle callback Close the IRQ, and then ENABLE_IRQ () before exiting the thread callback. Assuming the Action->handler does not block the IRQ, the following diagram shows the Irq_progress flag, the local interrupt status, and the state that triggered the other CPUs during the level break: 3.1   , the color in the level-triggering interrupt state represents a different state:
status |
Red |
Green |
irq_progress |
& nbsp true |
false |
allow local CPU interrupts |
ban |
allow |
Whether the device is allowed to trigger interrupts again (possibly by other CPUs) |
No |
& nbsp Allow |
4. Handle_edge_irq The function is used to handle the flow control operation of the edge triggering interrupt. Edge-triggered interrupts are characterized by an interrupt request only if the level of the device's interrupt request pin (middle wire break) jumps (from high to low or low to high), because the jump is instantaneous, and does not hold the level as if the level interrupt, so improper handling is particularly easy to miss an interrupt request, in order to avoid this situation , the time to shield interrupts must be as short as possible. The kernel developers are obviously aware of this, in the case where the IRQ_PROGRESS flag is not set before the interrupt is processed, only the ACK IRQ, and no mask IRQ, in order to reset the device interrupt request pin, after which the interrupt processing, Another CPU can respond to the same IRQ request again if the irq_progress has been set to indicate that another CPU is processing the last request for the IRQ, in which case he simply sets the irqs_pending flag and then exits after Mask_ack_irq, The interrupt request is left to the original CPU for processing. Because it is MASK_ACK_IRQ, the system actually allows only one interrupt to be suspended.
[CPP]View Plaincopy
- if (Unlikely (irqd_irq_disabled (& Desc->irq_data) | |
- irqd_irq_inprogress (& Desc->irq_data) | | !desc->action) {
-
- desc->istate |= irqs_pending;
-     MASK_ACK_IRQ (desc);
- goto out_unlock;
- }
- }
-   
- desc->irq_data.chip-> Irq_ack (&desc->irq_data);
From the above analysis, it can be known that during the processing interrupt, another request may be suspended after another CPU response, so after processing this request also to determine the irqs_pending flag, if set, the current CPU to continue processing by another CPU "delegation" request. The kernel sets a loop here to handle this situation until the IRQS_PENDING flag is invalid, and because the other CPU will mask the IRQ when it responds to and hangs the IRQ, the IRQ is unmask again in the loop, So that another CPU can respond again and suspend the IRQ:
[CPP]View Plaincopy
- do {
- ......
- if (Unlikely (Desc->istate & irqs_pending)) {
- if (!irqd_irq_disabled (&desc->irq_data) &&
- Irqd_irq_masked (&desc->irq_data))
- UNMASK_IRQ (DESC);
- }
- Handle_irq_event (DESC);
- } while ((Desc->istate & irqs_pending) &&
- !irqd_irq_disabled (&desc->irq_data));
The irqs_pending flag is cleared in the handle_irq_event.
Figure 4.1 The colors in the edge-triggered interrupt state represent different states:
State |
Red |
Green |
Irq_progress |
TRUE |
FALSE |
Whether to allow local CPU interrupts |
Ban |
Allow |
Whether to allow the device to trigger interrupts again (possibly by other CPUs) |
Ban |
Allow |
is in the interrupt context |
In interrupt context |
In the process context |
As can be seen in Figure 4.1, during the processing of software interrupts (SOFTIRQ), this is still in the context of the interrupt, but the local interrupt of the CPU is open, which indicates that the nesting interrupt is allowed to occur, but it does not matter, because the important processing has been completed, is nested only the software interrupt part. This is what the kernel distinguishes between top and bottom two parts. 5. HANDLE_FASTEOI_IRQ modern interrupt controllers typically implement mid-stop control functions on hardware, such as the GIC Universal Interrupt controller in an arm system. For this interrupt controller, the CPU only needs to emit an end of interrupt (EOI) after each interrupt, and we don't have to focus on when to mask and when to unmask. But while thinking about perfection, things always have a special time, so the kernel gives us the opportunity to intervene, which uses the Preflow_handler field in the IRQ_DESC structure to invoke the callback through the Preflow_handler function before the formal processing of the interrupt.
[CPP]View Plaincopy
- void
- HANDLE_FASTEOI_IRQ (unsigned int IRQ, struct Irq_desc *desc)
- {
- Raw_spin_lock (&desc->lock);
- if (Unlikely (Irqd_irq_inprogress (&desc->irq_data)))
- if (!irq_check_poll (DESC))
- goto out;
- ......
- if (Unlikely (!desc->action | | irqd_irq_disabled (&DESC->IRQ_DATA))) {
- Desc->istate |= irqs_pending;
- MASK_IRQ (DESC);
- goto out;
- }
- if (desc->istate & Irqs_oneshot)
- MASK_IRQ (DESC);
- Preflow_handler (DESC);
- Handle_irq_event (DESC);
- OUT_EOI:
- Desc->irq_data.chip->irq_eoi (&desc->irq_data);
- Out_unlock:
- Raw_spin_unlock (&desc->lock);
- return;
- ......
- }
In addition, the kernel also provides another EOI version of the function:
HANDLE_EDGE_EOI_IRQ, its processing is similar to HANDLE_EDGE_IRQ, but does not need to implement mask and unmask logic. 6. Handle_percpu_irq This function is used for SMP systems, and when an IRQ is processed on only one CPU, we can eliminate the need to protect the data with a spin lock, and we do not need to handle interrupt nesting between CPUs, so the function is simple:
[CPP]View Plaincopy
- void
- HANDLE_PERCPU_IRQ (unsigned int IRQ, struct Irq_desc *desc)
- {
- struct Irq_chip *chip = irq_desc_get_chip (DESC);
- KSTAT_INCR_IRQS_THIS_CPU (IRQ, DESC);
- if (chip->irq_ack)
- Chip->irq_ack (&desc->irq_data);
- HANDLE_IRQ_EVENT_PERCPU (DESC, desc->action);
- if (CHIP->IRQ_EOI)
- Chip->irq_eoi (&desc->irq_data);
- }
7. Handle_nested_irq
This function is used to implement one of the interrupt sharing mechanisms, when multiple interrupts share a single interrupt line, we can take the break as a parent interrupt, the individual device sharing the interrupt as a child interrupt, in the interrupt thread of the parent interrupt to determine and distribute the response to which device request, after the actual request of the child device is obtained, Call HANDLE_NESTED_IRQ to respond to interrupts. Therefore, the function is executed in the context of the process, and we do not need to scan and execute the action list in the IRQ_DESC structure. A parent interrupt must explicitly inform the interrupt subsystem through the Irq_set_nested_thread function at initialization: These sub-interrupts are thread nested interrupt types so that when the driver requests these sub-interrupts, the kernel does not establish its own interrupt thread for them, and all child interrupts share the interrupt thread of the parent interrupt.
[CPP]View Plaincopy
- void Handle_nested_irq (unsigned int IRQ)
- {
- ......
- Might_sleep ();
- RAW_SPIN_LOCK_IRQ (&desc->lock);
- ......
- Action = desc->action;
- if (Unlikely (!action | | irqd_irq_disabled (&DESC->IRQ_DATA)))
- Goto Out_unlock;
- Irqd_set (&desc->irq_data, irqd_irq_inprogress);
- RAW_SPIN_UNLOCK_IRQ (&desc->lock);
- Action_ret = Action->thread_fn (ACTION->IRQ, action->dev_id);
- RAW_SPIN_LOCK_IRQ (&desc->lock);
- Irqd_clear (&desc->irq_data, irqd_irq_inprogress);
- Out_unlock:
- RAW_SPIN_UNLOCK_IRQ (&desc->lock);
- }
Linux Interrupt (Interrupt) subsystem Three: mid-stop control processing layer "turn"