设备树和Platform架构--4--platform bus概述及其初始化
1 概述1.1 platform(平台)总线出现背景Platform总线是Linux设备驱动模型为了保持设备驱动的统一性而虚拟出来的总线。因为对于usb设备、i2c设备、pci设备、spi设备等等,他们与cpu的通信都是直接挂在相应的总线下面与cpu进行数据交互的,但是在嵌入式系统当中,并不是所有的设备都能够归属于这些常见的总线,在嵌入式系统里面,SoC系统中集成的独立的外设控制器...
1 概述
1.1 platform(平台)总线出现背景
Platform总线是Linux设备驱动模型为了保持设备驱动的统一性而虚拟出来的总线。因为对于usb设备、i2c设备、pci设备、spi设备等等,他们与cpu的通信都是直接挂在相应的总线下面与cpu进行数据交互的,但是在嵌入式系统当中,并不是所有的设备都能够归属于这些常见的总线,在嵌入式系统里面,SoC系统中集成的独立的外设控制器、挂接在SoC内存空间的外设却不依附与此类总线。所以Linux驱动模型为了保持完整性,将这些设备挂在一条虚拟的总线上(platform总线),而不至于使得有些设备挂在总线上,另一些设备没有挂在总线上。
从Linux2.6内核起,便引入这套新的驱动管理和注册机制:platform_device 和 platform_driver 。Linux 中大部分的设备驱动,都可以使用这套机制,设备用 struct platform_device 表示;驱动用 struct platform_driver 进行注册。
linux_platform_driver 机制和传统的device_driver机制(即:通过 driver_register 函数进行注册)相比,一个十分明显的优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中使用这些资源时,通过 platform device提供的标准接口进行申请并使用。
platform 是一个虚拟的地址总线,相比 PCI、USB,它主要用于描述SOC上的片上资源。platform 所描述的资源有一个共同点:在CPU 的总线上直接取址。平台设备会分到一个名称(用在驱动绑定中)以及一系列诸如地址和中断请求号(IRQ)之类的资源。
1.2 platform相关的代码
platform总线相关代码:driver\base\platform.c 文件
相关结构体定义: include\linux\platform_device.h 文件
1.3 Linux platform总线、platform设备、platform驱动之间的关系及框架
(1)总线用于将设备和驱动绑定;在系统注册设备时,寻找与之匹配的驱动;在系统注册驱动时,寻找与之匹配的设备,匹配由总线完成。
(2)对于任何一种Linux设备驱动模型下的总线都由两个部分组成:描述设备相关的结构体和描述驱动相关的结构体
(3)platform bus是一条虚拟总线,platform_device为相应的设备,platform_driver为相应的驱动。
(4)与传统的bus/device/driver机制相比,platform由内核统一进行管理,提高了代码的可移植性和安全性。所谓的platform_device并不是与字符设备、块设备和网络设备并列的概念,而是Linux系统提供的一种附加手段。
(5)Linux总线、设备、驱动的模型框架如下图所示
1.4 总线设备驱动框架原理
在总线设备驱动模型中,需关心总线、设备和驱动这3个实体,总线将设备和驱动绑定。
(1)当系统向内核注册每一个驱动程序时,都要通过调用platform_driver_register函数将驱动程序注册到总线(bus),并将其放入所属总线的drv链表中,注册驱动的时候会调用所属总线的match函数寻找该总线上与之匹配的每一个设备,如果找到与之匹配的设备则会调用相应的probe函数将相应的设备和驱动进行绑定;
(2)当系统向内核注册每一个设备时,可以通过调用platform_device_register函数,也可以通过解析DTB由内核完成platform device的创建,并将设备platform device注册到总线platform bus,并将其放入所属总线的dev链表中,注册设备的时候同样也会调用所属总线的match函数寻找该总线上与之匹配的每一个驱动程序,如果找到与之匹配的驱动程序时会调用相应的probe函数将相应的设备和驱动进行绑定;而这一匹配的过程是由总线自动完成的。
2 Platform bus初始化
2.1 关键结构体
struct 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结构体
};
2.2 Platform bus初始化流程
platform bus的初始化由函数platfrom_bus_init()函数来完成。由于platform bus本身也是一种设备,因此调用了原始的系统接口device_register()和bus_register()分别完成platform_bus设备和platform_bus_type本身的初始化;
start_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(); #device初始化
>>>buses_init(); #bus初始化
>>>classes_init(); #class初始化
>>>platform_bus_init(); #platform bus初始化 kernel/drivers/base/platform.c
>>>early_platform_cleanup();
>>>error = device_register(&platform_bus);
>>>error = bus_register(&platform_bus_type);
>>>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,
};
其中,
early_platform_cleanup() // 进行一些早期的平台清理
device_register(&platform_bus) //注册设备 (在/sys/devices/目录下建立 platform目录对应的设备对象 /sys/devices/platform/)
bus_register(&platform_bus_type) // 将Platform bus总线注册进系统
下面给出这两个函数的实现
//向系统注册一个设备
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);
#ifdef CONFIG_GENERIC_MSI_IRQ
INIT_LIST_HEAD(&dev->msi_list);
#endif
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);
>>>.....
int 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;
}
更多推荐
所有评论(0)