RK3568驱动指南|第七篇-设备树-第64章 device_node转换成platform_device实验

瑞芯微RK3568芯片是一款定位中高端的通用型SOC,采用22nm制程工艺,搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码,支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU,可用于轻量级人工智能应用。RK3568 支持安卓 11 和 linux 系统,主要面向物联网网关、NVR 存储、工控平板、工业检测、工控盒、卡拉 OK、云终端、车载中控等行业。


【公众号】迅为电子

【粉丝群】824412014(加群获取驱动文档+例程)

【视频观看】嵌入式学习之Linux驱动(第七期_设备树_全新升级)_基于RK3568

【购买链接】迅为RK3568开发板瑞芯微Linux安卓鸿蒙ARM核心板人工智能AI主板


第64章 device_node转换成platform_device实验

在上一章中,我们学习了dtb二进制文件展开成device_node的具体流程,而device_node这时候还并不能跟内核中的platform_driver进行对接,而为了让操作系统能够识别和管理设备,需要将设备节点转换为平台设备。

64.1 转换规格

在之前学习的平台总线模型中,device部分是用platform_device结构体来描述硬件资源的,所以内核最终会将内核认识的device_node树转换platform_ device,但是并不是所有的device_node都会被转换成platform_ device,只有满足要求的才会转换成platform_ device,转换成platform_device的节点可以在/sys/bus/platform/devices下查看,那device_node节点要满足什么要求才会被转换成platform_device呢?

根据规则1,首先遍历根节点下包含 compatible 属性的子节点,对于每个子节点,创建一个对应的 platform_device。

根据规则2,遍历包含 compatible 属性为 "simple-bus"、"simple-mfd" 或 "isa" 的节点以及它们的子节点。如果子节点包含 compatible 属性值则会创建一个对应的platform_device。

根据规则3,检查节点的 compatible 属性是否包含 "arm" 或 "primecell"。如果是,则不将该节点转换为 platform_device,而是将其识别为 AMBA 设备。

接下来将通过几个设备树示例对上述规则进行实践。

举例1:

/dts-v1/;

/ {
    model = "This is my devicetree!";
    #address-cells = <1>;
    #size-cells = <1>;

    chosen {
        bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0, 115200";
    };

    cpu1: cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a35", "arm,armv8";
        reg = <0x0 0x1>;
    };

    aliases {
        led1 = "/gpio@22020101";
    };

    node1 {
        #address-cells = <1>;
        #size-cells = <1>;

        gpio@22020102 {
            reg = <0x20220102 0x40>;
        };
    };

    node2 {
        node1-child {
            pinnum = <01234>;
        };
    };

    gpio@22020101 {
        compatible = "led";
        reg = <0x20220101 0x40>;
        status = "okay";
    };
};

在上面的设备树中,总共有chosen、cpu1: cpu@1、aliases、node1、node2、gpio@22020101

这六个节点,其中前五个节点都没有compatible属性,所以并不会被转换为platform_device,而最后一个gpio@22020101节点符合规则一,在根节点下,且有compatible属性,所以最后会转换为platform_device。

举例2:

/dts-v1/;

/ {
    model = "This is my devicetree!";
    #address-cells = <1>;
    #size-cells = <1>;

    chosen {
        bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0, 115200";
    };

    cpu1: cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a35", "arm,armv8";
        reg = <0x0 0x1>;
    };

    aliases {
        led1 = "/gpio@22020101";
    };

    node1 {
        #address-cells = <1>;
        #size-cells = <1>;
		    compatible = "simple-bus";
        gpio@22020102 {
            reg = <0x20220102 0x40>;
        };
    };

    node2 {
        node1-child {
            pinnum = <01234>;
        };
    };

    gpio@22020101 {
        compatible = "led";
        reg = <0x20220101 0x40>;
        status = "okay";
    };
};

相较于示例1的设备树,这里在node1节点中添加了 compatible 属性,但是这个compatible属性值为simple-bus,我们需要继续看他的子节点,子节点 gpio@22020102 并没有compatible属性值,所以这里的node1节点不会被转换。

