点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-优快云博客
原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!
4.2.5 根据DTS完成platform设备创建
4.2.5.1 初始化入口及概述
上一章节所述timer设备,为系统提供时钟,所以需要提前初始化。接下来展开DTS中定义的其它设备的初始化过程。默认都会创建struct platform_device,但是也有特例会创建struct amba_device。承接之前的章节,增加第(4)~(6)步,依次展开。
在 Linux 设备驱动开发中,许多外设并没有特定的总线(如 PCI、USB 、SPI、I2C等)支持。为了统一管理和简化这些设备的驱动开发,Linux 内核引入了 platform 虚拟总线。platform 总线是一种虚拟的总线模型,用于管理那些没有专用总线的设备。
平台总线:platform 总线是一个虚拟的总线,用于管理那些没有特定总线支持的设备。它提供了一种统一的机制来注册和管理这些设备及其驱动程序。
平台设备:挂接在 platform 总线上的设备称为 platform 设备,由 struct platform_device 结构体描述。platform 设备通常通过设备树(Device Tree)节点或板级支持包(BSP)定义,并在内核启动时被创建和注册到 platform 总线上。
平台驱动:挂接在 platform 总线上的驱动程序称为 platform 驱动,由 struct platform_driver 结构体描述。platform 驱动程序通常包含设备的探测(probe)和移除(remove)函数,以及一个匹配表(of_match_table 或 platform_device_id),用于匹配设备树节点或平台设备。
通过 platform 总线模型,Linux 内核能够以一种统一和灵活的方式管理各种无特定总线支持的设备,从而简化了设备驱动的开发和维护。Platform总线的定义和注册如下图所示:
AMBA(Advanced Microcontroller Bus Architecture)是由 ARM 公司设计的一种标准,用于连接和管理片上系统(SoC)中的各个组件。AMBA 总线提供了一种高效、灵活的通信机制,使得 SoC 中的处理器、外设和其他组件能够相互通信。
在 Linux 内核中,AMBA 总线同样采用了一种虚拟总线模型,用于管理和配置 AMBA 设备。
AMBA 设备:挂接在 AMBA 总线上的设备称为 AMBA 设备,由 struct amba_device 结构体描述。AMBA 设备通常通过设备树节点定义,并在内核启动时被创建和注册到 AMBA 总线上。“arm,primecell” 是一个在设备树(Device Tree, DTS/DTSI)中常用的兼容属性(compatible property),用于标识 ARM PrimeCell 设备。PrimeCell 是 ARM 公司设计的一系列外设 IP 核,这些外设通过 AMBA 总线连接到片上系统(SoC)中。
AMBA 驱动:挂接在 AMBA 总线上的驱动程序称为 AMBA 驱动,由 struct amba_driver 结构体描述。AMBA 驱动程序通常包含设备的探测(probe)和移除(remove)函数,以及一个匹配表(amba_id),用于匹配设备树节点或 AMBA 设备。AMBA 驱动程序通过 amba_driver_register 和 amba_driver_unregister 函数注册和卸载。
Amba总线的定义和注册如下图所示:
4.2.5.2 哪些DTS节点创建设备
扫描的入口函数是of_platform_default_populate_init,它是怎么被调用的呢?利用arch_initcall_sync宏定义。
在 Linux 内核中,initcall 宏用于在启动过程中注册不同的初始化函数。这些宏按优先级分类,确保在内核启动的不同阶段执行特定的初始化任务。数字越小,优先级越高,越早执行。异步的初始化函数会尽早执行,而同步(数字后面加后缀s)的初始化函数会等待所有同级别的异步初始化函数完成后才执行。arch_initcall_sync的优先级是3s,即优先级为3,属于同步初始化。
沿着如下调用关系,走到of_platform_populate,遍历DTS根节点的所有子节点,调用 of_platform_bus_create 创建并注册平台设备。
(of_platform_default_populate_init->of_platform_default_populate->of_platform_populate)
int of_platform_populate( struct device_node *root, // 设备树的根节点 const struct of_device_id *matches, // 匹配表,用于过滤设备树节点 const struct of_dev_auxdata *lookup, // 辅助数据,用于查找额外的设备信息 struct device *parent // 父设备,用于创建新的平台设备 ) { struct device_node *child; int rc = 0; // 获取或查找根节点 root = root ? of_node_get(root) : of_find_node_by_path("/"); if (!root) return -EINVAL; // 打印调试信息 pr_debug("%s()\n", __func__); pr_debug(" starting at: %pOF\n", root); // 遍历根节点的所有子节点 for_each_child_of_node(root, child) { // 创建并注册平台设备 rc = of_platform_bus_create(child, matches, lookup, parent, true); if (rc) { of_node_put(child); // 释放子节点的引用计数 break; // 如果创建失败,跳出循环 } } // 设置根节点的填充标志 of_node_set_flag(root, OF_POPULATED_BUS); // 释放根节点的引用计数 of_node_put(root); // 返回最终的错误码 return rc; } |
of_platform_bus_create函数用于从设备树中创建并注册一个平台设备,并递归地处理其子节点。它的逻辑包括:检查节点属性、跳过特定节点、查找辅助数据、处理 ARM PrimeCell 设备、创建平台设备、递归处理子节点、设置填充标志。
static int of_platform_bus_create( struct device_node *bus, // 当前处理的设备树节点 const struct of_device_id *matches, // 匹配表,用于过滤设备树节点 const struct of_dev_auxdata *lookup, // 辅助数据,用于查找额外的设备信息 struct device *parent, // 父设备,用于创建新的平台设备 bool strict // 是否严格模式,要求必须有 `compatible` 属性 ) { const struct of_dev_auxdata *auxdata; struct device_node *child; struct platform_device *dev; const char *bus_id = NULL; void *platform_data = NULL; int rc = 0; // 检查是否有 compatible 属性(严格模式) if (strict && (!of_get_property(bus, "compatible", NULL))) { pr_debug("%s() - skipping %pOF, no compatible prop\n", __func__, bus); return 0; } // 跳过特定节点 if (unlikely(of_match_node(of_skipped_node_table, bus))) { pr_debug("%s() - skipping %pOF node\n", __func__, bus); return 0; } // 检查是否已填充 if (of_node_check_flag(bus, OF_POPULATED_BUS)) { pr_debug("%s() - skipping %pOF, already populated\n", __func__, bus); return 0; } // 查找辅助数据 auxdata = of_dev_lookup(lookup, bus); if (auxdata) { bus_id = auxdata->name; platform_data = auxdata->platform_data; } // 处理 ARM PrimeCell 设备 if (of_device_is_compatible(bus, "arm,primecell")) { of_amba_device_create(bus, bus_id, platform_data, parent); return 0; } // 创建平台设备 dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent); if (!dev || !of_match_node(matches, bus)) return 0; // 递归处理子节点 for_each_child_of_node(bus, child) { pr_debug(" create child: %pOF\n", child); rc = of_platform_bus_create(child, matches, lookup, &dev->dev, strict); if (rc) { of_node_put(child); // 释放子节点的引用计数 break; // 如果创建失败,跳出循环 } } // 设置填充标志 of_node_set_flag(bus, OF_POPULATED_BUS); // 返回最终的错误码 return rc; } |
- 检查节点属性
检查是否有 compatible 属性,如果没有,则不创建设备节点。
- 处理 ARM PrimeCell 设备
在上一章节已经解释了AMBA总线及AMBA设备。此处遇到了“arm,primecell”,做特殊处理,调用of_amba_device_create创建amba_device并加入amba总线。
- 创建平台设备
调用of_platform_device_create_pdata,创建platfrom device,以及相应的sys下的节点,例如下图的例子。同时,也会把每个platform device的virq和hwirq完成映射,在下一小节展开。
- 递归处理子节点
DTS根节点下的子节点,如果compatible属性与of_default_bus_match_table中的定义匹配,那么就递归调用of_platform_bus_create,扫描下一级子节点,并建立对应的platform设备。
上述of_default_bus_match_table定义的{ .compatible = "arm,amba-bus", }容易让人困惑,因为已经对“arm,primecell”做了特殊处理。查看了一个ARM64的dts,发现有定义“arm,primecell”的节点,但是直接放在了DTS的根节点下,而不是包含在"arm,amba-bus"节点中。所以,因为DTS不规范,导致了内核需要特殊处理。
在/sys/bus/amba中确实有这两个设备:
针对这个递归,反向思考一下,对于DTS根节点下定义的PCIE总线控制器、USB总线控制器、SPI总线控制器、I2C总线控制器等,只会创建出总线控制器对应的platform device,并不会递归扫描其子节点。这些总线控制器的slave设备,由各自的总线控制器子系统自行处理。
4.2.5.3 建立hwirq与irq的映射
of_platform_device_create_pdata会调用of_device_alloc创建platform device,并设置其总线为dev.bus = &platform_bus_type。
static struct platform_device *of_platform_device_create_pdata( struct device_node *np, // 设备树节点 const char *bus_id, // 设备的名称 void *platform_data, // 平台数据 struct device *parent // 父设备 ) { struct platform_device *dev; // 检查设备是否可用和是否已填充 if (!of_device_is_available(np) || of_node_test_and_set_flag(np, OF_POPULATED)) return NULL; // 分配平台设备结构体 dev = of_device_alloc(np, bus_id, parent); if (!dev) goto err_clear_flag; // 设置 DMA 掩码 dev->dev.coherent_dma_mask = DMA_BIT_MASK(32); if (!dev->dev.dma_mask) dev->dev.dma_mask = &dev->dev.coherent_dma_mask; // 设置总线类型和平台数据 dev->dev.bus = &platform_bus_type; dev->dev.platform_data = platform_data; of_msi_configure(&dev->dev, dev->dev.of_node); // 添加设备 if (of_device_add(dev) != 0) { platform_device_put(dev); goto err_clear_flag; } return dev; err_clear_flag: // 清除填充标志 of_node_clear_flag(np, OF_POPULATED); return NULL; } |
沿着of_device_alloc,会发现它会层层调用走到__irq_domain_alloc_irqs。走到这里,逻辑就和4.2.4.3的逻辑一模一样了,不需要重复描述了。
小节一下,到此为止,已经完全搞通搞透了物理中断号hwirq如何转换成Linux中断号virq。接下来将思路转向中断处理函数的注册和处理过程。
点击查看系列文章 =》 Interrupt Pipeline系列文章大纲-优快云博客
原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!