Linux interrupt (Interrupt) subsystem 4: Driver Interface Layer & interrupt general logic layer

Source: Internet
Author: User

In the first article in this series: Linux interrupt (Interrupt) subsystem: the basic principle of the interrupt system. I divide the General interrupt subsystem into four levels, the boundary between the driver interface layer and the interrupt universal logic layer is not very clear, because many interfaces of the interrupt general logic layer can be used by the driver, it can also be used by the hardware encapsulation layer, so I will discuss the two parts together.

In this chapter, I will discuss the standard interfaces and internal implementation mechanisms provided by these two layers. Almost all interfaces are centered on the irq_desc and irq_chip struct, readers who are unfamiliar with these two structures can read the previous articles.

/*************************************** **************************************** **********************/
Statement: the content of this blog is created at http://blog.csdn.net/droidphone. please refer to it for help. Thank you!
/*************************************** **************************************** **********************/

1. Enable and disable IRQ

The interrupt subsystem provides a series of function interfaces for enabling and disabling IRQ. The most basic one is:

  • Disable_irq (unsigned int IRQ );
  • Enable_irq (unsigned int IRQ );
These two APIs should be used in pairs. disable_irq can be called for multiple nested calls. To re-enable IRQ, enable_irq must be called the same number of times. Therefore, the depth field in the irq_desc structure is used to manage the nesting depth of these two APIs. When an IRQ is applied by the driver for the first time, the initial value of depth is set to 0 by default, and the corresponding IRQ is open. Let's take a look at the call process of disable_irq:

Figure 1.1 call process of disable_irq

The function starts to use the asynchronous internal function _ disable_irq_nosync (), the asynchronous mode ignores whether the IRQ is being processed (handler is running or the interrupted thread is not finished ). Some interrupt Controllers may be attached to a slow bus. Therefore, before further processing, use irq_get_desc_buslock to obtain the bus lock (chip-> irq_bus_lock will be called eventually ), then enter the internal function _ disable_irq:

void __disable_irq(struct irq_desc *desc, unsigned int irq, bool suspend){if (suspend) {if (!desc->action || (desc->action->flags & IRQF_NO_SUSPEND))return;desc->istate |= IRQS_SUSPENDED;}if (!desc->depth++)irq_disable(desc);}

The previous sentence is about suspend processing. In the last two sentences, only when the previous depth is 0 will the irq_disable function be used to call the interrupt controller's callback chip-> irq_mask, otherwise, simply add the value of depth to 1. The irq_disable function also uses irq_state_set_disabled and irq_state_set_masked to set the irqd_irq_disabled and irqd_irq_mask flags of irq_data.flag.

At the end of disable_irq, synchronize_irq is called. This function uses the irq_inprogress flag to ensure that all handler in the action linked list has been processed and then waits for all interrupt threads of the IRQ to exit through wait_event. In this case, you should not use this API to disable IRQ In the interrupt context, and make sure that the function that calls this API cannot own the resources of the IRQ processing function or thread, otherwise, a deadlock will occur !! If you need to disable IRQ in both cases, the interrupt sub-system provides us with another API that does not perform any wait action:

  • Disable_irq_nosync ();

The APIS for the interrupt subsystem to open IRQ are:

  • Enable_irq ();

There is no need to provide a synchronous version to enable IRQ, because no handler or thread is running before IRQ is enabled. Let's take a look at the processing of depth, which is processed in the internal function _ enable_irq:

void __enable_irq(struct irq_desc *desc, unsigned int irq, bool resume){if (resume) {            ......}switch (desc->depth) {case 0: err_out:WARN(1, KERN_WARNING "Unbalanced enable for IRQ %d\n", irq);break;case 1: {                ......irq_enable(desc);                ......}default:desc->depth--;}}

When the value of depth is 1, irq_enable () is actually called. It finally enables the corresponding disconnection in the interrupt controller through the chip-> unmask or chip-> enable callback, if depth is not 1, simply subtract 1. If the value is already 0, the driver must call enable_irq, which indicates that the driver is not properly handled, resulting in imbalance between enable and disable. The kernel will print a warning message: unbalanced enable for irq xxx.

2. Interrupt the internal data structure access interface of the subsystem

