第二阶段: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;// ① 必须有父设备master=kzalloc(size+sizeof(*master),GFP_KERNEL);// ② 一次分配两块if(!master)returnNULL;device_initialize(&master->dev);// ③ 初始化内嵌的 devicemaster->bus_num=-1;// ④ 总线号还没分配master->num_chipselect=1;// ⑤ 默认 1 个片选master->dev.class=&spi_master_class;// ⑥ 关联 classmaster->dev.parent=dev;// ⑦ 父设备pm_suspend_ignore_children(&master->dev,true);// ⑧ 电源管理spi_master_set_devdata(master,&master[1]);// ⑨ 私有数据指针returnmaster;}

步骤详解

② 一次分配两块连续内存 —— 知识点 1 的变种

master=kzalloc(size+sizeof(*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.class=&spi_master_class;// 指向第一阶段 class_register 创建的 classmaster->dev.parent=dev;// 父设备 = platform 设备(如 2000000.ecspi)

device_initializemaster->dev.kobj.kref设为 1。注意master->dev.bus没有设置——因为控制器是 platform 设备,不是 SPI 设备。controller 自己也挂在 platform_bus 上。


检验问题(答对才进 spi_register_master)

Q1kzalloc(size + sizeof(*master))分配了多少字节?如果 size=0 会怎样?

Q2&master[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_id=ATOMIC_INIT((1<<15)-1);// 32767 起始structdevice*dev=master->dev.parent;intstatus=-ENODEV;intdynamic=0;if(!dev)return-ENODEV;// ① 解析设备树 cs-gpiosstatus=of_spi_register_master(master);if(status)returnstatus;// ② 检查 + 分配 bus_numif(master->num_chipselect==0)return-EINVAL;if((master->bus_num<0)&&master->dev.of_node)master->bus_num=of_alias_get_id(master->dev.of_node,"spi");if(master->bus_num<0){master->bus_num=atomic_dec_return(&dyn_bus_id);dynamic=1;}// ③ 初始化队列/锁/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_flag=0;init_completion(&master->xfer_completion);if(!master->max_dma_len)master->max_dma_len=INT_MAX;// ④ 设备命名 + 注册dev_set_name(&master->dev,"spi%u",master->bus_num);status=device_add(&master->dev);if(status<0)gotodone;// ⑤ 创建消息队列(或警告老式驱动)if(master->transfer)dev_info(dev,"master is unqueued, this is deprecated\n");else{status=spi_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(知识点 3)

status=of_spi_register_master(master);

从设备树cs-gpios属性读取片选 GPIO:

&ecspi1 { 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——设备树设置了spi0spi1别名。


③ 初始化队列和锁

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);// 名:"spi0"status=device_add(&master->dev);

做了三件事:

1. 在 /sys/devices/platform/2008000.ecspi/ 下创建 spi0 目录 2. 因为 master->dev.class = &spi_master_class(spi_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{status=spi_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 { spi@0 { compatible = "..."; reg = <0>; }; // → spi0.0 spi@1 { compatible = "..."; reg = <1>; }; // → spi0.1 };

这就是第一阶段和第二阶段产生结果的地方——第一阶段创建了空目录/sys/bus/spi/devices/,第二阶段把spi0.0spi0.1填进去。


检验问题(社招标准)

先回答这 4 题,再进入 ⑤ 的深入。

Q1(L1)master->bus_num从哪来?你的板子 ECSPI1 的 bus_num 是 0,是怎么确定的?

Q2(L2)master->dev.class = &spi_master_class在 spi_alloc_master 中设置,dev_set_name(&master->dev, "spi%u", bus_num)在 spi_register_master 中设置。为什么 class 在 alloc 阶段设置,而名字在 register 阶段设置?

Q3(L3):第 ③ 步初始化的锁中,queue_lock(spinlock)和bus_lock_mutex(mutex)各自保护什么?为什么一个用 spinlock 一个用 mutex?(提示:考虑它们分别在什么上下文中被使用)

Q4(L3)device_add失败 goto done 直接返回错误码。第 ⑤ 步失败却要device_del再 goto done。为什么同样是失败,清理动作不同?

⑤ spi_master_initialize_queue

第一层:入口函数

staticintspi_master_initialize_queue(structspi_master*master){intret;master->transfer=spi_queued_transfer;// ① 替换函数指针if(!master->transfer_one_message)master->transfer_one_message=spi_transfer_one_message;// ② 默认实现ret=spi_init_queue(master);// ③ 创建线程if(ret)gotoerr_init_queue;master->queued=true;ret=spi_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->running=false;// 先标记:还没开始运行master->busy=false;// 当前没有正在传输的消息kthread_init_worker(&master->kworker);// A 初始化工作管理器master->kworker_task=kthread_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*master=spi->master;spin_lock_irqsave(&master->queue_lock,flags);// Q3 里的 spinlock!if(!master->running){// 队列停掉了spin_unlock_irqrestore(&master->queue_lock,flags);return-ESHUTDOWN;}msg->actual_length=0;// 还没传输任何字节msg->status=-EINPROGRESS;// 标记:传输进行中list_add_tail(&msg->queue,&master->queue);// 知识点3:包裹放到货架if(!master->busy&&need_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->running=true;// 开始营业master->cur_msg=NULL;// 当前无消息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 继续睡眠

检验问题

Q1(L1)spi_init_queue创建了哪三样东西?每样东西在现实生活中对应什么?

Q2(L2)master->transfer被替换成了spi_queued_transfer。如果控制器驱动不想用排队模式,它应该怎么绕过去?(提示:看spi_register_master第 ⑤ 步的 if-else 判断)

Q3(L3)spi_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;spi=of_register_spi_device(master,nc);// 每个子节点创建一个spi_device}}

对应设备树:

&ecspi1 { ← master->dev.of_node flash: w25q128@0 { ← 子节点1,遍历时处理 compatible = "jedec,spi-nor"; reg = <0>; spi-max-frequency = <104000000>; }; spidev: spi@1 { ← 子节点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_device

spi=spi_alloc_device(master);// 类似 spi_alloc_master,分配 + 初始化

② 生成 modalias —— 驱动的匹配凭据

rc=of_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_select

rc=of_property_read_u32(nc,"reg",&value);spi->chip_select=value;// reg=<0> → CS0, reg=<1> → 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存在 = CPHA=1。spi-tx-bus-width/spi-rx-bus-width检查值(2=DUAL, 4=QUAD)。

⑤ 解析 spi-max-frequency

rc=of_property_read_u32(nc,"spi-max-frequency",&value);if(rc)gotoerr_out;// 必须有!没有就失败spi->max_speed_hz=value;

这是唯一一个必填属性。如果设备树里没写spi-max-frequencyspi_device创建失败。

⑥ 保存设备树节点指针

of_node_get(nc);// 引用计数 +1spi->dev.of_node=nc;// 以后通过 spi->dev.of_node 访问设备树

of_node_get增加引用计数,防止设备树节点在 spi_device 还在使用时被释放。这和知识点 3 里klist_devices_get增加设备引用计数的道理一样。

⑦ 注册到 SPI 子系统

rc=spi_add_device(spi);// 第三阶段的核心!

调用device_addspi_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/,现在有了真实的设备。


检验问题

Q1(L1):设备树里reg = <1>对应spi_device的哪个字段?compatible = "jedec,spi-nor"生成了什么modalias

Q2(L2):如果设备树子节点没写spi-max-frequency,会发生什么?在哪一行代码报错?

Q3(L3)spi_add_device内部调用device_add,这时会触发什么匹配流程?如果已经注册了匹配的spi_driverprobe会在这个上下文中被调用吗?(提示:想想drivers_autoprobe