嵌入式Linux设备驱动开发(一)

2019-07-12 23:03发布

设备驱动开发是Linux开发领域一个非常重要的部分,在Linux源码的85%都是驱动程序的代码。设备驱动开发不仅需要了解硬件底层的知识,还需要拥有操作系统的背景。驱动程序追求的是高效,稳定,驱动程序发生的问题有可能直接导致整个系统的崩溃。
驱动程序不主动运行,意味着驱动程序是等待应用程序来调用的。应用程序通过内核来调用驱动程序,实现与实际硬件设备的通信。Linux外设通常可以分为字符型设备、块设备、网络设备三种类型。
设备驱动开发很复杂,好的驱动不好写,比如一个驱动程序被多个进程使用,这就需要考虑到并发和竞态的问题。 驱动程序可以在编译内核时静态编译到内核当中,也可以通过模块化的方式在需要时进行加载。驱动程序在模块化编译完成后会以.ko作为扩展名,我们可以使用insmod命令进行加载,使用rmmod命令进行卸载,可以使用lsmod命令查看当前运行的内核当中已经加载的模块。 1、在通常的驱动程序代码中,我们可以找到一下两个函数: module_init(irfpa_module_init); module_exit(irfpa_module_exit); 当我们使用insmod命令加载模块时,模块化的初始化函数会自动被调用,用来向内核注册驱动程序。系统使用module_init来标记初始化函数。当使用rmmod命令卸载模块时,模块的清除函数被调用。清除函数使用 module_exit来标记。
模块化的结构是Linux推荐的一种方式,其方便系统的精简和组织,这也是Linux很好的一个特性,实现了在运行时可扩展的目的。 **2、**sys文件系统简介
在2.6内核之前,绝大多数的驱动程序最终都被映射到/dev目录下,对于字符型驱动程序而言,采用file_operations类型的数据结构来组织驱动程序。这使得/dev目录变得越来越繁琐,对与普通用户而言,变得不具可读性。之后就采用sys文件系统来组织驱动。
sys文件系统是一个类似于proc文件系统的特殊文件系统,它由系统自动维护,用于将系统中的设备组织成层次结构,并向用户模式程序提供详细的内核数据结构信息。在sys目录下有:
block:包含所有的块设备
devices:包含系统所有的设备,并根据设备挂载的总线类型组织成层次结构。
bus:包含系统中所有的总线类型。
drivers:包含内核中所有已注册的设备驱动程序。
class:包含系统中的设备类型(我们自定义的设备类型就在此目录中)
对于用户程序而言,若要对设备进行操作,只需在sys目录下找到对应的文件,对其进行读写即可完成。 3、设备驱动程序的作用在于提供机制,而不是策略。简单的说驱动程序是实现“需要什么功能”(机制),而用户程序利用驱动程序“如何使用这些功能”(策略)来完成不同的任务。我觉得就是我们编出一个模块,用户程序调用这个模块。
在1、中两个宏帮助我们向内核标记了模块的初始化与清除函数。在驱动程序中主要的注册信息都在irfpa_module_init这个函数中完成了。命令insmod将自动调用这个函数。 /**注册设备配置,注册驱动platform_driver_register * irfpa_module_init - register the Device Configuration. * Returns 0 on success, otherwise negative error. */ static int __init irfpa_module_init(void) { return platform_driver_register(&irfpa_platform_driver); } 从Linux2.6内核起,引入一套新的驱动管理和注册机制:platform_device 和 platform_driver 。Linux 中大部分的设备驱动,都可以使用这套机制,设备用 platform_device 表示;驱动用 platform_driver 进行注册。platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序中用使用这些资源时,通过platform device提供的标准接口进行申请并使用。 platform 是一个虚拟的地址总线,相比 PCI、USB,它主要用于描述SOC上的片上资源。platform 所描述的资源有一个共同点:在CPU 的总线上直接取址。平台设备会分到一个名称(用在驱动绑定中)以及一系列诸如地址和中断请求号(IRQ)之类的资源。 platform_device_系列函数,实际上是注册了一个叫platform的虚拟总线。使用约定是如果一个不属于任何总线的设备,例如蓝牙,串口等设备,都需要挂在这个虚拟总线上。 4、要用注册一个platform驱动的步骤:
1,注册设备platform_device_register
2,注册驱动platform_driver_register,过程中在系统寻找注册的设备(根据.name),找到后运行.probe进行初始化。
注册时候的两个名字必须一样,才能match上,才能work。
在设备树中,已经把FPGA实现的逻辑作为设备挂载在ARM处理器的操作系统中,这里就不用注册设备了,只需要注册驱动就好了。 irfpa: irfpa@43c00000 { compatible = "sitp,irfpa-1.00.a"; reg = < 0x43c00000 0x100 0x43000000 0x100 0x18000000 0x8000000 0x40000000 0x800>; //irfpa vdma irfpa_buf irfpa_xinf }; platform_driver_register(&irfpa_platform_driver) 会向系统注册irfpa_platform_driver这个驱动程序,这个函数会根据 irfpa_platform_driver中的”sitp,irfpa-1.00.a”内容,搜索系统注册的device中有没有这个platform_device,如果有,就会执行 platform_driver(也就是irfpa_platform_driver的类型)中的.probe函数。 注意:对只需要初始化运行一次的函数都加上__init属性,__init 宏告诉编译器如果这个模块被编译到内核则把这个函数放到(.init.text)段,module_exit的参数卸载时同__init类似,如果驱动被编译进内核,则__exit宏会忽略清理函数,因为编译进内核的模块不需要做清理工作,显然__init和__exit对动态加载的模块是无效的,只支持完全编译进内核。
这里实现的是驱动:
驱动注册中,需要实现的结构体是:platform_driver 。 /* Driver Structure */ static struct platform_driver irfpa_platform_driver = { .probe = irfpa_drv_probe, .remove = __devexit_p(irfpa_drv_remove), .driver = { .owner = THIS_MODULE, .name = DRIVER_NAME, .of_match_table = irfpa_of_match, //好像置为NULL了 }, }; #ifdef CONFIG_OF static struct of_device_id irfpa_of_match[] __devinitdata = { { .compatible = "sitp,irfpa-1.00.a", }, { /* end of table */} }; MODULE_DEVICE_TABLE(of, irfpa_of_match); #else #define irfpa_of_match NULL #endif /* CONFIG_OF */ 在驱动程序的初始化函数中,调用了platform_driver_register()注册 platform_driver 。需要注意的是:platform_driver 和 platform_device 中的 name 变量的值必须是相同的 。这样在 platform_driver_register() 注册时,会将当前注册的 platform_driver 中的 name 变量的值和已注册的所有 platform_device 中的 name 变量的值进行比较,只有找到具有相同名称的 platform_device 才能注册成功。当注册成功时,会调用 platform_driver 结构元素 probe 函数指针。
platform_driver_register()的注册过程:
1 platform_driver_register(&irfpa_platform_driver)
2 driver_register(&irfpa_platform_driver->driver)
3 bus_add_driver(&irfpa_platform_driver)
4 driver_attach(&irfpa_platform_driver)
5 bus_for_each_dev(&irfpa_platform_driver->bus, NULL, &irfpa_platform_driver, __driver_attach)
6 __driver_attach(irfpa, &irfpa_platform_driver)
7 driver_probe_device(&irfpa_platform_driver, irfpa)
8 really_probe(irfpa, &irfpa_platform_driver)
在really_probe()中:为设备指派管理该设备的驱动:dev->driver = drv, 调用probe()函数初始化设备:drv->probe(dev)
这里真正调用了probe函数。
典型的Platform device是系统中的各种自主设备,包括各种桥接在外围总线上的port-based device和host,以及各种集成在SOC platform上的控制器。他们都有一个特点,那就是CPU BUS可以直接寻址,或者特殊的情况platform_device连接在其他总线上,但是它的寄存器是可以被直接寻址的。
Platform device有一个名字,用来进行driver的绑定;还有诸如中断,地址之类的一些资源列表
Probe()函数必须验证指定设备的硬件是否真的存在,probe()可以使用设备的资源,包括时钟,platform_data等
a、platform_driver_register(&&irfpa_platform_driver) //注册平台驱动 int platform_driver_register(struct platform_driver *drv) { 。。。 return driver_register(&drv->driver); } b、driver_register(&drv->driver); //注册驱动到bus,@drv: 要注册的驱动
这个函数开始先判断bus->p是否为空,如果不为空然后判断驱动跟驱动的总线是否有冲突的函数注册,如果有冲突就给出警告信息,然后在注册在bus上的driver寻找是否有跟要注册的driver相同,有则表明驱动已被注册过,返回错误。经过上面的验证后,将驱动添加注册到bus上,如果没问题,则再将驱动添加到同一属性的组中,在sysfs下表现为同一个目录。 int driver_register(struct device_driver *drv) { int ret; struct device_driver *other; BUG_ON(!drv->bus->p); //判断bus->p是否为空,如果drv->bus->p为空,则打印失败信息以及panic信息。其实这个主要是判断bus是否存在,这个结论还需要论证! 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 ", drv->name); other = driver_find(drv->name, drv->bus); //这个函数的功能就是查找bus上已经注册的驱动,和要注册的驱动比较,如果找到,则返回找到的驱动。bus->p->drivers_kset是bus上已经注册的驱动的kobject的结合,会传给kset_find_obj()作为参数。kset_find_obj()它会查找在kset->list上的每一个kobject与改驱动的名字是否有同名字的,如果找到则返回改kobject。 if (other) { put_driver(other); printk(KERN_ERR "Error: Driver '%s' is already registered, " "aborting... ", drv->name); return -EBUSY; } ret = bus_add_driver(drv); //经过上面的验证后,将驱动添加注册到bus上,见下面函数分析 if (ret) return ret; ret = driver_add_groups(drv, drv->groups); //如果grop不为空的话,将在驱动文件夹下创建以group名字的子文件夹,然后在子文件夹下添加group的属性文件 if (ret) bus_remove_driver(drv); return ret; } c、bus_add_driver(drv); //添加驱动到总线 int bus_add_driver(struct device_driver *drv) { struct bus_type *bus; struct driver_private *priv; int error = 0; bus = bus_get(drv->bus); //找到该drv所属的bus,其实就是增加该bus->p->subsys->kobject->kref的引用计数 if (!bus) return -EINVAL; pr_debug("bus: '%s': add driver %s ", bus->name, drv->name); priv = kzalloc(sizeof(*priv), GFP_KERNEL); //分配driver_private结构 if (!priv) { error = -ENOMEM; goto out_put_bus; } klist_init(&priv->klist_devices, NULL, NULL); //初始化priv->klist_devices priv->driver = drv; //将该drv赋值给priv->driver drv->p = priv; //而drv的drv->p又等于priv priv->kobj.kset = bus->p->drivers_kset; //指向bus的drvier容器 error = kobject_init_and_add(&priv->kobj, &driver_ktype, NULL, "%s", drv->name); //驱动的kobject初始化和添加dir到sysfs中,可以看出kobject_init()的功能就是初始化kobject结构中的成员状态。kobject_add主要设置drvier的kobject和bus之间的层次关系,然后在sysfs中建立该驱动的文件夹。拿i2c总线举个例子吧,i2c总线注册好后将会有如下文件夹结构/sys/bus/i2c/,在/sys/bus/i2c/文件夹下会有如下文件夹uevent devices、drivers、drivers_probe、drivers_autoprobe,当你注册驱动的时候,将会在/sys/bus/i2c/drivers/下注册一个改驱动的文件夹,比如ov7675,那么它将会注册成/sys/bus/i2c/drivers/ov7675/,其实这些文件夹都对应一个kobject,通过kset容器组成一个很清晰的层次结构。 if (error) goto out_unregister; if (drv->bus->p->drivers_autoprobe) { //这个变量默认是为1的 error = driver_attach(drv); //匹配函数,后面会分析 if (error) goto out_unregister; } klist_add_tail(&priv->knode_bus, &bus->p->klist_drivers); //将priv->knode_bus添加到bus->p->klist_drivers,见4-3部分 module_add_driver(drv->owner, drv); //添加drv的module,见4-4部分 error = driver_create_file(drv, &driver_attr_uevent); //在sysfs的目录下创建文件uevent属性文件,见4-5分析 if (error) { printk(KERN_ERR "%s: uevent attr (%s) failed ", __func__, drv->name); } error = driver_add_attrs(bus, drv); //给driver添加bus上的所有属性 if (error) { /* How the hell do we get out of this pickle? Give up */ printk(KERN_ERR "%s: driver_add_attrs(%s) failed ", __func__, drv->name); } error = add_bind_files(drv); //添加绑定文件,driver_attr_bind 和 driver_attr_unbind见4-5分析 if (error) { /* Ditto */ printk(KERN_ERR "%s: add_bind_files(%s) failed ", __func__, drv->name); } kobject_uevent(&priv->kobj, KOBJ_ADD); //产生一个KOBJ_ADD uevent return 0; out_unregister: kfree(drv->p); drv->p = NULL; kobject_put(&priv->kobj); out_put_bus: bus_put(bus); return error; } d、driver_attach(drv);//为驱动寻找相应的设备、//该函数将调用bus_for_each_dev()。 int driver_attach(struct device_driver *drv) { return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach); } //该函数将调用bus_for_each_dev()。//监测到bus设备,调用__driver_attach( ) e、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) return -EINVAL; klist_iter_init_node(&bus->p->klist_devices, &i, (start ? &start->p->knode_bus : NULL)); //将bus中的已注册的device列表放到迭代器中,方便索引 while ((dev = next_device(&i)) && !error) //将驱动逐个地与列表中每一个的device匹配,可能一个驱动匹配好几个设备 error = fn(dev, data); //这个fn就是上面传下来的__driver_attach klist_iter_exit(&i); return error; } f、__driver_attach(struct device * dev, void * data) //dev 为使用驱动程式的设备结构体 //i2c总线根据设备client名字和id_table中的名字进行匹配的。如果匹配了,则返回id值,在i2c_device_match中则返回真。也就是bus的match函数将会返回真。那将会进入driver_probe_device()。 static int __driver_attach(struct device *dev, void *data) { struct device_driver *drv = data; //*锁定设备并尝试绑定设备,出错返回0,因为我们需要一直尝试绑定设备 //*若有些驱动不支持设备就返回错误 * 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)) //跟名字的意思一样,driver跟device尝试匹配,这里看bus的总线的match函数是否已经注册,如果没注册则直接返回1,如果注册,则调用注册的匹配函数。 return 0; if (dev->parent) /* Needed for USB */ down(&dev->parent->sem); down(&dev->sem); if (!dev->driver) driver_probe_device(drv, dev); up(&dev->sem); if (dev->parent) up(&dev->parent->sem); return 0; } g、driver_probe_device(drv, dev) int driver_probe_device(struct device_driver *drv, struct device *dev) { int ret = 0; if (!device_is_registered(dev)) //首先判断这个device是否已经注册 return -ENODEV; pr_debug("bus: '%s': %s: matched device %s with driver %s ", drv->bus->name, __func__, dev_name(dev), drv->name); ret = really_probe(dev, drv); //转而调用really_probe() return ret; } h、really_probe(dev, drv) //调用driver的probe( ),dev为设备结构体 static atomic_t probe_count = ATOMIC_INIT(0); //记录probe数目 static DECLARE_WAIT_QUEUE_HEAD(probe_waitqueue); //probe队列 static int really_probe(struct device *dev, struct device_driver *drv) { int ret = 0; atomic_inc(&probe_count); //原子增加计数 pr_debug("bus: '%s': %s: probing driver %s with device %s ", drv->bus->name, __func__, drv->name, dev_name(dev)); WARN_ON(!list_empty(&dev->devres_head)); dev->driver = drv; //把驱动赋值给dev->drvier if (driver_sysfs_add(dev)) { //主要是添加driver和dev之间的连接文件, 在driver目录下添加以dev->kobj名字的连接文件,连接到device,或同样在device目录下添加‘driver’为名字的连接文件连接到drvier printk(KERN_ERR "%s: driver_sysfs_add(%s) failed ", __func__, dev_name(dev)); goto probe_failed; } if (dev->bus->probe) { //如果bus的probe注册就执行,否则执行driver的probe,这也是函数开始时检测的原因! ret = dev->bus->probe(dev);//调用driver的probe( ),dev为设备结构体 if (ret) goto probe_failed; } else if (drv->probe) { //这里才真正调用了驱动的probe ret = drv->probe(dev); if (ret) goto probe_failed; } driver_bound(dev); //driver绑定dev, 将设备的驱动node添加到diver的klist_devices中,初始化一个klist_node,并将klist联系起来 ret = 1; pr_debug("bus: '%s': %s: bound device %s to driver %s ", drv->bus->name, __func__, dev_name(dev), drv->name); goto done; probe_failed: devres_release_all(dev); driver_sysfs_remove(dev); dev->driver = NULL; if (ret != -ENODEV && ret != -ENXIO) { /* driver matched but the probe failed */ printk(KERN_WARNING "%s: probe of %s failed with error %d ", drv->name, dev_name(dev), ret); } /* * Ignore errors returned by ->probe so that the next driver can try * its luck. */ ret = 0; done: atomic_dec(&probe_count); wake_up(&probe_waitqueue); return ret; } 至此才开始执行程序中的probe函数。