文章目录
为什么要引入设备模型?
说到底,还是为了可维护性和兼容性。
总线:总线是CPU和设备交互的通道,所有设备要连接到总线上。总线包含虚拟总线和外设总线。
设备:将系统中所有硬件设备的共同属性抽象出来。
类:具有相同功能或属性的设备。
驱动:硬件设备对应的驱动程序。
分离优势:
在驱动和设备分离的时候就说过,对于相同功能的设备,只需要修改设备树,驱动就不需要改了。
注意:设备和驱动必须挂载在同一个总线上才能被 probe 到。
sys 文件系统:
sysfs 把连接到系统上的设备模型组织成一个层次结构,并且可以向用户空间导出内核数据结构及属性。
kobject 往往不会被单独使用,但是只要包含一个 kobject 对象,就可以把高级对象接入到设备模型中。例如:
struct cdev{
struct kobject kobj;
......
}
struct platform_device{
......
struct device dev; // dev 中也嵌入了 kobject
......
}
其实这么说来,总线、设备、类、驱动都内嵌了一个 kobject,所以 kobject 是设备模型的基石。
kobject 也就是内核对象,每一个 kobject 都对应 /sys/ 下的一个目录。
/* include/linux/kobject.h */
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
struct kernfs_node *sd; /* sysfs directory entry */
struct kref kref;
#ifdef CONFIG_DEBUG_KOBJECT_RELEASE
struct delayed_work release;
#endif
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
kset 是 kobject 的集合
/* include/linux/kobject.h */
struct kset {
struct list_head list;
spinlock_t list_lock;
struct kobject kobj;
const struct kset_uevent_ops *uevent_ops;
};
案例 1:创建 kobject
struct kobject *mykobject_1;
struct kobject *mykobject_2;
struct kobject *mykobject_3;
struct kobj_type mytype;
static int mykobj_init(void)
{
...
mykobject_1=kobject_create_and_add("mykobject01",NULL);
mykobject_2=kobject_create_and_add("mykobject02",mykobject_1);
/* mykobject_2 的父节点指向 mykobject_1 */
mykobject_3=kzalloc(sizeof(struct kobject), GFP_KERNEL);
int ret=kobject_init_and_add(mykobject_3, &mytype, NULL, "%s", "mykobject03");
...
}
static int mykobj_exit(void)
{
...
kobject_put(mykobject01);
kobject_put(mykobject02);
kobject_put(mykobject03);
...
}
案例 2:创建 kset
struct kobject *mykobject_1;
struct kobject *mykobject_2;
struct kset *mykset;
struct kobj_type mytype;
static int mykobj_init(void)
{
int ret;
mykset=kset_create_and_add("mykset", NULL, NULL); //目录名
mykobject_1=kzalloc(sizeof(struct kobject), GFP_KERNEL);
mykobject_1->kset=mykset;
int ret=kobject_init_and_add(mykobject_1, &mytype, NULL, "%s", "mykobject01");
mykobject_2=kzalloc(sizeof(struct kobject), GFP_KERNEL);
mykobject_2->kset=mykset;
int ret=kobject_init_and_add(mykobject_2, &mytype, NULL, "%s", "mykobject02");
...
}
static int mykobj_exit(void)
{
...
kobject_put(mykobject01);
kobject_put(mykobject02);
kset_unregister(mykset);
...
}
加载模块到内核后,可以在 sys/ 目录下看到 myket 目录,并且该目录下还有 mykobject01、mykobject02 两个目录。
kobject 如何与 sysfs 关联?
kobject_create_and_add
kobject_add
kobject_add_varg
kobject_add_internal
create_dir
sysfs_create_dir_ns(fs/sysfs/dir.c创建目录函数)
- 无父目录、无 kset,则在 sysfs 的根目录(即/sysfs/)下创建目录。
- 无父目录、有 kset,则在 kset 下创建目录,并将 kobj 加入到 kset.list。
- 有父目录、无 kset,则在 parent 下创建目录。
- 有父目录、有 kset,则在 parent 下创建目录,并将 kobj 加入 kset_list。
sysfs层次分析
/sys/devices :连接到总线的全部设备。
/sys/bus:Linux系统支持并且已经注册的总线,从总线这个角度展示现在有哪些总线以及总线下连接了什么设备和驱动。
/sys/class:对设备归类,类下所有设备都是 /sys/devices 的软链接。
归类的好处:
例如:控制一个GPIO
//使用类的gpio控制
echo 1 > /sys/class/gpio/gpio157/value
echo 1 > /sys/device/platform/fe770000.gpio/gpiochip4/gpio/gpio157/value
归类的好处就是路径更短。
引用计数器
记录设备文件被某个驱动、某块内存用到的情况,每被引用一次,kref +1,每被取消一次,kref -1。当计数为0的时候,自动调用我们注册的释放函数来释放驱动和内存。
防止了设备被卸载时,正在使用设备的程序崩溃。
struct kref {
refcount_t refcount;
};
typedef struct refcount_struct {
atomic_t refs;
} refcount_t;
typedf struct{
int counter;
}atomic_t;
所以,来见一个老同学:kobject_put( struct kobject *kobj )
之前用它来释放 kobject 对象,其实就是将该设备的引用次数 -1。
void refcount_set(refcount_t *t, int n); //设置引用计数为 n
void kref_init( struct kref *kref); //初始化为 1
void kref_get( struct kref *kref); //引用计数 +1
int kref_put( struct kref *kref, void (*release) ( struct kref *kref));
//引用计数 -1,如果引用次数为 0,将使用 release 函数来执行释放操作
注意:添加子 kobject 时,父 kobject 会 +1。
如果注册 kobject 时没有实现 release 函数,那么系统就会给出警告。
案例:添加释放函数
struct kobject *mykobject;
static void dynamic_kobj_release(struct kobject *kobj)
{
pr_debug("kobject: (%p): %s\n", kobj, __func__);
kfree(kobj);
}
static struct kobj_type mytype = {
.release = dynamic_kobj_release, // 释放函数存放到 kobj_type 结构体中
};
static int mykobj_init(void)
{
...
mykobject=kzalloc(sizeof(struct kobject), GFP_KERNEL);
/* 将含有释放函数的 kobj_type 结构体注册进 kobject */
int ret=kobject_init_and_add(mykobject, &mytype, NULL, "%s", "mykobject");
...
}
static int mykobj_exit(void)
{
...
kobject_put(mykobject);
...
}
添加文件属性
struct kobj_type {
void (*release)(struct kobject *kobj); //释放kobject和其占用资源的函数
const struct sysfs_ops *sysfs_ops; //操作下一个属性数组的方法
struct attribute **default_attrs; //属性数组
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
void (*get_ownership)(struct kobject *kobj, kuid_t *uid, kgid_t *gid);
};
struct sysfs_ops {
ssize_t (*show)(struct kobject *kobj, struct attribute *attr, char *buffer);
ssize_t (*store)(struct kobject *kobj, struct attribute *attr, const char *buffer, size_t size);
};
struct attribute {
char *name; //属性的名字
struct module *owner;
//指向模块的指针
mode_t mode; //属性的保护位:S_IRUGO(只读)、S_IWUSR(只写)
};
添加属性就需要对 sysfs_ops 和 default_attrs 两个成员进行设置。
案例:
struct mykobj{
struct kobject kobj;
int value1;
int value2;
}
struct mykobj *mykobject;
static void dynamic_kobj_release(struct kobject *kobj)
{
//因为传进来的kobject不是完整的自定义结构,需要获得结构体头指针才能释放掉全部数据
struct mykobj *mykobject=container_of(kobj, struct mykobj, kobj);
pr_debug("kobject: (%p): %s\n", kobj, __func__);
kfree(mykobject);
}
struct attribute value1={
.name="value1",
.mode="0666",
}
struct attribute value2={
.name="value2",
.mode="0666",
}
/* 将属性保存到属性数组,注册属性是通过数组的方式注册的 */
struct attribute *myattr[]={
&value1,
&value2,
NULL,
}
ssize_t myshow(struct kobject *kobj, struct attribute *attr, char *buf)
{
ssize_t count;
struct mykobj *mykobject=container_of(kobj, struct mykobj, kobj);
if(strcmp(attr->name, "value1")==0)
count=sprintf(buf, "%d\n", mykobject->value1);
if(strcmp(attr->name, "value2")==0)
count=sprintf(buf, "%d\n", mykobject->value2);
return count;
}
ssize_t mystore(struct kobject *kobj, struct attribute *attr, char *buf, size_t size)
{
struct mykobj *mykobject=container_of(kobj, struct mykobj, kobj);
if(strcmp(attr->name, "value1")==0)
sscanf(buf, "%d\n", &mykobject->value1);
if(strcmp(attr->name, "value2")==0)
sscanf(buf, "%d\n", &mykobject->value2);
return size;
}
struct sysfs_ops myops={
.show=myshow,
.store=mystore,
}
static struct kobj_type mytype = {
.release = dynamic_kobj_release,
.default_attrs = myattr,
.sysfs_ops=myops,
}
static int mykobj_init(void)
{
...
mykobject_1=kzalloc(sizeof(struct kobject), GFP_KERNEL);
mykobject->value1 = 1;
mykobject->value2 = 2;
int ret=kobject_init_and_add(mykobject->kobj, &mytype, NULL, "%s", "mykobject");
...
}
static int mykobj_exit(void)
{
...
kobject_put(mykobject);
...
}
生成目录:/sys/mykobject
目录下包含两个属性:value1、value2
cat value1
1
cat value2
2
echo 3 > value1
echo 4 > value2
cat value1
3
cat value2
4
针对无 kobj_type 的创建文件的方法
static int mykobj_init(void)
{
...
mykobject = kobject_create_and_add("mykobject", NULL);
/* 使用这种方法,Linux内核会帮我们实现一个 kobj_type 结构体,并且添加默认读写方法 */
int ret = sysfs_create_file(mykobject, &value1.attr);
int ret = sysfs_create_file(mykobject, &value2.attr);
return 0;
}
上面明显没有自己实现属性和读写方法,但是调用 sysfs_create_file 之后,Linux系统会帮我们调用默认的读写操作函数。
默认的读写方法:
static ssize_t kobj_attr_show(struct kobject *kobj, struct attribute *attr);
static ssize_t kobj_attr_store(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count);
const struct sysfs_ops kobj_sysfs_ops = {
.show = kobj_attr_show,
.store = kobj_attr_store,
}
EXPORT_SYSBOL_GPL(kobj_sysfs_ops);
总线
正常情况下没必要我们自己注册总线。
struct bus_type {
const char *name; /*总线名称*/
const char *dev_name;
struct device *dev_root;
struct device_attribute *dev_attrs; /* use dev_groups instead */
const struct attribute_group **bus_groups; /*总线属性*/
const struct attribute_group **dev_groups; /*设备属性*/
const struct attribute_group **drv_groups; /*驱动属性*/
int (*match)(struct device *dev, struct device_driver *drv); //当插入device或driver时,均会执行该函数进行匹配
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 (*online)(struct device *dev);
int (*offline)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state);
int (*resume)(struct device *dev);
const struct dev_pm_ops *pm;
const struct iommu_ops *iommu_ops;
struct subsys_private *p; //子系统指针
struct lock_class_key lock_key;
};
案例:注册总线
int mybus_match(struct device *dev, struct device_driver *drv)
{
return (strcmp(dev_name(dev), drv->name)==0)
}
int mybus_probe(struct device *dev)
{
struct device_driver *drv = dev->driver;
if(drv->probe)
drv->probe(dev);
return 0;
}
/* 实现自己的总线结构体 */
struct bus_type mybus = {
.name = "mybus",
.match = mybus_match,
.probe = mybus_probe,
};
static int bus_init(void)
{
int ret = bus_register(&mybus);
return 0;
}
static void bus_exit(void)
{
bus_unregister(&mybus);
}
注册该总线后,在 /sys/bus/ 目录中会出现一个 mybus 目录;
并且在该目录下,系统自动帮我们创建了两个文件夹:device、driver
三个属性文件:drivers_autoprobe(自动匹配驱动)、drivers_probe、uevent
那么问题来了,如何创建自己的属性文件?
bus_create_file() 的本质也是调用了 sysfs_create_file()
bus_put() 本质上也是在对引用计算处理。
在总线上创建文件其实和在其他 sysfs 的目录中创建是同样的原理,无非是 API 指定的父目录不同而已,而属性、操作更是完全相同。
int mybus_match(struct device *dev, struct device_driver *drv)
{
return (strcmp(dev_name(dev), drv->name)==0)
}
int mybus_probe(struct device *dev)
{
struct device_driver *drv = dev->driver;
if(drv->probe)
drv->probe(dev);
return 0;
}
/* 实现自己的总线结构体 */
struct bus_type mybus = {
.name = "mybus",
.match = mybus_match,
.probe = mybus_probe,
};
ssize_t mybus_show(struct bus_type *bus, char *buf)
{
/* 读取时打印出 show bus attr */
return sprintf(buf, "%s\n", "show bus attr");
}
struct bus_attribue mybus_attr = {
.attr = {
.name = "value",
.mode = 0664,
}
.show = mybus_show,
}
static int bus_init(void)
{
int ret = bus_register(&mybus);
ret = bus_create_file(&mybus, &mybus_attr);
return ret;
}
static void bus_exit(void)
{
bus_remove_file(&mybus, &mybus_attr);
bus_unregister(&mybus);
}
总线如何被注册?
先来思考一个问题:为什么注册了总线之后,总线目录下会自动生成 device、driver、drivers_autoprobe、drivers_probe、uevent
struct device{
......
struct kobject kobj;
......
}
struct bus_type{
......
struct device *dev_root;
......
}
总线 bus_type 结构体包含的 device、driver 等成员都是自带 kobject 结构体。
子系统
/* 表示当前总线的子系统的私有数据 */
struct subsys_private {
struct kset subsys; // 可以看到子系统其实是一个 kset
struct kset *devices_kset;
struct list_head interfaces;
struct mutex mutex;
struct kset *drivers_kset;
struct klist klist_devices;
struct klist klist_drivers;
struct blocking_notifier_head bus_notifier;
unsigned int drivers_autoprobe:1;
struct bus_type *bus;
struct kset glue_dirs;
struct class *class;
};
子系统是Linux的一种机制,用于将特定功能的实现抽象为一个子系统。
例如:
文件系统子系统
设备驱动子系统
系统调用子系统
网络子系统
内存子系统
在总线注册 bus_register() 中初始化了两个链表:
klist_init(&priv->klist_devices, list_devices_get, klist_devices_put);
/* 初始化一个内核链表,用来存储当前注册到的总线下的设备对象;
priv 是当前注册的总线下的所属的子系统的私有数据,将 priv 里面的成员变量 klist_devices 设置为新创建的内核链接
priv 可以记录当前注册的总线下的所有设备信息,方便进行查找和操作
*/
klist_init(&priv->klist_drivers, NUtLL, NULL);
/* 初始化一个内核链表,用来存储当前注册的总线下已注册的驱动对象;
priv 是当前注册的总线下的所属的子系统的私有数据,将 priv 里面的成员变量 klist_drivers 设置为新创建的内核链接
priv 可以记录当前注册的总线下的所有设备信息;
注意:因为没有对引用计数加减操作,所以不能对驱动的内核链表进行增删操作。
*/
所以当设备需要驱动时,是在同一个总线下遍历链表找到名字匹配的驱动。
platform
platform总线是如何注册的?
/* driver/base/platform.c */
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
of_platform_register_reconfig_notifier();
return error;
}
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);
/* 所以实际上用总线的创建API创建了一个名为 platform 的总线 */
首先分析 match 函数。平台总线匹配优先级是:of_match_table > id_table > name
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);
/* 第一:有现成的驱动直接匹配 */
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
/* 第二:在设备树中找 */
if (of_driver_match_device(dev, drv))
return 1;
/* 这个算了,现在没用了 */
if (acpi_driver_match_device(dev, drv))
return 1;
/* 设备之前匹配方式,即自己写的设备文件 */
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);
}
to_platform_device()、to_platform_driver() 实际上都是 container_of(),用于找到两个数据(dev 和 drv)所在的结构体头地址。
获得完整的 platform_device 地址 和 完整的 platform_driver 地址后再选择数据比较,完成匹配。
struct platform_device {
const char * name;
u32 id;
struct device dev;
u32 num_resources;
struct resource * resource;
};
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;
};
如何在总线下注册设备?
extern struct bus_type mybus;
void (*release)(struct device *dev){
printk("666!!!");
}
struct device mydevice = {
.init_name = "mydevice",
.bus = &mybus,
.release = myrelease,
.devt = ((255<<20|0)), //次设备号是20位,我们这里只写主设备号
}
EXPORT_SYSTEM_GPL(mydevice);
static int device_init(void)
{
int ret=device_register(&mydevice);
return ret;
}
static void device_exit(void)
{
device_unregister(&mydevice)
}
加载到内核之后,在 /sys/devices/ 目录中就出现了 mydevice 目录。
在 /sys/bus/mybus/devices/ 目录中,也出现了 mydevice 目录。
设备是如何注册的?
/* driver/base/core.c */
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
EXPORT_SYMBOL_GPL(device_register);
函数的输入参数是struct device结构体类型的指针变量,代表即将被初始化的逻辑设备。
device_initialize(dev) 会将 dev 结构体中的 kobj.kset 设置成 device_kset,所以会在 sys/device 目录下去创建对应的设备目录。
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); /* 设置设备的节点号,-1 表示未分配 */
#ifdef CONFIG_GENERIC_MSI_IRQ
INIT_LIST_HEAD(&dev->msi_list); /* msi_list 包含设备使用的 MSI-X 表 */
#endif
......
dev->links.status = DL_DEV_NO_DRIVER; /* 属性设置为 DL_DEV_NO_DRIVER,这意味着设备尚未被驱动程序识别 */
}
EXPORT_SYMBOL_GPL(device_initialize);
device_add() 非常复杂,首先对逻辑设备对象部分字段进行初始化,然后将此逻辑设备加到Linux内核系统的设备驱动程序模型中。函数能够自动地在/sys/devices/virtual目录下创建新的逻辑设备目录,在/dev目录下创建与逻辑类对应的设备文件。
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct kobject *kobj;
struct class_interface *class_intf;
int error = -EINVAL;
struct kobject *glue_dir = NULL;
dev = get_device(dev);
...
if (!dev->p) {
error = device_private_init(dev);
...
}
...
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
...
parent = get_device(dev->parent);
kobj = get_device_parent(dev, parent);
...
if (parent && (dev_to_node(dev) == NUMA_NO_NODE))
set_dev_node(dev, dev_to_node(parent));
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL); //在父目录下创建自己的目录
...
error = device_create_file(dev, &dev_attr_uevent); //创建热插拔属性文件
error = device_add_class_symlinks(dev); //在class目录先创建一个软连接
error = device_add_attrs(dev); //添加属性文件
error = bus_add_device(dev); //这个函数内部也创建了设备的软连接
error = dpm_sysfs_add(dev);
...
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &dev_attr_dev);
...
error = device_create_sys_dev_entry(dev);
...
devtmpfs_create_node(dev);
}
if (dev->bus)
blocking_notifier_call_chain(&dev->bus->p->bus_notifier,
BUS_NOTIFY_ADD_DEVICE, dev);
kobject_uevent(&dev->kobj, KOBJ_ADD);
bus_probe_device(dev);
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);
if (dev->class) {
mutex_lock(&dev->class->p->mutex);
/* tie the class to the device */
klist_add_tail(&dev->knode_class,
&dev->class->p->klist_devices);
/* notify any interfaces that the device is here */
list_for_each_entry(class_intf,
&dev->class->p->interfaces, node)
if (class_intf->add_dev)
class_intf->add_dev(dev, class_intf);
...
}
platform 总线如何添加设备?
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
arch_setup_pdev_archdata(pdev);
return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);
platform_device_add() 这个函数是不是很眼熟,其实就是内部包装了 device_add()。
先来看之前的一个例子:当设备ID为 -1 时:
struct platform_device platform_device_test = {
.id = -1,
}
在 platform_device_add() 中是这样的:
int platform_device_add(struct platform_device *pdev)
{
...
if (!pdev->dev.parent)
pdev->dev.parent = &platform_bus;
pdev->dev.bus = &platform_bus_type;
switch (pdev->id) {
default:
dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);
break;
case PLATFORM_DEVID_NONE:
dev_set_name(&pdev->dev, "%s", pdev->name); //设备ID为 -1 时,设备名不添加设备ID
break;
case PLATFORM_DEVID_AUTO: //设备ID为 -2 时,自动补充设备ID,并且设备名带.auto
/*
* 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); //如果名称为NULL,就按照名称直接设置
p = r->parent;
if (!p) { //没有设置 parent,则根据资源类型寻找对应的资料 parent;找到就将其资源设置为 iomem_resource 或者 ioport_resource
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;
...
}
实际上,Linux系统为了方便管理设备资源,所有设备都会添加到资源树中。
每一个设备都有一个父资源,用于表示该资源所属的资源的根。
其实就把 platform 相关的属性添加完之后,继续使用 device_add() 把设备添加到设备链中。
但是有一个问题啊。
int __init platform_bus_init(void)
{
...
error = device_register(&platform_bus);
...
error = bus_register(&platform_bus_type);
...
}
为什么是先注册设备,再注册总线?
device_register() 会在 /sys/devices/ 目录中创建 platform 目录。
这个目录是所有 platform 目录的父目录。
理论上顺序反过来也没事,只需要保证设备都是挂载在 platform 目录中即可。
如何在总线中注册驱动?
这个就是新手写第一个驱动的时候的模板了呀。
extern struct bus_type mybus;
int mydriver_probe(struct device *dev)
{
...
}
int mydriver_remove(struct device *dev){
...
}
struct device_driver mydriver = {
.name = "mydriver",
.bus = &mybus,
.probe = mydriver_probe,
.remove = mydriver_remove,
}
static int mydriver_init(void)
{
int ret = driver_register(&mydriver)
return ret;
}
static void mydriver_exit(void)
{
driver_unregister(&mydriver)
}
驱动注册过程:
int driver_register(struct device_driver *drv)
{
struct device_driver *other;
...
if ((drv->bus->probe && drv->probe) ||
(drv->bus->remove && drv->remove) ||
(drv->bus->shutdown && drv->shutdown))
printk(KERN_WARNING "Driver '%s' needs updating - please use "
"bus_type methods\n", drv->name);
other = driver_find(drv->name, drv->bus);
if (other) { //驱动已经存在了,直接返回
printk(KERN_ERR "Error: Driver '%s' is already registered, "
"aborting...\n", drv->name);
return -EBUSY;
}
...
ret = bus_add_driver(drv);
...
ret = driver_add_groups(drv, drv->groups);
...
}
kobject_uevent(&drv->p->kobj, KOBJ_ADD);
...
}
device_driver() 按照描述是在总线上遍历驱动列表,看是否存在对应的驱动,有则返回该驱动。
struct device_driver *driver_find(const char *name, struct bus_type *bus)
{
struct kobject *k = kset_find_obj(bus->p->drivers_kset, name);
struct driver_private *priv;
if (k) {
/* Drop reference added by kset_find_obj() */
kobject_put(k);
priv = to_driver(k);
return priv->driver;
}
return NULL;
}
bus_add_driver() 比较复杂,但是总体上来说,就是获得驱动的总线,把驱动添加到总线的驱动链表,创建 kobject 文件,添加属性文件,把驱动添加到总线下的子系统中。
int bus_add_driver(struct device_driver *drv)
{
struct bus_type *bus;
struct driver_private *priv;
...
bus = bus_get(drv->bus);
...
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
...
klist_init(&priv->klist_devices, NULL, NULL);
priv->driver = drv;
drv->p = priv;
priv->kobj.kset = bus->p->drivers_kset;
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
if (drv->bus->p->drivers_autoprobe) { //是否自动匹配驱动
if (driver_allows_async_probing(drv))
async_schedule(driver_attach_async, drv); //异步添加驱动
else
error = driver_attach(drv); //直接添加驱动
}
module_add_driver(drv->owner, drv);
error = driver_create_file(drv, &driver_attr_uevent);
error = driver_add_groups(drv, bus->drv_groups);
if (!drv->suppress_bind_attrs) {
error = add_bind_files(drv);
}
return 0;
...
}
probe函数是如何执行的?
先看之前的代码:
if (drv->bus->p->drivers_autoprobe) { //是否自动匹配驱动
error = driver_attach(drv); //直接添加驱动
之前也说过,当总线下的 drivers_autoprobe 属性文件的内容为 1,那么注册该总线的设备就会自动匹配驱动。
调用顺序:
driver_attach //回调 bus_for_each_dev
bus_for_each_dev //遍历总线上的设备,若有,回调 __driver_attach
__driver_attch
driver_match_device
device_driver_attch
driver_probe_device
really_probe //到了这里才会执行我们编写的 probe 函数
/* 如果总线也有 probe 函数,那么会先执行总线的 probe 函数 */
设备在总线上的注册也有类似的流程:
bus_probe_device
device_initial_probe
__device_attch
bus_for_each_drv
__device_attch_driver
在设备注册中有一个新的概念:设备-驱动绑定
static int __device_attach(struct device *dev, bool allow_async)
{
...
ret = device_bind_driver(dev); //设备和驱动相绑定
...
}
} else {
struct device_attach_data data = {
.dev = dev,
.check_async = allow_async,
.want_async = false,
};
...
ret = bus_for_each_drv(dev->bus, NULL, &data,
__device_attach_driver);
...
}
/* 怎么绑定呢? */
int device_bind_driver(struct device *dev)
{
int ret = driver_sysfs_add(dev); //建立软连接
if (!ret)
driver_bound(dev);
return ret;
}
EXPORT_SYMBOL_GPL(device_bind_driver);
那么 platform 上的驱动是如何注册的?
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
extern int __platform_driver_register(struct platform_driver *,
struct module *);
再来看 __platform_driver_register() 的原型:
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);
}
看到 driver_register() 就明白了吧,又回到总线注册驱动了。
设备模型总结
Linux 系统内部开启了一个监视进程,负责接收和归类注册的设备和模块,然后给新设备匹配驱动,匹配了就能用,道理就这么简单。