转载自:http://blog.csdn.net/lizuobin2/article/details/51570196
<script type="text/javascript">
var username = "lizuobin2";var _blogger = username;var blog_address = "/lizuobin2";var static_host = "http://static.blog.csdn.net";
var currentUserName = ""; var fileName = '51570196';var commentscount = 0; var islock = false
window.quickReplyflag = true;
var totalFloor=0;
var isBole = false;
var isDigg = false;
var isExpert=false;
var isAdm=false;
</script>
<script src="//static.blog.csdn.net/public/switchHome/switchHome.js?v=2017.044"></script>
<script type="text/javascript" src="http://static.blog.csdn.net/Skin/skin3-template/fonts/iconfont.js"></script>
<script src="//csdnimg.cn/rabbit/exposure-click/main.js?v1.15.23"></script>
<script type="text/javascript" src="http://c.csdnimg.cn/pubfooter/js/tracking_for_recommend.js?v=0911" charset="utf-8"></script>
<script type="text/javascript" src="http://csdnimg.cn/pubfooter/js/tracking.js" charset="utf-8"></script>
<script type="text/javascript" src="http://static.blog.csdn.net/scripts/cnick.js" charset="utf-8"></script>
<link rel="stylesheet" href="http://static.blog.csdn.net/code/prettify.css" />
<script type="text/javascript" src="http://static.blog.csdn.net/code/prettify.js"></script>
<script type="text/javascript">
// Traffic Stats of the entire Web site By baidu
var _hmt = _hmt || [];
(function() {
var hm = document.createElement("script");
hm.src = "https://hm.baidu.com/hm.js?6bcd52f51e9b3dce32bec4a3997715ac";
var s = document.getElementsByTagName("script")[0];
s.parentNode.insertBefore(hm, s);
})();
// Traffic Stats of the entire Web site By baidu end
</script>
<meta name="description" content="在内核里,有各种各样的总线,如 usb_bus_type、spi_bus_type、pci_bus_type、platform_bus_type、i2c_bus_type 等,内核通过总线将设备与驱动分离。此文,基于 Linux2.6.32.2 简单分析设备驱动模型,以后看具体的总线设备模型时会更加清晰。
一、总线
/sys/bus 目录
int __init buses_ini” />
<title>Linux 设备总线驱动模型 - CSDN博客</title>
Linux 设备总线驱动模型
#include <linux/device.h>
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/string.h> #include "lddbus.h" MODULE_AUTHOR("Jonathan Corbet"); MODULE_LICENSE("Dual BSD/GPL"); static char *Version = "
Revision:1.9
R
e
v
i
s
i
o
n
:
1.9
"; //--------------------------------- bus ---------------------------------------- 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, }; //--------------------------------- device -------------------------------------- static ssize_t show_bus_version(struct bus_type *bus, char *buf) { return snprintf(buf, strlen(Version), "%s\n", Version); } static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL); // parent device static void ldd_bus_release(struct device *dev) { printk(KERN_DEBUG "lddbus release\n"); } static void ldd_dev_release(struct device *dev){ } struct device ldd_bus = { .init_name = "ldd0",<span style="white-space:pre"> </span>// ldd0 就是总线的名字,这里改成 ldd_bus 更恰当 .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); //--------------------------------- driver -------------------------------------- static ssize_t show_version(struct device_driver *driver, char *buf) { struct ldd_driver *ldriver = to_ldd_driver(driver); sprintf(buf, "%s\n", 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); //--------------------------------- bus ---------------------------------------- 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\n"); 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; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
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;
// 在/sys/bus/xxx/drivers 目录下创建目录
priv->kobj.kset = bus->p->drivers_kset;
error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,
"%s", drv->name);
// 匹配 dev
if (drv->bus->p->drivers_autoprobe) {
error = driver_attach(drv);
}
// 将driver 加入 Bus->p->kist_drivers链表
klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers);
// 如果设置了drv->mod_name 根据名字寻找模块
module_add_driver(drv->owner, drv);
// 在/sys/bus/xxx/drivers/创建属性文件
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 链表,该链表保存该驱动所支持的devices2、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/ (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 <linux/module.h>
#include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/device.h> #include <linux/sched.h> #include <asm/uaccess.h> #include <linux/io.h> #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; /* initial name of the device */
struct device_type *type;
struct semaphore sem; /* semaphore to synchronize calls to
struct bus_type *bus; /* type of bus device is on */
struct device_driver *driver; /* which driver has allocated this device */
void *platform_data; /* Platform specific data, device core doesn't touch it */
struct dev_pm_info power;
/* arch specific additions */
struct dev_archdata archdata;
dev_t devt; /* dev_t, creates the sysfs "dev" */
spinlock_t devres_lock;
struct list_head devres_head;
struct klist_node knode_class;
struct class *class;
const struct attribute_group **groups; /* optional groups */
void (*release)(struct device *dev);
};
int device_register(struct device *dev)
{
device_initialize(dev);
return device_add(dev);
}
void device_initialize(struct device *dev)
{
// 设置 dev->kobj.kset 为 devices_kset
dev->kobj.kset = devices_kset;
kobject_init(&dev->kobj, &device_ktype);
INIT_LIST_HEAD(&dev->dma_pools);
init_MUTEX(&dev->sem);
spin_lock_init(&dev->devres_lock);
INIT_LIST_HEAD(&dev->devres_head);
device_init_wakeup(dev, 0);
device_pm_init(dev);
set_dev_node(dev, -1);
}
int device_add(struct device *dev)
{
struct device *parent = NULL;
struct class_interface *class_intf;
dev = get_device(dev);
if (!dev->p) {
error = device_private_init(dev);
}
// 如果设置了 init_name 将 init_name 设置为dev->kobj->name
if (dev->init_name) {
dev_set_name(dev, "%s", dev->init_name);
dev->init_name = NULL;
}
// 将 parent_device.kobj 设置为dev->kobj->parent
parent = get_device(dev->parent);
setup_parent(dev, parent); // 这里需要根据 实例分析
/* use parent numa_node */
if (parent)
set_dev_node(dev, dev_to_node(parent));
// 在 xxxx 目录下创建目录 xxxx是其父设备 例如platform_bus
// 如果没有device->parent 则在/sys/ 目录下创建
error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);
/* notify platform of device entry */
if (platform_notify)
platform_notify(dev);
// 创建属性文件
error = device_create_file(dev, &uevent_attr);
// 如果有主次设备号 创建dev 属性文件
if (MAJOR(dev->devt)) {
error = device_create_file(dev, &devt_attr);
error = device_create_sys_dev_entry(dev);
devtmpfs_create_node(dev);
}
// 从 dev->class->p->class_subsys.kobj 目录下创建到 /sys/devices/xxxx/subsystem 的软连接
error = device_add_class_symlinks(dev);
// 设置属性文件
error = device_add_attrs(dev);
error = bus_add_device(dev);
error = dpm_sysfs_add(dev);
device_pm_add(dev);
/* Notify clients of device addition. This call must come
* after dpm_sysf_add() and before kobject_uevent().
*/
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); // device_attach(dev); 匹配drv
if (parent)
klist_add_tail(&dev->p->knode_parent,
&parent->p->klist_children);
if (dev->class) {
}
}
int bus_add_device(struct device *dev)
{
if (bus) {
// 创建属性文件
error = device_add_attrs(bus, dev);
// 创建 /sys/bus/$(bus->name)/devices/$(dev->name) 到 /sys/devices/$(dev->name) 的软连接
error = sysfs_create_link(&bus->p->devices_kset->kobj,
&dev->kobj, dev_name(dev));
// 创建 /sys/devices/$(dev->name)/subsystem 到 /sys/bus/$(bus->name)/devices/$(dev->name) 的软连接
error = sysfs_create_link(&dev->kobj,
&dev->bus->p->subsys.kobj, "subsystem");
// 创建 /sys/devices/$(dev->name)/bus 到 /sys/bus/$(bus->name)/devices/$(dev->name) 的软连接
error = make_deprecated_bus_links(dev); // return sysfs_create_link(&dev->kobj, &dev->bus->p->subsys.kobj, "bus");
// 将 dev 加入 bus->p->klist_devices 链表
klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);
}
return 0;
}
总结一下 device_register 函数的工作
1、设置 dev->kobj.kset 为 devices_kset
2、设置 dev->kobj.ktype 为 device_ktype
3、如果设置了 init_name 将 init_name 设置为dev->kobj->name
4、将 dev->kobj->parent 设置为 parent_device.kobj
5、在 xxxx 目录下创建目录 xxxx是其父设备 例如platform_bus 如果没有device->parent 则在/sys/ 目录下创建
6、platform_notify
7、创建属性文件
8、如果设置了 设备号,则创建属性文件 dev
9、创建各种软连接,其中/sys/bus/xxx/devices/目录下 的目录 为 /sys/devices 目录的软件接
10、blocking_notifier_call_chain
11、kobject_uevent
12、bus_probe_device 匹配drv ,这个匹配过程和前面注册driver时是一样的,最终都会调用到 really_probe ,匹配成功则调用probe函数,不再赘述。
下面来看个例子:
#include <linux/module.h>
#include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/device.h> #include <linux/sched.h> #include <asm/uaccess.h> #include "lddbus.h" static dev_t devid; static struct ldd_device ldd_dev = { .name = "myldd", .dev = { .init_name = "myldd", }, }; static int ldd_dev_init(void){ alloc_chrdev_region(&devid, 0, 1, "mylddtest"); //ldd_dev.dev.devt = devid; register_ldd_device(&ldd_dev); return 0; } static void ldd_dev_exit(void){ unregister_ldd_device(&ldd_dev); } module_init(ldd_dev_init); module_exit(ldd_dev_exit); MODULE_LICENSE("GPL");
device 相对driver 要复杂一些,insmod dev.ko 之后,我们可以在/sys/devices 目录下看到新增了一个目录 ldd0(ldd_bus) ,在 ldd0 (ldd_bus)目录下看到我们向ldd总线注册的myldd设备(ldd0是 myldd 的父设备),在/sys/bus/ldd/devices/ 目录下同样可以看到 myldd , 因为这里的Myldd 是指向 /sys/devices/ldd0/myldd 的软连接。
/sys/devices/ldd0/myldd/driver 目录 与该设备匹配的驱动程序,我们在Bus->match中设置的匹配条件–名字相同。
我们并未看到属性文件 dev ,是因为我们没有指定Myldd设备的设备号,将 dev.c 代码中的 ldd_dev.dev.devt = devid 注释去掉,卸载原来驱动,重新加载。
我们会发现,现在有了属性文件 dev ,cat dev 显示的是该设备的设备号,在此条件之下,mdev 会帮我们创建设备节点,因此在 /dev 目录下已经有了设备节点。
至此,简单的设备总线驱动模型就结束了,虽然例子很简单,但我相信对以后的理解各类负责的总线设备驱动模型也是会有帮助的。