1. device_node tree到device tree

device_node的层次结构建立之后,并没有struct device生成,那我们实现的驱动probe函数又是如何获取设备的呢? 这就需要有一个从device_node创建struct device的过程,这个过程并没有一个统一的函数来实现,而是各个总线的在初始化时,主动扫描设备树,根据device_node创建自己需要的设备。下边以platform总线为例说明。

dt_to_dt

of_platform_default_populate_init通过arch_initcall_sync加入到内核的 .init段,在内核初始化时被调用。

1.1. of_device_alloc

of_platform_device_create_pdata调用of_device_alloc来获取新建的 struct platform_device,这里先分析of_device_alloc。

of_device_alloc主要完成如下工作:

  1. 调用platform_device_alloc分配 struct platform_device
  2. 填充platform_device的resource数组,这是dts里为设备指定的资源
  3. device的of_node指向device_node,fwnode指向device_node的fwnode
  4. 默认父设备为platform_bus,platform_bus下可以挂普通设备和总线设备,如果是挂在二级总线下的设备,parent不为platform_bus。后续会根据parent来形成树形结构。
 1/// drivers/of/platform.c
 2/**
 3 * of_device_alloc - Allocate and initialize an of_device
 4 * @np: device node to assign to device
 5 * @bus_id: Name to assign to the device.  May be null to use default name.
 6 * @parent: Parent device.
 7 */
 8struct platform_device *of_device_alloc(struct device_node *np,
 9                  const char *bus_id,
10                  struct device *parent)
11{
12    struct platform_device *dev;
13    int rc, i, num_reg = 0;
14    struct resource *res, temp_res;
15
16    dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
17    if (!dev)
18        return NULL;
19
20    /* count the io resources */
21    while (of_address_to_resource(np, num_reg, &temp_res) == 0)
22        num_reg++;
23
24    /* Populate the resource table */
25    if (num_reg) {
26        res = kcalloc(num_reg, sizeof(*res), GFP_KERNEL);
27        if (!res) {
28            platform_device_put(dev);
29            return NULL;
30        }
31
32        dev->num_resources = num_reg;
33        dev->resource = res;
34        for (i = 0; i < num_reg; i++, res++) {
35            rc = of_address_to_resource(np, i, res);
36            WARN_ON(rc);
37        }
38    }
39
40    /// device的of_node指向device_node,fwnode指向device_node的fwnode
41    dev->dev.of_node = of_node_get(np);
42    dev->dev.fwnode = &np->fwnode;
43    dev->dev.parent = parent ? : &platform_bus;
44
45    if (bus_id)
46        dev_set_name(&dev->dev, "%s", bus_id);
47    else
48        of_device_make_bus_id(&dev->dev);
49
50    return dev;
51}
52EXPORT_SYMBOL(of_device_alloc);

of_device_alloc之后,device和device_node的关系图如下: dt_to_dt_struct

1.2. of_platform_device_create_pdata

of_platform_device_create_pdata主要完成如下工作:

  1. of_device_alloc分配platform_device
  2. 将bus设置为platform_bus_type
  3. 通过of_device_add调用device_add将设备添加到系统,device_add会关联parent和device,建立完整的树状结构
 1/**
 2 * of_platform_device_create_pdata - Alloc, initialize and register an of_device
 3 * @np: pointer to node to create device for
 4 * @bus_id: name to assign device
 5 * @platform_data: pointer to populate platform_data pointer with
 6 * @parent: Linux device model parent device.
 7 *
 8 * Return: Pointer to created platform device, or NULL if a device was not
 9 * registered.  Unavailable devices will not get registered.
10 */
11static struct platform_device *of_platform_device_create_pdata(
12                    struct device_node *np,
13                    const char *bus_id,
14                    void *platform_data,
15                    struct device *parent)
16{
17    struct platform_device *dev;
18
19    if (!of_device_is_available(np) ||
20        of_node_test_and_set_flag(np, OF_POPULATED))
21        return NULL;
22
23    dev = of_device_alloc(np, bus_id, parent);
24    if (!dev)
25        goto err_clear_flag;
26
27    dev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
28    if (!dev->dev.dma_mask)
29        dev->dev.dma_mask = &dev->dev.coherent_dma_mask;
30    dev->dev.bus = &platform_bus_type;
31    dev->dev.platform_data = platform_data;
32    of_msi_configure(&dev->dev, dev->dev.of_node);
33
34    if (of_device_add(dev) != 0) {
35        platform_device_put(dev);
36        goto err_clear_flag;
37    }
38
39    return dev;
40
41err_clear_flag:
42    of_node_clear_flag(np, OF_POPULATED);
43    return NULL;
44}