We know that the interrupt subsystem defines several important data structures, such as irq_desc, irq_chip, and irq_data, each field of the data structure controls or affects the behavior and Implementation of the interrupt subsystem and each IRQ. Generally, drivers should not directly access these data structures. Direct access will interrupt the encapsulation of the subsystem. Therefore, the interrupt subsystem provides us with a series of access interface functions, used to access these data structures.

API for accessing fields related to the irq_data structure:

Irq_set_chip (IRQ, * chip)/irq_get_chip (IRQ)Use the IRQ Number to set and obtain the irq_cip structure pointer;

Irq_set_handler_data (IRQ, * Data)/irq_get_handler_data (IRQ)Use the IRQ Number to set and obtain the irq_desc.irq_data.handler_data field. This field is the private data of each IRQ, which is usually used in the hardware encapsulation layer. For example, when the controller is interrupted for Cascade, the parent IRQ uses this field to save the Starting number of the Child IRQ.

Irq_set_chip_data (IRQ, * Data)/irq_get_chip_data (IRQ)Use the IRQ Number to set and obtain the irq_desc.irq_data.chip_data field, which is the private data of each interrupt controller and is usually used in the hardware encapsulation layer.

Irq_set_irq_type (IRQ, type)Used to set the electrical type of the interrupt. Optional types include:

  • Irq_type_edge_rising
  • Irq_type_edge_falling
  • Irq_type_edge_both
  • Irq_type_level_high
  • Irq_type_level_low

Irq_get_irq_data (IRQ)Obtain the irq_data structure pointer by using the IRQ Number;

Irq_data_get_irq_chip (irq_data * D)Use the irq_data pointer to obtain the irq_chip field;

Irq_data_get_irq_chip_data (irq_data * D)Use the irq_data pointer to obtain the chip_data field;

Irq_data_get_irq_handler_data (irq_data * D)Use the irq_data pointer to obtain the handler_data field;

Set the callback API for throttling control:

Irq_set_handler (IRQ, handle)Set the throttling callback field irq_desc.handle_irq. The handle type is irq_flow_handler_t.

Irq_set_chip_and_handler (IRQ, * chip, handle)Set the interrupt control callback field and irq_chip pointer: irq_desc.handle_irq and irq_desc.irq_data.chip.

Irq_set_chip_and_handler_name (IRQ, * chip, handle, * name)Set the interrupt control callback field, irq_chip pointer, and IRQ name: irq_desc.handle_irq, irq_desc.irq_data.chip, and irq_desc.name.

Irq_set_chained_handler (IRQ, * chip, handle)Set the throttling callback field irq_desc.handle_irq, and set the flag irq_norequest, irq_noprobe, and irq_nothread. This API is usually used to interrupt the Controller cascade. After the parent controller sets the throttling callback through, at the same time, the above three flag bits are set so that the disconnection of the parent controller cannot be applied by the driver.

3. Apply for interruption in the driver

In the system startup phase, the interrupt sub-system completes the necessary initialization work and prepares the driver to apply for the interrupted service. Generally, we use the following API to apply for the interrupted service:

request_threaded_irq(unsigned int irq, irq_handler_t handler,     irq_handler_t thread_fn,     unsigned long flags, const char *name, void *dev);

IRQThe IRQ Number to be applied for. For the arm system, the IRQ Number is usually defined in the platform-level code in advance, and sometimes can be dynamically applied.

HandlerThe interrupt service callback function runs in the interrupt context, and the local interrupt of the CPU is disabled. Therefore, the callback function should only execute the operation that requires a rapid response, the execution time should be as short as possible. It is best to leave the time-consuming work to the thread_fn callback processing below.

Thread_fnIf this parameter is not null, the kernel will create a kernel thread for the IRQ. When the interrupt occurs, if the return value of the handler callback is irq_wake_thread, the kernel will activate the interrupt thread, which is in the disconnection process, the callback function is called. Therefore, the callback function runs in the process context and allows blocking.

FlagsBit Flag for controlling interrupt behaviors, such as ir1__trigger_rising, ir1__trigger_low, and ir1__shared, which are defined in include/Linux/interrupt. h.

NameThe name of the device requesting the service interruption. The name can also be displayed in the/proc/interrupts file.

DevWhen multiple devices share the same IRQ, it is used as a handler parameter to distinguish different devices.

