Linux SPI 子系统(x86平台)

文章目录

  • Linux SPI 子系统(x86平台)
    • 前言
    • 总述
    • SPI 硬件系统与软件抽象之间的关系
    • SPI 驱动的 Probe 和 Match 过程
      • SPI Board Info
      • 从设备驱动的 Match 和 Probe 过程
    • SPI 核心层
    • 对于开发的一些简单指导
    • 总结
    • 参考资料

前言

写文在于交流和传播知识,本人才粗学浅,还请多多指教,板砖轻拍。

网络上很多 Linux SPI 驱动框架参考资料,但这些资料大部分以讲解源码为主,虽然 Linux 内核源码很清晰美妙,但内核版本众多,细枝末节处差异很大,不利于初学者进行对比学习。另外初学者需要阅读大量的源码才能明晰程序流程,这就增加了系统了解 SPI 子系统的难度,很难快速完成具体开发工作。因此本文主要以文字描述为主,源码为辅,重点在于理清与 SPI 有关的相关概念和 SPI 子系统的初始化的流程。

另外,本文主要描述 x86 体系下的 SPI 框架,也可作为 ARM 体系下 SPI 框架的参考,因为两种框架下的概念和原理都是相通的,只是有些地方的具体实现不同。

总述

SPI 是一种总线通讯协议,由总线控制器和从设备构成。Linux SPI 驱动包含两个部分,分别用于驱动 SPI 总线控制器和从设备,内核中的 SPI 子系统为这两种驱动提供了开发框架。

在系统探测到设备并挂载相关驱动的过程中,涉及到设备发现(探测或枚举)和驱动匹配(Match),对于总线上的设备,需要知道该设备挂载在哪条总线上,因此就需要知道该设备的总线号。在 SPI 子系统中,这些动作由 SPI Board Info(ACPI 或 Device Tree 中与 SPI 从设备有关的部分)、SPI 控制器驱动以及SPI 从设备驱动共同完成,它们各自的分工如下:

  1. SPI Board Info(ACPI 或 Device Tree 中与 SPI 从设备有关的部分):用于声明设备的存在,提供 SPI 从设备所在的总线号、片选号以及用于与 SPI 从设备匹配的 modalias 字段(与 SPI 从设备中的 name 字段匹配)。
  2. SPI 控制器驱动:用于驱动 SPI 总线控制器。
  3. SPI 从设备驱动:用于驱动 SPI 从设备。

接下来将从 SPI 硬件系统与软件抽象之间的关系,以及 SPI 驱动的探测过程两个方面展开说明。

SPI 硬件系统与软件抽象之间的关系

电子系统中有很多外设,有像 GPIO 这样简单的设备,也有像 LCD 控制器这样复杂的。有一类特殊的外设,用于实现总线通信,通常以控制器的角色出现,被称为总线控制器,例如 UART 控制器、以太网控制器,以及本文的主角 SPI 控制器。

SPI 控制器用于实现 SPI 总线通讯,该通讯采用主从通讯形式,一个主设备可以挂载多个 SPI 从设备,每个从设备通过片选(CS)信号来选定。核心 CPU 通过 SPI 控制器与从设备进行交互。SPI 控制器常常以某种形式挂载到核心 CPU 上,具体而言,在 x86 平台上,SPI 总线控制器通过 PCI 总线挂接到主 CPU 上;而 ARM 平台上往往是以片载外设的形式出现,直接挂接在 SOC 或 MCU 片内的系统总线上。

SPI 通讯离不开 SPI 总线控制器和从设备,因此在 Linux 系统中就需要为这两种对象开发驱动程序。用于驱动 SPI 总线控制器的驱动称为 SPI 控制器驱动,而用于驱动 SPI 从设备的驱动称为 SPI 设备驱动(个人觉得叫 SPI 从设备驱动似乎更贴切些,为便于区分,后文都将 SPI 设备驱动称作 SPI 从设备驱动)。从另一个角度来说,SPI 从设备驱动的工作就是基于 SPI 通讯来实现对从设备的控制和访问,相当于实现 SPI 通信协议与具体的设备控制/访问协议的相互转换,所以 SPI 从设备驱动又称为 SPI 协议驱动。从名称上可以看出,SPI 控制器驱动属于总线控制器驱动,然而 SPI 从设备是多种多样的,可能是 char 设备(如 spidev),也可能是 block 设备(如 SPI Flash),因此 SPI 从设备驱动也是有多种类型的。一条 SPI 总线(对应一个 SPI 总线控制器)上可以挂接多个 SPI 从设备,与之对应的是:一个 SPI 控制器驱动也可以挂接多个 SPI 从设备驱动。下图,详细描述了 Linux 系统中 SPI 驱动框架,以及各部分之间的相互关系。

  1. SPI 控制器驱动:相当于 master/controller,由 spi_master 描述,用于驱动 SPI 总线控制器,实现其初始化、中断回调等功能,在 /sys 目录下创建节点,不提供 file operation 接口,驱动类型为 SPI 总线控制器驱动。

  2. SPI 协议驱动(SPI 从设备驱动): 相当于 slave,用于驱动 SPI 总线上所挂接的设备,在 /dev 目录下创建设备节点,提供 open、read、write、ioctl 等 file operation 接口,驱动类型由设备本身所属设备驱动类型描述(char/block/…)。

有一些 SPI 从设备并不是由内核来为其提供驱动的,而是交给用户态去处理,此时需要在内核为这些从设备导出用户态操作的接口,Linux 内核为此提供了统一的内核程序——spidev,来完成此项工作。被 spidev 导出的接口以 spidev<总线号>.<子设备号/片选序号> 的文件形式出现在 /dev 系统目录下,如:spidev1.2 便是第二个 SPI 总线上的第三个从设备。之后就可以在用户态通过 open、read、write、ioctl、close 等标准接口操作 spidev 了。spidev 相当于一种特殊的 SPI 从设备驱动,即在 SPI 子系统框架下实现的字符设备,该设备能够从用户态进行直接 SPI 通讯。

了解 Linux SPI 子系统中两个主要角色(SPI 控制器驱动和 SPI 从设备驱动)后,就需要知道他们是如何被初始化的,以及如何与具体设备匹配上的,这涉及到 SPI 总线控制器、SPI 从设备的枚举和发现,以及驱动匹配等。

SPI 驱动的 Probe 和 Match 过程

有些总线设备是可以自动枚举到的,如 PCI 总线可以通过 BDF(总线号 Bus、设备号 Device 和功能号 Function) 来枚举设备并通过 Device ID 和 Vendor ID 来匹配驱动程序。然而有很多总线不能自动枚举设备并匹配驱动程序。因此内核提供了几种配置表用于声明某些设备的存在,对于 ARM 平台目前使用 Device Tree,而 x86 平台有 ACPI 表,或者干脆以平台设备的形式注册 Board Info。Linux 内核会扫描这些表或已注册的 Board Info,并根据其中的信息触发对应驱动程序的 Probe 流程。这个过程由系统内核框架实现的,不需要设备驱动开发人员关心,只需要写好 Device Tree,提供好 ACPI 表,或注册好 Board Info 即可,而这一般会有 Demo 可以参考。

在 x86 平台下,SPI 总线控制器作为 PCI 设备接入,由 PCI 枚举来探测到设备,并通过 Device ID 和 Vendor ID 来匹配 SPI 控制器驱动(Linux 内核程序结合硬件机制实现了该功能,具体 PCI 设备枚举和匹配过程可以参考 PCI 方面的专业资料),触发其 Probe 程序;然而 SPI 从设备是无法自动探测到的,需要在 ACPI 表中声明这些从设备(x86 平台),或在平台设备中注册相关的 SPI Board Info,以便内核能够匹配到正确的 SPI 设备驱动并触发其 Probe 程序。

以平台设备为例,看下 SPI 从设备的探测流程。由于 SPI 平台设备是通过 SPI Board Info 来声明设备存在的,所以先介绍 spi_board_info 的具体结构。然后再根据 SPI Board Info 一步步分析 SPI 从设备的 Probe 过程。

注:x86 系统的平台设备声明在 arch->x86->platform 下。

SPI Board Info

对于 spi_board_info,需要重点关注 bus_num 和 modalias,bus_num 将设备与对应的总线控制器驱动匹配,modalias 用于与 SPI 从设备匹配。以下是 spi_board_info 的具体结构,注意每个成员后面的注释:

struct spi_board_info {char    modalias[SPI_NAME_SIZE];        // 用于与 SPI 从设备驱动匹配,触发其 Probe 过程.const void   *platform_data;const struct property_entry *properties;void    *controller_data;int     irq;u32     max_speed_hz;u16     bus_num;        // 用于与 SPI 控制器中的 bus num 匹配,对应 spidev<总线号>.<子设备号/片选序号> 中的 总线号.u16     chip_select;    // 指定片选序号,将作为 spidev<总线号>.<子设备号/片选序号> 中的 子设备号/片选序号.u16     mode;           // SPI 有四种模式,见下图,具体参考 SPI 协议相关资料.
};


接下来,根据 bus_num 和 modalias 分析下从设备驱动的 Match 和 Probe 流程。

从设备驱动的 Match 和 Probe 过程

