linux驱动-设备驱动模型(platform设备)

文章介绍了Linux内核中的platform设备模型,包括platform_device和platform_driver的数据结构,以及如何通过设备树创建和匹配设备。还详细阐述了设备注册过程,如platform_device_register和platform_driver_register函数,以及设备和驱动的匹配策略,如driver_override、设备树匹配和id_table匹配。最后,文章讨论了如何通过设备树生成platform设备,并解析了内核如何处理设备树中的节点来创建平台设备。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

该系列文章阅读顺序:

  1. linux驱动-设备驱动模型(属性文件 kobject )
  2. linux驱动-设备驱动模型(kset)
  3. linux驱动-设备驱动模型(bus总线)
  4. linux驱动-设备驱动模型(device设备)
  5. linux驱动-设备驱动模型(driver驱动)
  6. linux驱动-设备驱动模型(class类)
  7. linux驱动-设备驱动模型(platform设备)

在计算机中有这样一类设备,它们通过各自的设备控制器,直接和 CPU 连接,CPU 可以通过常规的寻址操作访问它们(或者说访问它们的控制器)。这种连接方式,并不属于传统意义上的总线连接。但设备模型应该具备普适性,因此 Linux 就虚构了一条 Platform Bus ,供这些设备挂靠。

作者: baron

1、数据结构

1) platform_device

struct platform_device {
    const char  *name;         //设备名称
    int     id;
    bool        id_auto;
    struct device   dev;       // 真正的设备,嵌入在platform_device中
    u32     num_resources;     // 设备资源数量
    struct resource *resource; //

    const struct platform_device_id *id_entry;
    char *driver_override;     // 如果设置了这个名字,这用这个名字匹配驱动,它的优先级最高

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;
};

2) platform_driver

struct platform_driver {
    int (*probe)(struct platform_device *);
    int (*remove)(struct platform_device *);
    void (*shutdown)(struct platform_device *);
    int (*suspend)(struct platform_device *, pm_message_t state);
    int (*resume)(struct platform_device *);
    struct device_driver driver;
    const struct platform_device_id *id_table;
    bool prevent_deferred_probe;
};

3) resource

struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    unsigned long desc;
    struct resource *parent, *sibling, *child;
};

4) of_dev_auxdata

struct of_dev_auxdata {
    char *compatible;
    resource_size_t phys_addr;
    char *name;
    void *platform_data;
};

2、platform 总线的构建

platform 总线是内核提供的虚拟总线,它个构建依赖于前面的,bus,device,driver设备模型。首先内核提供了一个名字叫 “platform” 的默认总线,它是一个全局结构并且被 EXPORT_SYMBOL_GPL 导出,如下

struct bus_type platform_bus_type = {
    .name       = "platform",
    .dev_groups = platform_dev_groups,
    .match      = platform_match,
    .uevent     = platform_uevent,
    .pm     = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);

除此之外内核也提供了该总线下的一个设备,名字叫做 “platform_bus”

struct device platform_bus = {
    .init_name  = "platform",
};
EXPORT_SYMBOL_GPL(platform_bus);

这是一个设备结构,并不是bus,虽然他的名字叫做 platform_bus, 我也不知道为啥叫这个名字,第一次读我就以为这是个bus。它作为基本设备,它注册之后内核将会创建出如下节点

/sys/devices/platform

platform_bus 的结构只初始化了一个名字,为什么要注册一个只有名字的设备在这里,我想是为了方便管理,将它作为以后 platform 设备的父设备,以后只要是 platform 设备,都将出现在 /sys/devices/platform,一眼就能找出那些是 platform 设备。上述的 platform_bus_type 和 platform_bus 是在platform_bus_init 中被注册的,它被driver_init调用,即在内核启动的时候被创建。

int __init platform_bus_init(void)
{
    int error;

    early_platform_cleanup();

    error = device_register(&platform_bus); //创建platform总线
    if (error)
        return error;
    error =  bus_register(&platform_bus_type); //创建一个名为"platform"的设备
    if (error)
        device_unregister(&platform_bus);
    of_platform_register_reconfig_notifier();
    return error;
}

以后但凡是注册在 platform 总线上的设备都叫做 platform 设备,注册在该总线上的驱动叫做 platform 驱动

3、platform 设备接口

内核在开机时创建了 platform 总线,同时也提供了该总线相关操作函数

1) platform_device_register

使用这个函数注册一个 platform 设备