1.3. 添加platform_device的另一种方式

值得一提的是,除了platform设备除了在初始化时通过解析设备树添加,还可以通过如下函数手动添加:

1/// include/linux/platform_device.h
2platform_device_register_full
3platform_device_register_resndata
4platform_device_register_simple
5platform_device_register_data
6platform_device_add

比如如下代码:

 1#include <linux/module.h>
 2#include <linux/platform_device.h>
 3
 4/// ... ...
 5
 6static struct platform_driver pdrv = {
 7    .probe      = pdev_probe,
 8    .remove     = pdev_remove,
 9    .driver     = {
10        .name   = "pdev-demo",
11    },
12};
13
14static struct platform_device *pdev;
15
16static int __init pdev_init(void)
17{
18    int ret;
19
20    pdev = platform_device_register_simple("pdev-demo", 0, NULL, 0);
21    if (IS_ERR(pdev)) {
22        return PTR_ERR(pdev);
23    }
24
25    ret = platform_driver_register(&pdrv);
26
27    return ret;
28}
29
30static void __exit pdev_exit(void)
31{
32    platform_driver_unregister(&pdrv);
33    platform_device_unregister(pdev);
34}
35
36module_init(pdev_init);
37module_exit(pdev_exit);

2. 设备和驱动的匹配:of_match_table

设备和驱动的匹配是靠总线的match函数来实现的,如果驱动要匹配来自设备树的设备,需要调用of_driver_match_device,比如platform总线的match函数platform_match,内部就直接调用了of_driver_match_device。当然,除了匹配设备树,还可以匹配其他信息,比如设备名称和驱动名称。

 1/// include/linux/of_device.h
 2/**
 3 * of_driver_match_device - Tell if a driver's of_match_table matches a device.
 4 * @drv: the device_driver structure to test
 5 * @dev: the device structure to match against
 6 */
 7static inline int of_driver_match_device(struct device *dev,
 8                     const struct device_driver *drv)
 9{
10    return of_match_device(drv->of_match_table, dev) != NULL;
11}

3. 其他总线上的device_node tree到device tree

可以通过 git grep -Iw -p --heading of_driver_match_device drivers/来搜索会从设备树中匹配设备的总线,要调用这些总线的match函数,都需要提前将device_node tree为device tree,这个转换不一定是像platform总线在系统初始化时完成。比如i2c总线,这个转换是在注册i2c_adapter时,通过i2c_register_adapter调用of_i2c_register_devices来完成的。当然,注册的设备也不再是 struct platform_device,而是 struct i2c_client。至于其他总线,可以自行分析。

 1drivers/base/platform.c
 21331=static int platform_match(struct device *dev, struct device_driver *drv)
 31341:   if (of_driver_match_device(dev, drv))
 4drivers/bus/moxtet.c
 586=static int moxtet_match(struct device *dev, struct device_driver *drv)
 692:     if (of_driver_match_device(dev, drv))
 7drivers/bus/sunxi-rsb.c
 8133=static int sunxi_rsb_device_match(struct device *dev, struct device_driver *drv)
 9135:    return of_driver_match_device(dev, drv);