系统启动时,先执行 arch_initcall 中的定义的板级初始化程序,由该程序完成 spi_board_info 的注册,并由此形成一张 SPI 从设备列表。之后,在 x86 平台下,当系统进行 PCI 设备枚举时,将发现 SPI 总线控制器,并调用与之对应的 SPI 总线控制器驱动中的 Probe 程序,该 Probe 程序继续调用 regist 函数来注册 SPI master/controller,在这个注册函数中比较当前 SPI 控制器的总线号,如果与 SPI 从设备列表中的总线号对应,则将该从设备挂接到这个控制器上。在 4.19.23 版本内核中,以 pxa2xx 为例,可用以下函数调用关系来描述:

spi_register_board_info(){ /*Provide bus_num*/}  <--+|
pxa2xx_spi_probe()                                  |
{                                                   |devm_spi_register_controller()                  |{                                               |spi_register_controller()                   |{                                           |spi_match_controller_to_boardinfo()     |{                                   <---+/*** controller 的 bus_num 与* regist 的 spi_board_info* 中的 bus_num 一致则执行* spi_new_device()* 并在 /sys 目录下创建节点。*/}}}
}

一旦 SPI 从设备挂载到了对应的总线上,系统就会查找有无匹配的 SPI 从设备驱动,并触发其 Probe 过程。判断 SPI 从设备驱动是否与声明的平台设备相匹配,是通过比较 spi_driver 结构体中的 name 字段与 spi_board_info 结构体中的 modalias 字段是否一致来完成的,如果一致,则调用 SPI 从设备驱动的 Probe 程序。SPI 从设备驱动不但要继续完成 Match 和 Probe 过程,创建具体的设备对象和 /dev 目录下的设备节点,还要将设备控制和访问接口(file_operations)注册给系统,并实现对应功能。下面以 spidev 为例,看看 spi_driver 的基本结构,以及 SPI 从设备驱动的接口和主要工作:

/*** File operation.*/
static ssize_t spidev_write(...) {...}
static ssize_t spidev_read(...) {...}
static long spidev_ioctl(...) {...}
static int spidev_open(...) {...}
static int spidev_release(...) {...}static const struct file_operations spidev_fops = {.owner = THIS_MODULE,.write = spidev_write,.read =  spidev_read,.unlocked_ioctl = spidev_ioctl,.open = spidev_open,.release = spidev_release,...
};/*** Probe and remove.*/
static int spidev_probe(struct spi_device *spi)
{...// 形成上文说的 /dev/spidev<总线号>.<子设备号/片选序号> 文件.dev = device_create(spidev_class, &spi->dev, spidev->devt,spidev, "spidev%d.%d",spi->master->bus_num, spi->chip_select);...
}static int spidev_remove(...) {...}static struct spi_driver spidev_spi_driver = {.driver = {// 通过 name 来匹配..name = "spidev",// 通过 Device Tree 来匹配..of_match_table = of_match_ptr(spidev_dt_ids),// 通过 ACPI 表来匹配..acpi_match_table = ACPI_PTR(spidev_acpi_ids),},.probe = spidev_probe,.remove = spidev_remove,
};/*** Init and deinit.*/
static int __init spidev_init(void)
{...// 注册字符设备register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);class_create(THIS_MODULE, "spidev");spi_register_driver(&spidev_spi_driver);...
}
module_init(spidev_init);static void __exit spidev_exit(void)
{...spi_unregister_driver(&spidev_spi_driver);class_destroy(spidev_class);unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);...
}
module_exit(spidev_exit);

可以看出,代码一方面注册了 File Operation 方法用于实现用户态访问接口,另一方面则通过其 SPI 控制器来访问和控制实际设备。而从 spi_driver 结构体不难看出,spidev 不但可以驱动已注册的 SPI 平台设备,还可以驱动以 Device Tree 或 ACPI 表声明的 SPI 从设备(匹配 Device Tree 或 ACPI 表)。

SPI 核心层

有了 SPI 总线控制器驱动和 SPI 从设备驱动,SPI 子系统就可以工作了。但是我们发现,对于 SPI 子系统,有很多核心的代码是完全通用的,把这些共通代码抽取出来,便构建成了 SPI 核心层。

对于开发的一些简单指导

基于当前的 SPI 子系统框架,首先要有办法感知或声明 SPI 控制器以及从设备的存在,然后开发对应的驱动程序。

系统一般是通过总线枚举、Device Tree 或 ACPI 表来感知或声明设备存在。

有两种类型的设备驱动需要开发——SPI 控制器驱动和 SPI 从设备驱动。SPI 从设备与从设备驱动通过 name 字段进行匹配。

SPI 控制器驱动一般由芯片供应商或开源社区会提供,下游的开发者只需要实现 SPI 从设备驱动即可。对于 SPI 控制器驱动,可以参考 pxa2xx 这个驱动程序;对于 SPI 从设备驱动可以参考 spidev 这个驱动程序。

总结

