oxlm_1

  • 2025-01-22
  • 回复了主题帖: “我只要你的壳”之TI-82计算器改造(1)

    换屏不需要改软件的嘛?

  • 2025-01-21
  • 回复了主题帖: 有关《Linux内核深度解析》的文件系统需要的知识准备及剖析

    张尧学  我的学位证上还盖着他的章

  • 2025-01-20
  • 回复了主题帖: 2025新年花灯节,来场花式点灯秀吧!

    直接来个555定时器点灯?

  • 2025-01-19
  • 回复了主题帖: 《奔跑吧Linux内核1:基础架构》-01-总体介绍

    看到现在,我也感觉有必要申请卷1来看了,笨叔的书和Linux内核深度解析看问题的角度不一样,解析出来的内容也有差异。不过感觉现在没机会了

  • 发表了日志: 《Linux内核深度解析》-- 中断控制器注册逻辑

  • 发表了主题帖: 《Linux内核深度解析》-- 中断控制器注册逻辑

    本帖最后由 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内核深度解析》能让我们在一定程度上不用坐在电脑前对着代码去阅读,而仅仅需要在看到不理解的部分再去打开对应源码去再次梳理。

  • 回复了主题帖: 今年,你降薪了吗?

    年终少了,算不算降薪?

  • 回复了主题帖: 买一个逻辑分析仪,对写代码很有帮助

     用逻辑分析仪前最好先用示波器看看波形是不是符合标准,确认这个后再去看逻辑分析仪的数据

  • 2025-01-18
  • 回复了主题帖: 【回家】你们抢到回老家的票了吗?

    宁愿多花点钱开车回去 + 返程高速堵车,过年几天老家没车实在是不方便

  • 回复了主题帖: 想问一下c语言和c51的c语言有什么区别

    标准差别吧,C51很可能还是旧标准的,很多新标准的特性不支持

  • 2025-01-17
  • 回复了主题帖: 【新年新挑战,任务打卡赢好礼!】第一批获奖名单公布

    已确认

  • 回复了主题帖: 《Linux内核深度解析》-异常处理

    水手勇敢 发表于 2025-1-16 21:38 我写的不全,另外Linux内核确实比较难。 一个模块一个模块地啃还是可以搞懂,只是linux内核太大了,内容太多了,没书完全不知道从哪开始啃

  • 2025-01-15
  • 加入了学习《自己动手写嵌入式操作系统》,观看 时间片轮转和上下文切换

  • 加入了学习《自己动手写嵌入式操作系统》,观看 老师监管定期时间片轮转

  • 加入了学习《自己动手写嵌入式操作系统》,观看 采用硬件delay实现sleep函数

  • 加入了学习《自己动手写嵌入式操作系统》,观看 为什么延时1秒会出现睡眠2秒的问题

  • 加入了学习《自己动手写嵌入式操作系统》,观看 多任务访问外设操作

  • 加入了学习《自己动手写嵌入式操作系统》,观看 任务切换的上下文混淆问题

  • 加入了学习《自己动手写嵌入式操作系统》,观看 任务调度器的实现

  • 加入了学习《自己动手写嵌入式操作系统》,观看 通过修改SP,来修改调用的函数

最近访客

< 1/3 >

统计信息

已有35人来访过

  • 芯积分:127
  • 好友:1
  • 主题:16
  • 回复:54

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言