10drivers/gpu/drm/drm_mipi_dsi.c
1150=static int mipi_dsi_device_match(struct device *dev, struct device_driver *drv)
1255:     if (of_driver_match_device(dev, drv))
13drivers/hsi/hsi_core.c
1440=static int hsi_bus_match(struct device *dev, struct device_driver *driver)
1542:     if (of_driver_match_device(dev, driver))
16drivers/net/phy/mdio_bus.c
17979=static int mdio_bus_match(struct device *dev, struct device_driver *drv)
18989:    if (of_driver_match_device(dev, drv))
19drivers/rpmsg/rpmsg_core.c
20475=static int rpmsg_dev_match(struct device *dev, struct device_driver *drv)
21492:    return of_driver_match_device(dev, drv);
22drivers/slimbus/core.c
2333=static int slim_device_match(struct device *dev, struct device_driver *drv)
2439:     if (of_driver_match_device(dev, drv))
25drivers/soc/qcom/apr.c
26341=static int apr_device_match(struct device *dev, struct device_driver *drv)
27348:    if (of_driver_match_device(dev, drv))
28drivers/spi/spi.c
29363=static int spi_match_device(struct device *dev, struct device_driver *drv)
30373:    if (of_driver_match_device(dev, drv))
31drivers/spmi/spmi.c
3246=static int spmi_device_match(struct device *dev, struct device_driver *drv)
3348:     if (of_driver_match_device(dev, drv))
34drivers/tty/serdev/core.c
3586=static int serdev_device_match(struct device *dev, struct device_driver *drv)
3695:     return of_driver_match_device(dev, drv);
37drivers/usb/common/ulpi.c
3837=static int ulpi_match(struct device *dev, struct device_driver *driver)
3948:             return of_driver_match_device(dev, driver);

4. linux acpi如何识别设备

在Linux系统中,ACPI(Advanced Configuration and Power Interface)是一种用于操作系统与硬件平台之间进行电源管理和配置的标准接口。ACPI定义了如何在操作系统中枚举、配置和管理硬件设备,包括电源管理设备、事件驱动设备等。

对于通用硬件错误源(Generic Hardware Error Source,GHES)设备的识别,Linux内核通过ACPI表(如HEST,Hardware Error Source Table)来进行。HEST表描述了硬件错误源的信息,包括错误类型、错误状态等。Linux内核中的ACPI子系统会解析HEST表,并据此创建相应的错误处理设备。

具体来说,Linux内核中的ACPI子系统通过以下步骤识别GHES设备:

解析HEST表:在系统启动时,内核会解析HEST表,获取硬件错误源的信息。HEST表通常包含多个错误源描述,每个描述对应一个GHES设备。

创建错误处理设备:对于HEST表中的每个GHES描述,内核会创建一个对应的错误处理设备。这些设备通常是platform_device类型的设备,表示一个通用的硬件错误源。

注册错误处理设备:创建的设备会被注册到设备系统中,以便用户空间程序可以通过设备文件(如/dev/mem)或设备驱动来访问和控制这些设备。

处理错误事件:一旦GHES设备检测到硬件错误事件,它会将事件信息通知给内核的错误处理子系统。内核会根据事件的类型和严重程度,采取相应的错误处理措施,如记录错误信息、重置设备、触发系统崩溃等。

需要注意的是,具体的设备识别和错误处理过程可能会因硬件平台、操作系统版本和内核配置的不同而有所差异。因此,在实际应用中,建议参考相关的硬件文档、操作系统文档和内核源代码,以获取更详细和准确的信息。

 1/// linux-6.6/drivers/acpi/apei/hest.c
 2static int __init hest_parse_ghes(struct acpi_hest_header *hest_hdr, void *data)
 3{
 4	struct platform_device *ghes_dev;
 5	struct ghes_arr *ghes_arr = data;
 6	int rc, i;
 7
 8	if (!is_generic_error(hest_hdr))
 9		return 0;
10
11	if (!((struct acpi_hest_generic *)hest_hdr)->enabled)
12		return 0;
13	for (i = 0; i < ghes_arr->count; i++) {
14		struct acpi_hest_header *hdr;
15		ghes_dev = ghes_arr->ghes_devs[i];
16		hdr = *(struct acpi_hest_header **)ghes_dev->dev.platform_data;
17		if (hdr->source_id == hest_hdr->source_id) {
18			pr_warn(FW_WARN HEST_PFX "Duplicated hardware error source ID: %d.\n",
19				hdr->source_id);
20			return -EIO;
21		}
22	}
23	ghes_dev = platform_device_alloc("GHES", hest_hdr->source_id);
24	if (!ghes_dev)
25		return -ENOMEM;
26
27	rc = platform_device_add_data(ghes_dev, &hest_hdr, sizeof(void *));
28	if (rc)
29		goto err;
30
31	rc = platform_device_add(ghes_dev);
32	if (rc)
33		goto err;
34	ghes_arr->ghes_devs[ghes_arr->count++] = ghes_dev;
35
36	return 0;
37err:
38	platform_device_put(ghes_dev);
39	return rc;
40}