linux中断机制的核心数据结构 irq_desc, 它完整地描述了一条中断线 (或称为 “中断通道” )。其中irq_desc 结构在 include/linux/irqdesc.h 中定义:
struct irq_desc { struct irq_data irq_data; unsigned int __percpu *kstat_irqs; irq_flow_handler_t handle_irq;/* 高层次的中断事件处理函数 */ #ifdef CONFIG_IRQ_PREFLOW_FASTEOI irq_preflow_handler_t preflow_handler; #endif struct irqaction *action; /* 行为链表IRQ action list */ unsigned int status_use_accessors; unsigned int core_internal_state__do_not_mess_with_it; unsigned int depth; /* 关中断次数nested irq disables */ unsigned int wake_depth; /* 唤醒次数nested wake enables */ unsigned int irq_count; /* 发生的中断次数For detecting broken IRQs */ unsigned long last_unhandled; /* 滞留时间Aging timer for unhandled count */ unsigned int irqs_unhandled; raw_spinlock_t lock;/*自旋锁*/ struct cpumask *percpu_enabled; #ifdef CONFIG_SMP const struct cpumask *affinity_hint; struct irq_affinity_notify *affinity_notify; #ifdef CONFIG_GENERIC_PENDING_IRQ cpumask_var_t pending_mask; #endif #endif unsigned long threads_oneshot; atomic_t threads_active; wait_queue_head_t wait_for_threads; #ifdef CONFIG_PROC_FS struct proc_dir_entry *dir; #endif struct module *owner; const char *name; } ____cacheline_internodealigned_in_smp;
其中irq_data 结构在 include/linux/irq.h 中定义:
struct irq_data { unsigned int irq; unsigned long hwirq; unsigned int node; unsigned int state_use_accessors; struct irq_chip *chip; /* 低层次的硬件操作 */ struct irq_domain *domain; void *handler_data;/* chip 方法使用的数据*/ void *chip_data; /* chip 私有数据 */ struct msi_desc *msi_desc; cpumask_var_t affinity; };
其中irq_desc 结构在 include/linux/irqdesc.h 中定义:
struct irqaction { irq_handler_t handler;//中断处理函数,注册时提供 void *dev_id;//设备id void __percpu *percpu_dev_id; struct irqaction *next;//如果有中断共享,则继续执行, irq_handler_t thread_fn; struct task_struct *thread; unsigned int irq;//中断号,注册时提供 unsigned int flags; unsigned long thread_flags;//中断标志,注册时提供 unsigned long thread_mask;//中断掩码 const char *name;//中断名称 struct proc_dir_entry *dir;//指向IRQn相关的/proc/irq/n目录的描述符 } ____cacheline_internodealigned_in_smp;
在注册中断号为irq的中断服务程序时,系统会根据注册参数封装相应的irqaction结构体。并把中断号为irq的irqaction结构体写入irq_desc [irq]->action。这样就把设备的中断请求号与该设备的中断服务例程irqaction联系在一起了。当CPU接收到中断请求后,就可以根据中断号通过irq_desc []找到该设备的中断服务程序。 它的定义在kernel/irq/irqdesc.c:
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = { [0 ... NR_IRQS-1] = { .handle_irq = handle_bad_irq, .depth = 1, .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock), } };
start_kernel()函数调用early_irq_init()和init_IRQ()两个函数来初始化中断管理系统,其实就是初始化irq_desc的结构。
在start_kernel()函数中调用了early_irq_init()函数,这个函数在kernel/irq/irqdesc.c文件中定义。这个函数将用于管理中断的irq_desc[NR_IRQS]数组的每个元素的部分字段设置为确定的状态,它设置每一个成员的中断号
int __init early_irq_init(void) { int count, i, node = first_online_node; struct irq_desc *desc; init_irq_default_affinity(); printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS); desc = irq_desc; count = ARRAY_SIZE(irq_desc); for (i = 0; i < count; i++) { desc[i].kstat_irqs = alloc_percpu(unsigned int); alloc_masks(&desc[i], GFP_KERNEL, node); raw_spin_lock_init(&desc[i].lock); lockdep_set_class(&desc[i].lock, &irq_desc_lock_class); desc_set_defaults(i, &desc[i], node, NULL); } return arch_early_irq_init(); }
early_irq_init属于与硬件和平台无关的通用逻辑层,它完成irq_desc结构的内存申请,为它们其中某些字段填充默认值,完成后调用体系相关的arch_early_irq_init函数完成进一步的初始化工作,不过ARM体系没有实现arch_early_irq_init。
接着,start_kernel调用定义在arch\arm\kernel\irq.c的init_IRQ()函数,它会直接调用所属板子machine_desc结构体中的init_irq回调。machine_desc通常在板子的特定代码中,使用MACHINE_START和MACHINE_END宏进行定义。
void __init init_IRQ(void) { machine_desc->init_irq(); }
machine_desc在setup_arch()函数中被初始化
mdesc = setup_machine_fdt(__atags_pointer); if (!mdesc) mdesc = setup_machine_tags(machine_arch_type); machine_desc = mdesc;
struct machine_desc类型的结构,mach_desc里定义了一些关键的体系架构相关的信息。比如我手上这个板子的源码:
DT_MACHINE_START(sama5_dt, "Atmel SAMA5 (Device Tree)") /* Maintainer: Atmel */ .timer = &at91sam926x_timer, .map_io = at91_map_io, .handle_irq = at91_aic5_handle_irq, .init_early = at91_dt_initialize, .init_irq = at91_dt_init_irq, .init_machine = at91_dt_device_init, .dt_compat = sama5_dt_board_compat, MACHINE_END static const char *at91_dt_board_compat[] __initdata = { "atmel,at91sam9", NULL };
MACHINE_START或DT_MACHINE_START宏的作用是对mach_desc结构体进行初始化。然后setup_arch去为相应的芯片找到相应的结构体。我们跟踪代码看看:
static void __init at91_dt_init_irq(void) { of_irq_init(irq_of_match); }of_irq_init 扫描 和 初始 匹配 中断 控制器,扫描设备树匹配中断控制器节点,并调用其初始化函数,
void __init of_irq_init(const struct of_device_id *matches) { struct device_node *np, *parent = NULL; struct intc_desc *desc, *temp_desc; struct list_head intc_desc_list, intc_parent_list; INIT_LIST_HEAD(&intc_desc_list); INIT_LIST_HEAD(&intc_parent_list); for_each_matching_node(np, matches) { if (!of_find_property(np, "interrupt-controller", NULL)) continue; /* * Here, we allocate and populate an intc_desc with the node * pointer, interrupt-parent device_node etc. */ desc = kzalloc(sizeof(*desc), GFP_KERNEL); if (WARN_ON(!desc)) goto err; desc->dev = np; desc->interrupt_parent = of_irq_find_parent(np); if (desc->interrupt_parent == np) desc->interrupt_parent = NULL; list_add_tail(&desc->list, &intc_desc_list); } /* * The root irq controller is the one without an interrupt-parent. * That one goes first, followed by the controllers that reference it, * followed by the ones that reference the 2nd level controllers, etc. */ while (!list_empty(&intc_desc_list)) { /* * Process all controllers with the current 'parent'. * First pass will be looking for NULL as the parent. * The assumption is that NULL parent means a root controller. */ list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) { const struct of_device_id *match; int ret; of_irq_init_cb_t irq_init_cb; if (desc->interrupt_parent != parent) continue; list_del(&desc->list); match = of_match_node(matches, desc->dev); if (WARN(!match->data, "of_irq_init: no init function for %s\n", match->compatible)) { kfree(desc); continue; } pr_debug("of_irq_init: init %s @ %p, parent %p\n", match->compatible, desc->dev, desc->interrupt_parent); irq_init_cb = match->data; ret = irq_init_cb(desc->dev, desc->interrupt_parent); if (ret) { kfree(desc); continue; } /* * This one is now set up; add it to the parent list so * its children can get processed in a subsequent pass. */ list_add_tail(&desc->list, &intc_parent_list); } /* Get the next pending parent that might have children */ desc = list_first_entry(&intc_parent_list, typeof(*desc), list); if (list_empty(&intc_parent_list) || !desc) { pr_err("of_irq_init: children remain, but no parents\n"); break; } list_del(&desc->list); parent = desc->dev; kfree(desc); } list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) { list_del(&desc->list); kfree(desc); } err: list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) { list_del(&desc->list); kfree(desc); } }
这个函数完成对于中断控制器的初始化,并且设置中断描述符的相应的函数指针的值,以在中断发生时发生时,调用这些函数来完成芯片级的处理。为每个irq_desc结构安装irq_chip指针,使他指向正确的中断控制器所对应的irq_chip结构的实例,同时,如果该平台中的中断线有多路复用(多个中断公用一个irq中断线)的情况,还应该初始化irq_desc中相应的字段和标志,以便实现中断控制器的级联。