举例3:

/dts-v1/;

/ {
    model = "This is my devicetree!";
    #address-cells = <1>;
    #size-cells = <1>;

    chosen {
        bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0, 115200";
    };

    cpu1: cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a35", "arm,armv8";
        reg = <0x0 0x1>;
    };

    aliases {
        led1 = "/gpio@22020101";
    };

    node1 {
        #address-cells = <1>;
        #size-cells = <1>;
		    compatible = "simple-bus";
        gpio@22020102 {
			compatible = "gpio";
            reg = <0x20220102 0x40>;
        };
    };

    node2 {
        node1-child {
            pinnum = <01234>;
        };
    };

    gpio@22020101 {
        compatible = "led";
        reg = <0x20220101 0x40>;
        status = "okay";
    };
};

相较于示例2的设备树,这里在node1节点的子节点 gpio@22020102 中添加了 compatible 属性,node1节点的compatible属性值为simple-bus,然后需要继续看他的子节点,子节点 gpio@22020102 的compatible属性值为gpio,所以这里的gpio@22020102节点会被转换成platform_device。

举例4:

/dts-v1/;

/ {
    model = "This is my devicetree!";
    #address-cells = <1>;
    #size-cells = <1>;

    chosen {
        bootargs = "root=/dev/nfs rw nfsroot=192.168.1.1 console=ttyS0,115200";
    };

    cpul: cpu@1 {
        device_type = "cpu";
        compatible = "arm,cortex-a35", "arm,armv8";
        reg = <0x0 0x1>;

        amba {
            compatible = "simple-bus";
            #address-cells = <2>;
            #size-cells = <2>;
            ranges;

            dmac_peri: dma-controller@ff250000 {
                compatible = "arm,p1330", "arm,primecell";
                reg = <0x0 0xff250000 0x0 0x4000>;
                interrupts = ,
                             ;
                #dma-cells = <1>;
                arm,pl330-broken-no-flushp;
                arm,p1330-periph-burst;
                clocks = <&cru ACLK DMAC_PERI>;
                clock-names = "apb_pclk";
            };

            dmac_bus: dma-controller@ff600000 {
                compatible = "arm,p1330", "arm,primecell";
                reg = <0x0 0xff600000 0x0 0x4000>;
                interrupts = ,
                             ;
                #dma-cells = <1>;
                arm,pl330-broken-no-flushp;
                arm,pl330-periph-burst;
                clocks = <&cru ACLK_DMAC_BUS>;
                clock-names = "apb_pclk";
            };
        };
    };
};

amba 节点的compatible值为simple-bus,不会被转换为 platform_device,而是作为父节点用于组织其他设备,所以需要来查看他的子节点。

dmac_peri: dma-controller@ff250000 节点: 该节点的 compatible 属性包含 "arm,p1330" 和 "arm,primecell",根据规则3,该节点不会被转换为 platform_device,而是被识别为 AMBA 设备。

dmac_bus: dma-controller@ff600000 节点: 该节点的 compatible 属性包含 "arm,p1330" 和 "arm,primecell",根据规则3,该节点不会被转换为 platform_device,而是被识别为 AMBA 设备。

64.2 转换流程源码分析

首先进入到内核源码目录下的“drivers/of/platform.c”文件中,找到第555行,具体内容如下所示:

arch_initcall_sync(of_platform_default_populate_init);

arch_initcall_sync 是 Linux 内核中的一个函数,用于在内核初始化过程中执行架构相关的初始化函数。它属于内核的初始化调用机制,用于确保在系统启动过程中适时地调用特定架构的初始化函数。

在Linux内核的初始化过程中,各个子系统和架构会注册自己的初始化函数。这些初始化函数负责完成特定子系统或架构相关的初始化工作,例如初始化硬件设备、注册中断处理程序、设置内存映射等。而 arch_initcall_sync 函数则用于调用与当前架构相关的初始化函数。