Next we will analyze the workflow of request_threaded_irq. The function first extracts the corresponding irq_desc instance pointer according to the IRQ Number, and then allocates an irqaction structure. It initializes fields in the irqaction structure with the handler, thread_fn, irqflags, devname, and dev_id parameters, at the same time, we made some necessary conditions to judge whether the IRQ application is forbidden? Handler and thread_fn cannot be both null. Finally, most of the work is delegated to the _ setup_irq function:

desc = irq_to_desc(irq);if (!desc)return -EINVAL;if (!irq_settings_can_request(desc) ||    WARN_ON(irq_settings_is_per_cpu_devid(desc)))return -EINVAL;if (!handler) {if (!thread_fn)return -EINVAL;handler = irq_default_primary_handler;}action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);if (!action)return -ENOMEM;action->handler = handler;action->thread_fn = thread_fn;action->flags = irqflags;action->name = devname;action->dev_id = dev_id;chip_bus_lock(desc);retval = __setup_irq(irq, desc, action);chip_bus_sync_unlock(desc);

Enter the _ setup_irq function. If the ir1__sample_random flag is set in the flag, it will call rand_initialize_irq to influence the generation of random numbers. If the application is not a thread nested interrupt (for details about thread nested interrupt, refer to the handle_nested_irq section in the Linux interrupt (Interrupt) subsystem 3: handle_nested_irq section in the Process Layer of the throttling ), the thread_fn parameter is provided, which creates a kernel thread:

if (new->thread_fn && !nested) {struct task_struct *t;t = kthread_create(irq_thread, new, "irq/%d-%s", irq,   new->name);if (IS_ERR(t)) {ret = PTR_ERR(t);goto out_mput;}/* * We keep the reference to the task struct even if * the thread dies to avoid that the interrupt code * references an already freed task_struct. */get_task_struct(t);new->thread = t;}

If the irq_desc structure interrupt action linked list is not empty, it indicates that this IRQ has been applied by other devices. That is to say, this is a shared interrupt, therefore, we will determine whether the new application interruption is consistent with the following marks of the old application interruption:

  • The ir1__shared flag must be set.
  • The electrical triggering method must be exactly the same (ir1__trigger_xxxx)
  • Irqf_percpu must be consistent
  • Irqf_oneshot must be consistent

Checking these conditions is because multiple devices try to share a single disconnection. Imagine if a device requires a rising edge interruption and a device requires a level interruption, when the interruption arrives, the kernel does not know how to choose the appropriate traffic control operation. After checking, the function finds the pointer of the last irqaction instance in the action linked list.

/* add new interrupt at end of irq queue */do {thread_mask |= old->thread_mask;old_ptr = &old->next;old = *old_ptr;} while (old);shared = 1;

If this is not a shared interrupt or the first application for shared interrupt, the function will initialize the irq_desc structure interrupt thread wait structure: wait_for_threads, And the disable_irq function will use this field to wait for the end of all IRQ threads. Next, set the electrical trigger type of the interrupt controller, and then process some necessary ir1__xxxx flags. If the irqf_noautoen flag is not set, call irq_startup () to open the IRQ. In the irq_startup () function, enable_irq/disable_irq nested depth field depth is set to 0, indicating that the IRQ has been opened, if no disable_irq is called, enable_irq prints a warning message.

if (irq_settings_can_autoenable(desc))irq_startup(desc);else/* Undo nested disables: */desc->depth = 1;

Next, set the relationship between CPU and IRQ:

/* Set default affinity mask once everything is setup */setup_affinity(irq, desc, mask);

Then, link the new irqaction instance to the end of the action linked list:

new->irq = irq;*old_ptr = new;

Finally, wake up the interrupt thread and register the relevant/proc file node:

if (new->thread)wake_up_process(new->thread);register_irq_proc(irq, desc);new->dir = NULL;register_handler_proc(irq, new);

