专栏
https://blog.csdn.net/pengliang528/category_9316091.html
原文链接:https://blog.csdn.net/pengliang528/article/details/100556340
概念:
平台总线只是一种设备模型,其管理的设备本质不变,因此应用访问平台总线上的设备通过/dev这个通道也不会发生改变,只是将设备(符合平台总线设备共性)塞入驱动框架的平台总线来管理,相比字符设备而言只是增加了sys目录下的视图关系。
本章将以平台设备的添加、驱动的注册为切入点,逐步分析总线、平台总线设备的软件层次、组成框架来展示平台总线设备的详情。
应用示例
1.平台备添加设
static struct platform_device xxx_device = {
.name = "xxx",
.id = 0,
.dev= {
.platform_data = xxx_data,
},
.num_resources = ARRAY_SIZE(xxx_resources),
.resource = xxx_resources,
};
platform_device_register(&smsc_lan9217_device);
总线的挂载
1.总线框架
1.总线的初始化
-----itop4412_kernel_3.0/init/main.c-----
start_kernel
rest_init();
kernel_init(void * unused)
do_basic_setup();
----itop4412_kernel_3.0/drivers/base/init.c-----
// /sys下创建class、bus、devices、driver关键子目录、并初始化关键总线
driver_init();
// 依次生成/sys/devices、/sys/dev、/sys/dev/block、/sys/dev/char子目录及相关父kobj(作后续添加节点的根)
buses_init();
// 创建一个bus的kset对象,作为后续总线挂载的根节点并生成/sys/bus目录,不再延生,请查看kobject章节
bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
展开:
struct kset *kset_create_and_add(const char *name,const struct kset_uevent_ops *uevent_ops,struct kobject *parent_kobj)
name:所需要创建kset的name
uevent_ops:uevent操作集
parent_kobj:父节点对象
2.总线的注册:int bus_register(struct bus_type *bus)
1.申请一个总线资源对象,并绑定注入的总线包括总线名
2.将其根节点置为总线初始化所产生的所有总线根对象bus_kset
3.注册该总线至sysfs文件系统,通知用户态在/sys/bus下添加对应的总线目录
4.产生该总线下的devices、drivers目录、对应根节点、对应类型链表用于挂载对应这设备
5.设置总线的属性
3.总结
1.驱动框架模型是建立在kobject、sysfs基础之上,因此最终的操作都要归结至此,总线是属于其中的一个分支也需要满足
2.所有驱动框架实现需要依赖sysfs文件系统,因此该文件系统应该首先被注册
3.总线是虚拟的,并不对应实际的设备,因此总线的初始化并不依赖tmpfs文件系统
2.平台总线的注册流程
-----itop4412_kernel_3.0/init/main.c-----
start_kernel
vfs_caches_init(totalram_pages)
mnt_init();
// sysfs文件系统先初始化,设备模型需要依赖于sysfs文件系统
sysfs_init();
rest_init();
kernel_init(void * unused)
do_basic_setup();
// tmpfs文件系统先初始化,设备模型不仅在sys下创建视图,同时也会在/dev下创建用户访问通道,因为其本质上还是设备
init_tmpfs();
// /sys下创建class、bus、devices、driver关键子目录、并初始化关键总线
driver_init();
// 各模块初始化
do_initcalls();
-----itop4412_kernel_3.0/drivers/base/init.c----
driver_init();
// 依次生成/sys/devices、/sys/dev、/sys/dev/block、/sys/dev/char子目录及相关父kobj(作后续添加节点的根)
devices_init();
// 生成/sys/bus目录及父节点bus_kset作为后续添加子节点的根
buses_init();
// 生成/sys/class目录及父节点class_kset作为后续添加子节点的根
classes_init();
// 生成/sys/firmware目录及父节点firmware_kobj作为后续添加子节点的根
firmware_init();
// 平台总线初始化
platform_bus_init();
// 生成/sys/devices/system目录及父节点system_kset作为后续添加子节点的根
system_bus_init();
// 生成/sys/devices/system/cpu目录及父节点system_kset作为后续添加子节点的根
cpu_dev_init();
-----itop4412_kernel_3.0/drivers/base/platform.c---
platform_bus_init();
device_register(&platform_bus); // 注册平台总线设备、为虚拟设备,只有设备名无硬件资源
bus_register(&platform_bus_type); // 注册平台总线驱动
总结:
1>字符设备驱动不依赖于驱动模型,因此可以在内核资源完成后即可添加字符设备驱动。
2>由于平台总线模型需要依托/sys,因此需要先初始化sysfs文件系统。
3>平台总线管理的设备脱离不了设备的本质,而设备的共性就是在/dev下预留给用户的访问通道,因此在此之前tmpfs文件系统也需要被初始化。
4>平台总线依赖于总线,因此总线必须先初始化。
5>platform总线通常管理SOC上的控制芯片,而很多总线在初始化阶段需要初始化其控制器,因此需要优先注册platform总线。
6>平台总线归于/sys设备模型,因此需要满足其3要求(设备、驱动、总线)、而平台总线是一个虚拟的总线,因此在内核态虚拟一个这样的设备出来(只有设备名无硬件资源)。
总线、设备、驱动三者的关系视图
总线存在的意义
用来管理、做经分离后的硬件资源、驱动的匹配,此模型实现了热插拔
1.依据模型构建/sys/下各关键子目录及文件节点关联视图,以便用户访问,该关联视图见sysfs与kobject相关章节
设置依据kobject关联(如父节点、自身引用计数、所属总线、类别、创建并管理设备、驱动的链表)构建sysfs视图下的各个节点形成整体视图
2.构建一个统一模型,将硬件资源和驱动分离、构建该模型下的硬件资源和驱动的关联机制
本节着重以此为线索展开:
硬件资源:
相关接口:platform_device_register(struct platform_device *);
1>硬件资源以struct device为基类,进行当前所属设备类别的再次封装,所作动作分4类
1>构建4.1的视图
2>创建/dev下的访问通道
devtmpfs文件节点只分字符设备和块设备,同时通过kobject_uevent(&dev->kobj, KOBJ_ADD)来通知上层应用得mdev通过mknod来创建相应的设备节点,最终在/dev下创建对应的用于应用访问的设备通道,并与cdev_map形成关联,详见“字符设备驱动详解”4.2章节
3>在该总线上添加设备并探测驱动
bus_add_device(dev);
bus_probe_device(dev)
2>驱动
相关接口:int platform_driver_register(struct platform_driver *drv)
驱动以struct device_driver driver为基类,进行当前所属设备类别操作集的再次封装,所作动作分3类
1>构建4.1的视图
2>在该总线上探测设备并将device上的drv绑定为此drv //注意和设备绑定驱动之间的区别和关联,设备有drv成员,而drv没有设备成员,因为驱动是依赖于设备才存在意义
bus_add_driver(drv);
3>设置groups
3>匹配机制
见下一节
设备与驱动的相互探测
1.总线初始化
platform_bus_init()
bus_register(&platform_bus_type);
展开:
struct bus_type platform_bus_type = {
.name = "platform",
.dev_attrs = platform_dev_attrs,
.match = platform_match, // 匹配规则
.uevent = platform_uevent, // 环境变量设置规则,用于支持用户态的热插拔
.pm = &platform_dev_pm_ops, // 电源管理规则
};
platform_match
platform_match_id(dev, drv) // 类型匹配
platform_match_id(pdrv->id_table, pdev) // id匹配
strcmp(pdev->name, drv->name) // 名字匹配
总结:
挂载一条总线并指定总线的匹配规则、uevent处理、电源管理机制
2.设备探测驱动详解
device_add(struct device *dev)
bus_probe_device(dev);
device_attach(dev);
bus_for_each_drv(dev->bus, NULL, dev, __device_attach);
while ((drv = next_driver(&i)) && !error) { // 拿当前设备逐一探测该总线上挂载的所有设备驱动(遵顼之前设置的当前总线匹配原则)
error = fn(drv, data);
}
__device_attach(drv, data)
driver_match_device
drv->bus->match(dev, drv) // 按照5.1设定的总线匹配规则进行匹配,匹配上则进入对应设备驱动的实际探究
driver_probe_device(drv, dev)
really_probe(dev, drv)
展开:
dev->driver = drv; // 再device对象上先绑定驱动,如失败再解绑定
driver_sysfs_add(dev) // 在sysfs设备驱动模型对象上进行绑定(创建链接)
ret = drv->probe(dev); // 进入驱动的probe函数进行实际探究
3.驱动探测设备
platform_driver_register(struct platform_driver *drv)
driver_register(&drv->driver)
bus_add_driver(drv);
driver_attach(drv);
bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); // 拿当前驱动逐一探测该总线上挂载的所有设备(遵顼之前设置的当前总线匹配原则)
__driver_attach(dev, drv)
driver_match_device(drv, dev) // 与设备探测驱动一致
driver_probe_device(drv, dev)
->really_probe(dev, drv) // 与设备探测驱动一致
4.探测原理图
结构体及接口说明
1.结构体
1.设备:include/linux/platfrom_device.h
struct platform_device {
const char * name; // 平台设备的名字
int id; // ID 是用来区分如果设备名字相同的时候(通过在后面添加一个数字来代表不同的设备,因为有时候有这种需求)
struct device dev; // 内置的device结构体-------参考设备驱动模型
u32 num_resources; // 资源结构体数量
struct resource * resource; // 指向一个资源结构体数组
const struct platform_device_id *id_entry; // 用来进行与设备驱动匹配用的id_table表
/* MFD cell pointer */
struct mfd_cell *mfd_cell;
/* arch specific additions */
struct pdev_archdata archdata; // 自留地添加自己的东西
};
struct resource {
resource_size_t start; // 资源的起始值,如果是地址,那么是物理地址,不是虚拟地址
resource_size_t end; // 资源的结束值,如果是地址,那么是物理地址,不是虚拟地址
const char *name; // 资源名
unsigned long flags; // 资源的标示,用来识别不同的资源(如终端、内存、dma等资源)
struct resource *parent, *sibling, *child; // 资源指针,可以构成链表
};
2.驱动:include\linux\platform_device.h
struct platform_driver {
int (*probe)(struct platform_device *); // 查询设备,和device_driver中的是一样的功能,但是一般是使用device_driver中的那个
int (*remove)(struct platform_device *); // 卸载, 平台设备驱动的时候会调用这个函数,但是device_driver下面也有,具体调用的是谁这个就得分析了
void (*shutdown)(struct platform_device *); // 断电
int (*suspend)(struct platform_device *, pm_message_t state);// 休眠
int (*resume)(struct platform_device *); // 恢复
struct device_driver driver; // 内置的device_driver 结构体 -------参考设备驱动模型
const struct platform_device_id *id_table; // 该设备驱动支持的设备的列表 他是通过这个指针去指向 platform_device_id 类型的数组
};
3.总线
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结构体
};
static struct device_attribute platform_dev_attrs[] = {
__ATTR_RO(modalias),
__ATTR_NULL,
};
4.类
struct class {
const char *name;
struct module *owner;
struct class_attribute *class_attrs;
struct device_attribute *dev_attrs;
struct bin_attribute *dev_bin_attrs;
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, mode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm;
struct subsys_private *p;
};
2.接口
int platform_driver_register(struct platform_driver *); // 注册设备驱动
void platform_driver_unregister(struct platform_driver *); // 卸载设备驱动
int platform_device_register(struct platform_device *); // 注册设备
void platform_device_unregister(struct platform_device *); // 卸载设备
总结
1.设备是硬件唯一的实体,驱动只是软件层面的操作依赖于设备,因此设备预留了指向驱动的指针。从2者的结构体成员可知,相互之间的关联仅仅是匹配规则所需要的关键信息即name、所属总线,一旦匹配上则将驱动绑定之设备对应的驱动指针
对比2者结构体:
1>驱动:struct device_driver包含4类信息
1.匹配规则信息:name、of_match_table及所属bus
2.构建sys所需要的kobject、klist等
3.指明驱动支持哪些设备:struct list_head devices(驱动和设备是一对多的关系)
4.设备操作集
2>设备:struct device包含5类信息
1.匹配规则信息:name、所属bus
2.构建sys所需要的kobject、class相关信息
3.指明用哪个驱动:struct device_driver *driver(设备只能对应唯一的驱动)
4.设备的硬件资源:platform_data、dma等
5.设备的属性:设备号、设备访问互斥控制量
3>总线只是规定了挂载其上的驱动和设备之间的统一的匹配、环境变量设置、电源管理等规则
4>总线模型下支持设备和驱动的单独挂载(达到分离)、在各自注册中去匹配对方
5>总线模型下抽离了创建sys视图的共性