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设备驱动的核心工作,我总结为三点:

  1. 设备发现与初始化:发送Query Request,获取设备描述符、配置描述符、几何描述符等。
  2. 任务管理:处理Abort、Logical Unit Reset等操作。
  3. 电源管理:控制设备在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子系统从上层到硬件的完整调用链。

Linux内核UFS子系统架构图 应用层 / 文件系统 (VFS) SCSI 核心层 (scsi_mod) UFS 核心层 (ufshcd-core) ufs_hba / ufshcd_lrb / ufs_dev_info 主机控制器驱动 寄存器操作 / 传输层管理 UFS 设备驱动 设备发现 / 任务管理 / 电源管理 UFSHCI 硬件 (Host Controller) UFS 设备 (海力士/三星等) UniPro / M-PHY 物理层

从这张图你可以看到,整个UFS子系统是分层设计的。每一层各司其职,上层不关心下层的实现细节。这种设计的好处是:你换一款UFS设备,只需要改设备驱动那一层,主机控制器驱动基本不用动。

嗯,最后说一句。数据结构这东西,光看定义是不够的。我建议你拿到一份UFS驱动源码后,先别急着改代码。花半天时间,把ufs_hba、ufshcd_lrb、ufs_dev_info这三个结构体从头到尾捋一遍。每个字段是干什么的,在哪个函数里被赋值,在哪个函数里被使用。搞清楚了这些,你写起代码来会顺手很多。

总结一下:主机控制器驱动管“怎么发命令”,设备驱动管“设备怎么响应”,数据结构是它们之间的“共同语言”。这三者搞明白了,UFS驱动移植你就入门了。


专注资料整理