At this point, the application for IRQ is declared complete. When the interruption occurs, the processing path will be following: irq_desc.handle_irq, irqaction. handler, irqaction. thread_fn (irqaction. handler returns irq_wake_thread. It indicates the relationship between data structures after an IRQ is applied:

Figure 3.1 Relationship Between IRQ Data Structures

4. dynamic Expansion of IRQ numbers in mobile devices in the arm system, IRQ numbers are usually defined in the platform-level or board-level code according to the hardware connection in advance, the maximum number of IRQ values is also specified using the nr_irqs constant. In several situations, we hope to dynamically increase the number of IRQ in the system:
  • The config_sparse_irq Kernel configuration item is configured, And the irq_desc structure is dynamically managed using the base tree.
  • For multi-function composite devices, there are multiple internal interrupt sources, but there is only one interrupt Trigger pin. To achieve cross-platform driver, you do not want the IRQ of the interrupt source to be hard-coded in the board-level code.

The interrupt subsystem provides the following APIs for dynamically applying for/scaling IRQ numbers:

Irq_alloc_desc (node)Apply for an IRQ. The node is the ID of the memory node; Irq_alloc_desc_at (at, node)Apply for an IRQ at the specified position. If the specified position is occupied, the application fails; Irq_alloc_desc_from (from, node)Search from the specified position and apply for an IRQ; Irq_alloc_descs (IRQ, from, CNT, node)Apply for multiple consecutive IRQ numbers and start searching from the from position; Irq_free_descs (IRQ, CNT)Release IRQ resources. The above application functions (macros) will apply for the corresponding irq_desc structure for us and initialize it to the default state. If you want these IRQ to work properly, we also need to use the API mentioned in section 2 to set necessary fields, for example:
  • Irq_set_chip_and_handler_name
  • Irq_set_handler_data
  • Irq_set_chip_data

Irq_desc is an array for kernels that do not have the config_sparse_irq Kernel configuration item configured. dynamic expansion is impossible at all, but many drivers actually use the above APIs, especially the MFD driver, these drivers do not have to be configured with the config_sparse_irq option. If you do not want to modify these drivers, you have to compromise and define nr_irqs larger in your board-level code, set aside enough reserved quantity

5. Multi-Function compound device interrupt handling

In mobile device systems, there are a large number of multi-functional composite devices. The most common is that multiple functional components are integrated in a chip, or a module unit is integrated with functional components, these internal functional components can independently generate interrupt requests, but the chip or hardware module only has one interrupt request pin. We can process the interrupt requests of these devices in multiple ways, we will discuss these methods one by one.

5.1 single interrupt mode

For such a compound device, the device usually provides a certain method to allow the CPU to obtain the real interrupt source, such as an internal register and the gpio status. A single interrupt mode means that the driver requests only One IRQ, then reads the internal registers of the device in the interrupt processing program, obtains the interrupt source, and then processes the interrupt source differently, the following is a simplified code:

static int xxx_probe(device *dev){......irq = get_irq_from_dev(dev);ret = request_threaded_irq(irq, NULL, xxx_irq_thread,   IRQF_TRIGGER_RISING,   "xxx_dev", NULL);......return 0;}static irqreturn_t xxx_irq_thread(int irq, void *data){......irq_src = read_device_irq();switch (irq_src) {case IRQ_SUB_DEV0:ret = handle_sub_dev0_irq();break;case IRQ_SUB_DEV1:ret = handle_sub_dev1_irq();break;......default:ret = IRQ_NONE;break;}......return ret;}

5.2 shared interrupt mode

The shared interrupt mode makes full use of the features of the general interrupt subsystem. As discussed above, we know that the action field in the irq_desc structure corresponding to IRQ is essentially a linked list, this provides the necessary basis for implementing interrupt sharing. As long as we request the interrupt service multiple times with the same IRQ Number, there will be multiple irqaction instances on the action linked list, when an interrupt occurs, the interrupt subsystem traverses the action linked list and executes handler callback in irqaction instances one by one. Depending on the return value of handler callback, it determines whether to wake up the interrupt thread. It should be noted that when you apply for multiple interruptions, the IRQ numbers must be consistent, the flag parameters should be consistent, and the ir1__shared flag should be set. When using shared interrupt, it is best to provide both handler and thread_fn. in the respective interrupt handling callback handler, do the following:

  • Determine whether the interruption is from the current device;
  • If not from this device:
    • Irq_none;
  • If it is from this device:
    • Disable IRQ;
    • Irq_wake_thread is returned to wake up the interrupt thread. thread_fn will be executed;
5.3 Interrupt Controller cascade mode most multifunctional composite devices provide basic interrupt controller functions, for example, they can independently control the opening and closing of a subinterrupt, in addition, the sub-interrupt source can be easily obtained. For such a device, we can implement the interrupt controller in the device as a sub-controller, and then use the interrupt controller cascade mode. In this mode, each sub-device has its own independent IRQ Number, And the interrupted service is distributed through the parent interrupt. For the parent interrupt, the specific implementation steps are as follows:
  • First, the IRQ Number of the parent interrupt can be obtained from the predefined meaning of the board-level code or through the platform_data field of the device;
  • Use the IRQ Number of the parent interrupt and use the irq_set_chained_handler function to modify the flow control function of the parent interrupt;
  • Use the IRQ Number of the parent interrupt and use irq_set_handler_data to set the parameter of the traffic control function. This parameter must be used to identify the interrupt source of the subcontroller;
  • Implement the flow control function of the parent interrupt. You only need to obtain and calculate the IRQ Number of the sub-device, and then call generic_handle_irq;

The specific implementation steps for sub-devices are as follows:

  • Implement an irq_chip structure for the interrupt controller in the device to implement the necessary callback, such as irq_mask, irq_unmask, and irq_ack;
  • Cycle each sub-device and perform the following actions:
    • Apply for an IRQ Number for each sub-device using the irq_alloc_descs function;
    • Use irq_set_chip_data to set necessary cookie data;
    • Use irq_set_chip_and_handler to set the irq_chip instance of the sub-controller and the flow control handler of the sub-IRQ. Standard flow control functions, such as handle_edge_irq;
  • The driver of the sub-device uses the IRQ Number applied for by the sub-device and applies for service interruption according to the normal process.
5.4 interrupt thread nesting mode this mode is similar to the interrupt controller cascade mode. However, in cascade mode, the parent interrupt does not need to request the interrupt service through request_threaded_irq, instead, it directly replaces the traffic control callback of the parent interrupt to implement secondary distribution of the subinterrupt in the traffic control callback of the parent interrupt. However, this may cause inconvenience in some cases, because the traffic control callback needs to obtain the interrupt source of the sub-controller, and the traffic control callback runs in the interrupt context, for devices that need to access the sub-controller through the slow bus, access in the interrupt context is obviously not suitable. In this case, we can place the sub-interrupt distribution in the parent interrupt thread, this is what I call the interrupt thread nesting mode. The following is a general implementation process: for parent interruptions, the specific implementation steps are as follows:
  • First, the IRQ Number of the parent interrupt can be obtained from the predefined meaning of the board-level code or through the platform_data field of the device;
  • Use the IRQ Number of the parent interrupt and use the request_threaded_irq function to request the interrupt service. The thread_fn and dev_id parameters must be provided;
  • The dev_id parameter must be used to identify the interrupt source of the sub-controller;
  • Implement the thread_fn function of the parent interrupt. You only need to obtain and calculate the IRQ Number of the sub-device, and then call handle_nested_irq;

The specific implementation steps for sub-devices are as follows:

  • Implement an irq_chip structure for the interrupt controller in the device to implement the necessary callback, such as irq_mask, irq_unmask, and irq_ack;
  • Cycle each sub-device and perform the following actions:
    • Apply for an IRQ Number for each sub-device using the irq_alloc_descs function;
    • Use irq_set_chip_data to set necessary cookie data;
    • Use irq_set_chip_and_handler to set the irq_chip instance of the sub-controller and the flow control handler of the sub-IRQ. Standard flow control functions, such as handle_edge_irq;
    • Use the irq_set_nested_thread function to enable the thread nesting feature of the sub-device IRQ;
  • The driver of the sub-device uses the IRQ Number applied for by the sub-device and applies for service interruption according to the normal process.

The thread nesting feature of the sub-device IRQ should be enabled. When request_threaded_irq is used to apply for the interruption service of the sub-device, the handler parameter is provided, and the interruption sub-system will not use it, at the same time, it will not create an interrupt thread for it. The thread_fn callback of the sub-device is called through handle_nested_irq in the thread of the parent interrupt, that is, although the sub-interrupt has its own independent IRQ Number, however, they do not have independent interrupt threads, but share the interrupted service threads of the parent interrupt.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.