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 通信。