当内核启动时,调用 rest_init() 函数来启动初始化过程。在初始化过程中,arch_initcall_sync 函数会被调用,以确保所有与当前架构相关的初始化函数按照正确的顺序执行。这样可以保证在启动过程中,特定架构相关的初始化工作得到正确地完成。

而of_platform_default_populate_init函数的作用是在内核初始化过程中自动解析设备树,并根据设备树中的设备节点创建对应的 platform_device 结构。它会遍历设备树中的设备节点,并为每个设备节点创建一个对应的 platform_device 结构,然后将其注册到内核中,使得设备驱动程序能够识别和操作这些设备。该函数的具体内容如下所示:

static int __init of_platform_default_populate_init(void)
{
    struct device_node *node;

    // 暂停设备链接供应商同步状态
    device_links_supplier_sync_state_pause();

    // 如果设备树尚未填充,则返回错误码
    if (!of_have_populated_dt())
        return -ENODEV;

    /*
     * 显式处理某些兼容性,因为我们不想为/reserved-memory中的每个具有“compatible”的节点创建platform_device。
     */
    for_each_matching_node(node, reserved_mem_matches)
        of_platform_device_create(node, NULL, NULL);

    // 查找节点 "/firmware"
    node = of_find_node_by_path("/firmware");
    if (node) {
        // 使用该节点进行设备树平台设备的填充
        of_platform_populate(node, NULL, NULL, NULL);
        of_node_put(node);
    }

    // 填充其他设备
    fw_devlink_pause();
    of_platform_default_populate(NULL, NULL, NULL);
    fw_devlink_resume();

    return 0;
}

第6行:暂停设备链接供应商的同步状态,确保设备链接的状态不会在此过程中被改变。

第9行:检查设备树是否已经被填充。如果设备树尚未填充,则返回错误码 -ENODEV。

第16行:遍历设备树中与 reserved_mem_matches 匹配的节点。这些节点是 /reserved-memory 中具有 "compatible" 属性的节点。

第17行:为 /reserved-memory 中匹配的节点创建 platform_device 结构。这些节点不会为每个节点都创建 platform_device,而是根据需要进行显式处理。

第20行:在设备树中查找路径为 "/firmware" 的节点。

第23行:使用找到的节点填充设备树中的平台设备。这些节点可能包含与固件相关的设备。

第28行:暂停固件设备链接,确保在填充其他设备时链接状态不会改变。

第29行:填充设备树中的其他设备。

第30行:恢复固件设备链接。

上诉内容中我们要着重关注的是第29行的of_platform_default_populate(NULL, NULL, NULL)函数,找到该函数的定义之后如下所示:

int of_platform_default_populate(struct device_node *root,
				 const struct of_dev_auxdata *lookup,
				 struct device *parent)
{
	return of_platform_populate(root, of_default_bus_match_table, lookup,
				    parent);
}

该函数的作用是调用 of_platform_populate 函数来填充设备树中的平台设备,并使用默认的设备匹配表 of_default_bus_match_table,设备匹配表内容如下所示:

const struct of_device_id of_default_bus_match_table[] = {
	{ .compatible = "simple-bus", },
	{ .compatible = "simple-mfd", },
	{ .compatible = "isa", },
#ifdef CONFIG_ARM_AMBA
	{ .compatible = "arm,amba-bus", },
#endif /* CONFIG_ARM_AMBA */
	{} /* Empty terminated list */
};

上述的设备匹配表就是我们在第一小节中第2条规则,,函数将自动根据设备树节点的属性匹配相应的设备驱动程序,并填充内核的平台设备列表。接下来找到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 节点的引用计数;否则,在设备树中根据路径查找 root 节点
	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);

	// 暂停设备链接供应商同步状态
	device_links_supplier_sync_state_pause();

	// 遍历 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;
		}
	}

	// 恢复设备链接供应商同步状态
	device_links_supplier_sync_state_resume();

	// 设置 root 节点的 OF_POPULATED_BUS 标志
	of_node_set_flag(root, OF_POPULATED_BUS);

	// 释放 root 节点的引用计数
	of_node_put(root);

	return rc;
}

该函数的具体执行步骤如下:

第10行:检查给定的设备树节点 node 是否为有效节点。如果节点为空,函数将立即返回。

第21行:遍历设备树节点的子节点,查找与平台设备相关的节点。这些节点通常具有 compatible 属性,用于匹配设备驱动程序。

第23行:对于每个找到的平台设备节点,创建一个 platform_device 结构,并根据设备树节点的属性设置该结构的各个字段。

第25行:将创建的 platform_device 添加到内核的平台设备列表中,以便设备驱动程序能够识别和操作这些设备。

接下来对该函数的第23行核心代码of_platform_bus_create(child, matches, lookup, parent, true)函数进行讲解,该函数的具体定义如下所示:

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)
{
	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;
	}

	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;
}

第14行:如果 strict 为真且设备节点 bus 没有兼容性属性,则输出调试信息并返回 0。这个条件判断确保设备节点具有 compatible 属性,因为 compatible 属性用于匹配设备驱动程序,对应我们在上一小节的第1条规则。

第21行:如果设备节点 bus 在被跳过的节点表中,则输出调试信息并返回 0。这个条件判断用于跳过不想创建设备的节点。

第27行:如果设备节点 bus 的 OF_POPULATED_BUS 标志已经设置,则输出调试信息并返回 0。这个条件判断用于避免重复创建已经填充的设备节点。

第34行:使用 lookup 辅助数据结构查找设备节点 bus 的特定配置信息,并将其赋值给变量 bus_id 和 platform_data。这个步骤用于获取设备节点的特定配置信息,以便在创建平台设备时使用,由于这里传入的参数为NULL,所以下面的条件判断并不会被执行。

第39行:如果设备节点 bus 兼容于 "arm,primecell",则调用 of_amba_device_create 函数创建 AMBA 设备,并返回 0,对应我们在上一小节学习的第3条规则。

第47行:调用 of_platform_device_create_pdata函数创建平台设备,并将其赋值给变量 dev。然后,检查设备节点 bus是否与给定的匹配表 `matches` 匹配。如果平台设备创建失败或者设备节点不匹配,那么返回 0。

第51行-第58行:遍历设备节点 bus 的每个子节点 child,并递归调用 of_platform_bus_create 函数来创建子节点的平台设备。

接下来对该函数的第47行 of_platform_device_create_pdata函数进行讲解,该函数的具体定义如下所示:

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;

	/* 设置平台设备的一些属性 */
	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);
	of_reserved_mem_device_init_by_idx(&dev->dev, dev->dev.of_node, 0);

	/* 将平台设备添加到设备模型中 */
	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;

第10行:函数会检查设备节点的可用性,即检查设备树对应节点的status属性。如果设备节点不可用或已经被填充,则直接返回 NULL。

第15行:函数调用 of_device_alloc 分配一个平台设备结构体,并将设备节点指针、设备标识符和父设备指针传递给它。如果分配失败,则跳转到 err_clear_flag 标签处进行错误处理。

第19行,函数设置平台设备的一些属性。它将 coherent_dma_mask 属性设置为 32 位的 DMA 位掩码,并检查 dma_mask 属性是否为 NULL。如果 dma_mask 为 NULL,则将其指向 coherent_dma_mask。然后,函数设置平台设备的总线类型为 platform_bus_type,并将平台数据指针存储在 platform_data 属性中。接着,函数调用 of_msi_configure 和 of_reserved_mem_device_init_by_idx 来配置设备的 MSI 和保留内存信息。

第29行:函数调用 of_device_add 将平台设备添加到设备模型中。如果添加失败,则释放已分配的平台设备,并跳转到 err_clear_flag 标签处进行错误处理。

至此,关于device_node转换成platform_device的具体流程就分析完成了,函数调用流程图如下(图 64-1)所示:

RK3568驱动指南|第七篇-设备树-第64章 device_node转换成platform_device实验_第1张图片


RK3568驱动指南|第七篇-设备树-第64章 device_node转换成platform_device实验_第2张图片

你可能感兴趣的:(RK3568,驱动开发,linux)