中断
中断是外围设备通知处理器的一种机制。
1. 中断控制器
外围设备不是把中断请求直接发送给处理器,而是发给中断控制器,由中断控制器转发给处理器。
不同种类的中断控制器的访问方法存在差异,为了屏蔽差异,内核定义了中断控制器描述符irq_chip,每种中断控制器自定义各种操作函数。GIC v2控制器的描述符如下:
drivers/irqchip/irq-gic.c
tatic const struct irq_chip gic_chip = {
.irq_mask = gic_mask_irq,
.irq_unmask = gic_unmask_irq,
.irq_eoi = gic_eoi_irq,
.irq_set_type = gic_set_type,
.irq_get_irqchip_state = gic_irq_get_irqchip_state,
.irq_set_irqchip_state = gic_irq_set_irqchip_state,
.flags = IRQCHIP_SET_TYPE_MASKED |
IRQCHIP_SKIP_SET_WAKE |
IRQCHIP_MASK_ON_SUSPEND,
};
2. 中断域
一个大型系统可能有多个中断控制器,这些中断控制器可以级联,一个中断控制器作为中断源连接到另一个中断控制器,但只有一个中断控制器作为根控制器直接连接到处理器。为了把每个中断控制器本地的硬件中断映射到全局唯一的Linux中断号(也称为虚拟中断),内核定义了中断域irq_domain,每个中断控制器由自己的中断域。
2.1. 创建中断域
中断控制器的驱动程序使用分配函数irq_domain_add_*()创建和注册中断域。
2.2. 创建映射
创建中断域以后,需要向中断域添加硬件中断号到Linux中断号的映射,内核提供了函数irq_create_mapping:
unsigned int irq_create_mapping(struct irq_domain *host, irq_hw_number_t hwirq);
输入参数是中断域和硬件中断号,返回Linux中断号。
该函数首先分配Linux中断号,然后把硬件中断号到Linux中断号的映射添加到中断域。
2.3. 查找映射
中断处理程序需要根据硬件中断号查找Linux中断号,内核提供了函数irq_find_mapping:
unsigned int irq_find_mapping(struct irq_domain *host, irq_hw_number_t hwirq);
输入参数是中断域和硬件中断号,返回Linux中断号。
【文章福利】小编推荐自己的Linux内核技术交流群:【977878001】整理一些个人觉得比较好得学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!前100进群领取,额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)
内核资料直通车:Linux内核源码技术学习路线+视频教程代码资料
学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
3. 中断控制器驱动初始化
3.1. 设备树源文件
ARM64架构使用扁平设备树(Flattened Device Tree,FDT)描述板卡的硬件信息,好处是可以把板卡的特定的代码从内核中删除,编译生成通用的板卡无关的内核。
设备树源文件是文本文件,扩展名是“.dts”,需要在设备树源文件中描述中断的相关信息:
(1)中断控制器的信息
(2)对于作为中断源的外围设备,需要描述设备连接到哪个中断控制器,使用哪个硬件中断号
3.2. 中断控制器匹配表
在GIC v2控制器的驱动程序中,定义了多个类型为of_device_id的静态变量,成员compatible是驱动程序支持的设备的名称,成员data是初始化函数,编译器把这些静态变量放在专用的节“__irqchip_of_table”里面。
我们把节“__irqchip_of_table”称为中断控制器匹配表,里面每个表项的格式是结构体of_device_id。
drivers/irqchip/irq-gic.c
IRQCHIP_DECLARE(gic_400, "arm,gic-400", gic_of_init);
...
IRQCHIP_DECLARE(cortex_a15_gic, "arm,cortex-a15-gic", gic_of_init);
IRQCHIP_DECLARE(cortex_a9_gic, "arm,cortex-a9-gic", gic_of_init);
...
把宏IRQCHIP_DECLARE展开以后是:
static const struct of_device_id __of_table_cortex_gic_400
__section(__irqchip_of_table)
= { .compatible = "arm,gic-g400",
.data = gic_of_init }
...
static const struct of_device_id __of_table_cortex_a15_gic
__section(__irqchip_of_table)
= { .compatible = "arm,cortex-a15-gic",
.data = gic_of_init }
static const struct of_device_id __of_table_cortex_a9_gic
__section(__irqchip_of_table)
= { .compatible = "arm,cortex-a9-gic",
.data = gic_of_init }
...
3.3. 初始化
在内核初始化的时候,匹配设备树文件中的中断控制器的属性“compatible”和内核的中断控制器匹配表,找到合适的中断控制器驱动程序,执行驱动程序的初始化函数。
start_kernel() -> init_IRQ() -> irqchip_init()
drivers/irqchip/irqchip.c
void __init irqchip_init(void)
{
of_irq_init(__irqchip_of_table); // 参数是中断控制器匹配表的起始地址__irqchip_of_table
...
}
(1)函数of_irq_init
driver/of/irq.c
/**
* of_irq_init - Scan and init matching interrupt controllers in DT
* @matches: 0 terminated array of nodes to match and init function to call
*
* This function scans the device tree for matching interrupt controller nodes,
* and calls their initialization functions in order with parents first.
*/
void __init of_irq_init(const struct of_device_id *matches)
{
const struct of_device_id *match;
struct device_node *np, *parent = NULL;
struct of_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_and_match(np, matches, &match) { /* 遍历设备树文件的设备节点。如果属性compatible和中断控制器匹配表中的任何一条表项的字段compatible匹配,处理如下 */
if (!of_property_read_bool(np, "interrupt-controller") ||
!of_device_is_available(np)) /* 如果没有节点属性interrupt-controller,说明设备不是中断控制器,忽略该设备 */
continue;
if (WARN(!match->data, "of_irq_init: no init function for %s\n",
match->compatible))
continue;
/*
* Here, we allocate and populate an of_intc_desc with the node
* pointer, interrupt-parent device_node etc.
*/
desc = kzalloc(sizeof(*desc), GFP_KERNEL); /* 分配一个of_intc_desc实例 */
if (WARN_ON(!desc)) {
of_node_put(np);
goto err;
}
desc->irq_init_cb = match->data; /* 成员irq_init_cb保存初始化函数 */
desc->dev = of_node_get(np); /* 成员dev保存本设备的device_node */
desc->interrupt_parent = of_irq_find_parent(np); /* 成员inte