3、Linux内核UFS子系统:UFS主机控制器驱动框架、UFS设备驱动架构、关键数据结构解析
好,我们进入第三章。这一章可以说是整个UFS驱动移植的骨架。你想想看,不管你是要移植海力士的UFS芯片,还是三星的,最终都要跟Linux内核的UFS子系统打交道。这个子系统长什么样?主机控制器驱动怎么搭?设备驱动又是什么套路?我们今天一次性说清楚。
3.1 UFS主机控制器驱动框架
先说说主机控制器驱动。说白了,它就是CPU和UFS设备之间的“翻译官”。CPU发的是AHI(Advanced Host Interface)协议,UFS设备懂的是UniPro/M-PHY物理层协议。主机控制器驱动要做的,就是把这两边串起来。
我个人习惯把主机控制器驱动分成三层来看:
- 底层:直接操作寄存器。比如配置UFSHCI(UFS Host Controller Interface)的基地址、中断使能、Doorbell寄存器等。
- 中间层:处理UTP(UFS Transport Protocol)传输层。说白了就是组装命令、管理请求队列。
- 上层:对接SCSI核心层。因为UFS在Linux里被抽象成一个SCSI设备,所以上层要处理好SCSI命令到UFS命令的映射。
嗯,这里要注意一个关键点:主机控制器驱动不负责具体的UFS协议细节。它只负责把命令发出去,把数据搬回来。至于命令怎么解析、设备怎么响应,那是UFS设备驱动的事。
核心思路:主机控制器驱动 = 寄存器操作 + 传输层管理 + SCSI适配。
3.2 UFS设备驱动架构
设备驱动这边,情况稍微复杂一点。UFS设备本身是一个“黑盒子”,内部有自己的一套逻辑:
- 它有自己的固件(FW)
- 它有自己的任务管理器(Task Manager)
- 它支持多个逻辑单元(LU,Logical Unit)
我在项目中遇到过一个问题:某款海力士UFS芯片在初始化时,总是卡在查询设备描述符这一步。后来发现是设备驱动没有正确设置查询超时时间。你看,这种坑不踩一次,你根本想不到。
UFS设备驱动的核心工作,我总结为三点:
- 设备发现与初始化:发送Query Request,获取设备描述符、配置描述符、几何描述符等。
- 任务管理:处理Abort、Logical Unit Reset等操作。
- 电源管理:控制设备在Active、Sleep、Power Down等状态间切换。
说白了,设备驱动就是UFS设备的“管家”。它要管设备的状态、管设备的配置、管设备的异常处理。
避坑指南:我曾经在移植时忽略了设备驱动的电源管理部分,结果设备在休眠后无法唤醒。后来查了海力士的datasheet才发现,某些UFS设备需要先发送特定的POWER_MODE命令,才能进入低功耗状态。这个顺序搞反了,设备就“死”给你看。
3.3 关键数据结构解析
数据结构这块,是写代码时最绕不开的。Linux内核UFS子系统中,有几个核心结构体你必须烂熟于心。
3.3.1 struct ufs_hba
这是主机控制器的“身份证”。每个UFS主机控制器对应一个ufs_hba实例。它包含了:
- 寄存器映射地址(mmio_base)
- 中断号(irq)
- 传输请求队列(lrb)
- 设备信息(dev_info)
- 电源管理状态(curr_dev_pwr_mode)
你写驱动时,大部分时间都在跟这个结构体打交道。我习惯把它叫做“大管家”,因为它管着所有跟主机控制器相关的资源。
3.3.2 struct ufshcd_lrb
这个结构体代表一个传输请求。每个UFS命令(比如读、写、查询)都会对应一个lrb。它里面包含了:
- 命令描述符指针(ucd_req_ptr)
- 响应描述符指针(ucd_rsp_ptr)
- 数据缓冲区的S/G列表(sg_table)
- 任务标签(task_tag)
嗯,这里有个细节:任务标签是唯一标识一个命令的。设备返回响应时,会带上这个标签,主机控制器才知道是哪个命令完成了。
3.3.3 struct ufs_dev_info
这个结构体存储UFS设备的信息。比如:
- 制造商ID(wmanufacturerid)
- 产品名称(wproductname)
- 固件版本(fw_version)
- 支持的逻辑单元数量(bnumberlu)
你想想看,如果没有这个结构体,你怎么知道插上的是海力士的UFS还是三星的?怎么知道它支持哪些特性?所以,设备枚举完成后,第一件事就是把dev_info填好。
注意事项:不同厂商的UFS设备,其描述符的格式可能略有差异。比如海力士的某些型号,在几何描述符中会额外增加一个保留字段。如果你直接按标准协议解析,可能会读到错误的数据。我建议你在解析描述符时,先打印原始数据对比一下datasheet。
3.4 整体架构图
说了这么多,不如一张图来得直观。下面这张SVG图,展示了UFS子系统从上层到硬件的完整调用链。
从这张图你可以看到,整个UFS子系统是分层设计的。每一层各司其职,上层不关心下层的实现细节。这种设计的好处是:你换一款UFS设备,只需要改设备驱动那一层,主机控制器驱动基本不用动。
嗯,最后说一句。数据结构这东西,光看定义是不够的。我建议你拿到一份UFS驱动源码后,先别急着改代码。花半天时间,把ufs_hba、ufshcd_lrb、ufs_dev_info这三个结构体从头到尾捋一遍。每个字段是干什么的,在哪个函数里被赋值,在哪个函数里被使用。搞清楚了这些,你写起代码来会顺手很多。
总结一下:主机控制器驱动管“怎么发命令”,设备驱动管“设备怎么响应”,数据结构是它们之间的“共同语言”。这三者搞明白了,UFS驱动移植你就入门了。