int platform_device_register(struct platform_device *pdev)
{
    int ret;
#ifdef CONFIG_MTPROF
    unsigned long long ts = 0;
#endif
    TIME_LOG_START();
    //对pdev->dev做一些初始化
    device_initialize(&pdev->dev);
    // 空函数,啥也没干
    arch_setup_pdev_archdata(pdev);
    //真正的注册函数
    ret = platform_device_add(pdev);
    TIME_LOG_END();
    bootprof_pdev_register(ts, pdev);
    return ret;
}

真正的注册函数是 platform_device_add

int platform_device_add(struct platform_device *pdev)
{
    int i, ret;

    if (!pdev)
        return -EINVAL;

    if (!pdev->dev.parent)
        pdev->dev.parent = &platform_bus; //设置设备的父设备为platform_bus

    pdev->dev.bus = &platform_bus_type; //设置bus为platform_bus_type

    switch (pdev->id) { 设置 pdev->dev->init_name 
    default:
        dev_set_name(&pdev->dev, "%s.%d", pdev->name,  pdev->id);
        break;
    case PLATFORM_DEVID_NONE:	// -1
        dev_set_name(&pdev->dev, "%s", pdev->name);
        break;
    case PLATFORM_DEVID_AUTO: 	// -2
        /*
         * Automatically allocated device ID. We mark it as such so
         * that we remember it must be freed, and we append a suffix
         * to avoid namespace collision with explicit IDs.
         */
        ret = ida_simple_get(&platform_devid_ida, 0, 0, GFP_KERNEL);
        if (ret < 0)
            goto err_out;
        pdev->id = ret;
        pdev->id_auto = true;
        dev_set_name(&pdev->dev, "%s.%d.auto", pdev->name, pdev->id);
        break;
    }

    for (i = 0; i < pdev->num_resources; i++) {
        struct resource *p, *r = &pdev->resource[i];

        if (r->name == NULL)
            r->name = dev_name(&pdev->dev);

        p = r->parent;
        if (!p) {
            if (resource_type(r) == IORESOURCE_MEM)
                p = &iomem_resource;
            else if (resource_type(r) == IORESOURCE_IO)
                p = &ioport_resource;
        }

        if (p && insert_resource(p, r)) {
            dev_err(&pdev->dev, "failed to claim resource %d\n", i);
            ret = -EBUSY;
            goto failed;
        }
    }

    pr_debug("Registering platform device '%s'. Parent at %s\n",
         dev_name(&pdev->dev), dev_name(pdev->dev.parent));
    
    //向内核注册这个设备
    ret = device_add(&pdev->dev);
    if (ret == 0)
        return ret;

 failed:
    if (pdev->id_auto) {
        ida_simple_remove(&platform_devid_ida, pdev->id);
        pdev->id = PLATFORM_DEVID_AUTO;
    }

    while (--i >= 0) {
        struct resource *r = &pdev->resource[i];
        if (r->parent)
            release_resource(r);
    }

 err_out:
    return ret;
}

该函数设置设备的父设备为platform_bus,以后凡是挂接在 platform 总线上的设备都将使用 platform_bus 作为它的父设备。即所有的设备都将在下面目录生成

/sys/devices/platform/xxx

2) platform_driver_register

同样内核也提供了 platform 驱动的注册函数

#define platform_driver_register(drv) \
    __platform_driver_register(drv, THIS_MODULE)

int __platform_driver_register(struct platform_driver *drv,
                struct module *owner)
{
    drv->driver.owner = owner;
    drv->driver.bus = &platform_bus_type;
    drv->driver.probe = platform_drv_probe;
    drv->driver.remove = platform_drv_remove;
    drv->driver.shutdown = platform_drv_shutdown;

    //将驱动注册进总线
    return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__platform_driver_register);
  • 1, 可以看出 drv 的,probe,rmove 函数被分别初始化为 platform_drv_probe,platform_drv_remove。最后调用 driver_register 将驱动注册进总线

  • 2, 对于 platform 设备的注册最后会调用,device_add,它最终会遍历platform_bus_type上所有的 drv,并对每一个 drv 调用 platform_match函数。

  • 3, 而对于platform_driver 的注册会调用 driver_register,它最终会遍历 platform_bus_type 上左右的d ev,对每一个dev都调用platform_match 函数。

像这种交叉遍历的方式在内核中很常见,input子系统中也使用这样的方式。也就是无论如何只要总线上有设备,驱动注册的时候就会去与之匹配,同理总线上有驱动,设备注册时就会去与之匹配。

