在设备驱动模型中,引入总线的概念可以对驱动代码和设备信息进行分离。但是驱动中总线的概念是软件层面的一种抽象,与我们SOC中物理总线的概念并不严格相等。
- 物理总线:芯片与各个功能外设之间传送信息的公共通信干线,其中又包括数据总线、地址总线和控制总线,以此来传输各种通信时序。
- 驱动总线:负责管理设备和驱动。制定设备和驱动的匹配规则,一旦总线上注册了新的设备或者是新的驱动,总线将尝试为他们进行配对。
一般对于、、这些常见类型的物理总线来说,Linux内核会自动创建与之相应的驱动总线,因此设备、设备、设备自然是注册挂载在相应的总线上。但是,实际项目开发中还有很多结构简单的设备,对他们进行控制并不需要特殊的时序。他们也就没有相应的物理总线,比如led、蜂鸣器和按键等,Linux内核将不会为他们创建相应的驱动总线。为了使这部分设备的驱动开发也能够遵循设备驱动模型,Linux内核引入了一种虚拟的总线–平台总线(platform_bus)。
平台总线用于管理、挂载那些没有相应物理总线的设备,这些设备被称为平台设备,对应的设备驱动则被称为平台驱动。平台设备驱动的核心依然是Linux设备驱动模型,平台设备使用结构体来进行表示,其继承了设备驱动模型中的device结构体。而平台驱动使用结构体来进行表示,其则是继承了设备驱动模中的device_driver结构体。
平台设备
platform_device结构体
- name: 设备名称,总线进行匹配时,会比较设备和驱动的名称是否一致;
- id: 指定设备的编号,Linux支持同名的设备,而同名设备之间则是通过该编号进行区分;
- dev: Linux设备模型中的device结构体,linux内核大量使用了面向对象思想,platform_device通过继承该结构体可复用它的相关代码,方便内核管理平台设备;
- num_resources: 记录资源的个数,当结构体成员resource存放的是数组时,需要记录resource数组的个数,内核提供了宏定义ARRAY_SIZE用于计算数组的个数;
- resource: 平台设备提供给驱动的资源,如irq,dma,内存等等。该结构体会在接下来的内容进行讲解;
- id_entry: 平台总线提供的另一种匹配方式,原理依然是通过比较字符串,这部分内容会在平台总线小节中讲,这里的id_entry用于保存匹配的结果;
何为设备信息
平台设备的工作是为驱动程序提供设备信息,设备信息包括硬件信息和软件信息两部分。
- 硬件信息:驱动程序需要使用到什么寄存器,占用哪些中断号、内存资源和IO口等。
- 软件信息:以太网卡设备中的MAC地址、I2C设备中的设备地址、SPI设备的片选信号线等等。
对于硬件信息,使用结构体来保存设备所提供的资源,比如设备使用的中断编号,寄存器物理地址等,结构体原型如下:
- name: 指定资源的名字,可以设置为NULL;
- start、end: 指定资源的起始地址以及结束地址
- flags: 用于指定该资源的类型,在Linux中,资源包括I/O、Memory、Register、IRQ、DMA、Bus等多种类型,最常见的有以下几种:
设备驱动程序的主要目的是操作设备的寄存器。不同架构的计算机提供不同的操作接口,主要有IO端口映射和IO內存映射两种方式。 对应于IO端口映射方式,只能通过专门的接口函数(如inb、outb)才能访问; 采用IO内存映射的方式,可以像访问内存一样,去读写寄存器。在嵌入式中,基本上没有IO地址空间,所以通常使用IORESOURCE_MEM。
在资源的起始地址和结束地址中,对于IORESOURCE_IO或者是IORESOURCE_MEM,他们表示要使用的内存的起始位置以及结束位置; 若是只用一个中断引脚或者是一个通道,则它们的start和end成员值必须是相等的。
而对于软件信息,这种特殊信息需要我们以私有数据的形式进行封装保存,我们注意到platform_device结构体中, 有个device结构体类型的成员dev。在前面章节,我们提到过Linux设备模型使用device结构体来抽象物理设备, 该结构体的成员platform_data可用于保存设备的私有数据。platform_data是void *类型的万能指针, 无论你想要提供的是什么内容,只需要把数据的地址赋值给platform_data即可, 还是以GPIO引脚号为例,示例代码如下:
将保存了GPIO引脚号的变量pin地址赋值给platform_data指针,在驱动程序中通过调用平台设备总线中的核心函数,可以获取到我们需要的引脚号。
注册/注销平台设备
函数参数和返回值如下:
参数: pdev: platform_device类型结构体指针
返回值:
- 成功: 0
- 失败: 负数
函数参数和返回值如下:
参数: pdev: platform_device类型结构体指针
返回值: 无
平台驱动
platform_driver结构体
- probe: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当总线为设备和驱动匹配上之后,会回调执行该函数。我们一般通过该函数,对设备进行一系列的初始化。
- remove: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当我们移除某个平台设备时,会回调执行该函数指针,该函数实现的操作,通常是probe函数实现操作的逆过程。
- driver: Linux设备模型中用于抽象驱动的device_driver结构体,platform_driver继承该结构体,也就获取了设备模型驱动对象的特性;
- id_table: 表示该驱动能够兼容的设备类型。
platform_device_id结构体原型如下所示:
- 是数组用于指定驱动的名称,总线进行匹配时,会依据该结构体的name成员与platform_device中的变量name进行比较匹配
- 则是用于来保存设备的配置。我们知道在同系列的设备中,往往只是某些寄存器的配置不一样,为了减少代码的冗余, 尽量做到一个驱动可以匹配多个设备的目的
注册/注销平台驱动
函数参数和返回值如下:
参数: drv: platform_driver类型结构体指针
返回值:
- 成功: 0
- 失败: 负数
参数: drv: platform_driver类型结构体指针
返回值: 无
平台驱动获取设备信息
platform_get_resource()函数通常会在驱动的probe函数中执行,用于获取平台设备提供的资源结构体,最终会返回一个struct resource类型的指针,该函数原型如下:
参数:
- dev: 指定要获取哪个平台设备的资源;
- type: 指定获取资源的类型,如IORESOURCE_MEM、IORESOURCE_IO等;
- num: 指定要获取的资源编号。每个设备所需要资源的个数是不一定的,为此内核对这些资源进行了编号,对于不同的资源,编号之间是相互独立的。
返回值:
- 成功: struct resource结构体类型指针
- 失败: NULL
假若资源类型为IORESOURCE_IRQ,平台设备驱动还提供以下函数接口,来获取中断引脚,
参数:
- pdev: 指定要获取哪个平台设备的资源;
- num: 指定要获取的资源编号。
返回值:
- 成功: 可用的中断号
- 失败: 负数
对于存放在device结构体中成员platform_data的软件信息,我们可以使用dev_get_platdata函数来获取,函数原型如下所示:
参数:
- dev: struct device结构体类型指针
返回值: device结构体中成员platform_data指针
平台总线
平台总线注册和匹配方式
在Linux的设备驱动模型中,总线是最重要的一环。每当有新的设备或者新的驱动加入到总线时,总线便会调用函数对新增的设备或驱动,进行配对。内核中使用来抽象描述系统重的总线,平台总线结构体原型如下:
内核用platform_bus_type来描述平台总线,该总线在linux内核启动的时候自动进行注册。这里重点是platform总线的match函数指针,该函数指针指向的函数将负责实现平台总线和平台设备的匹配过程。对于每个驱动总线, 它都必须实例化该函数指针。
id_table匹配方式
在定义结构体platform_driver时,我们需要提供一个id_table的数组,该数组说明了当前的驱动能够支持的设备。当加载该驱动时,总线的match函数发现id_table非空, 则会比较id_table中的name成员和平台设备的name成员,若相同,则会返回匹配的条目。
示例
编译一个和两个文件,两个device都是杂项设备,共用主设备号,动态分配不同的次设备号,共用一个驱动。
再来看看sys目录下
总结
-
驱动代码中最好不要使用全局变量,因为驱动一般是支持多设备的,如果有全局变量会有冲突。
-
字符设备驱动中在open函数中可以使用container_of拿到设备信息,但是杂项设备驱动中怎么拿到设备信息暂时未知。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/bian-cheng-ri-ji/22812.html