该系列文章阅读顺序:
- linux驱动-设备驱动模型(属性文件 kobject )
- linux驱动-设备驱动模型(kset)
- linux驱动-设备驱动模型(bus总线)
- linux驱动-设备驱动模型(device设备)
- linux驱动-设备驱动模型(driver驱动)
- linux驱动-设备驱动模型(class类)
- 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 种,按照优先级如下:
- 如果设置了driver_override,则匹配和driver_override相同名字的设备,它的优先级最高
- 使用设备树方式匹配,这是目前比较常用的方式之一
- 电源相关方式匹配
- 如果设置了id_table, 则与id_table中的名字进行匹配
- 最后比较驱动和设备的名称,也是比较常用的方式之一
其中比较常用的是设备树和设备驱动名称进行匹配,下面详细分析一下设备树匹配流程。
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