借助通用驱动spidev实现SPI全双工通信

一、核心概念

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引脚行为

  1. 第一次写入:DOUT输出芯片复位后的默认值(通常是0或未定义)
  2. 之后每次写入: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[]中有数据
相关推荐
单片机系统设计2 小时前
基于STM32的宠物智能喂食系统
c语言·stm32·单片机·嵌入式硬件·物联网·毕业设计·宠物
雾削木2 小时前
STM32 HAL DS1302时钟模块
stm32·单片机·嵌入式硬件
沪漂的码农3 小时前
FlexCAN寄存器完全解读
stm32·单片机·嵌入式硬件·can
申克Lab15 小时前
STM32 FreeRTOS 消息队列
java·stm32·嵌入式硬件
Digitally20 小时前
如何通过 5 种方法轻松格式化 USB 驱动器
stm32·单片机·嵌入式硬件
BT-BOX21 小时前
STM32各系列芯片编译支持包 Pack下载
stm32·单片机·嵌入式硬件
GUET_一路向前21 小时前
【 在有返回值的函数里直接 return;到底返回值是多少?】
stm32
云山工作室1 天前
基于单片机的飞机客舱窗帘控制系统(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计