【linux kernel 3.18】Platform总线驱动注册和注销流程

【linux kernel 3.18】Platform总线驱动注册和注销流程

一、前言

前言主要阐述了编写该技术文档的目的,使用的平台环境,以及看懂该文档所需要掌握的基本知识,方便学习者按图索骥,填充相关的知识体系。

linux驱动开发的过程中,我们每天会接触到大量的代码,会遇到不同的总线和设备,有时会忘记某些设备驱动的注册、使用和注销的流程,或者由于没有系统化地掌握、注册注销流程的一些细节,导致代码编译不通过,通过了却无法调试成功,使得工程师本应该专注于核心代码的实现和优化,却花费了大量的时间去检测框架上的错误。因此,本技术文档的第一个目的,就是将驱动设备的注册、注销过程流程化,加快编写效率,也方便工程师查漏。

但是,为了突出流程化的这个特点,该文档应当具有的一个特点就是,即使是一个尚未接触过linux驱动(但有一定编程基础)的新人,看到该文档也应该能够大致明白整个流程,从而迅速上手,实现某些驱动功能,完成某些较为简单的工作任务。因此,该文档也应当具有一定的机制原理介绍。

本文就是在技术性和流程化的两端中取平衡。

内核版本Linux 3.18linux不同版本的驱动框架差异较大,尤其是kernel 3.x版本引入了设备树,与2.x版本有着巨大的差别,因此本文中所讲的代码、机制,不一定适用于所有版本,要根据工作环境来灵活选择是否采用本文中的内核机制)

预备知识:C语言(熟练)、内核模块的加载与卸载(熟悉)、 linux总线驱动设备框架(熟悉)、设备树文件编写知识(了解)

*程度水平

了解:阅读过相关的技术资料,知道其中的技术原理,但并没有经过实践。

熟悉:阅读过相关的工程代码或者开放源代码,知道该项知识是如何应用到实际的工程中的。

熟练:能够熟练地应用该项知识完成一定的工程任务。

精通:能够详细地阐述该项知识的技术实现细节,能够分析该项知识在实际应用中客观存在的优点和缺点,并能够横向对比相似的技术,比较它们的优劣之处。

 

*英文名限定

为了区分代码中的英文和普通的英文,方便读者阅读理解,在此将正文中出现的英文进行了划分,划分如下:

英文正文:英文名首部不加任何解释

英文函数名:英文名后添加小括号,如device_init()。为了节省行文篇幅,在正文中就不写返回类型和函数参数了

英文变量/结构体:英文名首部添加”.”,.bus_type

所有的函数名和英文变量/结构体都是该函数/变量在内核代码中的实际名称,没有缩写或者简写,可以直接搜索内核工程找到相应的定义

 

二、Platform总线驱动注册过程(linux3.18+device tree

 

1. of_device_id结构体,用于匹配设备树。

例:

static struct of_device_id  xxx_of_match[]={

{.compatible=”xxx_device_provider,xxx_device”,},

{},

}

.compatible中的内容即为设备树下挂载在platform总线下的设备的属性compatible,当调用platform_driver_register()时,系统就会调用.platform_bus下的match()函数将设备与驱动配对。

若该驱动能够对应多个设备,则可以继续往.xxx_of_match里添加设备匹配信息。

 

2.写platform_driver结构体并填充相关的成员函数

例:

static struct platform_driver xxx_platform_driver={

.remove=xxx_remove,

.shutdown=xxx_shutdown,

.probe=xxx_probe,

.driver={

.name=”xxx”,

.owner=THIS_MODULE,

.of_match_table=xxx_of_match

}

}

其中.remove.shutdown.probe均是函数指针,需要自己根据具体的设备来编写函数实体。.driver就是linux最原始的driver结构体,所有总线设备的驱动结构体如i2c_driver都是内嵌了该driver的结构体。关于driver结构体的完整作用,在总线、设备、驱动模型的知识点中应当会有解析,可以在网络上去搜索,在此不铺开介绍。但在这个结构体里面有一个.of_match_table成员,由于它和设备匹配息息相关,在此必须阐述一下,顺便帮助阅读者理清驱动设备匹配的过程。

在platform驱动注册时会使用platform_driver_register()来进行注册,这个函数在检测driver的一些参数后若没有错误,会调用bus_add_driver()来进行驱动的添加,在该函数中会检测设备链表是否有误,若检测无误则会调用driver_attach(),在该函数中会遍历platform总线下的设备链表,在每一个设备节点会调用__driver_attach()函数来检测注册的驱动是否和当前节点的设备匹配,并完成probe工作。在__driver_attach()中首先会调用driver_match_device(),搜寻当前设备节点是否和注册的驱动匹配,在driver_match_device()中会调用驱动所挂靠的总线下的match()函数,完成匹配的检测,在此以.platform_bus_type下的platform_match()为例,在platform_match()中(请打开工程搜索相应的函数),我们看到,驱动与设备的匹配优先通过设备树方法(即使用.of_match_table这个成员),匹配失败时再通过ACPI方法,若再次失败最后才是用之前讲过的id_table来进行匹配。当匹配成功后,__driver_attach()会调用总线的probe()函数,一般情况下总线的probe()最终会进入驱动的xxx_probe(),在platform驱动就是.platform_driver中的.probe指向的函数,完成probe工作。

 

为了更好地填充成员函数,下面将结构体原型贴出:

struct platform_driver {

int (*probe)(struct platform_device *);

int (*remove)(struct platform_device *);

void (*shutdown)(struct platform_device *);

int (*suspend)(struct platform_device *, pm_message_t state);

int (*resume)(struct platform_device *);

struct device_driver driver;

const struct platform_device_id *id_table;

bool prevent_deferred_probe;

};

.Probe指向的函数在注册设备时被自动调用,.remove在设备注销时被自动调用,.shutdown在系统关机时自动调用,.suspend在系统休眠时自动调用,.resume在系统从休眠状态恢复时自动调用。

因此,可以利用这些函数控制设备的启用关断,达到休眠省电的功效。

3.调用platform_driver_register完成platform设备的注册

例:

platform_driver_register(&xxx_platform_driver);

该函数传入的参数是上一个步骤所创建的.platform_driver结构体的地址,即.platform_driver指针。

一般将该注册函数放在module初始化的函数中,若注册失败返回非零值,成功返回零。

以上,完成对platform_driver的注册。由于设备信息已经包含在了device tree中,因此已经不用自己创建device类型了。

 

三、Platform总线驱动注销过程

调用platform_driver_unregister(&xxx_platform_driver);

调用该函数会自动调用remove函数,完成设备实体的关闭。

一般将该注销函数放在module的卸载函数中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值