21 Linux Kernel SPI 框架

在Linux内核中,SPI架构主要分为三个层次:SPI设备驱动层、SPI适配器驱动层和SPI核心层。

框图如下:

+-------------------------------------------------------+
|         SPI 设备驱动层 (Device Driver Layer)           |
|                                                       |
|    +---------------------------+     +------------+   |
|    | SPI 传感器驱动 (特定设备)   |    | spidev.c   |   | <-- 用户空间接口
|    | (例如:温度传感器、显示屏)   |    | (通用设备) |   |
|    |  - probe                   |    |  - probe   |   |
|    |  - remove                  |    |  - remove  |   |
|    |  - 定制化逻辑               |    |  - 文件操作 |   |
|    +----------------------------+    +------------+    |
|                                                        |
+--------------------------------------------------------+ 
                     ↑
                     | 数据传递 (通过 API 调用,如 spi_sync, spi_async)
                     ↓
+-----------------------------------------+
|            SPI 核心层 (Core Layer)       |
|                                          |
|     +------------------------------+     |
|     | spi.c                         |    | <-- 核心逻辑和基础设施,协调设备和控制器
|     |  - Device Registration        |    |
|     |  - Message Handling           |    |
|     |  - Transfer API (spi_sync,    |    |
|     |    spi_async, etc.)           |    |
|     |  - Bus Management             |    |
|     +-------------------------------+    |
|                                          |
|     +-------------------------------+    |
|     | spi-bus.c                     |    | <-- 管理SPI总线,负责设备探测、移除等操作
|     |  - Bus Management Functions   |    |
|     |  - Device Matching            |    |
|     +-------------------------------+    |
+------------------------------------------+
                   ↑
                   | 数据传递 (通过总线管理器)
                   ↓
+------------------------------------------------+
|      SPI 控制器驱动层 (Controller Driver Layer) |
|                                                |
|       +--------------------------------+       |
|       | spi-master.c                   |       | <-- 控制SPI主控制器与硬件的通信
|       |  - Controller Setup            |       |
|       |  - Data Transfer               |       |
|       |  - Interrupt/DMA Handling      |       |
|       +--------------------------------+       |
+------------------------------------------------+
                     ↑
                     | 数据传递 (transfer_one等API)
                     ↓
+-------------------------------------------------------------+
|       SPI 控制器驱动层 (Controller Driver Layer)             |
|       drivers/spi/spi-rockchip.c 针对Rockchip SPI控制器      |
|       drivers/spi/spi-imx.c(针对 Freescale/NXP i.MX 处理器)| <- 物理通信接口(SPI总线物理层)
|                                                             | 
|  - 与硬件通信(时序控制、数据传输)                            | 
|  - 通过SPI总线与实际设备通信                                  |    
+-------------------------------------------------------------+
                       ↑
                       | SPI 时序波形 + 寄存器映射
                       ↓
+-------------------------------------------------+
|       SPI从设备 (例如:传感器、闪存芯片等)        |
|       - SCLK (时钟信号)                         |
|       - MOSI (主输出从输入)                      |
|       - MISO (主输入从输出)                      |
|       - SS (从设备选择信号)                      |
+-------------------------------------------------+

21.1 Linux Kernel SPI 各层之间的关系

SPI 设备驱动层

这是设备驱动的顶层部分,处理具体的SPI从设备(如传感器、存储器)的操作。spidev.c提供了用户空间与SPI设备交互的接口,可以通过/dev/spidevX.Y进行通信。设备驱动的probe()函数在此层注册SPI设备,remove()负责卸载设备。

SPI 核心层

由spi.c和spi-bus.c组成,核心层是连接SPI设备驱动和控制器驱动的中间层。spi.c负责SPI设备的注册、消息的管理、传输API的调用(如spi_sync()、spi_async())和总线管理。spi-bus.c负责SPI总线的管理,匹配设备与控制器。

SPI 控制器驱动层

由spi-master.c实现,负责SPI主控制器(Master)的初始化、配置和与硬件的实际数据传输。它处理低层的控制器设置、数据传输过程,以及通过中断或DMA方式优化数据流。

21. 2 Linux SPI 数据流程

当应用程序通过打开 /dev/spidev.0 设备与SPI设备进行通信时,数据从应用层到内核层,再到SPI设备的传输过程可以分为以下几个步骤。

21.2.1 应用层打开 SPI 设备文件

在应用层,应用程序通过调用 open() 系统调用打开 /dev/spidev.0 设备文件。/dev/spidev.0 是由 SPI 的用户空间驱动 spidev 创建的一个设备节点,它允许用户空间程序直接与 SPI 总线通信。

int fd = open("/dev/spidev0.0", O_RDWR);

21.2.2 读取/写入数据

