Lazy loaded image
🥳嵌入式 Linux开发
Linux驱动基础05-平台设备驱动
Words 3676Read Time 10 min
2024-9-11
2025-3-25
type
date
slug
category
icon
password
解决驱动代码和设备信息耦合的问题,linux 提出了设备驱动模型。在设备驱动模型中,引入总线的概念可以对驱动代码和设备信息进行分离。但是驱动中总线的概念是软件层面的一种抽象,与我们 SOC 中物理总线的概念并不严格相等:
  • 物理总线:芯片与各个功能外设之间传送信息的公共通信干线,其中又包括数据总线、地址总线和控制总线,以此来传输各种通信时序。
  • 驱动总线:负责管理设备和驱动。制定设备和驱动的匹配规则,一旦总线上注册了新的设备或者是新的驱动,总线将尝试为它们进行配对。
对于 I2C、SPI、USB 这些常见类型的物理总线来说,Linux 内核会自动创建与之相应的驱动总线,因此 I2C 设备、SPI 设备、USB 设备自然是注册挂载在相应的总线上。
结构简单的设备,比如 led、rtc 时钟、蜂鸣器、按键等等,没有相应的物理总线,Linux 内核将不会为它们创建相应的驱动总线
为了使这部分设备的驱动开发也能够遵循设备驱动模型,Linux 内核引入了一种虚拟的总线—平台总线(platform bus)
  • 平台总线用于管理、挂载那些没有相应物理总线的设备(即平台设备);
  • 对应的设备驱动则被称为平台驱动

一、平台总线

1.1 平台总线注册和匹配方式

在 Linux 的设备驱动模型中,总线是最重要的一环。总线负责匹配设备和驱动,维护着两个链表,里面记录着已注册的平台设备和平台驱动。每当有新的设备或者是新的驱动加入到总线时,总线便会调用 platform_match 函数对新增的设备或驱动,进行配对。
内核用platform_bus_type结构体(相较于bus_type,添加platform前缀)来描述平台总线。结构体定义如下:
  1. 平台总线该总线在 linux 内核启动的时候自动进行注册。见下面代码段:
    1. 第5行:向 linux 内核注册platform平台总线
  1. platform_match 匹配平台总线和平台设备,对于每个驱动总线, 它都必须实例化该函数指针。
      • 第4-5行:这里调用了to_platform_device()to_platform_driver()宏。这两个宏定义的原型如下:
        • devdriverplatform_deviceplatform_driver的成员变量,通用 container_of 获取到正在进行匹配的platform_driverplatform_device
      • 第8-21行:platform总线提供了四种匹配方式,存在着优先级:设备树机制 > ACPI匹配模式 > id_table方式 > 字符串比较。虽然匹配方式五花八门,但是并没有涉及复杂的算法,只是比较设备和驱动某个成员的字符串是否相同。
        • 设备树通过比较compatible的值,在下一节详细介绍匹配过程;
        • acpi 主要是用于电源管理,基本上用不到;
        • id_table匹配方式
        • 倘若我们的驱动没有提供前三种方式的其中一种,那么总线进行匹配时,只能比较platform_device中的name字段以及嵌在platform_driver中的device_drivername字段。

1.2 id_table 匹配方式

定义结构体 platform_driver时,我们需要提供一个 id_table的数组,该数组说明了当前驱动能够支持的设备。
当加载该驱动时,总线的 match 函数发现 id_table非空, 则会比较id_table中的name成员和平台设备的name成员,若相同,则会返回匹配的条目。
  • 第一个参数为驱动提供的 id_table
  • 第二个参数则是待匹配的平台设备
notion image
简而言之,平台驱动支持的设备列表id_tables中设备,和平台设备名称匹配,就将平台设备中id_entry赋值为对应设备。

二、平台设备

2.1 platform_device结构体

内核使用platform_device结构体来描述平台设备,结构体原型如下:
  • name:设备名称,总线进行匹配时,会比较设备和驱动的名称是否一致;
  • id:指定设备的编号,Linux支持同名的设备,而同名设备之间则是通过该编号进行区分;
  • dev:Linux设备模型中的device结构体,linux 内核大量使用了面向对象思想,platform_device 通过继承该结构体可复用它的相关代码,方便内核管理平台设备;
  • num_resources:记录资源的个数,当结构体成员resource存放的是数组时,需要记录resource数组的个数,内核提供了宏定义ARRAY_SIZE用于计算数组的个数;
  • resource:平台设备提供给驱动的资源,如irq,dma,内存等等。该结构体会在接下来的内容进行讲解;
  • id_entry: 用于保存1.2节 id_table 匹配的结果;

2.2 何为设备信息?

平台设备的工作是为驱动程序提供设备信息,设备信息包括硬件信息和软件信息两部分。
  • 硬件信息:驱动程序需要使用到什么寄存器,占用哪些中断号、内存资源、IO口等等
  • 软件信息:以太网卡设备中的MAC地址、I2C设备中的设备地址、SPI设备的片选信号线等等
