本文将简要地介绍Linux总线设备驱动模型及其实现方式,最后,本文将会以平台总线为例介绍设备和驱动程序的实现过程。
1 总线设备驱动模型总体介绍及其实现方式
1.1 总线设备驱动模型总体介绍
如果cpu和外设或者外设和外设想要进行通讯,需要将通信的双方挂载到一条总线上,这里的总线可以是具体的总线,如IIC、CAN总线等,也可以是虚拟的总线,如平台总线。随着技术的不断进步,系统的拓扑结构也越来越复杂,对热插拔,跨平台移植性的要求也越来越高,2.4内核已经难以满足这些需求。为适应这种形势的需要,从Linux 2.6内核开始提供了全新的设备模型(当然也可以选择不用这样的方式实现)。其示意图如下所示:

总线设备驱动模型可分为总线、设备和驱动三个部分,当将一个设备加入到总线上时,内核会在这条总线上寻找该设备对应的驱动;当将一个驱动加入到一条总线上时,内核会在该总线上寻找与该驱动对应的设备。匹配的规则根据不同类型的总线及设备特征进行定义。
1.2 总线实现方式
(1):总线的描述
在 Linux 内核中, 总线由 bus_type 结构表示,定义在
struct bus_type {
const char *name; /*总线名称*/
int (*match) (struct device *dev, struct
device_driver *drv); /*驱动与设备的匹配函数*/
………
}
其中,match函数的定义为:
int (*match)(struct device * dev, struct device_driver * drv)
设备和驱动的匹配规则在此函数中定义,match函数的职责为:当一个新设备或者新驱动被添加到这个总线时,该函数被调用,用于判断指定的驱动程序是否能处理指定的设备。若可以,则返回非零,若不可以则返回0值。
(2):总线的注册和注销
总线的注册使用如下函数
bus_register(struct bus_type *bus)
若成功,新的总线将被添加进系统,并可在/sys/bus 下看到相应的目录。
总线的注销使用:
void bus_unregister(struct bus_type *bus)
若成功,则总线将会从系统中删除。
1.3 设备的实现方式
(1):设备的描述方式
在 Linux内核中, 设备由struct device结构表示。
struct device {
{
const char *init_name; /*设备的名字*/
struct bus_type *bus; /*设备所在的总线*/
………
}
其中,init_name为设备的名称,*bus指向总线的描述结构。
(2):设备的注册和注销
设备的注册使用如下函数:
int device_register(struct device *dev);
当一个设备成功地添加到一条总线上时可以在/sys/bus/xxx_bus/目录下看到相应的设备。
设备的注销使用如下函数:
void device_unregister(struct device *dev);
1.4 驱动实现方式
(1):驱动的描述方式
在 Linux内核中, 驱动由 device_driver结构表示:
struct device_driver {
{
const char *name; /*驱动名称*/
struct bus_type *bus; /*驱动程序所在的总线*/
int (*probe) (struct device *dev);/*设备匹配成功后调用的函数*/
………
}
其中,*name为驱动的名称,可以作为一种设备和驱动匹配的条件;*bus指向所要加入的总线的描述结构;*probe函数指针指向一个操作函数,该函数会在设备和驱动匹配成功之后(即match函数返回为1时)被调用,一般用来完成对设备的初始化操作。
(2):驱动的注册和注销
驱动的注册使用如下函数
int driver_register(struct device_driver *drv);
驱动的注销使用:
void driver_unregister(struct device_driver *drv);
2 平台设备驱动举例
上面介绍了总线驱动的相关数据结构,下面以平台设备驱动为例进一步分析。有了上面的总线驱动的基本数据结构,linux其他总线驱动都是对上面数据结构的再封装。
下面先列举一个简单按键驱动:
2.1 device 注册
#include
#include
#include
#include MODULE_LICENSE("GPL");
#define GPFCON 0x56000050
//所用的资源描述
static struct resource key_resource[] = {[0] = {.start = GPFCON,.end = GPFCON + 8,.flags = IORESOURCE_MEM,},[1] = {.start = IRQ_EINT0,.end = IRQ_EINT2,.flags = IORESOURCE_IRQ,},
};
struct platform_device key_device = {.name = "my-key",.id = 0,.num_resources = ARRAY_SIZE(key_resource),.resource = key_resource,
};
static int button_init()
{platform_device_register(&key_device);return 0;
}
static void button_exit()
{ platform_device_unregister(&key_device);
}
module_init(button_init);
module_exit(button_exit);
2.2 driver注册
#include
#include
#include
#include
#include
#include
#include
#include MODULE_LICENSE("GPL");struct work_struct *work;struct timer_list buttons_timer;unsigned int key_num = 0;wait_queue_head_t key_q;struct resource *res;
struct resource *res_irq;
unsigned int *key_base;
void work_func(struct work_struct *work)
{mod_timer(&buttons_timer, jiffies + (HZ /10));
}
void buttons_timer_function(unsigned long data)
{unsigned int key_val;key_val = readw(key_base+1)&0x1; if (key_val == 0)key_num = 4;key_val = readw(key_base+1)&0x4;if (key_val == 0)key_num = 3;wake_up(&key_q);
}
irqreturn_t key_int(int irq, void *dev_id)
{//1. 检测是否发生了按键中断//2. 清除已经发生的按键中断//3. 提交下半部schedule_work(work);//return 0;return IRQ_HANDLED;}
void key_hw_init()
{unsigned short data;data = readw(key_base);data &= ~0b110011;data |= 0b100010;writew(data,key_base);
}
int key_open(struct inode *node,struct file *filp)
{return 0;
}
ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
{ wait_event(key_q,key_num);copy_to_user(buf, &key_num, 4);key_num = 0;return 4;
}
struct file_operations key_fops =
{.open = key_open,.read = key_read,
};
struct miscdevice key_miscdev = {.minor = 200,.name = "key",.fops = &key_fops,
};
int key_probe(struct platform_device *pdev)
{int ret,size;ret = misc_register(&key_miscdev);if (ret !=0)printk("register fail!\n");//注册中断处理程序res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);request_irq(res_irq->start,key_int,IRQF_TRIGGER_FALLING,"key",(void *)4);request_irq(res_irq->end,key_int,IRQF_TRIGGER_FALLING,"key",(void *)3);//按键初始化res = platform_get_resource(pdev, IORESOURCE_MEM, 0);size = (res->end - res->start) + 1;key_base = ioremap(res->start, size);key_hw_init();//. 创建工作work = kmalloc(sizeof(struct work_struct),GFP_KERNEL);INIT_WORK(work, work_func);/* 初始化定时器 */ init_timer(&buttons_timer); buttons_timer.function = buttons_timer_function; /* 向内核注册一个定时器 */ add_timer(&buttons_timer); /*初始化等待队列*/init_waitqueue_head(&key_q);return 0;
}
int key_remove(struct platform_device *dev)
{free_irq(res_irq->start, (void *)4);free_irq(res_irq->end, (void *)3);iounmap(key_base);misc_deregister(&key_miscdev);return 0;
}
static struct platform_driver key_driver = {.probe = key_probe,.remove = key_remove,.driver = {.owner = THIS_MODULE,.name = "my-key",},
};
static int button_init()
{return platform_driver_register(&key_driver);
}
static void button_exit()
{ platform_driver_unregister(&key_driver);
}
module_init(button_init);
module_exit(button_exit);
3 平台设备驱动详解
3.1 平台总线初始化
系统初始化的时候,会初始化平台总线:
kernel_init
----------->kernel_init_freeable
-------------->do_basic_setup
---------------->driver_init
------------------>platform_bus_init
struct device platform_bus = {.init_name = "platform",
};
struct bus_type platform_bus_type = {.name = "platform",.dev_attrs = platform_dev_attrs,.match = platform_match,.uevent = platform_uevent,.pm = &platform_dev_pm_ops,
};
int __init platform_bus_init(void)
{int error;early_platform_cleanup();
-------------------------------------------------------------(1)error = device_register(&platform_bus);//平台总线也被抽象成一个设备,所以注册平台总线设备if (error)return error;
-------------------------------------------------------------(2)error = bus_register(&platform_bus_type);//注册平台总线if (error)device_unregister(&platform_bus);return error;
}
(1)平台总线也被抽象成一个设备,device_register的详细说明,可以参考这篇文章:
https://blog.csdn.net/oqqYuJi12345678/article/details/103057226
该函数会在/sys/devices/下面注册platform文件夹,后面注册的平台设备会放到该platform文件夹下
(2)向内核总线系统注册平台总线
内核通过 bus_register 进行 Bus 注册,注册到了 bus_kest
系统初始化的时候有如下代码:
int __init buses_init(void)
{// /sys/bus 目录 这里创建的bus_kset = kset_create_and_add("bus", &bus_uevent_ops, NULL);if (!bus_kset)return -ENOMEM;return 0;
}
下面看一下bus_register具体做了什么:
int bus_register(struct bus_type *bus)
{int retval;struct bus_type_private *priv;priv = kzalloc(sizeof(struct bus_type_private), GFP_KERNEL);
/* 1. bus 与 prv 相互建立联系 */// 私有数据 .bus -> bus 本身priv->bus = bus;// bus->p 指向 privbus->p = priv;// 内核通知链BLOCKING_INIT_NOTIFIER_HEAD(&priv->bus_notifier);/* 设置 bus->prv->subsys->kobj */// 设置 priv->subsys.kobj.name = bus->name 对应于/sys/ 目录下的目录名retval = kobject_set_name(&priv->subsys.kobj, "%s", bus->name);// 所有的 priv->subsys.kobj.kset 指向 bus_kse 对应于图中④与六的关系priv->subsys.kobj.kset = bus_kset;// 所有的priv->subsys.kobj.ktype 等于 bus_ktypepriv->subsys.kobj.ktype = &bus_ktype;priv->drivers_autoprobe = 1;//device和driver注册的时候会检查该位/* 注册 kset (bus->prv->subsys priv->devices_kset priv->drivers_kset) */ // 注册 priv->subsys ,由于 priv->subsys.kobj.kset = bus_kset,所以会在 /sys/bus/目录下创建 目录 如/sys/bus/platformretval = kset_register(&priv->subsys);// sysfs_create_file(&bus->p->subsys.kobj, &bus_attr_uevent->attr);retval = bus_create_file(bus, &bus_attr_uevent);// 由于 priv->subsys.kobj.kset = bus_kset ,因此会创建 /sys/bus/XXX/devices 目录 如 /sys/bus/platform/devicespriv->devices_kset = kset_create_and_add("devices", NULL,&priv->subsys.kobj);// 同理 创建 /sys/bus/XXX/devices 目录 如 /sys/bus/platform/driverspriv->drivers_kset = kset_create_and_add("drivers", NULL,&priv->subsys.kobj);// 初始化 klist_devices 并设置get put 函数 初始化 klist_drivers 不知为何没有get put ?klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put);klist_init(&priv->klist_drivers, NULL, NULL);retval = add_probe_files(bus); // static inline int add_probe_files(struct bus_type *bus) { return 0; }// 添加 bus->attrs 属性文件retval = bus_add_attrs(bus);return 0;}
上面函数主要是在/sys/bus/下面注册了platform目录,然后在/sys/bus/platform/目录下面创建devices和drivers目录,以及其他的一些属性文件。
看一下/sys/bus/platform/具体有哪些文件和目录:
3.2 平台设备注册
上面的平台设备驱动调用了platform_device_register函数注册device:
int platform_device_register(struct platform_device *pdev)
{device_initialize(&pdev->dev);arch_setup_pdev_archdata(pdev);return platform_device_add(pdev);
}
platform_device_register函数先通过device_initialize函数初始化platform_device的device成员,然后调用platform_device_add向内核添加一个平台设备。
int platform_device_add(struct platform_device *pdev)
{int i, ret = 0;if (!pdev) /* 如果pdev为空则返回EINVAL */return -EINVAL;/* 如果pdev->dev.parent为空则将pdev->dev.parent设置为platform_bus */if (!pdev->dev.parent)pdev->dev.parent = &platform_bus;pdev->dev.bus = &platform_bus_type; /* 设置总线类型 */if (pdev->id != -1) /* 如果id = -1则表示自动分配name */dev_set_name(&pdev->dev, "%s.%d", pdev->name, pdev->id);elsedev_set_name(&pdev->dev, pdev->name);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);p = r->parent;if (!p) {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)) {printk(KERN_ERR"%s: failed to claim resource %d\n",dev_name(&pdev->dev), i);ret = -EBUSY;goto failed;}}pr_debug("Registering platform device '%s'. Parent at %s\n",dev_name(&pdev->dev), dev_name(pdev->dev.parent));/* 向内核添加一个device */ret = device_add(&pdev->dev);if (ret == 0)return ret;failed:while (--i >= 0) {struct resource *r = &pdev->resource[i];unsigned long type = resource_type(r);if (type == IORESOURCE_MEM || type == IORESOURCE_IO)release_resource(r);}return ret;
}
可以看到platform_device_register函数就是对device_add的封装。已经讨论过几回了,现在重点关注device_add中的几个部分:
int device_add(struct device *dev)
{struct device *parent = NULL;struct kobject *kobj;struct class_interface *class_intf;int error = -EINVAL;
..........................................................//在上面函数中dev->parent被设置为platform_bus,所以后面会在/sys/devices/platform/下面创建设备文件parent = get_device(dev->parent);kobj = get_device_parent(dev, parent);if (kobj)dev->kobj.parent = kobj;/* use parent numa_node */if (parent)set_dev_node(dev, dev_to_node(parent));/* first, register with generic layer. *//* we require the name to be set before, and pass NULL */
//在/sys/devices/platform/下面创建设备文件error = kobject_add(&dev->kobj, dev->kobj.parent, NULL);if (error)goto Error;..............................................................
--------------------------------------------------------(1)error = bus_add_device(dev);
.................................................if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_ADD_DEVICE, dev);kobject_uevent(&dev->kobj, KOBJ_ADD);
-----------------------------------------------------------(2)bus_probe_device(dev);if (parent)//添加到bus的链表上klist_add_tail(&dev->p->knode_parent,&parent->p->klist_children);。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。
}
(1)bus_add_device的最重要工作是把device添加到bus的链表中:
int bus_add_device(struct device *dev)
{struct bus_type *bus = bus_get(dev->bus);int error = 0;if (bus) {pr_debug("bus: '%s': add device %s\n", bus->name, dev_name(dev));error = device_add_attrs(bus, dev);if (error)goto out_put;error = sysfs_create_link(&bus->p->devices_kset->kobj,&dev->kobj, dev_name(dev));if (error)goto out_id;error = sysfs_create_link(&dev->kobj,&dev->bus->p->subsys.kobj, "subsystem");if (error)goto out_subsys;klist_add_tail(&dev->p->knode_bus, &bus->p->klist_devices);//把device添加到bus链表中}return 0;out_subsys:sysfs_remove_link(&bus->p->devices_kset->kobj, dev_name(dev));
out_id:device_remove_attrs(bus, dev);
out_put:bus_put(dev->bus);return error;
}
(2)bus_probe_device遍历bus上面的driver链表,看看是否有匹配的drvier要probe:
void bus_probe_device(struct device *dev)
{struct bus_type *bus = dev->bus;struct subsys_interface *sif;int ret;if (!bus)return;if (bus->p->drivers_autoprobe) { //bus在之前注册时候设置了drivers_autoproberet = device_attach(dev);//执行该函数WARN_ON(ret < 0);}mutex_lock(&bus->p->mutex);list_for_each_entry(sif, &bus->p->interfaces, node)if (sif->add_dev)sif->add_dev(dev, sif);mutex_unlock(&bus->p->mutex);
}
调用device_attach进行匹配:
int device_attach(struct device *dev)
{int ret = 0;device_lock(dev);
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。ret = bus_for_each_drv(dev->bus, NULL, dev, __device_attach);pm_request_idle(dev);out_unlock:device_unlock(dev);return ret;
}
主要函数为bus_for_each_drv:
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start,void *data, int (*fn)(struct device_driver *, void *))
{struct klist_iter i;struct device_driver *drv;int error = 0;if (!bus)return -EINVAL;klist_iter_init_node(&bus->p->klist_drivers, &i,start ? &start->p->knode_bus : NULL);while ((drv = next_driver(&i)) && !error)//遍历bus->p->klist_drivers链表,调用__device_attach进行匹配error = fn(drv, data);klist_iter_exit(&i);return error;
}
依次调用bus上面挂载的driver,利用__device_attach函数进行匹配:
static inline int driver_match_device(struct device_driver *drv,struct device *dev)
{return drv->bus->match ? drv->bus->match(dev, drv) : 1;
}
static int __device_attach(struct device_driver *drv, void *data)
{struct device *dev = data;if (!driver_match_device(drv, dev))return 0;return driver_probe_device(drv, dev);
}
匹配成功以后,driver_match_device函数返回不为0,进一步调用driver_probe_device函数,进行driver probe。
从前面知道platform总线的match函数为platform_match:
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);/* Attempt an OF style match first */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI style match */if (acpi_driver_match_device(dev, drv))return 1;/* Then try to match against the id table */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);
}
可以看到较新版的内核除了匹配设备和驱动名字,还有其他多种匹配手段了。再看一下driver_probe_device函数:
int driver_probe_device(struct device_driver *drv, struct device *dev)
{int ret = 0;
//device已经注册过,则不再probeif (!device_is_registered(dev))return -ENODEV;pr_debug("bus: '%s': %s: matched device %s with driver %s\n",drv->bus->name, __func__, dev_name(dev), drv->name);pm_runtime_barrier(dev);ret = really_probe(dev, drv);pm_request_idle(dev);return ret;
}static int really_probe(struct device *dev, struct device_driver *drv)
{int ret = 0;dev->driver = drv; //device 指向driver
。。。。。。。。。。。。。。。。。。。ret = drv->probe(dev);//调用driver的probe函数
。。。。。。。。。。。。。。。。。。。。driver_bound(dev);//driver和device绑定ret = 1;}
static void driver_bound(struct device *dev)
{
。。。。。。。。。。。。。。。。。。。。
//把device 挂入driver链表,一个driver可以匹配多个deviceklist_add_tail(&dev->p->knode_driver, &dev->driver->p->klist_devices);/** Make sure the device is no longer in one of the deferred lists and* kick off retrying all pending devices*/driver_deferred_probe_del(dev);driver_deferred_probe_trigger();if (dev->bus)blocking_notifier_call_chain(&dev->bus->p->bus_notifier,BUS_NOTIFY_BOUND_DRIVER, dev);
}
可以看到最后会调用driver的probe函数,并且把device挂入driver的链表中。一个driver可以对应多个device。需要注意的是,上面really_probe里面调用的probe函数还不是真正平台驱动的probe函数,他是平台设备驱动框架提供的probe函数platform_drv_probe:
static int platform_drv_probe(struct device *_dev)
{struct platform_driver *drv = to_platform_driver(_dev->driver);//获取平台驱动结构struct platform_device *dev = to_platform_device(_dev);int ret;if (ACPI_HANDLE(_dev))acpi_dev_pm_attach(_dev, true);ret = drv->probe(dev);//调用平台驱动的probeif (ret && ACPI_HANDLE(_dev))acpi_dev_pm_detach(_dev, true);return ret;
}
3.4 平台驱动注册
上面的例子中调用platform_driver_register来向内核注册平台驱动:
int platform_driver_register(struct platform_driver *drv)
{drv->driver.bus = &platform_bus_type;if (drv->probe)//platform_driver 设置了probe函数,则device_driver需要设置platform_drv_probe,匹配的时候先会进入通用驱动框架,调用device_driver的probedrv->driver.probe = platform_drv_probe;if (drv->remove)drv->driver.remove = platform_drv_remove;if (drv->shutdown)drv->driver.shutdown = platform_drv_shutdown;return driver_register(&drv->driver);
}
driver_register的核心函数为bus_add_driver
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/platform/drivers 目录下创建目录priv->kobj.kset = bus->p->drivers_kset;error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL,"%s", drv->name);// 匹配 devif (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/platform/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来进行配对:
int driver_attach(struct device_driver *drv)
{return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);
}
int bus_for_each_dev(struct bus_type *bus, struct device *start,void *data, int (*fn)(struct device *, void *))
{struct klist_iter i;struct device *dev;int error = 0;if (!bus || !bus->p)return -EINVAL;klist_iter_init_node(&bus->p->klist_devices, &i,(start ? &start->p->knode_bus : NULL));while ((dev = next_device(&i)) && !error)error = fn(dev, data);klist_iter_exit(&i);return error;
}
static int __driver_attach(struct device *dev, void *data)
{struct device_driver *drv = data;/** Lock device and try to bind to it. We drop the error* here and always return 0, because we need to keep trying* to bind to devices and some drivers will return an error* simply if it didn't support the device.** driver_probe_device() will spit a warning if there* is an error.*/if (!driver_match_device(drv, dev))return 0;if (dev->parent) /* Needed for USB */device_lock(dev->parent);device_lock(dev);if (!dev->driver)driver_probe_device(drv, dev);device_unlock(dev);if (dev->parent)device_unlock(dev->parent);return 0;
}
int driver_probe_device(struct device_driver *drv, struct device *dev)
{int ret = 0;if (!device_is_registered(dev))return -ENODEV;pr_debug("bus: '%s': %s: matched device %s with driver %s\n",drv->bus->name, __func__, dev_name(dev), drv->name);pm_runtime_barrier(dev);ret = really_probe(dev, drv);pm_request_idle(dev);return ret;
}
可以看到和device匹配driver的过程是类似的,不再展开细说了。
该文有些内容整理自:https://www.cnblogs.com/blackeyes/p/5066948.html