一旦设备文件打开,应用程序可以通过标准的 read() 和 write() 系统调用,或者通过 ioctl() 来执行读写操作。

  • 写入数据: 使用 write() 函数发送数据到 SPI 设备。例如:

    uint8_t data[] = {0xA1, 0xB2};  // 要写入的 SPI 数据
    write(fd, data, sizeof(data));
  • 读取数据: 使用 read() 函数从 SPI 设备读取数据。例如:

    uint8_t buffer[2];
    read(fd, buffer, sizeof(buffer));

    但在大多数情况下,SPI 通常是双向通信的,因此更常见的操作是使用 ioctl() 执行全双工数据传输。

  • ioctl SPI 传输: 使用 ioctl() 通过 SPI 总线执行同步传输。在 spidev 中,常用的 SPI_IOC_MESSAGE(n) 是执行 SPI 数据传输的接口。例如:

    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx_data,
        .rx_buf = (unsigned long)rx_data,
        .len = ARRAY_SIZE(tx_data),
        .speed_hz = 500000,
        .delay_usecs = 0,
        .bits_per_word = 8,
    };
    ioctl(fd, SPI_IOC_MESSAGE(1), &tr);

    21.2.3 从应用层到内核层的数据传递

    当应用层发出 write(), read() 或 ioctl() 系统调用时,数据从应用层传递到内核中的 spidev 驱动层。以下是具体的流程:

  • 系统调用进入内核
    1.) write() 和 read():
    当应用层调用 write() 或 read() 时,内核会调用 spidev 驱动中的 spidev_write() 和 spidev_read() 函数。这些函数位于内核中的 spidev.c 文件中,负责处理用户空间数据与内核的 SPI 控制器之间的数据传递。
    2.) ioctl()
    ioctl() 系统调用则会调用 spidev_ioctl() 函数,这个函数可以处理 SPI 的各种配置和数据传输请求。尤其是通过 SPI_IOC_MESSAGE,它允许用户空间发起双向数据传输操作。

  • spidev 与 SPI 核心层交互
    在 spidev 驱动处理完应用程序的请求后,它会通过SPI核心层(SPI Core Layer)与SPI控制器驱动通信。
    1.) spi_sync()
    无论是 write()、read() 还是 ioctl(),最终 spidev 驱动会调用 spi_sync() 或 spi_async() 来发起SPI通信请求。spi_sync() 是一个同步的API,它会将一个 spi_message 对象传递给 SPI 核心层。

    spi_sync(spi_device, &message);

    2.) spi_message
    spi_message 是一个内核结构体,用来描述一次SPI传输,它包含一个或多个 spi_transfer,而每个 spi_transfer 包含实际的数据缓冲区(用于MOSI和MISO)。

    struct spi_message {
        struct list_head    transfers;                   // transfer 列表
        void                (*complete)(void *context);  // 传输完成回调
        ...
    };
  • SPI 核心层调用 SPI 控制器驱动
    SPI 核心层收到 spi_sync() 的传输请求后,会将传输请求交给与具体硬件控制器(SPI控制器)对应的控制器驱动(通常是 spi_master.c 实现的驱动)。这是通过 SPI 核心中的 spi_master 结构体完成的:
    spi_master 中的 .transfer 函数指针指向具体的SPI控制器驱动实现,用于发起实际的SPI传输。

    master->transfer(master, &message);

    spi-master.c 是 Linux 内核 SPI 子系统的一部分,主要用于管理SPI 主控制器(SPI Master Controller)。在 Linux 中,SPI 控制器被抽象为 spi_master 结构体,而 spi-master.c 提供了对 spi_master 结构的初始化、注册、管理和与设备通信等功能。比如:
    spi_alloc_master():为一个 SPI 主控制器分配内存,并初始化相应的 spi_master 结构。
    spi_register_master():注册 spi_master,将 SPI 控制器添加到内核的 SPI 子系统中,使其可以被 SPI 设备驱动所使用。

  • SPI 控制器驱动层与硬件通信
    SPI 控制器驱动根据 spi_message 和 spi_transfer 中提供的数据信息,配置 SPI 控制器硬件寄存器,最终通过物理 SPI 总线与目标 SPI 设备进行数据通信:
    1.) 发送数据:SPI 控制器驱动通过 MOSI 线将数据发送给从设备
    2.) 接收数据:同时,从 SPI 设备通过 MISO 线将数据返回给主设备。
    SPI 控制器根据时钟信号(SCLK)同步数据传输,并确保发送与接收的数据正确无误。

  • 数据回传到应用层
    SPI 控制器驱动完成数据传输后,SPI 核心层将传输结果返回给 spidev 驱动。
    spidev 驱动再通过 write()、read() 或 ioctl() 将数据或状态返回给应用程序

数据流, 如下:

+------------------------------------------------------------+
|                 应用层 (Application Layer)                 |
|             open()    write()    read()   ioctl()          |
+------------------------------------------------------------+
                              ↓
