platformbus及其初始化过程分析
platform基本概念
对于usb设备、i2c设备、pci设备、spi设备等等,他们与cpu的通信都是直接挂在相应的总线下面与cpu进行数据交互的,但是在嵌入式系统中,并不是所有的设备都可以归属到这些常见的总线中。所有linux驱动模型为了保持完整性,将这些设备挂载在一条虚拟的总线上,即platform总线,从而避免了有些设备挂载在总线上,而有些设备没有挂载在总线上。
linux_platform_driver 机制和传统的device_driver机制(即:通过 driver_register 函数进行注册)相比,一个十分明显的优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时,通过 platform device提供的标准接口进行申请并使用。
1.1paltform驱动管理和注册机制
设备用struct platform_device表示,驱动用struct platform_driver表示
platform 是一个虚拟的地址总线,相比 PCI、USB,它主要用于描述SOC上的片上资源。platform 所描述的资源有一个共同点:在CPU 的总线上直接取址。平台设备会分到一个名称(用在驱动绑定中)以及一系列诸如地址和中断请求号(IRQ)之类的资源。
Linux platform总线、platform设备、platform驱动之间的关系及框架
(1) 当系统向内核注册每一个驱动程序时,都要通过调用platform_driver_register函数将驱动程序注册到总线(bus),并将其放入所属总线的drv链表中,注册驱动的时候会调用所属总线的match函数寻找该总线上与之匹配的每一个设备,如果找到与之匹配的设备则会调用相应的probe函数将相应的设备和驱动进行绑定;
(2) 当系统向内核注册每一个设备时,可以通过调用platform_device_register函数,也可以通过解析DTB由内核完成platform device的创建,并将设备platform device注册到总线platform bus,并将其放入所属总线的dev链表中,注册设备的时候同样也会调用所属总线的match函数寻找该总线上与之匹配的每一个驱动程序,如果找到与之匹配的驱动程序时会调用相应的probe函数将相应的设备和驱动进行绑定;而这一匹配的过程是由总线自动完成的。
platform bus关键结构体
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30struct bus_type {
const char *name; // 总线名字
struct bus_attribute *bus_attrs; // 该总线的属性
struct device_attribute *dev_attrs; // 该总线下设备的属性
struct driver_attribute *drv_attrs; // 该总线下设备驱动的属性
int (*match)(struct device *dev, struct device_driver *drv); // 该总线下设备与设备驱动的匹配函数
int (*uevent)(struct device *dev, struct kobj_uevent_env *env); // 事件函数 热拨插
int (*probe)(struct device *dev); // 总线下的 探针函数
int (*remove)(struct device *dev); // 总线下的 卸载函数
void (*shutdown)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm; // 电源管理相关的
struct bus_type_private *p; // 总线的私有数据 p->subsys.kobj 表示该总线在驱动模型中对应的对象
};
struct bus_type_private {
struct kset subsys; // 这个是bus主要的kset
struct kset *drivers_kset; // 这个kset指针用来指向该总线的 drivers目录的
struct kset *devices_kset; // 这个kse指针用来指向该总线的devices目录的
struct klist klist_devices; // 用来挂接该总线下的设备的一个链表头
struct klist klist_drivers; // 用来挂接该总线下的设备驱动的一个链表头
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1; // 是否需要在设备驱动注册时候子自动匹配设备
struct bus_type *bus; // 指向本bus结构体
};platform bus初始化流程
platform bus的初始化由函数platfrom_bus_init()函数来完成。由于platform bus本身也是一种设备,因此调用了原始的系统接口device_register()和bus_register()分别完成platform_bus设备和platform_bus_type本身的初始化;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40start_kernel() // init/main.c
>>>rest_init();
>>>pid = kernel_thread(kernel_init, NULL, CLONE_FS);
>>>kernel_init(void *unused)
>>>kernel_init_freeable();
>>>do_basic_setup();
>>> driver_init(); // kernel/drivers/base/init.c
>>>devices_init();
>>>buses_init();
>>>classes_init();
>>>platform_bus_init();
>>>early_platform_cleanup();// 进行一些早期的平台清理
>>>error = device_register(&platform_bus);//注册设备 (在/sys/devices/目录下建立 platform目录对应的设备对象 /sys/devices/platform/)
>>>error = bus_register(&platform_bus_type); // 将Platform bus总线注册进系统
>>>of_platform_register_reconfig_notifier();
>>>do_initcalls();// init/main.c
>>>for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
do_initcall_level(level);
>>> for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
do_one_initcall(*fn);//此处调用相关模块的初始化
其中,
struct device platform_bus = {
.init_name = "platform",
};
static const struct dev_pm_ops platform_dev_pm_ops = {
.runtime_suspend = pm_generic_runtime_suspend,
.runtime_resume = pm_generic_runtime_resume,
USE_PLATFORM_PM_SLEEP_OPS
};
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};c1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32//向系统注册一个设备
int device_register(struct device *dev)
>>>device_initialize(dev)//初始化设备结构体成员
>>>device_add(dev);//通过调用kobject_add()将设备对象添加到系统中,加入到设备链表
//初始化设备结构体成员
void device_initialize(struct device *dev)
{
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
mutex_init(&dev->mutex);
lockdep_set_novalidate_class(&dev->mutex);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_pm_init(dev);
set_dev_node(dev, -1);
INIT_LIST_HEAD(&dev->msi_list);
INIT_LIST_HEAD(&dev->links.consumers);
INIT_LIST_HEAD(&dev->links.suppliers);
dev->links.status = DL_DEV_NO_DRIVER;
}
int device_add(struct device *dev)
>>>dev = get_device(dev);
>>>error = device_private_init(dev);
>>>dev_set_name(dev, "%s", dev->init_name);
>>>parent = get_device(dev->parent);
>>>kobj = get_device_parent(dev, parent);
>>>set_dev_node(dev, dev_to_node(parent));
>>>error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
>>>.....1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74int bus_register(struct bus_type *bus)
{
int retval;
struct bus_type_private *priv; // 定义一个bus_type_private 结构体指针
priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL); // 申请分配内存
if (!priv)
return -ENOMEM;
priv->bus = bus; // 使用 priv->bus 指向我们传进来的bus
bus->p = priv; // 通过 bus->p 指向priv 这里其实就是将bus与priv建立关系,这个跟之前的evice、class的设计是一样的
BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name); // 给我们的bus在设备驱模型中的对象设置名字 bus->p->subsys.kobj
if (retval)
goto out;
// 这里就是对bus的私有数据进行一些填充
priv->subsys.kobj.kset = bus_kset; // 设置bus对象的父对象 也就是 /sys/bus 这目录 作为他的上层目录 所有的具体的总线类型对象都是在这个目录下
priv->subsys.kobj.ktype = &bus_ktype; // 设置bus对象的 对象类型为 bus_ktype
priv->drivers_autoprobe = 1; // 配置为在注册设备或者是注册设备驱动时自动进行配置 这个就决定为什么我们在注册设备或者是设备驱动能够进行自动匹配
retval = kset_register(&priv->subsys); // 注册kset结构体(内部会调用kobject_add_internal函数,也就是将bus对象添加到 /sys/bus/目录下, /sys/bus/xxx_busType 对应具体的总线)
if (retval)
goto out;
retval = bus_create_file(bus, &bus_attr_uevent); // 在该bus下建立属性文件 (对应的就是 bus下的 uevent属性)
if (retval)
goto bus_uevent_fail;
priv->devices_kset = kset_create_and_add("devices", NULL, // 在具体总线的目录下创建 kset 容器对象 /sys/bus/xxx_busType/devices
&priv->subsys.kobj); // 通过priv->devices_kset指针去指向 这个目录对应的对象
if (!priv->devices_kset) {
retval = -ENOMEM;
goto bus_devices_fail;
}
priv->drivers_kset = kset_create_and_add("drivers", NULL, // /sys/bus/xxx_busType /drivers
&priv->subsys.kobj); // 通过 priv->drivers_kset 指针去指向 这个目录对应的对象
if (!priv->drivers_kset) {
retval = -ENOMEM;
goto bus_drivers_fail;
}
klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); // 初始化链表 klist
klist_init(&priv->klist_drivers, NULL, NULL); // 初始化链表 klist
retval = add_probe_files(bus); // 添加探针文件 其实内部做的还是添加属性文件 /sys/bus/xxx_busType/drivers_probe /sys/bus/xxx_busType/drivers_autoprobe
if (retval)
goto bus_probe_files_fail;
retval = bus_add_attrs(bus); // 根据 bus->bus_attrs 中的属性设置来添加属性文件
if (retval)
goto bus_attrs_fail;
pr_debug("bus: '%s': registered\n", bus->name);
return 0;
bus_attrs_fail:
remove_probe_files(bus);
bus_probe_files_fail:
kset_unregister(bus->p->drivers_kset);
bus_drivers_fail:
kset_unregister(bus->p->devices_kset);
bus_devices_fail:
bus_remove_file(bus, &bus_attr_uevent);
bus_uevent_fail:
kset_unregister(&bus->p->subsys);
kfree(bus->p);
out:
bus->p = NULL;
return retval;
}