4.2.5 根据DTS完成platform设备创建

点击查看系列文章 =》 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系列文章大纲-优快云博客

原创不易,需要大家多多鼓励!您的关注、点赞、收藏就是我的创作动力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值