一、核心概念
1.1 SPI本质上是全双工协议
SPI物理层工作原理:
MOSI ────────────────> (主机发送)
CLK ════════════════> (时钟同步)
MISO <──────────────── (从机发送)
每个时钟周期都是双向传输:
- 主机发送1位的同时,从机也发送1位
- 16个时钟周期 = 同时传输16位TX和16位RX
关键理解:
- spidev的
read()/write()只是用户态API的简化接口 - 底层硬件永远是全双工的
write()时MISO线仍在接收数据(只是被丢弃)read()时MOSI线仍在发送数据(通常发送0x00)
1.2 全双工ioctl
c
xfer[0].tx_buf = (unsigned long)tx_buf; // 发送缓冲区
xfer[0].rx_buf = (unsigned long)rx_buf; // 接收缓冲区 ←注意这里
xfer[0].len = 2;
status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
这个ioctl调用的是spidev_message()函数:
c
/* 同时指定了tx_buf和rx_buf = 全双工传输 */
if (u_tmp->tx_buf) {
k_tmp->tx_buf = tx_buf;
// 从用户空间拷贝要发送的数据
}
if (u_tmp->rx_buf) {
k_tmp->rx_buf = rx_buf;
// 预留接收缓冲区
}
二、TLC5615 DAC芯片的工作机制
2.1 芯片手册解读
TLC5615是10位串行DAC,时序如下:
典型写入时序:
CS ‾‾‾‾\____________________/‾‾‾‾
CLK ____/‾\_/‾\_/‾\_ ... _/‾\____
DIN ────< D9 D8 D7 ... D0 X X >──
↑ 高位在前
DOUT ───< 上次写入的值回显 >──────
↑ 这就是ioctl读到的数据!
2.2 DAC芯片的回显机制(Shift Register Echo)
TLC5615的DOUT引脚行为:
- 第一次写入:DOUT输出芯片复位后的默认值(通常是0或未定义)
- 之后每次写入:DOUT输出上一次写入的数据
这是移位寄存器类DAC的标准设计:
内部结构简化模型:
┌─────────────┐
DIN ────>│ Shift Reg │────> DOUT (上次的值)
│ (16-bit) │
└──────┬──────┘
↓
┌─────────────┐
│ DAC Latch │────> VOUT (当前的值)
│ (10-bit) │
└─────────────┘
三、只发送不接收的测试
3.1 纯写模式(丢弃RX数据)
c
memset(xfer, 0, sizeof(xfer));
xfer[0].tx_buf = (unsigned long)tx_buf;
xfer[0].rx_buf = 0; // 不接收数据
xfer[0].len = 2;
status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
底层仍然在接收,只是数据被丢弃在内核的rx_buffer中。
3.2 使用read()/write()
c
// 使用write()
write(fd, tx_buf, 2);
// 内部调用 spidev_sync_write()
// rx_buf仍在接收,但数据被丢弃
// 使用read()
read(fd, rx_buf, 2);
// 内部调用 spidev_sync_read()
// tx_buf发送的是未初始化数据(通常是上次残留)
用户态API 内核实现 硬件行为
───────── ───────── ─────────
write() → 只用tx_buf → MOSI发送,MISO接收但丢弃
read() → 只用rx_buf → MOSI发送0x00,MISO接收
ioctl() → tx+rx都用 → MOSI和MISO同时工作(全双工)
TLC5615的特性
- 回显上次写入值是芯片的设计特性
- 可用于写后验证(write-verify)
- 典型应用:检测SPI通信是否正常
分割线
ioctl全双工传输的完整追溯
我将从用户态一路追踪到硬件寄存器操作,展示SPI全双工的完整实现。
一、用户态:ioctl系统调用入口
1.1 用户态代码
c
struct spi_ioc_transfer xfer[1];
xfer[0].tx_buf = (unsigned long)tx_buf; // 用户态TX缓冲区地址
xfer[0].rx_buf = (unsigned long)rx_buf; // 用户态RX缓冲区地址
xfer[0].len = 2;
status = ioctl(fd, SPI_IOC_MESSAGE(1), xfer);
// ↑ ↑ ↑
// 文件描述符 命令码 参数
1.2 SPI_IOC_MESSAGE宏展开
c
// include/uapi/linux/spi/spidev.h
#define SPI_IOC_MAGIC 'k'
#define SPI_IOC_MESSAGE(N) \
_IOW(SPI_IOC_MAGIC, 0, char[SPI_MSGSIZE(N)])
#define SPI_MSGSIZE(N) \
((((N)*(sizeof(struct spi_ioc_transfer))) < (1 << _IOC_SIZEBITS)) \
? ((N)*(sizeof(struct spi_ioc_transfer))) : 0)
// 对于 SPI_IOC_MESSAGE(1):
// cmd = _IOW('k', 0, char[32]) // 假设sizeof(spi_ioc_transfer)=32
// = 0x40206B00 (实际值取决于架构)
二、spidev驱动层:spidev_ioctl
2.1 进入spidev_ioctl
c
// drivers/spi/spidev.c
static long
spidev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0;
int retval = 0;
struct spidev_data *spidev;
struct spi_device *spi;
u32 tmp;
unsigned n_ioc;
struct spi_ioc_transfer *ioc;
printk(KERN_INFO "[SPIDEV_TRACE] ioctl: cmd=0x%08x, arg=0x%lx\n", cmd, arg);
/* 检查魔数 */
if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC)
return -ENOTTY;
/* 检查访问权限 */
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
if (err == 0 && _IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
spidev = filp->private_data;
spin_lock_irq(&spidev->spi_lock);
spi = spi_dev_get(spidev->spi);
spin_unlock_irq(&spidev->spi_lock);
if (spi == NULL)
return -ESHUTDOWN;
mutex_lock(&spidev->buf_lock);
switch (cmd) {
/* ... 其他case ... */
default:
/* 进入这个分支! */
printk(KERN_INFO "[SPIDEV_TRACE] ioctl: entering SPI_IOC_MESSAGE path\n");
/* 解析并拷贝用户态传输结构 */
ioc = spidev_get_ioc_message(cmd,
(struct spi_ioc_transfer __user *)arg, &n_ioc);
if (IS_ERR(ioc)) {
retval = PTR_ERR(ioc);
break;
}
if (!ioc)
break;
printk(KERN_INFO "[SPIDEV_TRACE] ioctl: n_ioc=%u, ioc=%px\n", n_ioc, ioc);
/* 执行传输! */
retval = spidev_message(spidev, ioc, n_ioc);
kfree(ioc);
break;
}
mutex_unlock(&spidev->buf_lock);
spi_dev_put(spi);
return retval;
}
三、解析用户态传输结构
3.1 spidev_get_ioc_message
c
static struct spi_ioc_transfer *
spidev_get_ioc_message(unsigned int cmd, struct spi_ioc_transfer __user *u_ioc,
unsigned *n_ioc)
{
struct spi_ioc_transfer *ioc;
u32 tmp;
/* 检查命令格式 */
if (_IOC_TYPE(cmd) != SPI_IOC_MAGIC
|| _IOC_NR(cmd) != _IOC_NR(SPI_IOC_MESSAGE(0))
|| _IOC_DIR(cmd) != _IOC_WRITE)
return ERR_PTR(-ENOTTY);
/* 计算传输段数量 */
tmp = _IOC_SIZE(cmd);
if ((tmp % sizeof(struct spi_ioc_transfer)) != 0)
return ERR_PTR(-EINVAL);
*n_ioc = tmp / sizeof(struct spi_ioc_transfer);
printk(KERN_INFO "[SPIDEV_TRACE] get_ioc: n_ioc=%u, size=%u\n", *n_ioc, tmp);
if (*n_ioc == 0)
return NULL;
/* 分配内核空间 */
ioc = kmalloc(tmp, GFP_KERNEL);
if (!ioc)
return ERR_PTR(-ENOMEM);
/* 从用户空间拷贝传输结构 */
if (__copy_from_user(ioc, u_ioc, tmp)) {
kfree(ioc);
return ERR_PTR(-EFAULT);
}
printk(KERN_INFO "[SPIDEV_TRACE] get_ioc: ioc[0].tx_buf=0x%llx, rx_buf=0x%llx, len=%u\n",
ioc[0].tx_buf, ioc[0].rx_buf, ioc[0].len);
return ioc;
}
四、核心:spidev_message(全双工关键)
4.1 完整带调试的spidev_message
c
static int spidev_message(struct spidev_data *spidev,
struct spi_ioc_transfer *u_xfers, unsigned n_xfers)
{
struct spi_message msg;
struct spi_transfer *k_xfers;
struct spi_transfer *k_tmp;
struct spi_ioc_transfer *u_tmp;
unsigned n, total, tx_total, rx_total;
u8 *tx_buf, *rx_buf;
int status = -EFAULT;
printk(KERN_INFO "[SPIDEV_TRACE] message: ENTER, n_xfers=%u\n", n_xfers);
/* 初始化SPI消息结构 */
spi_message_init(&msg);
/* 为内核传输结构分配空间 */
k_xfers = kcalloc(n_xfers, sizeof(*k_tmp), GFP_KERNEL);
if (k_xfers == NULL)
return -ENOMEM;
printk(KERN_INFO "[SPIDEV_TRACE] message: k_xfers allocated at %px\n", k_xfers);
/* ========== 关键步骤1:构造传输段 ========== */
tx_buf = spidev->tx_buffer; // 内核TX bounce buffer起始地址
rx_buf = spidev->rx_buffer; // 内核RX bounce buffer起始地址
total = 0;
tx_total = 0;
rx_total = 0;
for (n = n_xfers, k_tmp = k_xfers, u_tmp = u_xfers;
n;
n--, k_tmp++, u_tmp++) {
k_tmp->len = u_tmp->len;
total += k_tmp->len;
printk(KERN_INFO "[SPIDEV_TRACE] message: xfer[%u] len=%u\n",
n_xfers - n, k_tmp->len);
/* 检查溢出 */
if (total > INT_MAX || k_tmp->len > INT_MAX) {
status = -EMSGSIZE;
goto done;
}
/* ========== 全双工关键点A:设置RX缓冲区 ========== */
if (u_tmp->rx_buf) {
rx_total += k_tmp->len;
if (rx_total > bufsiz) {
status = -EMSGSIZE;
goto done;
}
/* 设置内核RX缓冲区地址 */
k_tmp->rx_buf = rx_buf;
printk(KERN_INFO "[SPIDEV_TRACE] message: xfer[%u] rx_buf=%px (kernel), "
"user_rx=0x%llx\n",
n_xfers - n, k_tmp->rx_buf, u_tmp->rx_buf);
/* 检查用户空间地址可写性 */
if (!access_ok(VERIFY_WRITE, (u8 __user *)(uintptr_t)u_tmp->rx_buf,
u_tmp->len))
goto done;
rx_buf += k_tmp->len; // 指针后移
} else {
/* 如果用户不需要接收,rx_buf为NULL */
k_tmp->rx_buf = NULL;
printk(KERN_INFO "[SPIDEV_TRACE] message: xfer[%u] NO rx_buf\n",
n_xfers - n);
}
/* ========== 全双工关键点B:设置TX缓冲区 ========== */
if (u_tmp->tx_buf) {
tx_total += k_tmp->len;
if (tx_total > bufsiz) {
status = -EMSGSIZE;
goto done;
}
/* 设置内核TX缓冲区地址 */
k_tmp->tx_buf = tx_buf;
/* 从用户空间拷贝数据到内核TX buffer */
if (copy_from_user(tx_buf, (const u8 __user *)(uintptr_t)u_tmp->tx_buf,
u_tmp->len)) {
goto done;
}
printk(KERN_INFO "[SPIDEV_TRACE] message: xfer[%u] tx_buf=%px (kernel), "
"data: %02x %02x\n",
n_xfers - n, k_tmp->tx_buf, tx_buf[0], tx_buf[1]);
tx_buf += k_tmp->len; // 指针后移
} else {
/* 如果用户不需要发送,tx_buf为NULL */
k_tmp->tx_buf = NULL;
printk(KERN_INFO "[SPIDEV_TRACE] message: xfer[%u] NO tx_buf\n",
n_xfers - n);
}
/* 拷贝其他传输参数 */
k_tmp->cs_change = !!u_tmp->cs_change;
k_tmp->tx_nbits = u_tmp->tx_nbits;
k_tmp->rx_nbits = u_tmp->rx_nbits;
k_tmp->bits_per_word = u_tmp->bits_per_word;
k_tmp->delay_usecs = u_tmp->delay_usecs;
k_tmp->speed_hz = u_tmp->speed_hz;
if (!k_tmp->speed_hz)
k_tmp->speed_hz = spidev->speed_hz;
printk(KERN_INFO "[SPIDEV_TRACE] message: xfer[%u] speed=%u Hz, "
"bits_per_word=%u, cs_change=%d\n",
n_xfers - n, k_tmp->speed_hz, k_tmp->bits_per_word,
k_tmp->cs_change);
/* 添加到消息链表 */
spi_message_add_tail(k_tmp, &msg);
}
printk(KERN_INFO "[SPIDEV_TRACE] message: Constructed msg with %u transfers, "
"total=%u, tx_total=%u, rx_total=%u\n",
n_xfers, total, tx_total, rx_total);
/* ========== 关键步骤2:执行同步传输 ========== */
status = spidev_sync(spidev, &msg);
if (status < 0) {
printk(KERN_ERR "[SPIDEV_TRACE] message: spidev_sync FAILED, status=%d\n",
status);
goto done;
}
printk(KERN_INFO "[SPIDEV_TRACE] message: spidev_sync SUCCESS, status=%d\n",
status);
/* ========== 关键步骤3:拷贝接收数据回用户空间 ========== */
rx_buf = spidev->rx_buffer;
for (n = n_xfers, u_tmp = u_xfers; n; n--, u_tmp++) {
if (u_tmp->rx_buf) {
printk(KERN_INFO "[SPIDEV_TRACE] message: copying rx_buf[%u] "
"to user (len=%u, data: %02x %02x)\n",
n_xfers - n, u_tmp->len, rx_buf[0], rx_buf[1]);
if (__copy_to_user((u8 __user *)(uintptr_t)u_tmp->rx_buf, rx_buf,
u_tmp->len)) {
status = -EFAULT;
goto done;
}
rx_buf += u_tmp->len;
}
}
status = total;
done:
kfree(k_xfers);
printk(KERN_INFO "[SPIDEV_TRACE] message: EXIT, status=%d\n", status);
return status;
}
4.2 关键数据结构
c
/* 此时内存布局 */
用户空间:
tx_buf[2] = {0x01, 0x90} // 用户态缓冲区
rx_buf[2] = {未初始化}
内核空间:
spidev->tx_buffer: [0x01][0x90][...4094字节...]
spidev->rx_buffer: [0x??][0x??][...4094字节...]
k_xfers[0]:
.tx_buf = spidev->tx_buffer (指向0x01)
.rx_buf = spidev->rx_buffer (接收位置)
.len = 2
.speed_hz = 20000000
五、SPI核心层:spidev_sync → spi_sync
5.1 spidev_sync包装
c
static ssize_t
spidev_sync(struct spidev_data *spidev, struct spi_message *message)
{
DECLARE_COMPLETION_ONSTACK(done);
int status;
struct spi_device *spi;
printk(KERN_INFO "[SPIDEV_TRACE] sync: ENTER, message=%px\n", message);
spin_lock_irq(&spidev->spi_lock);
spi = spidev->spi;
spin_unlock_irq(&spidev->spi_lock);
if (spi == NULL) {
printk(KERN_ERR "[SPIDEV_TRACE] sync: device removed!\n");
status = -ESHUTDOWN;
} else {
printk(KERN_INFO "[SPIDEV_TRACE] sync: calling spi_sync(spi=%px, msg=%px)\n",
spi, message);
/* 进入SPI核心层 */
status = spi_sync(spi, message);
printk(KERN_INFO "[SPIDEV_TRACE] sync: spi_sync returned status=%d\n",
status);
}
if (status == 0) {
status = message->actual_length;
printk(KERN_INFO "[SPIDEV_TRACE] sync: actual_length=%u\n",
message->actual_length);
}
return status;
}
5.2 进入SPI核心层
c
// drivers/spi/spi.c
int spi_sync(struct spi_device *spi, struct spi_message *message)
{
int ret;
printk(KERN_INFO "[SPI_CORE_TRACE] spi_sync: ENTER, spi=%px, msg=%px\n",
spi, message);
mutex_lock(&spi->master->bus_lock_mutex);
/* 设置完成量 */
message->complete = spi_complete;
message->context = &done;
message->spi = spi;
printk(KERN_INFO "[SPI_CORE_TRACE] spi_sync: calling __spi_async\n");
/* 异步提交 */
ret = __spi_async(spi->master, message);
if (ret == 0) {
printk(KERN_INFO "[SPI_CORE_TRACE] spi_sync: waiting for completion...\n");
/* 等待传输完成 */
wait_for_completion(&done);
printk(KERN_INFO "[SPI_CORE_TRACE] spi_sync: transfer completed!\n");
ret = message->status;
}
mutex_unlock(&spi->master->bus_lock_mutex);
printk(KERN_INFO "[SPI_CORE_TRACE] spi_sync: EXIT, ret=%d\n", ret);
return ret;
}
六、平台驱动层:spi-imx.c
6.1 __spi_async → master->transfer
c
// drivers/spi/spi.c
static int __spi_async(struct spi_master *master, struct spi_message *message)
{
/* ... 验证代码 ... */
printk(KERN_INFO "[SPI_CORE_TRACE] __spi_async: calling master->transfer\n");
/* 调用平台驱动的transfer函数 */
return master->transfer(master->dev, message);
// ↑
// 这是 spi_imx_transfer (i.MX平台)
}
6.2 spi_imx_transfer
c
// drivers/spi/spi-imx.c
static int spi_imx_transfer(struct spi_device *spi, struct spi_message *mesg)
{
struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
struct spi_transfer *t;
printk(KERN_INFO "[SPI_IMX_TRACE] transfer: ENTER, mesg=%px\n", mesg);
/* 激活片选 */
spi_imx->chipselect(spi, BITBANG_CS_ACTIVE);
/* 遍历消息中的所有传输段 */
list_for_each_entry(t, &mesg->transfers, transfer_list) {
printk(KERN_INFO "[SPI_IMX_TRACE] transfer: processing xfer=%px, "
"len=%u, tx_buf=%px, rx_buf=%px\n",
t, t->len, t->tx_buf, t->rx_buf);
/* ========== 全双工核心:spi_imx_transfer_one ========== */
spi_imx_transfer_one(spi_imx, t);
}
/* 取消片选 */
spi_imx->chipselect(spi, BITBANG_CS_INACTIVE);
printk(KERN_INFO "[SPI_IMX_TRACE] transfer: EXIT\n");
mesg->complete(mesg->context); // 唤醒spi_sync中的等待
return 0;
}
七、硬件操作:真正的全双工实现
7.1 spi_imx_transfer_one(关键!)
c
static void spi_imx_transfer_one(struct spi_imx_data *spi_imx,
struct spi_transfer *t)
{
const u8 *tx_buf = t->tx_buf;
u8 *rx_buf = t->rx_buf;
unsigned int len = t->len;
unsigned int i;
printk(KERN_INFO "[SPI_IMX_TRACE] transfer_one: ENTER, len=%u\n", len);
printk(KERN_INFO "[SPI_IMX_TRACE] transfer_one: tx_buf=%px, rx_buf=%px\n",
tx_buf, rx_buf);
/* ========== 全双工实现:逐字节收发 ========== */
for (i = 0; i < len; i++) {
u8 tx_data, rx_data;
/* 步骤1:准备发送数据 */
if (tx_buf) {
tx_data = tx_buf[i];
} else {
tx_data = 0x00; // 如果没有TX数据,发送0
}
printk(KERN_INFO "[SPI_IMX_TRACE] transfer_one: byte[%u] TX=0x%02x\n",
i, tx_data);
/* 步骤2:写入TX FIFO */
writel(tx_data, spi_imx->base + MXC_CSPITXDATA);
// ↑ 写入硬件发送寄存器
// 此时MOSI线开始输出,同时MISO线也在接收!
/* 步骤3:等待传输完成 */
while (!(readl(spi_imx->base + MXC_CSPIINT) & MXC_INT_RR))
cpu_relax(); // 等待RX FIFO有数据
/* 步骤4:读取RX FIFO */
rx_data = readl(spi_imx->base + MXC_CSPIRXDATA);
// ↑ 从硬件接收寄存器读取
// 这是MISO线上同时接收到的数据!
printk(KERN_INFO "[SPI_IMX_TRACE] transfer_one: byte[%u] RX=0x%02x\n",
i, rx_data);
/* 步骤5:保存接收数据 */
if (rx_buf) {
rx_buf[i] = rx_data;
}
// 如果rx_buf为NULL,数据被丢弃
}
printk(KERN_INFO "[SPI_IMX_TRACE] transfer_one: EXIT\n");
}
7.2 硬件寄存器操作详解
c
/* i.MX SPI寄存器定义 */
#define MXC_CSPITXDATA 0x04 // TX数据寄存器
#define MXC_CSPIRXDATA 0x00 // RX数据寄存器
#define MXC_CSPIINT 0x0C // 中断状态寄存器
#define MXC_INT_RR 0x08 // RX FIFO Ready标志
/* 硬件传输时序 */
写入 MXC_CSPITXDATA:
→ 硬件自动产生时钟CLK
→ MOSI引脚输出tx_data的每一位
→ 同时MISO引脚接收从机数据
→ 接收完成后设置MXC_INT_RR标志
→ 数据存入MXC_CSPIRXDATA寄存器
读取 MXC_CSPIRXDATA:
→ 从RX FIFO取出数据
→ 清除MXC_INT_RR标志
八、完整调用链总结
用户态:
ioctl(fd, SPI_IOC_MESSAGE(1), xfer)
↓
─────────────────────────────────────
内核态 VFS层:
sys_ioctl()
↓ file->f_op->unlocked_ioctl
─────────────────────────────────────
spidev驱动层:
spidev_ioctl()
↓ 识别为SPI_IOC_MESSAGE命令
spidev_get_ioc_message()
↓ 从用户空间拷贝spi_ioc_transfer
spidev_message()
├─ 拷贝TX数据: copy_from_user(tx_buf, user_tx, len)
├─ 设置TX指针: k_xfer->tx_buf = spidev->tx_buffer
├─ 设置RX指针: k_xfer->rx_buf = spidev->rx_buffer
└─ 调用 spidev_sync()
↓
─────────────────────────────────────
SPI核心层:
spi_sync()
├─ 设置完成量
└─ __spi_async()
↓ master->transfer()
─────────────────────────────────────
平台驱动层(spi-imx.c):
spi_imx_transfer()
├─ 激活片选
├─ spi_imx_transfer_one()
│ └─ for (i = 0; i < len; i++)
│ ├─ writel(tx_data, TXDATA寄存器) ← MOSI发送
│ ├─ 等待RX Ready标志
│ └─ readl(RXDATA寄存器) → rx_data ← MISO接收
└─ 取消片选
↓
─────────────────────────────────────
硬件层:
SPI控制器
├─ 产生时钟CLK
├─ MOSI输出tx_data
├─ MISO接收rx_data
└─ 同时进行(全双工)
↓
─────────────────────────────────────
返回路径:
spi_imx_transfer_one()
→ 数据已写入rx_buf(内核bounce buffer)
spidev_message()
→ __copy_to_user(user_rx, rx_buf, len) ← 拷贝回用户空间
ioctl返回
→ 用户态rx_buf[]中有数据