第二阶段:SPI 控制器注册

第二阶段:SPI 控制器注册
第二阶段SPI 控制器注册目标理解一个 SPI 控制器比如 i.MX6ULL 的 ECSPI是怎么在 Linux 中注册的。两个核心函数spi_alloc_master(dev, size) 分配内存 初步初始化 ↓ spi_register_master(master) 注册到 SPI 子系统2.1 spi_alloc_master —— 分配 SPI 控制器逐行分析structspi_master*spi_alloc_master(structdevice*dev,unsignedsize){structspi_master*master;if(!dev)returnNULL;// ① 必须有父设备masterkzalloc(sizesizeof(*master),GFP_KERNEL);// ② 一次分配两块if(!master)returnNULL;device_initialize(master-dev);// ③ 初始化内嵌的 devicemaster-bus_num-1;// ④ 总线号还没分配master-num_chipselect1;// ⑤ 默认 1 个片选master-dev.classspi_master_class;// ⑥ 关联 classmaster-dev.parentdev;// ⑦ 父设备pm_suspend_ignore_children(master-dev,true);// ⑧ 电源管理spi_master_set_devdata(master,master[1]);// ⑨ 私有数据指针returnmaster;}步骤详解② 一次分配两块连续内存 —— 知识点 1 的变种masterkzalloc(sizesizeof(*master),GFP_KERNEL);返回的内存块size sizeof(spi_master) 字节 master ─→ ┌──────────────────────────┐ │ struct spi_master │ ← sizeof(spi_master) 字节 │ ├── bus_num │ 你的核心数据 │ ├── dev (内嵌 device) │ │ ├── transfer_one │ │ └── ... │ ├──────────────────────────┤ ← master 1 的位置 │ 驱动私有数据 │ ← size 字节控制器驱动用自己的数据 │ (spi_imx_data 等) │ 比如 spi_imx_data 结构体 └──────────────────────────┘ 一块内存前半是 spi_master后半是驱动的私有空间。知识点 1内嵌struct device dev是内嵌在 spi_master 里的一次分配一起管理。⑨ 私有数据指针 —— 数组越界的巧妙用法spi_master_set_devdata(master,master[1]);master[1]等价于*(master 1)即跳过整个sizeof(spi_master)后的地址——恰好是驱动私有数据区的开头。以后驱动代码通过spi_master_get_devdata(master)拿到这个指针强制转换成自己的数据结构比如struct spi_imx_data *。③⑥ 连接第一阶段device_initialize(master-dev);// 知识点 1内嵌 device 初始化master-dev.classspi_master_class;// 指向第一阶段 class_register 创建的 classmaster-dev.parentdev;// 父设备 platform 设备如 2000000.ecspidevice_initialize将master-dev.kobj.kref设为 1。注意master-dev.bus没有设置——因为控制器是 platform 设备不是 SPI 设备。controller 自己也挂在 platform_bus 上。检验问题答对才进 spi_register_masterQ1kzalloc(size sizeof(*master))分配了多少字节如果 size0 会怎样Q2master[1]是什么地址为什么master[1]不会越界Q3master-dev.class spi_master_class连接了第一阶段哪个函数的产物这个连接导致什么 sysfs 现象Q4为什么master-dev.bus没有设成spi_bus_type提示想想 spi_master 是从哪注册的2.2 spi_register_master —— 注册控制器现在看完整源码分 8 步。步骤全景intspi_register_master(structspi_master*master){staticatomic_tdyn_bus_idATOMIC_INIT((115)-1);// 32767 起始structdevice*devmaster-dev.parent;intstatus-ENODEV;intdynamic0;if(!dev)return-ENODEV;// ① 解析设备树 cs-gpiosstatusof_spi_register_master(master);if(status)returnstatus;// ② 检查 分配 bus_numif(master-num_chipselect0)return-EINVAL;if((master-bus_num0)master-dev.of_node)master-bus_numof_alias_get_id(master-dev.of_node,spi);if(master-bus_num0){master-bus_numatomic_dec_return(dyn_bus_id);dynamic1;}// ③ 初始化队列/锁/completionINIT_LIST_HEAD(master-queue);spin_lock_init(master-queue_lock);spin_lock_init(master-bus_lock_spinlock);mutex_init(master-bus_lock_mutex);mutex_init(master-io_mutex);master-bus_lock_flag0;init_completion(master-xfer_completion);if(!master-max_dma_len)master-max_dma_lenINT_MAX;// ④ 设备命名 注册dev_set_name(master-dev,spi%u,master-bus_num);statusdevice_add(master-dev);if(status0)gotodone;// ⑤ 创建消息队列或警告老式驱动if(master-transfer)dev_info(dev,master is unqueued, this is deprecated\n);else{statusspi_master_initialize_queue(master);if(status){device_del(master-dev);gotodone;}}// ⑥ 挂到全局 spi_master_listspin_lock_init(master-statistics.lock);mutex_lock(board_lock);list_add_tail(master-list,spi_master_list);list_for_each_entry(bi,board_list,list)spi_match_master_to_boardinfo(master,bi-board_info);mutex_unlock(board_lock);// ⑦ 从设备树和 ACPI 创建 spi_deviceof_register_spi_devices(master);acpi_register_spi_devices(master);done:returnstatus;}逐步详解① 解析 cs-gpios知识点 3statusof_spi_register_master(master);从设备树cs-gpios属性读取片选 GPIOecspi1 { cs-gpios gpio4 26 GPIO_ACTIVE_LOW; // 你的板子 };解析后存入master-cs_gpios[]数组num_chipselect自动调整。② 分配 bus_num优先级 1设备树别名 aliases { spi0 ecspi1; } → bus_num 0 优先级 2动态分配从 32767 往下减 → bus_num 32766, 32765...你的板子走优先级 1——设备树设置了spi0、spi1别名。③ 初始化队列和锁INIT_LIST_HEAD(master-queue);// 知识点3消息队列锚点init_completion(master-xfer_completion);// 传输完成信号量master 现在像一个快递站 queue → 待发送包裹的货架空 queue_lock → 货架的锁 bus_lock_mutex → 传送带的使用权锁 xfer_completion → 此包裹已送达的签收单④ device_add —— 最核心连接第一阶段dev_set_name(master-dev,spi%u,master-bus_num);// 名spi0statusdevice_add(master-dev);做了三件事1. 在 /sys/devices/platform/2008000.ecspi/ 下创建 spi0 目录 2. 因为 master-dev.class spi_master_classspi_alloc_master 中设的 → 在 /sys/class/spi_master/ 下自动创建符号链接 spi0 3. 因为 master-dev.bus 未设platform 设备不触发 bus-match验证——你的板子上ls/sys/class/spi_master/# spi0 spi1 spi4ls/sys/devices/platform/2008000.ecspi/spi0/⑤ 创建消息队列if(master-transfer)// 老式驱动自己处理传输else{statusspi_master_initialize_queue(master);// 标准路径}spi_master_initialize_queue做 3 件事下一节细讲1. master-transfer spi_queued_transfer ← 知识点2替换函数指针 2. spi_init_queue ← 创建 kthread_worker 线程 3. spi_start_queue ← running true⑥ 挂到全局链表知识点 3 实战list_add_tail(master-list,spi_master_list);// ↑ master 内的挂钩 ↑ 第一阶段见过的全局锚点spi_master_list全局第一阶段 spi.c:417 ├── spi4 (GPIO) ├── spi0 (2008000.ecspi) └── spi1 (2010000.ecspi)以后遍历这个链表就能找到所有控制器。⑦ 创建 spi_device连接第三阶段of_register_spi_devices(master);遍历master-dev.of_node的每个子节点为每个外设创建spi_device。例如你的设备树ecspi1 { spi0 { compatible ...; reg 0; }; // → spi0.0 spi1 { compatible ...; reg 1; }; // → spi0.1 };这就是第一阶段和第二阶段产生结果的地方——第一阶段创建了空目录/sys/bus/spi/devices/第二阶段把spi0.0、spi0.1填进去。检验问题社招标准先回答这 4 题再进入 ⑤ 的深入。Q1L1master-bus_num从哪来你的板子 ECSPI1 的 bus_num 是 0是怎么确定的Q2L2master-dev.class spi_master_class在 spi_alloc_master 中设置dev_set_name(master-dev, spi%u, bus_num)在 spi_register_master 中设置。为什么 class 在 alloc 阶段设置而名字在 register 阶段设置Q3L3第 ③ 步初始化的锁中queue_lockspinlock和bus_lock_mutexmutex各自保护什么为什么一个用 spinlock 一个用 mutex提示考虑它们分别在什么上下文中被使用Q4L3device_add失败 goto done 直接返回错误码。第 ⑤ 步失败却要device_del再 goto done。为什么同样是失败清理动作不同⑤ spi_master_initialize_queue第一层入口函数staticintspi_master_initialize_queue(structspi_master*master){intret;master-transferspi_queued_transfer;// ① 替换函数指针if(!master-transfer_one_message)master-transfer_one_messagespi_transfer_one_message;// ② 默认实现retspi_init_queue(master);// ③ 创建线程if(ret)gotoerr_init_queue;master-queuedtrue;retspi_start_queue(master);// ④ 启动队列if(ret){spi_destroy_queue(master);gotoerr_start_queue;}return0;err_start_queue:spi_destroy_queue(master);err_init_queue:returnret;}① 替换函数指针知识点 2 实战master-transfer原本可能是控制器驱动设置的老式回调。这里强行替换成 spi 核心层的spi_queued_transfer。以后任何对master-transfer(spi, msg)的调用实际执行的都是排队逻辑。② 默认实现如果控制器驱动没提供transfer_one_message给一个默认版本——遍历 msg 里的 transfer 链表逐个调用transfer_one。第二层spi_init_queue —— 创建内核线程staticintspi_init_queue(structspi_master*master){master-runningfalse;// 先标记还没开始运行master-busyfalse;// 当前没有正在传输的消息kthread_init_worker(master-kworker);// A 初始化工作管理器master-kworker_taskkthread_run(kthread_worker_fn,// B 创建内核线程master-kworker,%s,dev_name(master-dev));if(IS_ERR(master-kworker_task))returnPTR_ERR(master-kworker_task);kthread_init_work(master-pump_messages,spi_pump_messages);// C 绑定工作函数if(master-rt)// D 可选实时优先级sched_setscheduler(master-kworker_task,SCHED_FIFO,param);return0;}三样东西被创建出来kthread_worker ┌──────────────┐ │ 工作管理器 │ ← A 初始化 │ 管理任务队列 │ └──────┬───────┘ │ 绑定 ┌──────▼───────┐ │ kworker_task │ ← B 内核线程 │ spi0 │ 等待任务执行任务继续等待 └──────┬───────┘ │ 执行 kthread_work ────▼──────┐ ┌────────────────────┐ │ │ pump_messages │←─┘ ← C 工作任务 │ function: │ 被提交后线程醒来执行它 │ spi_pump_messages │ └────────────────────┘类比快递站 kthread_worker 站长办公室管理待办任务列表 kworker_task 快递员一个一直在的人有活就干没活睡觉 pump_messages 去XX小区送这个包裹的指令单 流程 有人寄包裹 → 把指令单放站长办公室 → 拍醒快递员 快递员醒来 → 拿起指令单 → 执行 spi_pump_messages → 送完包裹 回站长办公室 → 发现没新指令 → 继续睡觉第三层spi_queued_transfer —— 消息入队这是每次有人要发 SPI 数据时实际调用的函数。staticint__spi_queued_transfer(structspi_device*spi,structspi_message*msg,bool need_pump){structspi_master*masterspi-master;spin_lock_irqsave(master-queue_lock,flags);// Q3 里的 spinlockif(!master-running){// 队列停掉了spin_unlock_irqrestore(master-queue_lock,flags);return-ESHUTDOWN;}msg-actual_length0;// 还没传输任何字节msg-status-EINPROGRESS;// 标记传输进行中list_add_tail(msg-queue,master-queue);// 知识点3包裹放到货架if(!master-busyneed_pump)// 传送带闲着kthread_queue_work(master-kworker,// 拍醒快递员master-pump_messages);spin_unlock_irqrestore(master-queue_lock,flags);return0;}逐行解释spin_lock_irqsave → 拿 spinlock关中断。因为可能在中断上下文被调用。 !master-running → 队列已关闭返回 -ESHUTDOWN。 actual_length 0 → 清空计数器。 status -EINPROGRESS → 正在传输中后面 spi_sync 会等它变成 0。 list_add_tail → 知识点3把 msg 挂到 master-queue 链表尾部。 包裹放到货架上。 !master-busy → 传送带SPI 总线没人用 need_pump → true普通调用/ false已持锁调用 kthread_queue_work → 提交 pump_messages 任务到 worker → 唤醒快递员线程 spin_unlock_irqrestore → 放锁开中断。spi_start_queue —— 正式开张staticintspi_start_queue(structspi_master*master){spin_lock_irqsave(master-queue_lock,flags);master-runningtrue;// 开始营业master-cur_msgNULL;// 当前无消息spin_unlock_irqrestore(master-queue_lock,flags);kthread_queue_work(master-kworker,master-pump_messages);// 第一次唤醒return0;}第一次唤醒是让spi_pump_messages执行发现队列是空的master-queue没有任何消息然后master-busy false线程继续睡眠。相当于开张仪式——快递员到店发现没包裹坐椅子上等。数据流全景某个驱动调用 spi_sync(spi, msg) │ ▼ master-transfer(spi, msg) ← 知识点2函数指针 │ ▼ spi_queued_transfer(spi, msg) ← 实际执行这个 │ ├── list_add_tail ← 知识点3消息挂到 master-queue ├── kthread_queue_work ← 提交 pump_messages 任务 │ ▼ kthread_worker 线程醒来 ← 上下文切换调用者 → 内核线程 │ ▼ spi_pump_messages(work) ← 第三阶段的核心 │ ├── 从 master-queue 取消息 ├── master-transfer_one_message(master, msg) │ └── 遍历 msg-transfers → master-transfer_one │ → spi_imx_transfer → 硬件寄存器 ├── msg-complete() ← 回调通知调用者 └── 取下一个消息 or 继续睡眠检验问题Q1L1spi_init_queue创建了哪三样东西每样东西在现实生活中对应什么Q2L2master-transfer被替换成了spi_queued_transfer。如果控制器驱动不想用排队模式它应该怎么绕过去提示看spi_register_master第 ⑤ 步的 if-else 判断Q3L3spi_queued_transfer中!master-busy的检查。如果busy为 true快递员正在送别的包裹新消息怎么被处理谁负责在下一次处理它⑥ list_add_tail board_info 匹配mutex_lock(board_lock);list_add_tail(master-list,spi_master_list);list_for_each_entry(bi,board_list,list)spi_match_master_to_boardinfo(master,bi-board_info);mutex_unlock(board_lock);list_add_tail —— 知识点 3 实战无须多讲你已经会的把新注册的 master 挂到全局spi_master_list。以后遍历这个链表就能找到所有 SPI 控制器。board_info 匹配 —— 你的板子基本不用board_list是板文件spi_register_board_info注册的设备信息。你的板子用设备树board_list大概率是空的list_for_each_entry直接跳过。⑦ of_register_spi_devices —— 设备树子节点 → spi_device这是第二阶段最重要的步骤。它桥接了控制器注册和设备创建。外层遍历子节点staticvoidof_register_spi_devices(structspi_master*master){structdevice_node*nc;if(!master-dev.of_node)// 没有设备树节点跳过return;for_each_available_child_of_node(master-dev.of_node,nc){if(of_node_test_and_set_flag(nc,OF_POPULATED))// 已经处理过跳过continue;spiof_register_spi_device(master,nc);// 每个子节点创建一个spi_device}}对应设备树ecspi1 { ← master-dev.of_node flash: w25q1280 { ← 子节点1遍历时处理 compatible jedec,spi-nor; reg 0; spi-max-frequency 104000000; }; spidev: spi1 { ← 子节点2遍历时处理 compatible rohm,dh2228fv; reg 1; spi-max-frequency 20000000; }; };for_each_available_child_of_node依次取出每个status ! disabled的子节点交给of_register_spi_device处理。内层of_register_spi_device —— 解析属性 创建 spi_device① 分配 spi_devicespispi_alloc_device(master);// 类似 spi_alloc_master分配 初始化② 生成 modalias —— 驱动的匹配凭据rcof_modalias_node(nc,spi-modalias,sizeof(spi-modalias));从设备树的compatible属性生成modalias。比如compatible jedec,spi-nor→modalias spi:jedec,spi-nor前面自动加spi:前缀对应知识点 2 中SPI_MODULE_PREFIX。这个modalias就是第四阶段spi_match_device做名称回退匹配时用的字符串。③ 解析 reg → chip_selectrcof_property_read_u32(nc,reg,value);spi-chip_selectvalue;// reg0 → CS0, reg1 → CS1④ 解析 SPI 模式标志if(of_find_property(nc,spi-cpha,NULL))spi-mode|SPI_CPHA;if(of_find_property(nc,spi-cpol,NULL))spi-mode|SPI_CPOL;if(of_find_property(nc,spi-cs-high,NULL))spi-mode|SPI_CS_HIGH;if(of_find_property(nc,spi-3wire,NULL))spi-mode|SPI_3WIRE;if(of_find_property(nc,spi-lsb-first,NULL))spi-mode|SPI_LSB_FIRST;只检查属性是否存在不关心值。spi-cpha存在 CPHA1。spi-tx-bus-width/spi-rx-bus-width检查值2DUAL, 4QUAD。⑤ 解析 spi-max-frequencyrcof_property_read_u32(nc,spi-max-frequency,value);if(rc)gotoerr_out;// 必须有没有就失败spi-max_speed_hzvalue;这是唯一一个必填属性。如果设备树里没写spi-max-frequencyspi_device创建失败。⑥ 保存设备树节点指针of_node_get(nc);// 引用计数 1spi-dev.of_nodenc;// 以后通过 spi-dev.of_node 访问设备树of_node_get增加引用计数防止设备树节点在 spi_device 还在使用时被释放。这和知识点 3 里klist_devices_get增加设备引用计数的道理一样。⑦ 注册到 SPI 子系统rcspi_add_device(spi);// 第三阶段的核心调用device_add把spi_device注册到设备模型。此时/sys/bus/spi/devices/spi0.0 出现 ← 连接 deivice_add 创建的 /sys/bus/spi/devices/spi0.1 出现 同时 device_add 触发 bus-match 匹配 → 如果有已注册的驱动自动尝试 match 和 probe数据流总结spi_register_master(master) └── of_register_spi_devices(master) └── for_each_available_child_of_node └── of_register_spi_device(master, nc) ├── spi_alloc_device(master) → 分配 spi_device ├── of_modalias_node → 生成 modalias ├── of_property_read_u32(reg) → chip_select ├── 解析 spi-cpha/cpol/... → mode 标志位 ├── 解析 spi-tx/rx-bus-width → DUAL/QUAD ├── of_property_read_u32(spi-max-frequency) → max_speed_hz └── spi_add_device(spi) → device_add → 触发匹配这是第一阶段和第二阶段交汇产生成果的位置——第一阶段搭的空架子/sys/bus/spi/devices/现在有了真实的设备。检验问题Q1L1设备树里reg 1对应spi_device的哪个字段compatible jedec,spi-nor生成了什么modaliasQ2L2如果设备树子节点没写spi-max-frequency会发生什么在哪一行代码报错Q3L3spi_add_device内部调用device_add这时会触发什么匹配流程如果已经注册了匹配的spi_driverprobe会在这个上下文中被调用吗提示想想drivers_autoprobe