static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    /* when driver_override is set, only bind to the matching driver */
	/* 如果设置了driver_override,则匹配和driver_override相同名字的驱动 */
    if (pdev->driver_override)
        return !strcmp(pdev->driver_override, drv->name);

    /* attempt an of style match first,使用设备树方式匹配 */
    if (of_driver_match_device(dev, drv))
        return 1;

    /* then try acpi style match */
	/* 电源相关,跳过 */
    if (acpi_driver_match_device(dev, drv))
        return 1;

    /* then try to match against the id table */
	/* 如果设置了id_table, 则与id_table中的名字进行匹配 */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != null;

    /* fall-back to driver name match */
	/* 比较驱动和设备的名称 */
    return (strcmp(pdev->name, drv->name) == 0);
}

从这个函数可以看出 platform 设备的匹配方式有 5 种,按照优先级如下:

  1. 如果设置了driver_override,则匹配和driver_override相同名字的设备,它的优先级最高
  2. 使用设备树方式匹配,这是目前比较常用的方式之一
  3. 电源相关方式匹配
  4. 如果设置了id_table, 则与id_table中的名字进行匹配
  5. 最后比较驱动和设备的名称,也是比较常用的方式之一

其中比较常用的是设备树和设备驱动名称进行匹配,下面详细分析一下设备树匹配流程。

static inline int of_driver_match_device(struct device *dev, const struct device_driver *drv)
{
    return of_match_device(drv->of_match_table, dev) != NULL;
}
const struct of_device_id *of_match_device(const struct of_device_id *matches, const struct device *dev)
{
    if ((!matches) || (!dev->of_node))
        return NULL;
    return of_match_node(matches, dev->of_node);
}
static const struct of_device_id *__of_match_node(const struct of_device_id *matches,
                       const struct device_node *node)
{
    const struct of_device_id *best_match = NULL;
    int score, best_score = 0;

    if (!matches)
        return NULL;

    for (; matches->name[0] || matches->type[0] || matches->compatible[0]; matches++) {
        score = __of_device_is_compatible(node, matches->compatible,
                          matches->type, matches->name);
        if (score > best_score) {
            best_match = matches;
            best_score = score;
        }
    }

    return best_match;
}
static int __of_device_is_compatible(const struct device_node *device,
                     const char *compat, const char *type, const char *name)
{
    struct property *prop;
    const char *cp;
    int index = 0, score = 0;

    /* Compatible match has highest priority */
    if (compat && compat[0]) {
        prop = __of_find_property(device, "compatible", NULL); /* 查找 compatible 节点 */
        for (cp = of_prop_next_string(prop, NULL); cp;
			//获取该节点的字符串
             cp = of_prop_next_string(prop, cp), index++)  
			{
			//获得的字符串和compat进行比较
            if (of_compat_cmp(cp, compat, strlen(compat)) == 0) {
                score = INT_MAX/2 - (index << 2);
                break;
            }
        }
        if (!score)
            return 0;
    }

    /* Matching type is better than matching name */
    if (type && type[0]) {
        if (!device->type || of_node_cmp(type, device->type))
            return 0;
        score += 2;
    }

    /* Matching name is a bit better than not */
    if (name && name[0]) {
        if (!device->name || of_node_cmp(name, device->name))
            return 0;
        score++;
    }

    return score;
}

再来看看 of_device_id 这个结构

struct of_device_id {
    char    name[32];
    char    type[32];
    char    compatible[128];
    const void *data;
};

我们用到的是 compatible 作为匹配的对象,从结构看最对大支持的长度为128个字节。从上述代码可以看出匹配的过程就是匹配 drv.id->compatible 和 dts中的 compatible 节点比较,例如: hall 的 dts 的节点如下

hall: hall{
    compatible = "mediatek,hall-gpio-int";
};

在驱动中如下配置

#ifdef CONFIG_OF
//创建一个 of_device_id 数组并初始化内部成员。
static const struct of_device_id hall_switch_of_match[] = {
    {.compatible = "mediatek,hall-gpio-int"},
    {},
};
#endif

//创建一个platform_driver结构并对里面的driver结构进行初始化
static struct platform_driver hall_driver = {
    .probe = hall_probe,
    .suspend = hall_suspend,
    .resume  = hall_resume,
    .remove = ln4913_remove,
    .driver = {
           .name = "ln4913_Driver",
           .of_match_table = hall_switch_of_match,
           },
};

