您的位置:首页 > 运维架构 > Linux

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

2016-03-10 01:34 513 查看
设备驱动开发是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\n", 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...\n", 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\n", 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\n",
__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\n",
__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\n",
__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\n",
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\n",
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\n",
__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\n",
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\n",
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函数。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: