SPI(Serial Peripheral Interface)作为一种同步串行通信协议,在嵌入式领域扮演着关键角色。Linux内核从早期版本就开始支持SPI子系统,但直到4.x版本才形成相对完善的架构。我们选择的4.9.88版本发布于2018年1月,属于长期支持(LTS)版本,其SPI子系统已经过多次迭代优化,具有较好的稳定性和代表性。
这个版本的核心改进包括:
提示:虽然4.9.88不是最新内核,但其SPI实现被后续版本大量继承,研究这个版本对理解现代Linux SPI架构非常有帮助。
Linux SPI子系统采用典型的分层架构:
code复制应用层
│
▼
SPI设备驱动 (e.g. spidev, mmc_spi)
│
▼
SPI核心层 (drivers/spi/spi.c)
│
▼
SPI控制器驱动 (e.g. spi-bcm2835)
│
▼
硬件层 (SoC SPI控制器)
核心层提供以下关键机制:
c复制struct spi_device {
struct device dev;
struct spi_controller *controller;
u32 max_speed_hz;
u8 chip_select;
u8 bits_per_word;
u16 mode;
// ...
};
struct spi_transfer {
const void *tx_buf;
void *rx_buf;
unsigned len;
// ...
};
struct spi_message {
struct list_head transfers;
// ...
};
这三个结构体构成了SPI通信的基础:
以虚拟SPI控制器为例:
c复制static struct spi_controller *spi_virtual_probe(struct platform_device *pdev)
{
struct spi_controller *ctlr;
ctlr = spi_alloc_controller(&pdev->dev, sizeof(*data));
ctlr->bits_per_word_mask = SPI_BPW_MASK(8);
ctlr->transfer_one = spi_virtual_transfer_one;
ctlr->setup = spi_virtual_setup;
return ctlr;
}
c复制static int spi_virtual_transfer_one(struct spi_controller *ctlr,
struct spi_device *spi,
struct spi_transfer *xfer)
{
/* 硬件操作伪代码 */
if (xfer->tx_buf)
write_to_fifo(xfer->tx_buf, xfer->len);
if (xfer->rx_buf)
read_from_fifo(xfer->rx_buf, xfer->len);
return 0;
}
SPI时钟的精确控制对高速设备至关重要:
c复制static int spi_set_clock(struct spi_device *spi, u32 hz)
{
struct spi_controller *ctlr = spi->controller;
u32 div;
/* 计算分频系数 */
div = DIV_ROUND_UP(ctlr->max_speed_hz, hz);
if (div < 2) div = 2;
/* 写入硬件寄存器 */
write_reg(SPI_CLKDIV_REG, div >> 1);
return 0;
}
注意:实际分频算法需参考具体SoC手册,某些芯片要求分频系数为2^n形式。
spidev是内核提供的通用SPI设备接口,无需编写驱动即可测试:
bash复制# 加载spidev(需在DTS中配置)
modprobe spidev
# 用户空间访问示例
int fd = open("/dev/spidev0.0", O_RDWR);
ioctl(fd, SPI_IOC_WR_MODE, SPI_MODE_0);
ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, 8);
ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, 1000000);
典型SPI设备驱动包含:
c复制static const struct of_device_id mydev_dt_ids[] = {
{ .compatible = "vendor,mydev" },
{}
};
static int mydev_probe(struct spi_device *spi)
{
struct mydev_priv *priv;
priv = devm_kzalloc(&spi->dev, sizeof(*priv), GFP_KERNEL);
spi_set_drvdata(spi, priv);
/* 初始化硬件 */
mydev_hw_init(spi);
/* 注册字符设备等 */
// ...
return 0;
}
static struct spi_driver mydev_driver = {
.driver = {
.name = "mydev",
.of_match_table = mydev_dt_ids,
},
.probe = mydev_probe,
// ...
};
启用DMA可显著提升大块数据传输效率:
c复制static int spi_setup_dma(struct spi_controller *ctlr)
{
ctlr->dma_tx = dma_request_chan(&ctlr->dev, "tx");
ctlr->dma_rx = dma_request_chan(&ctlr->dev, "rx");
if (IS_ERR(ctlr->dma_tx)) {
dev_warn(&ctlr->dev, "TX DMA not available");
ctlr->dma_tx = NULL;
}
// ...同理处理RX
ctlr->can_dma = spi_can_dma;
ctlr->flags |= SPI_CONTROLLER_MUST_TX;
}
bash复制cat /proc/device-tree/soc/spi@7e204000/status
bash复制echo 1 > /sys/module/spi/parameters/debug
dmesg -w
c复制// 在transfer_one回调中添加
ktime_t start = ktime_get();
// ...传输操作...
pr_info("Transfer took %lld ns", ktime_get_ns() - start);
症状:数据采样错误或完全无法通信
排查步骤:
常见表现:
解决方案:
c复制// 确保DTS中正确配置cs-gpios
spi@0 {
cs-gpios = <&gpio 8 0>, <&gpio 7 0>;
// ...
};
// 驱动中正确设置chip_select值
spi->chip_select = 0; // 对应第一个cs-gpio
稳健性增强方案:
c复制static int mydev_transfer(struct spi_device *spi, void *buf, size_t len)
{
struct spi_message msg;
struct spi_transfer xfer = {
.tx_buf = buf,
.len = len,
.delay_usecs = 100, // 适当增加延迟
};
spi_message_init(&msg);
spi_message_add_tail(&xfer, &msg);
return spi_sync(spi, &msg);
}
实现线程安全的SPI操作:
c复制static DEFINE_MUTEX(spi_lock);
int thread_safe_spi_write(struct spi_device *spi, void *buf, size_t len)
{
int ret;
mutex_lock(&spi_lock);
ret = spi_write(spi, buf, len);
mutex_unlock(&spi_lock);
return ret;
}
对于实时性要求高的场景:
c复制struct sched_param param = {
.sched_priority = 50,
};
sched_setscheduler(current, SCHED_FIFO, ¶m);
bash复制echo performance > /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
虽然主要是软件研究,但良好的硬件设计是基础:
PCB布线原则:
电源滤波:
抗干扰措施:
利用内核的kunit框架:
c复制#include <kunit/test.h>
static void spi_test_basic(struct kunit *test)
{
struct spi_device *spi = test->priv;
u8 tx[] = {0xAA, 0x55}, rx[2] = {0};
KUNIT_EXPECT_EQ(test, 0, spi_write_then_read(spi, tx, 2, rx, 2));
KUNIT_EXPECT_MEMEQ(test, tx, rx, 2);
}
static struct kunit_case spi_test_cases[] = {
KUNIT_CASE(spi_test_basic),
{}
};
static struct kunit_suite spi_test_suite = {
.name = "spi_test",
.test_cases = spi_test_cases,
.init = spi_test_init, // 初始化spi设备
};
bash复制# 连续传输测试
for i in {1..1000}; do
dd if=/dev/urandom of=/dev/spidev0.0 bs=4K count=100
done
# 速率统计
speed=$( (time -p dd if=/dev/zero of=/dev/spidev0.0 bs=1M count=100) 2>&1 | awk '/real/{print 100/$2}')
echo "Transfer speed: $speed MB/s"
虽然我们研究的是4.9.88,但了解后续变化很重要:
主要API变化:
新特性:
兼容性处理:
c复制#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,10,0)
struct spi_controller *ctlr;
#else
struct spi_master *ctlr;
#endif