从这里可以看出匹配其实是会遍历 hall_switch_of_match 数组中的 compatible 描述,也就是说一个驱动可以尝试匹配多个设备,直到匹配到为止。由前面的分析可知一旦匹配成功,就会调用 really_probe 函数

---> really_probe
     ---> dev->bus->probe(dev) //默认点调用这个,明显platform_bus_type,没有设置prob函数
     ---> drv->probe(dev) //如果没有设置 dev->bus->probe 函数,则调用这函数,platform 驱动在注册的时候呢,将其初始化为platform_drv_probe                                                                                                 

来看看 platform 总线提供的 probe 函数

static int platform_drv_probe(struct device *_dev)
{
    struct platform_driver *drv = to_platform_driver(_dev->driver);
    struct platform_device *dev = to_platform_device(_dev);
    int ret;

    ret = of_clk_set_defaults(_dev->of_node, false);
    if (ret < 0)
        return ret;

    ret = dev_pm_domain_attach(_dev, true);
    if (ret != -EPROBE_DEFER) {
        if (drv->probe) {
            ret = drv->probe(dev); //最后调用platform_driver结构中的probe函数
            if (ret)
                dev_pm_domain_detach(_dev, true);
        } else {
            /* don't fail if just dev_pm_domain_attach failed */
            ret = 0;
        }
    }

    if (drv->prevent_deferred_probe && ret == -EPROBE_DEFER) {
        dev_warn(_dev, "probe deferral not supported\n");
        ret = -ENXIO;
    }

    return ret;
}

可以看到其实最终调用了platform_driver结构中的probe函数。除此之外匹配id也是常用的方式,匹配代码如下

static const struct platform_device_id *platform_match_id(
            const struct platform_device_id *id,
            struct platform_device *pdev)
{
    while (id->name[0]) {
        if (strcmp(pdev->name, id->name) == 0) {
            pdev->id_entry = id;
            return id;
        }
        id++;
    }
    return NULL;
}

4、创建自己的 platform 设备

1) 用名字进行匹配

注册platform_device

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/platform_device.h>

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

struct platform_device  my_platform_dev = {
    .name = "my_platform", //名字要和驱动的名字一样
};

static int my_platform_dev_init(void)
{
    platform_device_register(&my_platform_dev);
    return 0;
}

static void my_platform_dev_exit(void)
{
    platform_device_register(&my_platform_dev);
}

module_init(my_platform_dev_init);
module_exit(my_platform_dev_exit);

注册platform_driver

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/platform_device.h>

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

int my_platform_probe(struct platform_device *pdev)
{
    printk("my_platform_probe\n");
    return 0;
}


int my_platform_remove(struct platform_device *pdev)
{
    printk("my_platform_remove\n");
    return 0;
}

struct platform_driver my_platform_driver = {
    .probe = my_platform_probe,
    .remove = my_platform_remove,
    .driver = {
                .name = "my_platform", //名字要和device一样
              },
};

static int my_platform_drv_init(void)
{
    platform_driver_register(&my_platform_driver);
    return 0;
}

static void my_platform_drv_exit(void)
{
    platform_driver_unregister(&my_platform_driver);
}

module_init(my_platform_drv_init);
module_exit(my_platform_drv_exit);

验证结果

tb8768p1_64_bsp:/cache # insmod my_platform_drive.ko
tb8768p1_64_bsp:/cache # insmod my_platform_device.ko

//内核打出my_platform_probe说明匹配成功
[ 2111.422598] <6>.(5)[3265:insmod]my_platform_probe

2) 用设备树方式匹配

dts增加配置

    my_platform_dts: my_platform_dts {
        compatible = "mediatek,my_platform";
    };

注册platform_driver

#include <linux/device.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/stat.h>
#include <linux/platform_device.h>

MODULE_AUTHOR("baron");
MODULE_LICENSE("GPL");

int my_platform_probe(struct platform_device *pdev)
{
    printk("my_platform_probe\n");
    return 0;
}


int my_platform_remove(struct platform_device *pdev)
{
    printk("my_platform_remove\n");
    return 0;
}

#ifdef CONFIG_OF
static const struct of_device_id my_platform_match[] = {
    {.compatible = "mediatek,my_platform"},
    {},
};
#endif

