尽管LDD3中说对多数程序员掌握设备驱动模型不是必要的,但对于嵌入式Linux的底层程序员而言,对设备驱动模型的学习非常重要。
Linux设备模型的目的:为内核建立一个统一的设备模型,从而又一个对系统结构的一般性抽象描述。换句话说,Linux设备模型提取了设备操作的共同属性,进行抽象,并将这部分共同的属性在内核中实现,而为需要新添加设备或驱动提供一般性的统一接口,这使得驱动程序的开发变得更简单了,而程序员只需要去学习接口就行了。 在内核里,有各种各样的总线,如 usb_bus_type、spi_bus_type、pci_bus_type、platform_bus_type、i2c_bus_type 等,内核通过总线将设备与驱动分离。此文,基于 Linux2.6.32.2 简单分析设备驱动模型,以后看具体的总线设备模型时会更加清晰。 设备模型是层次的结构,层次的每一个节点都是通过kobject实现的。在文件上则体现在sysfs文件系统。 关于kobkect,前面的文章已经分析过了,如果不清楚请移步 http://blog.csdn.net/lizuobin2/article/details/51523693
kobject 结构可能的层次结构如图: 关于 uevet mdev 前面也说过了,请参考 http://blog.csdn.net/lizuobin2/article/details/51534385 对于整个 设备总线驱动模型 的样子,大概如下图吧,也并不复杂。简单来说,bus 负责维护 注册进来的devcie 与 driver ,每注册进来一个device 或者 driver 都会调用 Bus->match 函数 将device 与 driver 进行配对,并将它们加入链表,
如果配对成功,调用Bus->probe或者driver->probe函数, 调用 kobject_uevent 函数设置环境变量,mdev进行创建设备节点等操作。后面,我们从 Bus driver 到 device三个部分进行详细的分析。
一、总线 内核通过 bus_register 进行 Bus 注册,那么它注册到了哪里,“根”在哪? 答案是 bus_kest
- int __init buses_init(void)
- {
-
- bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);
- if (!bus_kset)
- return -ENOMEM;
- return 0;
- }
bus 的类型为 bus_type
- 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;
- };
- struct bus_type_private {
- struct kset subsys;
- struct kset *drivers_kset;
- struct kset *devices_kset;
- struct klist klist_devices;
- struct klist klist_drivers;
- struct blocking_notifier_head bus_notifier;
- unsigned int drivers_autoprobe:1;
- struct bus_type *bus;
- };
- int bus_register(struct bus_type *bus)
- {
- int retval;
- struct bus_type_private *priv;
-
- priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);
-
- priv->bus = bus;
-
- bus->p = priv;
-
- BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
-
-
- retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);
-
- priv->subsys.kobj.kset = bus_kset;
-
- priv->subsys.kobj.ktype = &bus_ktype;
-
- priv->drivers_autoprobe = 1;
-
-
-
- retval = kset_register(&priv->subsys);
-
- retval = bus_create_file(bus, &bus_attr_uevent);
-
-
- priv->devices_kset = kset_create_and_add("devices", NULL,
- &priv->subsys.kobj);
-
- priv->drivers_kset = kset_create_and_add("drivers", NULL,
- &priv->subsys.kobj);
-
- klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);
- klist_init(&priv->klist_drivers, NULL, NULL);
-
- retval = add_probe_files(bus);
-
- retval = bus_add_attrs(bus);
-
- return 0;
-
- }
目前,能通过 bus_register 函数处理的工作有: 1、将 Bus 与 priv 相互建立联系
2、BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);
3、设置 bus->priv->subsys(kset).kobj 的名字为 bus->name
4、设置 bus->priv->subsys(kset).kobj.kset 指向 bus_kset
5、设置 bus->priv->subsys(kset).kobj.ktype 为 bus_ktype ,提供 show store 函数
6、设置 bus->priv->drivers_autoprobe = 1;
7、注册 bus->priv->subsys(kset) 对应于图中④与⑥的关系 由于4,且没有指定bus->priv->subsys(kset).kobj.Parent,会将 bus_kest.kobj 设置为 bus->priv->subsys(kset).kobj.Parent 因此,会将bus->priv->subsys(kset).kobj.entry 加入 bus_kest 链表,且会在/sys/bus目录下创建相应的总线目录/sys/bus/$(bus->name),例如 /sys/bus/platform
8、创建 bus_attr_uevent->attr 属性文件
9、创建并注册 devices_kset ,devices_kset.kobj.parent = bus->priv->subsys.kobj ,名字为 device ,因此会创建 /sys/bus/$(bus->name)/devices
10、创建并注册 drivers_kset ,drivers_kset.kobj.parent = bus->priv->subsys.kobj ,名字为 drivers ,因此会创建 /sys/bus/$(bus->name)/drivers
11、初始化 bus->priv->klist_devices 链表
12、初始化 bus->priv->klist_drivers 链表
13、创建 bus->bus_attrs 属性文件
下面来看个例子 ,修改自LDD3 。基于Linux 2.6.32.2 内核
-
- extern struct device ldd_bus;
- extern struct bus_type ldd_bus_type;
-
-
-
- struct ldd_driver {
- char *version;
- struct module *module;
- struct device_driver driver;
- struct driver_attribute version_attr;
- };
-
- #define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver);
-
-
- struct ldd_device {
- char *name;
- struct ldd_driver *driver;
- struct device dev;
- };
-
- #define to_ldd_device(dev) container_of(dev, struct ldd_device, dev);
-
- extern int register_ldd_device(struct ldd_device *);
- extern void unregister_ldd_device(struct ldd_device *);
- extern int register_ldd_driver(struct ldd_driver *);
- extern void unregister_ldd_driver(struct ldd_driver *);
- #include
- #include
- #include
- #include
- #include
- #include "lddbus.h"
-
- MODULE_AUTHOR("Jonathan Corbet");
- MODULE_LICENSE("Dual BSD/GPL");
- static char *Version = "$Revision: 1.9 $";
-
-
- static int ldd_match(struct device *dev, struct device_driver *drv)
- {
- struct ldd_device *pdev = to_ldd_device(dev);
-
- return !strncmp(pdev->name, drv->name, strlen(drv->name));
- }
-
- struct bus_type ldd_bus_type = {
- .name = "ldd",
- .match = ldd_match,
- };
-
-
- static ssize_t show_bus_version(struct bus_type *bus, char *buf)
- {
- return snprintf(buf, strlen(Version), "%s
", Version);
- }
-
- static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);
-
- static void ldd_bus_release(struct device *dev)
- {
- printk(KERN_DEBUG "lddbus release
");
- }
- static void ldd_dev_release(struct device *dev){ }
-
- struct device ldd_bus = {
- .init_name = "ldd0","white-space:pre">
- .release = ldd_bus_release
- };
-
- int register_ldd_device(struct ldd_device *ldddev)
- {
-
- ldddev->dev.bus = &ldd_bus_type;
- ldddev->dev.parent = &ldd_bus;
- ldddev->dev.release = ldd_dev_release;
- return device_register(&ldddev->dev);
- }
- EXPORT_SYMBOL(register_ldd_device);
-
- void unregister_ldd_device(struct ldd_device *ldddev)
- {
- device_unregister(&ldddev->dev);
- }
- EXPORT_SYMBOL(unregister_ldd_device);
-
-
- static ssize_t show_version(struct device_driver *driver, char *buf)
- {
- struct ldd_driver *ldriver = to_ldd_driver(driver);
-
- sprintf(buf, "%s
", ldriver->version);
- return strlen(buf);
- }
-
- int register_ldd_driver(struct ldd_driver *driver)
- {
- int ret;
-
- driver->driver.bus = &ldd_bus_type;
- ret = driver_register(&driver->driver);
- if (ret)
- return ret;
- driver->version_attr.attr.name = "version";
- driver->version_attr.attr.owner = driver->module;
- driver->version_attr.attr.mode = S_IRUGO;
- driver->version_attr.show = show_version;
- driver->version_attr.store = NULL;
- return driver_create_file(&driver->driver, &driver->version_attr);
- }
-
- void unregister_ldd_driver(struct ldd_driver *driver)
- {
- driver_unregister(&driver->driver);
- }
- EXPORT_SYMBOL(register_ldd_driver);
- EXPORT_SYMBOL(unregister_ldd_driver);
-
-
- static int __init ldd_bus_init(void)
- {
- int ret;
- device_register(&ldd_bus);
- ret = bus_register(&ldd_bus_type);
- if (ret)
- return ret;
- if (bus_create_file(&ldd_bus_type, &bus_attr_version))
- printk(KERN_NOTICE "Unable to create version attribute
");
-
- return ret;
- }
-
- static void ldd_bus_exit(void)
- {
- bus_unregister(&ldd_bus_type);
- }
-
- module_init(ldd_bus_init);
- module_exit(ldd_bus_exit);
insmod bus.ko 之后发现,/sys/bus 目录下多了一个 ldd目录,这个目录就是我们向内核注册的 总线 ldd ,该目录下有一个devices 和 drivers目录,因为现在并没有向该总线注册任何的驱动和设备,因此这两个文件夹是空的。 cat version 会调用show函数,显示我们在 Bus 中设置的属性。二、driver
- struct device_driver {
- const char *name;
- struct bus_type *bus;
-
- struct module *owner;
- const char *mod_name;
-
- bool suppress_bind_attrs;
-
- 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 attribute_group **groups;
-
- const struct dev_pm_ops *pm;
-
- struct driver_private *p;
- };
- int driver_register(struct device_driver *drv)
- {
- ret = bus_add_driver(drv);
- ret = driver_add_groups(drv, drv->groups);
- }
- int bus_add_driver(struct device_driver *drv)
- {
- struct bus_type *bus;
- struct driver_private *priv;
- int error = 0;
-
- 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);
-
- if (drv->bus->p->drivers_autoprobe) {
- error = driver_attach(drv);
- }
-
- klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
-
- module_add_driver(drv->owner, drv);
-
- error = driver_create_file(drv, &driver_attr_uevent);
-
- error = driver_add_attrs(bus, drv);
-
- if (!drv->suppress_bind_attrs) {
- error = add_bind_files(drv);
- }
-
- kobject_uevent(&priv->kobj, KOBJ_ADD);
- return 0;
- }
详细说一下driver匹配device的过程 在向Bus注册一个driver时,会调用到 driver_attch(drv) 来寻找与之配对的 deivice bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); 根据名字我们应该能猜测出来,调用Bus的每一个 dev 与 driver 进行 __driver_attach 在 __driver_attach 中,首先会调用到 driver_match_device 函数(return drv->bus->match ? drv->bus->match(dev, drv) : 1;)进行匹配, 如果匹配成功,则调用 driver_probe_device(drv, dev),然后调用 really_probe(dev, drv) really_probe 中干了四件大事 1、dev->driver = drv; 在dev 中记录driver ,配对成功了嘛,在男方族谱上记录一下女方的名字。。然而device_driver结构中并没有device成员,因此并没有在女方族谱上记录男方的名字。 2、driver_sysfs_add(dev) 在sysfs中该 dev.kobj 目录下创建与之匹配的driver的符号连接,名字为“driver” 在sysfs中该 driver.kobj 目录下创建与之匹配的device的符号连接,名字为 kobject_name(&dev->kobj) 3、调用 probe if (dev->bus->probe) {
ret = dev->bus->probe(dev);
} else if (drv->probe) {
ret = drv->probe(dev);
} 4、driver_bound klist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices); 如果 device 未绑定到一个 driver 链表,则将这个 device 放入 driver 链表中,看来一个device只能有一个driver,但是driver可以支持多个device总结一下 driver_register 的工作 1、初始化 drv->priv->klist_devices 链表,该链表保存该驱动所支持的devices
2、drv 与 priv 相互建立联系
3、设置 drv->priv->kobj.kset = bus->p->drivers_kset;
4、创建并注册 drv->priv->kobj ,设置 drv->priv->kobj.ktype = driver_ktype ,drv->priv->kobj.name = drv->name , drv->priv->kobj.parent = bus->p->drivers_kset.kobj 因此,会创建 /sys/bus/$(bus->name)/drivers/$(drv->name) 目录
5、调用 drv->bus->match(dev, drv) ,匹配dev ,匹配成功调用probe函数
6、将driver 加入 Bus->p->kist_drivers链表
7、创建属性文件
8、kobject_uevent(&priv->kobj, KOBJ_ADD);下面来看个例子:
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include "lddbus.h"
-
- struct ldd_driver ldd_drv = {
-
- .version = "version 1.0",
- .driver = {
- .name = "myldd",
- },
- };
-
- static int ldd_drv_init(void){
-
- register_ldd_driver(&ldd_drv);
- return 0;
- }
-
- static void ldd_drv_exit(void){
-
- unregister_ldd_driver(&ldd_drv);
- }
-
- module_init(ldd_drv_init);
- module_exit(ldd_drv_exit);
- MODULE_LICENSE("GPL");
insmod drv.ko 之后,我们会发现 /sys/bus/ldd/drivers 目录下多了一个 myldd 目录,这就是我们向内核注册的ldd总线上的myldd驱动程序。同样 cat version 会显示设定好的属性。三、device
- struct device {
- struct device *parent;
- struct device_private *p;
- struct kobject kobj;
- const char *init_name;
- struct