+------------------------------------------------------------+
|                 spidev 用户空间驱动 (spidev.c)              |
|          spidev_write()  spidev_read()  spidev_ioctl()     |
|  调用 SPI 核心层 API 进行传输,如 spi_sync(), spi_async()    |
+------------------------------------------------------------+
                              ↓
+------------------------------------------------------------+
|                    SPI 核心层 (spi.c)                      |
|             负责协调数据传输,调用 SPI 控制器驱动            |
|         使用 spi_master 结构体来转发数据到 SPI 控制器        |
+------------------------------------------------------------+
                            ↓
+------------------------------------------------------------+
|              SPI 控制器驱动层 (spi-master.c)                |
|          控制具体硬件寄存器,操作时钟、数据传输               |
|                通过 SPI 总线与物理设备通信                   |
+------------------------------------------------------------+
                            ↓
+------------------------------------------------------------+
|          SPI 总线 (spi-rockchip.c/spi-imx.c等)              |
|  MOSI (数据发送), MISO (数据接收), SCLK (时钟), SS (选择)    |
+------------------------------------------------------------+
                            ↓
+------------------------------------------------------------+
|                 SPI 从设备 (如传感器等)                     |
+------------------------------------------------------------+

21.4 主要数据结构

SPI 框架涉及多个重要的数据结构,包括 spidev.c、spi.c、spi-master.c 以及具体的 SPI 控制器驱动文件(如 spi-rockchip.c、spi-imx.c)。这些文件中的数据结构相互关联,形成了从应用层到硬件的完整 SPI 数据传输链路。

21.4.1 spi.c (SPI 核心层) 中的重要数据结构

1.) struct spi_device

struct spi_device {
    struct device dev;                // 设备描述符,嵌入在 dev 结构中
    struct spi_master *master;        // 关联的 SPI 控制器(master)
    u32 max_speed_hz;                 // SPI 最大速度
    u16 chip_select;                  // 片选编号
    u8 mode;                          // SPI 模式
};

struct spi_device:代表一个连接到 SPI 总线的从设备,主要用于管理设备的相关信息,包括片选编号、通信参数(如时钟频率)等。

它与用户空间的 spidev_data 结构体相互关联,spi_device 被 spidev 驱动用来访问底层硬件。

spi_device 结构体还包含了与 spi_master 相关的指针,用于与主控器通信。

2.) struct spi_message

struct spi_message {
    struct list_head transfers;       // 传输列表
    struct spi_device *spi;           // 关联的 SPI 设备
    unsigned int frame_length;        // 传输的帧长度
};

这是一个 SPI 传输消息队列的描述结构,用于描述多段 SPI 传输的消息链。spi_transfer 结构与 spi_message 结构相关联,每个 spi_message 包含多个spi_transfer,用于表示一次完整的 SPI 传输。 核心层通过这个结构将数据从 spi_device 传输到 spi_master。

3.) struct spi_transfer

struct spi_transfer {
    const void *tx_buf;               // 发送缓冲区
    void *rx_buf;                     // 接收缓冲区
    unsigned len;                     // 传输长度
    u32 speed_hz;                     // SPI 速度
};

spi_transfer 代表一次 SPI 数据的传输单元。它描述了要传输的实际数据和传输参数,比如发送缓冲区、接收缓冲区和传输长度。通过 spi_message_add_tail() 将一个或多个 spi_transfer 添加到 spi_message 中,最终提交给 spi_master 进行处理。

4.) struct spi_master

struct spi_master {
    struct device dev;                // 设备描述符,嵌入在 dev 结构中
    struct list_head queue;           // 传输队列
    int (*transfer_one)(struct spi_master *master, struct spi_device *spi, struct spi_transfer *t);
    void (*set_cs)(struct spi_device *spi, bool enable);  // 设置片选信号的回调
};

spi_master:代表一个 SPI 控制器(SPI 主设备),它由具体的 SPI 控制器驱动(如 spi-rockchip.c 或 spi-imx.c)初始化并注册到 SPI 核心框架中。

spi_master 通过提供一系列回调函数(如 transfer_one())来执行底层硬件传输操作。

spi_device 结构体中的 master 成员指向一个 spi_master 结构体,表明该 spi_device 是由哪个 SPI 主设备控制的。

5.) struct spidev_data

struct spidev_data {
    dev_t devt;                      // 设备号
    struct spi_device *spi;          // 指向 SPI 设备的指针
    struct mutex spi_lock;           // 用于同步的互斥锁
    u8 *buffer;                      // 缓冲区,用于用户空间传输的数据
};

spidev_data:这是一个用于 spidev 驱动的数据结构,代表用户空间访问的 SPI 设备节点 /dev/spidevX.X。

它包含了设备相关的信息,比如设备号 (devt)、spi_device 设备指针等。

该结构体与内核中的 spi_device 结构体相关联,用于实现用户空间通过 read()、write() 和 ioctl() 调用进行 SPI 通信。