struct platform_driver my_platform_driver = {
    .probe = my_platform_probe,
    .remove = my_platform_remove,
    .driver = {
                .name = "my_platform",
#ifdef CONFIG_OF
                .of_match_table = my_platform_match,
#endif
              },
};

static int my_platform_drv_init(void)
{
{
    platform_driver_register(&my_platform_driver);
    return 0;
}

static void my_platform_drv_exit(void)
{
    platform_driver_unregister(&my_platform_driver);
}

module_init(my_platform_drv_init);
module_exit(my_platform_drv_exit);

验证结果

XF-X2:/cache # insmod my_platform_drive.ko 

//加载模块时内核打印出probe
[ 1018.455974] <4>.(7)[3165:insmod]my_platform_probe

5、dts如何生成 platform设备

对于当前的内核,我们一般不会主动去创建一个 platform 设备,我们往往通过设备树的方式添加 platform 设备。 例如 i2c 设备的 dts 如下

/ {
    i2c0: i2c@11007000 {
        compatible = "mediatek,i2c";
        id = <0>;
        reg = <0 0x11007000 0 0x1000>,
            <0 0x11000080 0 0x80>;
        interrupts = <GIC_SPI 81 IRQ_TYPE_LEVEL_LOW>;
        clocks = <&infracfg_ao INFRACFG_AO_I2C0_CG>,
             <&infracfg_ao INFRACFG_AO_AP_DMA_CG>;
        clock-names = "main", "dma";
        clock-div = <5>;
        mediatek,hs_only;
        mediatek,skip_scp_sema;
    };
};

该 dts 将会在内核中被解析为一个设备名为 11007000.i2c 的 platform 设备, 解析的规则是什么,跟着源码看一下

1) of_platform_default_populate_init

static int __init of_platform_default_populate_init(void)
{
    struct device_node *node;
	
	......
    /* Populate everything else. */
    of_platform_default_populate(NULL, NULL, NULL); //解析设备树并创建对应的 platform 设备
	......

    return 0;
}
arch_initcall_sync(of_platform_default_populate_init);

2) of_platform_default_populate

//这里对传入的参数进行注释
//root = NULL
//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 */
 * };
 *
 */
//lookup = NULL
//parent = 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);
}

3) 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 ? 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) { //对根节点下的每一个二级节点调用 of_platform_bus_create
        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;
}

4) of_platform_bus_create

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;

    /* Make sure it has a compatible property */
    if (strict && (!of_get_property(bus, "compatible", NULL))) { //检测当前节点是否有 compatible 属性
        pr_debug("%s() - skipping %pOF, no compatible prop\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); // lookup 为 NULL 这里返回 NULL
    if (auxdata) {
        bus_id = auxdata->name;
        platform_data = auxdata->platform_data;
    }

    if (of_device_is_compatible(bus, "arm,primecell")) { //从注释可以看出这里只是为了兼容老的设备树文件
        /*
         * Don't return an error here to keep compatibility with older
         * device tree files.
         */
        of_amba_device_create(bus, bus_id, platform_data, parent);
        return 0;
    }

	//真正的 platform 设备创建函数
    dev = of_platform_device_create_pdata(bus, bus_id, platform_data, parent);
	////注意这里会和 dts节点进行匹配,如果匹配不上则会直接返回,因此一般情况下,是不会注册三级节点为设备节点
    if (!dev || !of_match_node(matches, bus)) 
        return 0;

    for_each_child_of_node(bus, child) { //遍历bus的子节点回调 of_platform_bus_create 创建对应的 platform 设备
        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;
}

5) 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;

    //检测 dts 中的 status 属性是否为ture,默认为ture
    //检测 OF_POPULATED 防止重复注册
    if (!of_device_is_available(np) ||
        of_node_test_and_set_flag(np, OF_POPULATED))
        return NULL;

    // 动态创建一个 platform_device 并做简单初始化
    // 检测该设备的 resources 并做相应的初始化
    // 初始化设备的设备节点,如果该设备节点不存在 reg 属性则使用 np->parent 作为其父设备
    // 如果有设备节点有 reg 属性则使用 "addr.node->name" 作为该设备的名字,否则使用 "node->full_name" 作为设备名
    dev = of_device_alloc(np, bus_id, parent);
    if (!dev)
        goto err_clear_flag;

    dev->dev.bus = &platform_bus_type; 	             //初始化总线类型
    dev->dev.platform_data = platform_data;          //初始化私有数据这里为NULL
    of_msi_configure(&dev->dev, dev->dev.of_node);   

    if (of_device_add(dev) != 0) { //注册 platform 设备
        platform_device_put(dev);
        goto err_clear_flag;
    }

    return dev;

err_clear_flag:
    of_node_clear_flag(np, OF_POPULATED);
    return NULL;
}

