Linux 中断子系统(四)中断驱动实现

原图

数据结构

理解一个软件架构最重要的是要理清楚里面的数据结构的内容以及数据结构之间的关系,在本文中列出数据结构之前先来说明几个问题。首先是中断控制器 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_check
request_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_check
request_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;

/* 1. 检查 irqflags,如果是 IRQF_SHARED 则必须提供 dev_id */
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;

/* 2. 通过中断号获得 irq_desc 结构体 */
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;
}

/* 3. 分配并设置 irqaction 结构体 */
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;
}

/* 4. 于完成中断的相关设置,包括中断线程化的处理*/
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;

/* 1. 如果没有提供 flags,则使用默认 flags */
if (!(new->flags & IRQF_TRIGGER_MASK))
new->flags |= irqd_get_trigger_type(&desc->irq_data);

/* 2. 检查中断是否嵌套到另一个中断线程中 */
nested = irq_settings_is_nested_thread(desc);
if (nested) {
if (!new->thread_fn) {
ret = -EINVAL;
goto out_mput;
}
/* 3. 将驱动程序为非嵌套中断处理提供的主处理程序替换为在调用时发出警告的虚拟函数。*/
new->handler = irq_nested_primary_handler;
} else {
if (irq_settings_can_thread(desc)) {
/* 4. 强制线程化 */
ret = irq_setup_forced_threading(new);
if (ret)
goto out_mput;
}
}

/* 5. 当提供了线程函数并且中断没有嵌套到另一个中断线程中时,创建一个处理程序线程。*/
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);

/* 6. 获取总线锁,因为下面的 irq_request_resources() 回调可能依赖于序列化*/
chip_bus_lock(desc);

/* 7. 第一个安装的动作请求资源 */
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;
}
}

/* 8. 下代码块必须以原子方式执行,以防止并发中断和任何其他未通过 desc->request_mutex 或可选总线锁序列化的管理调用。*/
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;
}

/* 9. 如果之前没有人设置过,则继承请求者提供的配置*/
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;

/* All handlers must agree on per-cpuness */
if ((old->flags & IRQF_PERCPU) !=
(new->flags & IRQF_PERCPU))
goto mismatch;

/* add new interrupt at end of irq queue */
do {
/* 10. 所有现有的 action->thread_mask 位,这样我们就可以找到这个新动作的下一个空位*/
thread_mask |= old->thread_mask;
old_ptr = &old->next;
old = *old_ptr;
} while (old);
shared = 1;
}

/* 11. 为 ONESHOT 设置此 irqaction 的线程掩码*/
if (new->flags & IRQF_ONESHOT) {
/*
* Unlikely to have 32 resp 64 irqs sharing one line,
* but who knows.
*/
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);

/* Setup the type (level, edge polarity) if configured: */
if (new->flags & IRQF_TRIGGER_MASK) {
ret = __irq_set_trigger(desc,
new->flags & IRQF_TRIGGER_MASK);

if (ret)
goto out_unlock;
}

/* 12. 激活中断,该激活必须独立于 IRQ_NOAUTOEN 发生*/
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;

/* Exclude IRQ from balancing if requested */
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);
/* Undo nested disables: */
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)
/* hope the handler works with current trigger mode */
pr_warn("irq %d uses trigger mode %u; requested %u\n",
irq, omsk, nmsk);
}

*old_ptr = new;

irq_pm_install_action(desc, new);

/* Reset broken irq detection when installing new handler */
desc->irq_count = 0;
desc->irqs_unhandled = 0;

/* 13. 检查我们之前是否禁用了 irq,重新启用它*/
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 内核深度解析》