本帖最后由 oxlm_1 于 2025-1-19 18:11 编辑
经过了漫长的阅读,最近终于看到了中断这部分,这也是这本书为数不多的与笨叔的《奔跑吧 Linux内核 第二版 卷2:调试与案例分析》有交集的两章中的一章,另一章是内核互斥技术。
中断控制器注册
说到中断控制器注册,就不得不说设备树,受益于林纳斯的那篇著名的骂娘邮件,中断控制器部分注册,关键信息部分也被放到了设备树中去了。
以3399的dts(见arch\arm64\boot\dts\rockchip\rk3399.dtsi)为例:
、/ {
compatible = "rockchip,rk3399";
interrupt-parent = <&gic>;
#address-cells = <2>;
#size-cells = <2>;
...
pmu_a53 {
compatible = "arm,cortex-a53-pmu";
interrupts = <GIC_PPI 7 IRQ_TYPE_LEVEL_LOW &ppi_cluster0>;
};
pmu_a72 {
compatible = "arm,cortex-a72-pmu";
interrupts = <GIC_PPI 7 IRQ_TYPE_LEVEL_LOW &ppi_cluster1>;
};
...
/* 定时器中断节点描述,由于没有指定中断父级中断节点,*/
/* 因此默认使用设备树上一级的父级中断节点,即interrupt-parent = <&gic>;*/
timer {
compatible = "arm,armv8-timer";
interrupts = <GIC_PPI 13 IRQ_TYPE_LEVEL_LOW 0>,
<GIC_PPI 14 IRQ_TYPE_LEVEL_LOW 0>,
<GIC_PPI 11 IRQ_TYPE_LEVEL_LOW 0>,
<GIC_PPI 10 IRQ_TYPE_LEVEL_LOW 0>;
arm,no-tick-in-suspend;
};
...
# 中断控制器信息
gic: interrupt-controller@fee00000 {
/* 表明使用的是gic-v3版的控制器,系统根据此信息决定启用v3版的gic控制器 */
compatible = "arm,gic-v3";
/* 中断单元数量是4个32位整数 */
#interrupt-cells = <4>;
#address-cells = <2>;
#size-cells = <2>;
ranges;
/* 表示是中断控制器 */
interrupt-controller;
/* 中断控制器寄存器的物理地址范围 */
/* 第一个参数是分发器 */
/* 第二个参数是起始地址 */
/* 第三个参数是寄存器长度 */
reg = <0x0 0xfee00000 0 0x10000>, /* GICD */
<0x0 0xfef00000 0 0xc0000>, /* GICR */
<0x0 0xfff00000 0 0x10000>, /* GICC */
<0x0 0xfff10000 0 0x10000>, /* GICH */
<0x0 0xfff20000 0 0x10000>; /* GICV */
/* 最关键的信息: 描述中断类型、硬件终端号以及中断触发方式*/
interrupts = <GIC_PPI 9 IRQ_TYPE_LEVEL_HIGH 0>;
its: interrupt-controller@fee20000 {
compatible = "arm,gic-v3-its";
msi-controller;
reg = <0x0 0xfee20000 0x0 0x20000>;
};
ppi-partitions {
ppi_cluster0: interrupt-partition-0 {
affinity = <&cpu_l0 &cpu_l1 &cpu_l2 &cpu_l3>;
};
ppi_cluster1: interrupt-partition-1 {
affinity = <&cpu_b0 &cpu_b1>;
};
};
};
...
};
以GIC控制器注册为例,驱动代码解析控制器部分如下:
struct of_device_id {
char name[32];
char type[32];
char compatible[128];
const void *data;
};
#define _OF_DECLARE(table, name, compat, fn, fn_type) \
static const struct of_device_id __of_table_##name \
__used __section(__##table##_of_table) \
= { .compatible = compat, \
.data = (fn == (fn_type)NULL) ? fn : fn }
#define OF_DECLARE_2(table, name, compat, fn) \
_OF_DECLARE(table, name, compat, fn, of_init_fn_2)
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init);
完整解析下来,就变成了以下信息:
static const struct of_device_id __of_table_gic_v3 \
__attribute__((__irqchip_of_table)) \
= { .compatible = "arm,gic-v3", \
.data = gic_of_init }
也就是说,这段代码其实就是实现了这么个功能,声明一个__of_table_gic_v3的struct of_device_id结构体,并将这个结构体放置于__irqchip_of_table段内。
而这个段就让人想到了linux和rtthread设备初始化上玩的贼溜的在编译时把函数链接到特定段内,程序运行时跑到特定段内去找到段内的函数逐个调用完成初始化的操作。而实际上是否是这么操作,还需要进一步的查看。
void __init irqchip_init(void)
{
of_irq_init(__irqchip_of_table);
acpi_probe_device_table(irqchip);
}
void __init init_IRQ(void)
{
irqchip_init();
if (!handle_arch_irq)
panic("No interrupt controller found.");
}
asmlinkage __visible void __init start_kernel(void)
{
...
init_IRQ();
...
}
至此,我们发现在这版的内核代码中,其实也是按照那种把函数放置结构体的某段内去实现调用的方式实现的,只是实现对象变成了数据,而不是函数。在内核初始化时,系统通过调用of_irq_int函数去查找__irqchip_of_table字段中的控制器来实现加载的。而of_irq_init的具体实现如下:
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循环是通过设备树和matches表,找到对应的interrupt-controller的节点,
// 找到后将此节点的信息添加至intc_desc_list列表中
for_each_matching_node_and_match(np, matches, &match) {
if (!of_find_property(np, "interrupt-controller", NULL) ||
!of_device_is_available(np))
continue;
if (WARN(!match->data, "of_irq_init: no init function for %s\n",
match->compatible))
continue;
desc = kzalloc(sizeof(*desc), GFP_KERNEL);
if (WARN_ON(!desc)) {
of_node_put(np);
goto err;
}
desc->irq_init_cb = match->data; // 此data就是IRQCHIP_DECLARE中的第三个参数,初始化回调函数
desc->dev = of_node_get(np);
desc->interrupt_parent = of_irq_find_parent(np);
if (desc->interrupt_parent == np)
desc->interrupt_parent = NULL;
list_add_tail(&desc->list, &intc_desc_list);
}
// 此循环是从intc_desc_list列表中逐个取出并执行初始化回调
while (!list_empty(&intc_desc_list)) {
// 初始化前后,将中断控制器从intc_desc_list中移除并添加至intc_parent_list列表中
// 由于parent默认指向空,因此优先初始化的是gic控制器,之后再是挂在gic控制器上的各种控制器
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
int ret;
if (desc->interrupt_parent != parent)
continue;
list_del(&desc->list);
of_node_set_flag(desc->dev, OF_POPULATED);
pr_debug("of_irq_init: init %s (%p), parent %p\n",
desc->dev->full_name,
desc->dev, desc->interrupt_parent);
ret = desc->irq_init_cb(desc->dev,
desc->interrupt_parent);
if (ret) {
of_node_clear_flag(desc->dev, OF_POPULATED);
kfree(desc);
continue;
}
list_add_tail(&desc->list, &intc_parent_list);
}
desc = list_first_entry_or_null(&intc_parent_list,
typeof(*desc), list);
if (!desc) {
pr_err("of_irq_init: children remain, but no parents\n");
break;
}
list_del(&desc->list);
parent = desc->dev;
kfree(desc);
}
list_for_each_entry_safe(desc, temp_desc, &intc_parent_list, list) {
list_del(&desc->list);
kfree(desc);
}
err:
list_for_each_entry_safe(desc, temp_desc, &intc_desc_list, list) {
list_del(&desc->list);
of_node_put(desc->dev);
kfree(desc);
}
}
看完注册入口后,我们会发现,绕来绕去,其实设备的注册又回到了IRQCHIP_DECLARE(gic_v3, "arm,gic-v3", gic_of_init),也就是说,如果我们了解了控制器的注册过程,其实我们只需要关注IRQCHIP_DECLARE这一个入口即可,其对应的注册回调函数为gic_of_init。
static int __init gic_init_bases(void __iomem *dist_base,
struct redist_region *rdist_regs,
u32 nr_redist_regions,
u64 redist_stride,
struct fwnode_handle *handle)
{
u32 typer;
int gic_irqs;
int err;
if (!is_hyp_mode_available())
static_key_slow_dec(&supports_deactivate);
if (static_key_true(&supports_deactivate))
pr_info("GIC: Using split EOI/Deactivate mode\n");
gic_data.fwnode = handle;
gic_data.dist_base = dist_base;
gic_data.redist_regions = rdist_regs;
gic_data.nr_redist_regions = nr_redist_regions;
gic_data.redist_stride = redist_stride;
/*
* Find out how many interrupts are supported.
* The GIC only supports up to 1020 interrupt sources (SGI+PPI+SPI)
*/
typer = readl_relaxed(gic_data.dist_base + GICD_TYPER);
gic_data.rdists.id_bits = GICD_TYPER_ID_BITS(typer);
gic_irqs = GICD_TYPER_IRQS(typer);
if (gic_irqs > 1020)
gic_irqs = 1020;
gic_data.irq_nr = gic_irqs;
gic_data.domain = irq_domain_create_tree(handle, &gic_irq_domain_ops,
&gic_data);
gic_data.rdists.rdist = alloc_percpu(typeof(*gic_data.rdists.rdist));
if (WARN_ON(!gic_data.domain) || WARN_ON(!gic_data.rdists.rdist)) {
err = -ENOMEM;
goto out_free;
}
set_handle_irq(gic_handle_irq);
if (IS_ENABLED(CONFIG_ARM_GIC_V3_ITS) && gic_dist_supports_lpis())
its_init(handle, &gic_data.rdists, gic_data.domain);
gic_smp_init();
gic_dist_init();
gic_cpu_init();
gic_cpu_pm_init();
return 0;
out_free:
if (gic_data.domain)
irq_domain_remove(gic_data.domain);
free_percpu(gic_data.rdists.rdist);
return err;
}
static int __init gic_of_init(struct device_node *node, struct device_node *parent)
{
void __iomem *dist_base;
struct redist_region *rdist_regs;
u64 redist_stride;
u32 nr_redist_regions;
int err, i;
dist_base = of_iomap(node, 0);
if (!dist_base) {
pr_err("%s: unable to map gic dist registers\n",
node->full_name);
return -ENXIO;
}
err = gic_validate_dist_version(dist_base);
if (err) {
pr_err("%s: no distributor detected, giving up\n",
node->full_name);
goto out_unmap_dist;
}
if (of_property_read_u32(node, "#redistributor-regions", &nr_redist_regions))
nr_redist_regions = 1;
rdist_regs = kzalloc(sizeof(*rdist_regs) * nr_redist_regions, GFP_KERNEL);
if (!rdist_regs) {
err = -ENOMEM;
goto out_unmap_dist;
}
for (i = 0; i < nr_redist_regions; i++) {
struct resource res;
int ret;
ret = of_address_to_resource(node, 1 + i, &res);
rdist_regs[i].redist_base = of_iomap(node, 1 + i);
if (ret || !rdist_regs[i].redist_base) {
pr_err("%s: couldn't map region %d\n",
node->full_name, i);
err = -ENODEV;
goto out_unmap_rdist;
}
rdist_regs[i].phys_base = res.start;
}
if (of_property_read_u64(node, "redistributor-stride", &redist_stride))
redist_stride = 0;
// 前面属于各种资源初始化和异常检测,最关键的点就是这个入口函数
err = gic_init_bases(dist_base, rdist_regs, nr_redist_regions,
redist_stride, &node->fwnode);
if (err)
goto out_unmap_rdist;
gic_populate_ppi_partitions(node);
gic_of_setup_kvm_info(node);
return 0;
out_unmap_rdist:
for (i = 0; i < nr_redist_regions; i++)
if (rdist_regs[i].redist_base)
iounmap(rdist_regs[i].redist_base);
kfree(rdist_regs);
out_unmap_dist:
iounmap(dist_base);
return err;
}
总结
笨叔的那本书,更多的是从应用的角度去看问题,所以会有一个很明显的特征,就是围绕中断怎么产生,芯片怎么获取中断,中间加上软件怎么启用中断并注册中断回调,触发中断后的各种中断处理方式来讲解中断部分的实现思路,而具体实现的部分,更多的是一笔带过的结论。而《Linux内核深度解析》这本书,侧重点放在了内核源码的解读,因此在内容上,更多的看到的是具体的代码实现。相比较于笨叔的版本,《Linux内核深度解析》能让我们在一定程度上不用坐在电脑前对着代码去阅读,而仅仅需要在看到不理解的部分再去打开对应源码去再次梳理。