对于硬件信息,使用结构体struct resource来保存设备所提供的资源,比如设备使用的中断编号,寄存器物理地址等,结构体原型如下:
  • name: 指定资源的名字,可以设置为NULL;
  • start、end: 指定资源的起始地址以及结束地址。
    • 若是只用一个中断引脚或者是一个通道,则它们的start和end成员值必须是相等的。
  • flags: 用于指定该资源的类型,在Linux中,资源包括I/O、Memory、Register、IRQ、DMA、Bus 等多种类型,最常见的有以下几种:
    • 资源宏定义
      描述
      IORESOURCE_IO
      用于IO地址空间,对应于IO端口映射方式
      在嵌入式中,基本上没有 IO 地址空间
      IORESOURCE_MEM
      用于外设的可直接寻址的地址空间
      操作设备的寄存器,主要有IO端口映射和 IO 內存映射两种方式。
      IORESOURCE_IRQ
      用于指定该设备使用某个中断
      IORESOURCE_DMA
      用于指定使用的DMA通道
对于软件信息,需要以私有数据的形式进行封装保存,我们注意platform_device结构体中,有个device结构体类型的成员dev,该结构体的成员platform_data可用于保存设备的私有数据。device 结构体可参考上一节。其中platform_datavoid *类型的万能指针,无论你想要提供的是什么内容,只需要把数据的地址赋值给platform_data即可,还是以GPIO引脚号为例,示例代码如下:
将保存了GPIO引脚号的变量pin地址赋值给platform_data指针,在驱动程序中通过调用平台设备总线中的核心函数,可以获取到我们需要的引脚号。

2.3 注册/注销平台设备

定义并初始化好platform_device结构体后,需要把它注册、挂载到平台设备总线上。
到这里,平台设备的知识已经讲解完毕,平台设备的主要内容是将硬件部分的代码与驱动部分的代码分开,注册到平台设备总线中。平台设备总线为设备和驱动之间搭建了一座桥—统一的数据结构以及函数接口,设备和驱动的数据交互直接在“这座桥上”进行。

三、平台驱动

3.1  platform_driver结构体

内核中使用 platform_driver 结构体来描述平台驱动,结构体原型如下所示:
  • probe:函数指针,驱动开发人员需在驱动中初始化该函数指针,当总线为设备和驱动匹配上之后,会回调执行该函数。一般通过该函数,对设备进行一系列的初始化。
  • remove:函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当我们移除某个平台设备时,会回调执行该函数指针,该函数实现的操作,通常是probe函数实现操作的逆过程。
  • driver:Linux设备模型中用于抽象驱动的 device_driver 结构体,platform_driver 继承该结构体,也就获取了设备模型驱动对象的特性;
  • id_table:表示该驱动能够兼容的设备类型。
platform_device_id结构体原型如下所示:
在platform_device_id这个结构体中,有两个成员,
  1. 第一个是数组用于指定驱动的名称,总线进行匹配时,会依据该结构体的name成员与platform_device中的变量name进行比较匹配,
  1. 另一个成员变量driver_data,则是用于来保存设备的配置。
我们知道在同系列的设备中,往往只是某些寄存器的配置不一样,为了减少代码的冗余, 尽量做到一个驱动可以匹配多个设备的目的。接下来以imx的串口为例,具体看下这个结构体的作用:
  • 第1-18行:声明了一个结构体数组,用来表示不同平台的串口类型;
  • 第20-42行:使用platform_device_id结构体中的driver_data成员来储存上面的串口信息。
当总线成功配对平台驱动以及平台设备时,会将对应的id_table条目赋值给平台设备的id_entry成员,而平台驱动的probe函数是以平台设备为参数, 这样的话,就可以拿到当前设备串口寄存器地址了。

3.2 注册/注销平台驱动

上面所讲的内容是最基本的平台驱动框架,只需要实现probe函数、remove函数,初始化platform_driver 结构体,并调用platform_driver_register进行注册即可。

3.3 平台驱动获取设备信息

在学习平台设备的时候,我们知道平台设备使用结构体resource来抽象表示硬件信息,而软件信息则可以利用设备结构体device中的成员platform_data来保存。 先看一下如何获取平台设备中结构体resource提供的资源。
platform_get_resource()函数通常会在驱动的probe函数中执行,用于获取平台设备提供的资源结构体,最终会返回一个struct resource类型的指针,该函数原型如下:
参数:
  • dev: 指定要获取哪个平台设备的资源;
  • type: 指定获取资源的类型,如IORESOURCE_MEM、IORESOURCE_IO等;
  • num: 指定要获取的资源编号。每个设备所需要资源的个数是不一定的,为此内核对这些资源进行了编号,对于不同的资源,编号之间是相互独立的。
返回值:
  • 成功: struct resource结构体类型指针
  • 失败: NULL

3.3.1 获取中断引脚

假若资源类型为IORESOURCE_IRQ,平台设备驱动还提供以下函数接口,来获取中断引脚,
参数:
  • pdev: 指定要获取哪个平台设备的资源;
  • num: 指定要获取的资源编号。
返回值:
  • 成功: 可用的中断号
  • 失败: 负数

3.3.2 获取软件信息

对于存放在device结构体中成员platform_data的软件信息,我们可以使用dev_get_platdata函数来获取,函数原型如下所示:
参数:
  • dev: struct device结构体类型指针
返回值: device结构体中成员platform_data指针
以上几个函数接口就是如何从平台设备中获取资源的常用的几个函数接口,到这里平台驱动部分差不多就结束了。
总结一下平台驱动需要实现probe函数,当平台总线成功匹配驱动和设备时,则会调用驱动的probe函数,在该函数中使用上述的函数接口来获取资源, 以初始化设备,最后填充结构体 platform_driver,调用platform_driver_register进行注册。
 
上一篇
Linux驱动基础04-总线设备驱动模型
下一篇
Linux驱动基础06-设备树引入和使用

Comments
Loading...