6) of_device_alloc

// 动态创建一个 platform_device 并做简单初始化
// 检测该设备的 resources 并做相应的初始化
// 初始化设备的设备节点,如果该设备节点不存在 reg 属性则使用 np->parent 作为其父设备
// 如果有设备节点有 reg 属性则使用 "addr.node->name" 作为该设备的名字,否则使用 "node->full_name" 作为设备名
struct platform_device *of_device_alloc(struct device_node *np,
                  const char *bus_id,
                  struct device *parent)
{
    struct platform_device *dev;
    int rc, i, num_reg = 0, num_irq;
    struct resource *res, temp_res;

	//动态创建一个 platform_device 并做简单初始化
    dev = platform_device_alloc("", PLATFORM_DEVID_NONE);
    if (!dev)
        return NULL;

    /* count the io and irq resources */
	//检测该设备的 resources 并做相应的初始化
    while (of_address_to_resource(np, num_reg, &temp_res) == 0)
        num_reg++;
    num_irq = of_irq_count(np);

    /* Populate the resource table */
    if (num_irq || num_reg) {
        res = kzalloc(sizeof(*res) * (num_irq + num_reg), GFP_KERNEL);
        if (!res) {
            platform_device_put(dev);
            return NULL;
        }

        dev->num_resources = num_reg + num_irq;
        dev->resource = res;
        for (i = 0; i < num_reg; i++, res++) {
            rc = of_address_to_resource(np, i, res);
            WARN_ON(rc);
        }
        if (of_irq_to_resource_table(np, res, num_irq) != num_irq)
            pr_debug("not all legacy IRQ resources mapped for %s\n",
                 np->name);
    }

    dev->dev.of_node = of_node_get(np);           // 初始化并增加设备节点引用计数
    dev->dev.fwnode = &np->fwnode;                
    
    // 如果没有父设备则使用 platform_bus 作为父设备,前面整个过程都没有设置父设备
    // 因此 dts 解析出来的设备将全部位于 /sys/devices/platform/ 下
    dev->dev.parent = parent ? : &platform_bus;   

    if (bus_id) //bus_id = NULL 因此调用 of_device_make_bus_id 设置设备名
        dev_set_name(&dev->dev, "%s", bus_id);
    else
        of_device_make_bus_id(&dev->dev);

    return dev;
}
EXPORT_SYMBOL(of_device_alloc);

7) of_device_make_bus_id

// 如果有设备节点有 reg 属性则使用 "addr.node->name" 作为该设备的名字
// 如果设备节点没有 reg 属性则使用 node->full_name 作为设备名,同时设置 node = node->parent
static void of_device_make_bus_id(struct device *dev)
{
    struct device_node *node = dev->of_node;
    const __be32 *reg;
    u64 addr;

    /* Construct the name, using parent nodes if necessary to ensure uniqueness */
    while (node->parent) {
        /*
         * If the address can be translated, then that is as much
         * uniqueness as we need. Make it the first component and return
         */
        reg = of_get_property(node, "reg", NULL); //获取 reg 属性
        if (reg && (addr = of_translate_address(node, reg)) != OF_BAD_ADDR) { //将 reg 表示的物理地址解析出来赋值给 addr
            //如果解析成功则使用 "addr.node->name" 作为该设备的名字
            dev_set_name(dev, dev_name(dev) ? "%llx.%s:%s" : "%llx.%s",
                     (unsigned long long)addr, node->name,
                     dev_name(dev));
            return;
        }

        /* format arguments only used if dev_name() resolves to NULL */
        // 如果没有 reg 属性则使用 node->full_name 作为设备名
        dev_set_name(dev, dev_name(dev) ? "%s:%s" : "%s",
                 kbasename(node->full_name), dev_name(dev));
        node = node->parent;
    }
}

8) 总结

  • 只要dts中的节点有 compatible 属性,将会在内核中将该节点转换为 platform 设备,该设备将出现在/sys/devices/platform/
  • 如果有设备节点有 reg 属性则使用addr.node->name作为该设备的名字
  • 如果设备节点没有 reg 属性则使用 node->full_name作为设备名,同时设置node = node->parent
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值