第七阶段:spidev.c —— 用户空间接口

📅 2026/7/19 15:20:21
第七阶段:spidev.c —— 用户空间接口
第七阶段spidev.c —— 用户空间接口完整数据流用户程序 内核 硬件 ──────── ──── ──── open(/dev/spidev0.0) → spidev_open ├── 分配 tx_buffer (4096字节) └── 分配 rx_buffer (4096字节) ioctl(SPI_IOC_MESSAGE(1)) → spidev_ioctl └── spidev_message ├── copy_from_user: 用户数据 → tx_buffer ├── 构造 spi_transfer spi_message ├── spidev_sync → spi_sync │ └── [第五阶段全部流程] │ → [第六阶段全部流程] │ → 硬件寄存器 → DMA/中断 ├── copy_to_user: rx_buffer → 用户数据 └── 返回spidev_init —— 模块初始化staticint__initspidev_init(void){// ① 注册字符设备主设备号 153名字 spiregister_chrdev(SPIDEV_MAJOR,spi,spidev_fops);// ② 创建 /sys/class/spidev/spidev_classclass_create(THIS_MODULE,spidev);// ③ 注册为 SPI 驱动 — 连接第四阶段spi_register_driver(spidev_spi_driver);// ↑ 这就是第四阶段的入口// 设备树里 compatiblerohm,dh2228fv → probe 被调用}spidev_probe —— 创建 /dev/spidevX.Xstaticintspidev_probe(structspi_device*spi){structspidev_data*spidev;unsignedlongminor;spidevkzalloc(sizeof(*spidev),GFP_KERNEL);spidev-spispi;// 找空闲次设备号minorfind_first_zero_bit(minors,N_SPI_MINORS);// 创建设备节点spidev-devtMKDEV(SPIDEV_MAJOR,minor);// 主设备号 153次设备号动态分配device_create(spidev_class,spi-dev,spidev-devt,spidev,spidev%d.%d,spi-master-bus_num,spi-chip_select);// → 创建 /dev/spidev0.0, /dev/spidev0.1 等list_add(spidev-device_entry,device_list);// 知识点3spidev-speed_hzspi-max_speed_hz;// 默认速度 设备树里的值}spidev_open —— 打开设备staticintspidev_open(structinode*inode,structfile*filp){// ① 在 device_list 中找对应次设备号的 spidev_datalist_for_each_entry(spidev,device_list,device_entry){if(spidev-devtinode-i_rdev)// 设备号匹配break;}// ② 分配 bounce buffer第一次打开时if(!spidev-tx_buffer)spidev-tx_bufferkmalloc(bufsiz,GFP_KERNEL);// 默认 4096 字节if(!spidev-rx_buffer)spidev-rx_bufferkmalloc(bufsiz,GFP_KERNEL);spidev-users;filp-private_dataspidev;// 以后 ioctl 通过这个拿 spidev_data}为什么需要 bounce buffer用户空间的tx_buf指针指向的内存可能被换出swapDMA 不能直接访问。必须先copy_from_user到内核空间的安全缓冲区tx_bufferDMA 从这个缓冲区读。spidev_message —— 核心用户数据到 SPI 传输staticintspidev_message(structspidev_data*spidev,structspi_ioc_transfer*u_xfers,unsignedn_xfers){structspi_messagemsg;structspi_transfer*k_xfers;// 内核版 transfer 数组spi_message_init(msg);k_xferskcalloc(n_xfers,sizeof(*k_tmp),GFP_KERNEL);tx_bufspidev-tx_buffer;// bounce buffer 起点rx_bufspidev-rx_buffer;// 遍历用户传入的每个 ioc_transferfor(nn_xfers;n;n--,k_tmp,u_tmp){k_tmp-lenu_tmp-len;if(u_tmp-rx_buf){// 有接收 → 预留 bounce buffer 空间k_tmp-rx_bufrx_buf;rx_bufk_tmp-len;}if(u_tmp-tx_buf){// 有发送 → 从用户空间拷贝数据k_tmp-tx_buftx_buf;copy_from_user(tx_buf,u_tmp-tx_buf,u_tmp-len);// ← 关键tx_bufk_tmp-len;}k_tmp-cs_change!!u_tmp-cs_change;k_tmp-bits_per_wordu_tmp-bits_per_word;k_tmp-speed_hzu_tmp-speed_hz;spi_message_add_tail(k_tmp,msg);// 加到 message 的 transfer 链表}// 发送→ 第五阶段 → 第六阶段statusspidev_sync(spidev,msg);// 传输完成 → 把收到的数据拷回用户空间rx_bufspidev-rx_buffer;for(nn_xfers;n;n--,u_tmp){if(u_tmp-rx_buf)copy_to_user(u_tmp-rx_buf,rx_buf,u_tmp-len);// ← 关键rx_bufu_tmp-len;}}两次 copy 的原因copy_from_user用户数据 → 内核安全缓冲区DMA 能用copy_to_user内核安全缓冲区 → 用户空间用户能读spidev_sync —— 调用 spi_syncstaticssize_tspidev_sync(structspidev_data*spidev,structspi_message*message){spispidev-spi;// 拿 spi_devicestatusspi_sync(spi,message);// → 第五阶段if(status0)statusmessage-actual_length;// 返回实际传输字节数returnstatus;}就一行核心调用。这就是你学了六个阶段的终点——spi_sync。检验Q1L1/dev/spidev0.0这个设备节点是谁创建的名字里0.0的两部分分别来自哪里device_create(spidev_class,spi-dev,spidev-devt,spidev,spidev%d.%d,spi-master-bus_num,spi-chip_select);// ↑ 第1个 %d ↑ 第2个 %dspi0.0spi0控制器总线号.0片选号 CS0。spi0.1 控制器 spi0 上的 CS1 设备。Q2L2为什么spidev_message要把用户空间的tx_buf用copy_from_user拷到spidev-tx_buffer而不是直接把用户指针赋给spi_transfer.tx_bufDMA 能用的内存和缺页。用户空间指针指向的内存可能被 swap 换出。当 DMA 硬件试图访问这块内存时它不能触发缺页中断DMA 控制器不是 CPU。结果就是 DMA 读到错误数据或直接失败。copy_from_user把数据拷到kmalloc分配的内核缓冲区——这块内存永远在物理内存中DMA 可以安全访问。一句话用户空间内存不能做 DMA必须 copy 到内核安全缓冲区。Q3L3spidev_open中分配的tx_buffer是在第一个用户open时才分配的而不是在spidev_probe中分配。为什么要延迟到open时如果 4096 字节bufsiz不够一次传输的 RX 数据总量会发生什么为什么延迟到 open 时分配而不是 probe 时—— 省内存。一个系统可能有 4 个 spidev 设备spi0.0、spi0.1、spi1.0、spi1.1每个 tx_buffer rx_buffer 8KB四个就是 32KB。如果设备从不被打开这 32KB 就浪费了。如果 4096 字节不够—— 不是分两次传输。spidev_message 里直接拒绝if(rx_totalbufsiz){status-EMSGSIZE;// ← 直接报错不拆分gotodone;}用户看到 ioctl 返回 -EMSGSIZE。解决方法insmod spidev.ko bufsiz8192增大 buffer。