最后晒一张来自网友的大图(来源见图中水印),系统总结了 SPI 子系统的 Probe 过程和各部分的功能:

参考资料

  1. linux设备模型之spi子系统
  2. PXA2xx SPI on SSP driver HOWTO
  3. Linux设备驱动剖析之SPI(一)
  4. Linux设备驱动剖析之SPI(二)
  5. Linux设备驱动剖析之SPI(三)

Linux SPI 子系统(x86平台)相关推荐

  1. Linux SPI子系统(2):SPI核心层

    目录 SPI核心层 1. SPI子系统初始化 2. 重要的数据结构 2.1 struct spi_controller 2.2 struct spi_driver 2.3 struct spi_dev ...

  2. Linux SPI子系统(3):SPI控制器驱动层

    目录 1. 前言 2. SPI控制器驱动 2.1 dts 2.2 匹配注册 2.3 probe函数 2.4 数据收发函数:spi_st_transfer_one 2.5 中断处理程序:spi_st_i ...

  3. Linux 定制X86平台操作系统

    /********************************************************************************** Linux 定制X86平台操作系 ...

  4. +++++++X86平台系统启动流程

    操作系统的组成.内核的功能.库.函数.头文件.函数名.Linux内核.X86平台系统启动流程 忘记密码如何登陆系统 操作系统的组成:kernel + rootfs , kernel + 应用程序 ke ...

  5. platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架

    platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...

  6. X86平台下基于grub2+busybo+linux-2.6.36制作linux系统

    X86平台下基于grub2+busybo+linux-2.6.36制作linux系统 一.下载内核源码,grub2源码,以及busybox源码: 下载grub2源码 ftp://ftp.gnu.org ...

  7. Linux 驱动 | SPI子系统

    SPI子系统 这些驱动的共同点: 主机端驱动和外设端驱动分离 通过一个核心层将某种总线协议进行抽象 外设端驱动通过核心层API间接调用主机驱动提供的传输函数进行收发数据 IIC.SPI等不支持热拔插的 ...

  8. linux内核添加spi驱动,Linux内核驱动之spi子系统spi协议.docx

    Linux内核驱动之spi子系统spi协议 概况 SPI接口是摩托罗拉首先提出的全双工三线同步串行外围接口SCK,MOSI,MISO,采用主从模式(Master Slave)架构:支持多slave模式 ...

  9. Linux坏掉检测,测起来!安兔兔评测Linux版上线 目前仅支持x86平台

    原标题:测起来!安兔兔评测Linux版上线 目前仅支持x86平台 [CNMO新闻]你用过安兔兔吗?可能大部分用过的人都是在安卓手机之上,其他产品上用的并不多.5月7日,安兔兔官方宣布,继Android ...

最新文章

  1. 寻找两个正序数组的中位数——冒泡排序(归并排序)
  2. 点云关键算法详解及实战剖析正式开讲!(涉及分割、配准、关键点、识别、重建等)...
  3. string来存放二进制数据
  4. java例7_在Java 7中处理周数
  5. 一文吃透PHP和HTML的嵌套写法
  6. UI干货素材模板|从做好网页头部内容设计开始!
  7. 为什么大家都会往大城市跑
  8. 【ACL2020论文尝鲜】何时采用BERT更加有效?
  9. linux高级知识,LAMP架构(一),MYSQL安装
  10. Web应用程序安全原理(Web服务面临的威胁)
  11. CoMP 协同多点传输技术
  12. 网站提示HTTP503Service Unavailable的处理办法
  13. Uint8 Uint16等的区别
  14. 电影推广思路详解,最权威的电影推广方案
  15. 2022程序员都推荐的算法编程课程终于来了!新手从入门到面试考点全覆盖,学到就是赚到!...
  16. 华为云HCS解决方案笔记HUAWEI CLOUD Stack【面试篇】
  17. 3DsMax —布尔制作口红
  18. 什么叫 Rss 订阅
  19. IDA7.0 MacOS报错Oops,IDA has almost crashed!解决方案
  20. flume采集hive日志写到hdfs问题

热门文章

  1. RV-LINK:将 GD32VF103C-START 开发板变成 RISCV-V 仿真器
  2. 拼音转换成汉字html,汉字转成拼音-用HTML实现
  3. csr867x入门之提示音配置(十二)
  4. Ubuntu下噩梦搬的hud-service进程CPU直接爆炸
  5. macbook上好用的解压软件_全能解压 Mac下最好用的解压缩软件
  6. 安卓苹果手机有效清除微信浏览器内部缓存
  7. linux_zsh/oh my zsh 版本检查/使用帮助(check and update)/安装最新版zsh
  8. springboot dubbo 既是提供者又是消费者demo源码
  9. 知识图谱是什么,知识图谱有什么特点?
  10. python账号_基于Python打造账号共享浏览器功能