前言
之前LINUX中断学习笔记(1)和LINUX中断学习笔记(2)介绍了Linux中断的一些基础知识,但是不够深入。最近公司所在团队逐渐在往内核进一步深入,仅有前边文章的浅显知识已不足以覆盖工作的需求,也无法和部门同事做深入的技术讨论。本文是在工作之余,进行代码的研究和资料的查找,希望可以将中断子系统尽量整理清楚。本文内容会不定期补充更新,也希望能对访问到本站的朋友有所帮助。
架构(Architecture)
硬件连接
1 | +---------+ |
- 中断控制器(IRQ Controller):负责对硬件中断进行管理。例如缓冲、优先级判断等。
- 硬件设备(Devices):中断接入中断控制器
如上图,系统中可能存在多个中断控制器(IRQ Controller)。中断控制器或者直接与CPU连接,或者通过级联方式接入上一级中断控制器,最后接入CPU。中断控制器可以与系统多个核心都有连接,根据一定方法选择响应该中断的CPU(即设定中断Affinity)
软件架构
如下图。Linux中断子系统软件架构主要分为三个层次,从上到下依次是:
- 设备驱动层:设备驱动,主要负责向系统注册真正的中断处理函数
- 硬件无关中断处理层:Linux中断子系统的核心代码
- CPU架构相关中断控制器:与CPU架构体系相关的中断处理代码,以及与特定中断控制器相关的中断处理代码
可以看出,Linux的中断处理架构非常清晰。硬件无关中断处理层,统一了中断处理的接口:避免了设备驱动实现者必须要关心不同CPU体系以及不同中断处理器的不同特性。
1 | +-------------------------------------------------+ |
内核目录结构
IRQ相关的Linux内核目录结构如下图所示:
1 | Linux/ |
内核数据结构以及API
为了实现上述的软件架构,将中断使用者和硬件相关的代码隔离开。内核定义了一系列了数据结构以及相应的操作函数。本节对其中重点的数据结构进行分析:
中断域(struct irq_domain)
在介绍irq_domain之前,先介绍什么是IRQ Domain。早期的系统,只有一个中断控制器,接入中断控制器的物理中断号都是不同的。但是随着计算机系统的发展,系统中可以挂接更多的中断控制器。特别是嵌入式系统的出现,类似GPIO这种也可以视作一种中断控制器。每个中断控制器都有自己中断线的物理编号,且这些物理编号会有重复。此时,Linux Kernel发展出了IRQ Domain的概念,来区分这些相同的物理中断编号。
IRQ Domain,顾名思义,即中断控制域。每个中断控制器都有自己的struct irq_domain结构体,以及自己下属的物理中断号,也不用担心物理中断号重复无法区分的问题。以“硬件连接”一节的图片为例,IRQ Controller 1及其下属的几根中断输入线作为一个中断控制域,而IRQ Controller 2作为另外一个中断控制域。
Figure 1. Linux中断控制域图例
可能会有朋友问了,“记得向Linux Kernel注册中断处理函数是唯一的中断号,也没有传入Domain ID之类的参数,这如何解释?”。原因很简单,因为这个唯一的中断号是Linux Kernel分配的全局唯一虚拟中断号,Linux通过一定方式将其和中断控制域的物理中断号关联。对每个中断控制域来讲,这种映射/关联主要有以下三类:
- 线性映射(Linear):固定大小的数组映射(物理中断号为数组索引)。当该中断控制域的物理中断号较连续且数量不大时使用。
- 基树映射(Radix Tree): 与线性映射相反,物理中断号比较大,或者不太连续时使用。
- 直接映射(No Map/Diret): 有些中断控制器支持物理中断号编程,其被分配到的虚拟中断号可以被直接设定到中断控制器。
- Legacy映射(传统映射?): 特殊的一种映射模式。当需要固定分配一部分中断编号范围时使用。设备驱动可以直接使用约定的虚拟中断号来进行IRQ操作。
数据结构
中断域数据结构的定义在内核目录include/linux/irq.h,其所有成员以及相应解释如下:
1 | struct irq_domain { |
中断域处理函数
中断域处理函数主要有以下几类:
中断域添加/删除函数irq_domain_add_*()/remove()系列,例:
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//线性映射添加
static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
unsigned int size,
const struct irq_domain_ops *ops,
void *host_data)
//直接映射添加
static inline struct irq_domain *irq_domain_add_nomap(struct device_node *of_node,
unsigned int max_irq,
const struct irq_domain_ops *ops,
void *host_data)
{
return __irq_domain_add(of_node_to_fwnode(of_node), 0, max_irq, max_irq, ops, host_data);
}
//Legacy映射添加
static inline struct irq_domain *irq_domain_add_legacy_isa(
struct device_node *of_node,
const struct irq_domain_ops *ops,
void *host_data)
{
return irq_domain_add_legacy(of_node, NUM_ISA_INTERRUPTS, 0, 0, ops,
host_data);
}
//基树映射添加
static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node,
const struct irq_domain_ops *ops,
void *host_data)
{
return __irq_domain_add(of_node_to_fwnode(of_node), 0, ~0, 0, ops, host_data);
}
//移除Domain
extern void irq_domain_remove(struct irq_domain *host);创建中断号映射的irq_create_XXX()系列
1
2
3
4
5
6
7
8
9
10
11
12//创建HWIRQ映射,返回对应的虚拟中断号
extern unsigned int irq_create_mapping(struct irq_domain *host,
irq_hw_number_t hwirq);
//根据Firmware Spec创建映射,一般与设备树DTS解析的信息有关
extern unsigned int irq_create_fwspec_mapping(struct irq_fwspec *fwspec);
//创建Direct Mapping
extern unsigned int irq_create_direct_mapping(struct irq_domain *host);
//创建固定映射
extern int irq_create_strict_mappings(struct irq_domain *domain,
unsigned int irq_base,
irq_hw_number_t hwirq_base,
int count);IRQ Domain Callback函数,供IRQ Chip Driver定义。而该中断域中的设备驱动在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20struct irq_domain_ops {
int (*match)(struct irq_domain *d, struct device_node *node,
enum irq_domain_bus_token bus_token); //
int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw); // 创建或者更新虚拟中断号及中断域物理中断号的映射
void (*unmap)(struct irq_domain *d, unsigned int virq);//删除映射
int (*xlate)(struct irq_domain *d, struct device_node *node,
const u32 *intspec, unsigned int intsize,
unsigned long *out_hwirq, unsigned int *out_type);//根据DTS节点以及对应的interrupt描述符,解析出物理中断号和中断出发方式
//以下为IRQ Domain 层次相关回调函数
int (*alloc)(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs, void *arg);
void (*free)(struct irq_domain *d, unsigned int virq,
unsigned int nr_irqs);
void (*activate)(struct irq_domain *d, struct irq_data *irq_data);
void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data);
int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec,
unsigned long *out_hwirq, unsigned int *out_type);
};
我们在后边的介绍中再回过头看这些回调函数的定义。
中断描述符 (struct irq_desc)
接着我们介绍中断描述符。中断描述符与Kernel中全局唯一的虚拟中断号关联。中断描述符或者采取固定分配,例如:
struct irq_desc irq_desc[NR_IRQS];
或者采用分散管理方式(管理上使用基树-Radix Tree做管理):
#ifdef CONFIG_SPARSE_IRQ
static RADIX_TREE(irq_desc_tree, GFP_KERNEL);#endif
1 | //分配cnt数量的连续中断号 |
而中断描述符的数据结构以及其与中断子系统其他数据结构的关系如下(本图以线性管理方式为例):
1 | +-------+-------+-------+------+-------+-------+ |
其中:
- handle_irq: High-level IRQ处理函数,一般在中断控制器初始化时定义
- irq_data: 中断相关数据
- 中断号
- 物理中断号
- IRQ中断域
- IRQ控制器芯片相关信息
- IRQ控制器芯片独有的数据
- action: 设备驱动注册的中断处理函数(即request_irq传入的Handler)
中断芯片 (struct irq_chip)
1 | struct irq_chip { |