⚠ 转载请注明出处:作者:ZobinHuang,更新日期:May.25 2022
本作品由 ZobinHuang 采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 进行许可,在进行使用或分享前请查看权限要求。若发现侵权行为,会采取法律手段维护作者正当合法权益,谢谢配合。
目录
有特定需要的内容直接跳转到相关章节查看即可。
基本概念
我们下面理清楚几个概念。
Interrupt 与 Exception
Hardware Interrupt : 我们将有外部硬件设备产生的中断信号称为硬件中断,由于它可以随时发生 (但仍然按照 CPU 时钟周期来产生),因此也被称为Asynchronous Interrupt ;-
Software Interrupt : 软件中断由 CPU 执行的指令产生,由于它伴随着 CPU 指令的执行而产生,因此也被称为Synchronous Interrupt 。具体可以分为两种情况:Exception : 当处理器在执行指令的过程中发现异常事件 (e.g. 页表不存在, 除 0 错误等) 的时候被触发,由 CPU 控制单元发出;Sysenter : 程序可以通过执行一些指令来产生软件中断 (e.g. x86 平台下的int
指令)
其中,对于 Exception,Intel 平台下又有如下的分类 intel_document_volume_3a_c6:
-
Fault : 该类异常通常可以被纠正,纠正后程序可以继续运行。当 Fault 异常发生时,处理器会记录下产生 Fault 异常的指令的地址 (i.e. 当前CS
和EIP
寄存器的值),作为异常处理程序的返回地址。在运行完异常处理程序后,处理器将返回原先产生 Fault 异常的指令并重新运行; -
Trap : 该类异常通常是由于处理器运行了Trapping Instruction 引起的,在运行完异常处理程序后,程序可以被继续运行。值得的注意的是此时异常程序返回后执行的是紧跟在 Trapping Instruction 后面的指令,这与 Fault 异常有所区别; -
Abort : 该类异常代表着处理器无法精确地获取导致异常的指令的地址,因此产生 Abort 异常后,程序将无法恢复。
Interrupt Service Routine
当中断发生时,CPU 会转移到相关的中断处理函数处,我们将该处理函数称为
Vector Number
在 Intel 处理器中,每一个中断都有一个全局唯一的硬件中断号,称为
Intel 0~255 号中断分布具体如下所示:
Hardware Interrupt
APIC 架构
Intel 平台的底层中断系统采用的是
下面是从 Intel 官方编程手册 intel_document_volume_3a_c10 中摘抄的 APIC 架构相关的说明图。
可以发现,在 Intel 至强和奔腾处理器中,I/O APIC 和 Local APIC,以及 Local APIC 之间使用的是 System Bus (e.g. PCI 总线) 进行连接,而在 Intel P6 系列处理器中,则采用的是 3-Wire APIC 总线进行连接。
Local APIC 中断处理结构
下面我们对 Local APIC 与中断处理相关的结构进行具体分析。
如
中断类型 | 描述 |
---|---|
本地中断源 |
|
CPU Core 本地连接的 I/O 设备产生的中断 | APIC 架构允许 I/O 设备通过和处理器的 LINT[1:0] 引脚相连来产生本地中断信号,也可以在这两个引脚上连接 8259 等中断控制芯片来控制外部设备的中断 |
APIC 计时器产生的中断 | 本地的 APIC 计时器在计数器到达设置的值后将会触发中断信号 |
Performance monitoring counter 产生的中断 | Intel P6,奔腾和至强处理器可以被设置为当 performance-monitoring 计数器溢出的时候,向处理器发送中断信号 |
温度传感器产生的中断 | |
APIC Internal Error 产生的中断 | 当对 Local APIC 的操作产生错误 (e.g. 访问一个不存在的寄存器) 时产生的中断信号 |
外部中断源 |
|
外部连接的 I/O 设备 | 外部连接的 I/O 设备会把它们产生的中断信号发送给 I/O APIC,后者会把中断信号转换为 Interrupt Message,通过 System Bus/APIC Bus 发送给选定 CPU Core 对应的 Local APIC |
Interrupt-processor Interrupts (IPIs) | Intel 处理器允许 CPU Core 在 System Bus 上将中断信息发送给其它 CPU Core(s) |
Local Vector Table (LVT)
针对本地中断,Local APIC 使用的是
Interrupt Command Register (ICR)
而针对外部中断,包括 IPI 和 外部 I/O 设备中断,它们是通过在系统总线上向 Local APIC 发送 Interrupt Message 来实现的中断通告。就 IPI 来说,CPU Core 可以通过向其本地的
在上面介绍的 LVT 和 ICR 中,我们可以看到与中断类型相关的 Delivery Mode
字段,其描述了最终向 CPU Core 发送的中断类型,下面我们对各种中断类型进行介绍:
- NMI:
- SMI:
- Fixed:
- INIT:
Interrupt Pending Resgiters
当 Local APIC 接收 (receive) 并且接受 (accept) 了 Fixed 类型的中断时,它会将其 pend 在对应 CPU Core 的
当 CPU 完成中断处理程序后,它会清除 ISR 中对应的比特位,并且向
Interrupt/Processor 优先级寄存器
Intel 处理器对中断的 Vector Number 进行了优先级的划分。上文说到 Intel 处理器一共支持 256 个中断,也即 8-bits。Vector Number 的 [7:4]
称为中断的 [3:0]
即中断在其 Interrupt-priority Class 中的相对位置,是中断分级的子类。值得注意的是,由于 Intel 把 0~31 号中断划分给 Intel 处理器自用,因此 Interrupt-priority Class 的合法取值范围为 2(0010
)~15(1111
),取值越大,优先级越高。
在 Local APIC 中,有两个与中断优先级相关的寄存器 ——
TPR 用于被操作系统软件设置,其包含的值代表着当前操作系统允许被中断的 Vector Number 的下限,低于该值的中断将被操作系统暂时封锁不予处理。因此 TPR 是一个可写的寄存器。如 [7:4]
的 Task-Priority Class 和 [3:0]
的 Task-Priority Sub-Class。
PPR 的值代表着当前处理器实际允许被中断的 Vector Number 的下限,其是基于 TPR 和 ISRV 的值产生的,其中 ISRV 的值指的是 ISR 寄存器中被置位的优先级最高的中断对应的 Vector,PPR 是一个只读寄存器,具体的值产生方法如下:
-
PPR[7:4]
: 等于TPR[7:4]
和ISRV[7:4]
中更大的那一个; -
PPR[3:0]
产生规则如下:- 如果
TPR[7:4]
>ISRV[7:4]
,那么PPR[3:0]
=TPR[3:0]
; - 如果
TPR[7:4]
<ISRV[7:4]
,那么PPR[3:0]
= 0; - 如果
TPR[7:4]
=ISRV[7:4]
,那么PPR[3:0]
是 model-specific 的
- 如果
只有当中断 Vector 代表的优先级大于 PPR 中存储的值时,CPU Core 才会被中断并且对中断予以处理。当然,我们上面的说明针对的是 Delivery Mode 为 Fixed 的中断。
Local APIC 中断处理流程
Local APIC 处理中断的流程如下所示:
中断相关内核定义
Generic IRQ Layer
从计算机系统架构的角度来看,中断系统在底层的异构性主要体现在两方面:
- 中断控制器的异构性: 不同的平台上的中断控制器各有千秋,我们上面看到的 APIC 架构是 x86 下的中断控制器结构;
- 中断编号的异构性: 不同的底层平台会使用不同的编号方法,支持的中断数量也不尽相同;
- 中断线电平逻辑的异构性: 不同的外围设备的中断器件产生的中断电平逻辑各有千秋,比如有
Level (电平触发型),Edge (边缘触发型) 等等,软件对不同的中断电平逻辑的相应要有所不同,因此产生异构性
由于底层系统的异构性,因此在 Linux 中提出了
对中断控制器的抽象
针对不同的底层平台,内核有着不同的底层代码分支,用于具体实现 CPU 与中断控制器之间的交互 (e.g. 屏蔽/取消屏蔽某个中断,设置中断优先级,以及在 SMP 平台上的亲和度等)。为了兼容不同平台上的不同的中断控制器,内核使用了结构体 irq_chip
对中断控制器进行统一的描述,利用该结构体向内核的其它部分提供了操作中断控制器的接口。这个结构体是在 include/linux/irq.h
linuxsrc_include_linux_irq_h 中定义的,其定义如下所示:
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/**
* struct irq_chip - hardware interrupt chip descriptor
*
* @parent_device: pointer to parent device for irqchip
* @name: name for /proc/interrupts
* @irq_startup: start up the interrupt (defaults to ->enable if NULL)
* @irq_shutdown: shut down the interrupt (defaults to ->disable if NULL)
* @irq_enable: enable the interrupt (defaults to chip->unmask if NULL)
* @irq_disable: disable the interrupt
* @irq_ack: start of a new interrupt
* @irq_mask: mask an interrupt source
* @irq_mask_ack: ack and mask an interrupt source
* @irq_unmask: unmask an interrupt source
* @irq_eoi: end of interrupt
* @irq_set_affinity: Set the CPU affinity on SMP machines. If the force
* argument is true, it tells the driver to
* unconditionally apply the affinity setting. Sanity
* checks against the supplied affinity mask are not
* required. This is used for CPU hotplug where the
* target CPU is not yet set in the cpu_online_mask.
* @irq_retrigger: resend an IRQ to the CPU
* @irq_set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
* @irq_set_wake: enable/disable power-management wake-on of an IRQ
* @irq_bus_lock: function to lock access to slow bus (i2c) chips
* @irq_bus_sync_unlock:function to sync and unlock slow bus (i2c) chips
* @irq_cpu_online: configure an interrupt source for a secondary CPU
* @irq_cpu_offline: un-configure an interrupt source for a secondary CPU
* @irq_suspend: function called from core code on suspend once per
* chip, when one or more interrupts are installed
* @irq_resume: function called from core code on resume once per chip,
* when one ore more interrupts are installed
* @irq_pm_shutdown: function called from core code on shutdown once per chip
* @irq_calc_mask: Optional function to set irq_data.mask for special cases
* @irq_print_chip: optional to print special chip info in show_interrupts
* @irq_request_resources: optional to request resources before calling
* any other callback related to this irq
* @irq_release_resources: optional to release resources acquired with
* irq_request_resources
* @irq_compose_msi_msg: optional to compose message content for MSI
* @irq_write_msi_msg: optional to write message content for MSI
* @irq_get_irqchip_state: return the internal state of an interrupt
* @irq_set_irqchip_state: set the internal state of a interrupt
* @irq_set_vcpu_affinity: optional to target a vCPU in a virtual machine
* @ipi_send_single: send a single IPI to destination cpus
* @ipi_send_mask: send an IPI to destination cpus in cpumask
* @irq_nmi_setup: function called from core code before enabling an NMI
* @irq_nmi_teardown: function called from core code after disabling an NMI
* @flags: chip specific flags
*/
struct irq_chip {
struct device *parent_device;
const char *name;
unsigned int (*irq_startup)(struct irq_data *data);
void (*irq_shutdown)(struct irq_data *data);
void (*irq_enable)(struct irq_data *data);
void (*irq_disable)(struct irq_data *data);
void (*irq_ack)(struct irq_data *data);
void (*irq_mask)(struct irq_data *data);
void (*irq_mask_ack)(struct irq_data *data);
void (*irq_unmask)(struct irq_data *data);
void (*irq_eoi)(struct irq_data *data);
int (*irq_set_affinity)(struct irq_data *data, const struct cpumask *dest, bool force);
int (*irq_retrigger)(struct irq_data *data);
int (*irq_set_type)(struct irq_data *data, unsigned int flow_type);
int (*irq_set_wake)(struct irq_data *data, unsigned int on);
void (*irq_bus_lock)(struct irq_data *data);
void (*irq_bus_sync_unlock)(struct irq_data *data);
void (*irq_cpu_online)(struct irq_data *data);
void (*irq_cpu_offline)(struct irq_data *data);
void (*irq_suspend)(struct irq_data *data);
void (*irq_resume)(struct irq_data *data);
void (*irq_pm_shutdown)(struct irq_data *data);
void (*irq_calc_mask)(struct irq_data *data);
void (*irq_print_chip)(struct irq_data *data, struct seq_file *p);
int (*irq_request_resources)(struct irq_data *data);
void (*irq_release_resources)(struct irq_data *data);
void (*irq_compose_msi_msg)(struct irq_data *data, struct msi_msg *msg);
void (*irq_write_msi_msg)(struct irq_data *data, struct msi_msg *msg);
int (*irq_get_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool *state);
int (*irq_set_irqchip_state)(struct irq_data *data, enum irqchip_irq_state which, bool state);
int (*irq_set_vcpu_affinity)(struct irq_data *data, void *vcpu_info);
void (*ipi_send_single)(struct irq_data *data, unsigned int cpu);
void (*ipi_send_mask)(struct irq_data *data, const struct cpumask *dest);
int (*irq_nmi_setup)(struct irq_data *data);
void (*irq_nmi_teardown)(struct irq_data *data);
unsigned long flags;
};
可以看到在结构体 irq_chip
中,定义了很多函数指针。在不同的平台的代码实现中,会实现这个函数集合的子集。如下所示是 x86 多核平台下的 irq_chip
实现,分别有上文介绍的 I/O APIC 版本和 Local APIC 版本。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// arch/x86/kernel/apic/io_apic.c
static struct irq_chip ioapic_chip __read_mostly = {
.name = "IO-APIC",
.irq_startup = startup_ioapic_irq,
.irq_mask = mask_ioapic_irq,
.irq_unmask = unmask_ioapic_irq,
.irq_ack = irq_chip_ack_parent,
.irq_eoi = ioapic_ack_level,
.irq_set_affinity = ioapic_set_affinity,
.irq_retrigger = irq_chip_retrigger_hierarchy,
.irq_get_irqchip_state = ioapic_irq_get_chip_state,
.flags = IRQCHIP_SKIP_SET_WAKE,
};
static struct irq_chip lapic_chip __read_mostly = {
.name = "local-APIC",
.irq_mask = mask_lapic_irq,
.irq_unmask = unmask_lapic_irq,
.irq_ack = ack_lapic_irq,
};
对中断编号的抽象
底层的中断控制器会用唯一的 struct irq_desc
,它是在 include/linux/irqdesc.h
linuxsrc_include_linux_irqdesc_h 中定义的,具体定义如下所示。
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/**
* struct irq_desc - interrupt descriptor
* @irq_common_data: per irq and chip data passed down to chip functions
* @kstat_irqs: irq stats per cpu
* @handle_irq: highlevel irq-events handler
* @action: the irq action chain
* @status_use_accessors: status information
* @core_internal_state__do_not_mess_with_it: core internal status information
* @depth: disable-depth, for nested irq_disable() calls
* @wake_depth: enable depth, for multiple irq_set_irq_wake() callers
* @tot_count: stats field for non-percpu irqs
* @irq_count: stats field to detect stalled irqs
* @last_unhandled: aging timer for unhandled count
* @irqs_unhandled: stats field for spurious unhandled interrupts
* @threads_handled: stats field for deferred spurious detection of threaded handlers
* @threads_handled_last: comparator field for deferred spurious detection of theraded handlers
* @lock: locking for SMP
* @affinity_hint: hint to user space for preferred irq affinity
* @affinity_notify: context for notification of affinity changes
* @pending_mask: pending rebalanced interrupts
* @threads_oneshot: bitfield to handle shared oneshot threads
* @threads_active: number of irqaction threads currently running
* @wait_for_threads: wait queue for sync_irq to wait for threaded handlers
* @nr_actions: number of installed actions on this descriptor
* @no_suspend_depth: number of irqactions on a irq descriptor with
* IRQF_NO_SUSPEND set
* @force_resume_depth: number of irqactions on a irq descriptor with
* IRQF_FORCE_RESUME set
* @rcu: rcu head for delayed free
* @kobj: kobject used to represent this struct in sysfs
* @request_mutex: mutex to protect request/free before locking desc->lock
* @dir: /proc/irq/ procfs entry
* @debugfs_file: dentry for the debugfs file
* @name: flow handler name for /proc/interrupts output
*/
struct irq_desc {
struct irq_common_data irq_common_data;
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;
irq_flow_handler_t handle_irq;
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 tot_count;
unsigned int irq_count; /* For detecting broken IRQs */
unsigned long last_unhandled; /* Aging timer for unhandled count */
unsigned int irqs_unhandled;
atomic_t threads_handled;
int threads_handled_last;
raw_spinlock_t lock;
struct cpumask *percpu_enabled;
const struct cpumask *percpu_affinity;
const struct cpumask *affinity_hint;
struct irq_affinity_notify *affinity_notify;
cpumask_var_t pending_mask;
unsigned long threads_oneshot;
atomic_t threads_active;
wait_queue_head_t wait_for_threads;
unsigned int nr_actions;
unsigned int no_suspend_depth;
unsigned int cond_suspend_depth;
unsigned int force_resume_depth;
struct proc_dir_entry *dir;
struct dentry *debugfs_file;
const char *dev_name;
struct rcu_head rcu;
struct kobject kobj;
struct mutex request_mutex;
int parent_irq;
struct module *owner;
const char *name;
} ____cacheline_internodealigned_in_smp;
内核在初始化的时候会为每一个中断源创建一个 irq_desc
实例,这些 irq_desc
实例会被存储在一个同样名为 irq_desc
的数组中,我们后面把这个数组称为
在结构体 irq_desc
中,类型为 irq_data
的成员 irq_data
存储了描述该中断源的底层信息,包括 Linux IRQ 编号,Hardware IRQ 编号,以及指向存储着中断控制器操作的 irq_chip
实例的指针。它是在 include/linux/irq.h
linuxsrc_include_linux_irq_h 中定义的,具体的定义如下所示:
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/**
* struct irq_data - per irq chip data passed down to chip functions
* @mask: precomputed bitmask for accessing the chip registers
* @irq: interrupt number
* @hwirq: hardware interrupt number, local to the interrupt domain
* @common: point to data shared by all irqchips
* @chip: low level interrupt hardware access
* @domain: Interrupt translation domain; responsible for mapping
* between hwirq number and linux irq number.
* @parent_data: pointer to parent struct irq_data to support hierarchy
* irq_domain
* @chip_data: platform-specific per-chip private data for the chip
* methods, to allow shared chip implementations
*/
struct irq_data {
u32 mask;
unsigned int irq;
unsigned long hwirq;
struct irq_common_data *common;
struct irq_chip *chip;
struct irq_domain *domain;
struct irq_data *parent_data;
void *chip_data;
};
中断处理逻辑
对于不同的设备来说,它们可能有着不同的中断触发类型 (e.g. irq_desc
中类型为 irq_flow_handler_t
的 handle_irq
成员来存储对应于该中断线上设备中断触发类型的 High-level 处理函数。内核在 kernel/irq/chip.c
linuxsrc_kernel_irq_chip_c 中提供了以下几个对应与不同中断触发类型的 High-level 处理函数。这些函数针对不同的中断线电平逻辑有着不同的 High-level 中断处理逻辑,但是它们的核心都是调用中断处理函数。我们在后面将会看到。
1
2
3
4
5
6
7handle_level_irq() // For level-triggered interrupts
handle_edge_irq() // For edge-triggered interrupts
handle_fasteoi_irq() // For interrupts that only need an EOI at the end of the handler
handle_simple_irq() // For simple interrupts
handle_percpu_irq() // For per-CPU interrupts
handle_bad_irq() // For spurious interrupts
// ...
在上面用于处理中断线电平的 High-level 的函数的包装下,当中断源发生中断时,由设备驱动注册在当前中断源上的中断处理函数需要被调用。irq_desc
中类型为 struct irqaction*
的成员 action
指向了一条存储着 struct irqaction
的链表。这些 struct irqaction
代表着中断描述符,各个中断描述符中存储着由各个设备驱动定义好并绑定在当前中断线上的中断处理逻辑。struct irqaction
是在 include/linux/interrupt.h
linuxsrc_include_linux_interrupt_h 中定义的,其定义如下所示:
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/**
* struct irqaction - per interrupt action descriptor
* @handler: interrupt handler function
* @name: name of the device
* @dev_id: cookie to identify the device
* @percpu_dev_id: cookie to identify the device
* @next: pointer to the next irqaction for shared interrupts
* @irq: interrupt number
* @flags: flags (see IRQF_* above)
* @thread_fn: interrupt handler function for threaded interrupts
* @thread: thread pointer for threaded interrupts
* @secondary: pointer to secondary irqaction (force threading)
* @thread_flags: flags related to @thread
* @thread_mask: bitmask for keeping track of @thread activity
* @dir: pointer to the proc/irq/NN/name entry
*/
struct irqaction {
irq_handler_t handler;
void *dev_id;
void __percpu *percpu_dev_id;
struct irqaction *next;
irq_handler_t thread_fn;
struct task_struct *thread;
struct irqaction *secondary;
unsigned int irq;
unsigned int flags;
unsigned long thread_flags;
unsigned long thread_mask;
const char *name;
struct proc_dir_entry *dir;
} ____cacheline_internodealigned_in_smp;
值得注意的是,当我们在一个
现在让我们串起来。当某个中断源发生中断时,基于 Linux IRQ 编号,内核基于该下标可以在 action
链表上挂载多个 irqaction
时,此时中断线将是共享的,即 irq_desc
数组中找到对应的 irq_desc
结构。此时 irq_desc
中的 handle_irq
指针指向的函数就会被调用,该函数会在正确处理对应中断线电平逻辑的情况下,调用在该中断线上的中断处理函数,这些中断处理函数是在中断描述符 irqaction
中被描述的。对于各条中断线,它们相应的中断处理函数被组织成一条链表的形式,挂在 irq_desc
结构的成员 action
上。
与中断相关的内核接口
Generic IRQ Layer 提供了各种各样的接口,方便设备驱动用于实现与中断相关的操作。理解这些接口将有助于我们后续对设备驱动的学习,在本节中我们将对这些接口进行阐述。
注册中断处理函数
1 | /* include/linux/interrupt.h */ |
request_irq
用于初始化一个 irqactioin
实例,并且绑定到由参数 irq
指定的 irq_desc
上去; 类型为 irq_handler_t
的参数 handler
指向了由设备驱动程序定义的中断处理函数; flags
是一个与中断管理相关的 bitmask,这个 bitmask 的各个位是在 include/linux/interrupt.h 中定义的,其中比较重要的位包括:
IRQF_SHARED
: 表示当前注册的中断处理函数允许和其它中断处理函数一起共享中断线;IRQF_TIMER
: 标记当前中断是一个定时器中断;IRQF_PERCPU
: 标识当前中断是 per-CPU 的中断;IRQF_NOBALANCING
: 标识将当前注册的中断排除在 IRQ Balancing 策略之外;
对于允许共享中断线的中断处理函数来说,在使用 request_irq
注册中断处理函数时,参数 dev
是必不可少的 (i.e. 不能设置为 NULL
,可以指向驱动模块程序中的任意地址) ldd_sharedirq。其原因是,在共享中断线的情况下,在 irq_desc
的 action
链表上将会存在多个 irqaction
,这个 dev
值将被用作区分这些 irqaction
的唯一凭据。当共享中断线上发生中断信号时,这些 irqaction
所代表的中断处理函数都会被调用,因此允许共享中断线的中断处理函数必须首先检查是否是自己的设备发生了中断,若是,再继续后续的中断处理。那么这些中断处理函数找到自己设备的方法就是这个 dev
值。考虑这样一种情况,我们在一条中断线上启用了两个使用同个驱动程序的外围设备,那么这两个设备会在对应的 irq_desc
的 action
链表上注册两个 irqaction
,这两个 irqaction
的区别在于它们的 dev
值是有区别的,并且这个 dev
值是由驱动程序自己维护的。当中断来临时,本质上同一个中断处理程序会被调用两次,但是两次调用传入的 dev
值是不同的,因此前后两次处理的设备是不同的。
当我们调用 request_irq
向一条中断线上注册一个允许共享中断的 irqaction
时,只有如下两个条件之一得到满足,才能注册成功 ldd_sharedirq。request_irq
注册成功将返回 0 值。
- 这条中断线上没有注册任何
irqaction
- 这条中断线上注册的
irqaction
都允许共享中断线
另外,传入 request_irq
的另一个重要参数是中断处理函数 handler
。中断处理函数的类型被定义为 irqreturn_t
,其函数原型如下:
1
typedef irqreturn_t (*irq_handler_t)(int, void *);
这个函数会在中断到来的时候被调用。其中传入的第一个 int
参数是 Linux IRQ 编号,第二个参数是我们上面介绍过的 dev
。irqreturn_t
的返回值是一个枚举类型,在 include/linux/irqreturn.h 中被定义,具体定义如下:
1
2
3
4
5
6
7
8
9
10
11/**
* enum irqreturn
* @IRQ_NONE interrupt was not from this device or was not handled
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
当允许共享中断线的中断处理程序检测到不是自己的设备产生了中断时,它应该返回 IRQ_NONE
; 如果中断被正确处理,它应该返回 IRQ_HANDLED
; 如果中断程序尝试唤醒一个 IRQ_WAKE_THREAD
,我们将在 threaded_interrupt_handler 中对此部分进行介绍。
解注册中断处理函数
1 | /* kernel/irq/manage.c */ |
free_irq
用于释放一个由 request_irq
分配的 irqaction
。其中传入的参数 irq
是 Linux IRQ 编号。如果释放的 irqaction
是一个共享中断线的中断处理程序,dev_id
用于指明具体取消的 irqaction
是哪一个。
注册 Threaded 中断处理线程
1 | /* include/linux/interrupt.h */ |
由设备驱动通过 request_irq
注册的中断处理程序运行在
除此之外,内核在中断系统中提供了一种可以在 Threaded Context 中运行中断处理程序的机制 —— request_threaded_irq
接口来申请一个可 threaded 的中断处理程序,该接口的函数原型如下所示:
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/**
* request_threaded_irq - allocate an interrupt line
* @irq: Interrupt line to allocate
* @handler: Function to be called when the IRQ occurs.
* Primary handler for threaded interrupts
* If NULL and thread_fn != NULL the default
* primary handler is installed
* @thread_fn: Function called from the irq handler thread
* If NULL, no irq thread is created
* @irqflags: Interrupt type flags
* @devname: An ascii name for the claiming device
* @dev_id: A cookie passed back to the handler function
*
* This call allocates interrupt resources and enables the
* interrupt line and IRQ handling. From the point this
* call is made your handler function may be invoked. Since
* your handler function must clear any interrupt the board
* raises, you must take care both to initialise your hardware
* and to set up the interrupt handler in the right order.
*
* If you want to set up a threaded irq handler for your device
* then you need to supply @handler and @thread_fn. @handler is
* still called in hard interrupt context and has to check
* whether the interrupt originates from the device. If yes it
* needs to disable the interrupt on the device and return
* IRQ_WAKE_THREAD which will wake up the handler thread and run
* @thread_fn. This split handler design is necessary to support
* shared interrupts.
*
* Dev_id must be globally unique. Normally the address of the
* device data structure is used as the cookie. Since the handler
* receives this value it makes sense to use it.
*
* If your interrupt is shared you must pass a non NULL dev_id
* as this is required when freeing the interrupt.
*
* Flags:
*
* IRQF_SHARED Interrupt is shared
* IRQF_TRIGGER_* Specify active edge(s) or level
*
*/
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)
在这个接口中,参数 handler
传入的是运行在 Interrupt Context 中的 Primary Interrupt Handler,参数 thread_fn
传入的是运行在 Threaded Context 的中断函数逻辑。按照这种方式注册的中断函数,当中断发生时,handler
所代表的 Primary Interrupt Handler 将首先被调用,在该函数返回 IRQ_WAKE_THREAD
后,thread_fn
所定义的后续中断处理逻辑将被调度,并在 Threaded Context 中被执行。
Theaded Interrupt Handler 有两种玩法。一种是在 Primary Interrupt Handler 中完成 Interrupt-critical 的工作,然后唤醒 Threaded Handler 进行处理,这种处理方式与 Deferred Work 的机制类似,对应的中断源将在 Primary Interrupt Handler 执行完成后被手动 Unmasked 恢复;第二种是把所有的中断处理逻辑都放到 Threaded Handler 中,Primary Interrupt Handler 中只会对中断源进行确认 (i.e. 考虑 Shared IRQ 的情况) 以及唤醒 Threaded Handler,在这种情况下,对应的中断源需要被 Masked 直至 Threaded Handler 完成其处理逻辑。为了实现对中断源的持续 Masked,我们可以通过以下其中一种方式予以实现:
- 在 Primary Interrupt Handler 中,在调度 Theaded Interrupt Handler 之前手动 Mask 掉中断,不要 Unmask 它;
- 在使用
request_threaded_irq
注册时,在参数irqflags
中使用IRQF_ONESHOT
标记进行标识
控制接口
Generic IRQ Layer 还提供了若干的用于控制各个 Linux 中断源的接口,下面进行介绍。
void disable_irq(unsigned int irq)
disable_irq
关闭了指定 Linux IRQ 所代表的中断线的响应。这是一个阻塞的函数调用,这个函数会等待当前指定的中断线上正在运行的 Handler 运行结束后,再完成对中断线的屏蔽。实际上,disable_irq
底层调用了 irq_chip
结构体的 irq_disable
函数完成了对中断线的屏蔽,它实现的是全局的对某条中断线的屏蔽。
Deferred Work
TODO