原图
数据结构
理解一个软件架构最重要的是要理清楚里面的数据结构的内容以及数据结构之间的关系,在本文中列出数据结构之前先来说明几个问题。首先是中断控制器 irq controller 这个在 arm 上一般是 gic,有可能会出现 irq controller 集联的情况,但是一般情况下是一个 controller,一个 controller 上有很多中断线,irq line 一般就是连接到 gpio 控制器上的,在一个中断线上可能有多个设备(共享中断)action,那么就涉及到这几种数据结构,描述中断控制器的数据结构可以组成链表,描述中断线 irq line 的数据结构可以组成链表,挂在中断线上的中断设备可以组成链表关系。 除此之外硬件中断号于 linux 中断号之间有一个映射关系,也需要抽象出数据结构来描述。下面看下 linux 内核下对中断的抽象出的数据结构,如下: 原图
irq_desc(描述中断线连接中断设备和中断控制器)
irq_desc 是中断描述符也就是用于描述硬件中断线的数据结构。他一方面通过 irq_data 连接中断控制器,一边连接中断设备,中断设备的操作需要通过该结构体找到 irq_data,进而找到中断控制器,毕竟中断使能等操作都是中断控制器提供的接口。该结构体功能包括该硬件 irq line 中断触发时,执行的对应操作,以及使用该 irq 的外设注册的操作接口(当一个硬件 irq line 是共享中断时,可注册多个操作接口)等信息
irq_domain(描述中断域)
用于描述 irq 域,该数据结构主要用于存储硬件 irq line 与 linux 中断号的映射关系,并提供 irq_domain_ops,实现该 irq 域下硬件 irq line 的 top handler 的设置等操作
irq_chip(描述中断控制器)
irq_chip 结构体是用于描述 irq controller 的,主要就是提供具体的中断控制器操作接口,中断控制器内部主要分为两部分,一个是分发器,一个是 cpu 接口,而这两部分是完成对中断的控制的,包括中断优先级设置,屏蔽和使能等操作,以及中断与 cpu 之间的绑定等操作
irqaction(描述中断处理方法)
irqaction 主要就是描述中断线上的设备以及处理方法了,提供了中断上下文处理 handle 以及线程化处理的 handle
irq_data
这个数据结构是一个桥梁用于连接 irq_desc 和 irq_chip 以及 irq_domain。
下面以 irq_desc 通过数组的方式组织为例,列出上述数据结构之间的组织关系: 原图
通过上图我们可以了解到的是,内核驱动中关于中断相关数据结构的组织方式,也就清楚了内核中关于中断的驱动框架了。
实现(扩展🙈)
设备驱动开发人员常用的接口是 request_irq()/request_threaded_irq,其定义如下:
1 2 3 4 5 6 7 8 9 10 11 static inline int __must_checkrequest_irq (unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) { return request_threaded_irq(irq, handler, NULL , flags, name, dev); } ... extern int __must_checkrequest_threaded_irq (unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long flags, const char *name, void *dev) ;
可以看到 request_irq 其实就是调用 request_threaded_irq 实现的,只是没有提供 thread_fn 成员,也就是没有提供中断下半部的实现函数。
这里第一个参数是 irq 也就是 linux 中断号,这个中断号驱动开发人员该如何获得呢,其实就是在设备树里指定的,其中 device 结构体里有 device_node 结构体里就有设备树相关信息,可以通过函数 platform_get_irq 获得中断号。有了这些信息接下来就是注册一个中断了,下面将会详细分析一下 request_threaded_irq 函数的实现方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 int request_threaded_irq (unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id) { struct irqaction *action ; struct irq_desc *desc ; int retval; if (irq == IRQ_NOTCONNECTED) return -ENOTCONN; if (((irqflags & IRQF_SHARED) && !dev_id) || ((irqflags & IRQF_SHARED) && (irqflags & IRQF_NO_AUTOEN)) || (!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) || ((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND))) return -EINVAL; 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; retval = irq_chip_pm_get(&desc->irq_data); if (retval < 0 ) { kfree(action); return retval; } retval = __setup_irq(irq, desc, action); if (retval) { irq_chip_pm_put(&desc->irq_data); kfree(action->secondary); kfree(action); } return retval; } EXPORT_SYMBOL(request_threaded_irq);
此调用分配中断资源并启用中断线和 IRQ 处理。如果你想为你的设备设置一个线程中断处理程序,那么你需要提供@handler 和@thread_fn。 @handler 仍然在硬中断上下文中被调用,并且必须检查中断是否来自设备,如果是,则需要禁用设备上的中断并返回 IRQ_WAKE_THREAD,这将唤醒处理程序线程并运行 @thread_fn 。 这种拆分处理程序设计对于支持共享中断是必要的。
dev_id 必须是全局唯一的。 通常设备数据结构的地址用作 cookie。如果中断是共享的,则必须传递一个非空的 dev_id,因为这是释放中断时所必需的字段。
该函数会调用__setup_irq 函数,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 static int __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new) { struct irqaction *old , **old_ptr ; unsigned long flags, thread_mask = 0 ; int ret, nested, shared = 0 ; if (!desc) return -EINVAL; if (desc->irq_data.chip == &no_irq_chip) return -ENOSYS; if (!try_module_get(desc->owner)) return -ENODEV; new->irq = irq; if (!(new->flags & IRQF_TRIGGER_MASK)) new->flags |= irqd_get_trigger_type(&desc->irq_data); nested = irq_settings_is_nested_thread(desc); if (nested) { if (!new->thread_fn) { ret = -EINVAL; goto out_mput; } new->handler = irq_nested_primary_handler; } else { if (irq_settings_can_thread(desc)) { ret = irq_setup_forced_threading(new); if (ret) goto out_mput; } } if (new->thread_fn && !nested) { ret = setup_irq_thread(new, irq, false ); if (ret) goto out_mput; if (new->secondary) { ret = setup_irq_thread(new->secondary, irq, true ); if (ret) goto out_thread; } } if (desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE) new->flags &= ~IRQF_ONESHOT; mutex_lock(&desc->request_mutex); chip_bus_lock(desc); if (!desc->action) { ret = irq_request_resources(desc); if (ret) { pr_err("Failed to request resources for %s (irq %d) on irqchip %s\n" , new->name, irq, desc->irq_data.chip->name); goto out_bus_unlock; } } raw_spin_lock_irqsave(&desc->lock, flags); old_ptr = &desc->action; old = *old_ptr; if (old) { unsigned int oldtype; if (desc->istate & IRQS_NMI) { pr_err("Invalid attempt to share NMI for %s (irq %d) on irqchip %s.\n" , new->name, irq, desc->irq_data.chip->name); ret = -EINVAL; goto out_unlock; } if (irqd_trigger_type_was_set(&desc->irq_data)) { oldtype = irqd_get_trigger_type(&desc->irq_data); } else { oldtype = new->flags & IRQF_TRIGGER_MASK; irqd_set_trigger_type(&desc->irq_data, oldtype); } if (!((old->flags & new->flags) & IRQF_SHARED) || (oldtype != (new->flags & IRQF_TRIGGER_MASK)) || ((old->flags ^ new->flags) & IRQF_ONESHOT)) goto mismatch; if ((old->flags & IRQF_PERCPU) != (new->flags & IRQF_PERCPU)) goto mismatch; do { thread_mask |= old->thread_mask; old_ptr = &old->next; old = *old_ptr; } while (old); shared = 1 ; } if (new->flags & IRQF_ONESHOT) { if (thread_mask == ~0UL ) { ret = -EBUSY; goto out_unlock; } new->thread_mask = 1UL << ffz(thread_mask); } else if (new->handler == irq_default_primary_handler && !(desc->irq_data.chip->flags & IRQCHIP_ONESHOT_SAFE)) { pr_err("Threaded irq requested with handler=NULL and !ONESHOT for %s (irq %d)\n" , new->name, irq); ret = -EINVAL; goto out_unlock; } if (!shared) { init_waitqueue_head(&desc->wait_for_threads); if (new->flags & IRQF_TRIGGER_MASK) { ret = __irq_set_trigger(desc, new->flags & IRQF_TRIGGER_MASK); if (ret) goto out_unlock; } ret = irq_activate(desc); if (ret) goto out_unlock; desc->istate &= ~(IRQS_AUTODETECT | IRQS_SPURIOUS_DISABLED | \ IRQS_ONESHOT | IRQS_WAITING); irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS); if (new->flags & IRQF_PERCPU) { irqd_set(&desc->irq_data, IRQD_PER_CPU); irq_settings_set_per_cpu(desc); if (new->flags & IRQF_NO_DEBUG) irq_settings_set_no_debug(desc); } if (noirqdebug) irq_settings_set_no_debug(desc); if (new->flags & IRQF_ONESHOT) desc->istate |= IRQS_ONESHOT; if (new->flags & IRQF_NOBALANCING) { irq_settings_set_no_balancing(desc); irqd_set(&desc->irq_data, IRQD_NO_BALANCING); } if (!(new->flags & IRQF_NO_AUTOEN) && irq_settings_can_autoenable(desc)) { irq_startup(desc, IRQ_RESEND, IRQ_START_COND); } else { WARN_ON_ONCE(new->flags & IRQF_SHARED); desc->depth = 1 ; } } else if (new->flags & IRQF_TRIGGER_MASK) { unsigned int nmsk = new->flags & IRQF_TRIGGER_MASK; unsigned int omsk = irqd_get_trigger_type(&desc->irq_data); if (nmsk != omsk) pr_warn("irq %d uses trigger mode %u; requested %u\n" , irq, omsk, nmsk); } *old_ptr = new; irq_pm_install_action(desc, new); desc->irq_count = 0 ; desc->irqs_unhandled = 0 ; if (shared && (desc->istate & IRQS_SPURIOUS_DISABLED)) { desc->istate &= ~IRQS_SPURIOUS_DISABLED; __enable_irq(desc); } raw_spin_unlock_irqrestore(&desc->lock, flags); chip_bus_sync_unlock(desc); mutex_unlock(&desc->request_mutex); irq_setup_timings(desc, new); if (new->thread) wake_up_process(new->thread); if (new->secondary) wake_up_process(new->secondary->thread); register_irq_proc(irq, desc); new->dir = NULL ; register_handler_proc(irq, new); return 0 ; ... }
到这里中断注册就完成了,下面以一张图来梳理函数调用关系,如下: 原图
总结
在上面的介绍中我们可以得到以下结论,中断注册就是构建上面的数据结构,将新的中断挂在相应的数据结构上,当中断发生时会通过前面介绍的中断向量表开始执行相应的函数,获得中断号,根据中断号找到 irq_desc 然后调用 irqaction 上的函数。
参考文献
https://www.cnblogs.com/LoyenWang/p/12996812.html
https://www.cnblogs.com/LoyenWang/p/13052677.html
《Linux 设备驱动开发》
《Linux 内核深度解析》