直接上干货。我最初啃 UFS3.1 协议的时候,最大的感受就是——JEDEC 那几百页英文规范摆在那,打开第一页就是一堆缩写,线头都找不到。但做存储固件或者调试手机平台的人,UFS 这块是绕不过去的坎。这篇东西我从 1 写到 4,把整个协议栈按我的学习路径拆开讲,讲清楚物理层、传输层、命令集和设备管理的底层逻辑,再把实战中踩过的坑一并分享给你。
1. 先把协议整体框架装进脑子里
1.1 UFS3.1 到底在“管”什么
UFS 3.1 是一整套存储接口规范,全称 Universal Flash Storage,由 JEDEC 发布,版本代号是 JESD223E。它不只是定义一个“闪存芯片的引脚”,而是定义了一整套从物理链路到命令交互的完整协议栈。它和普通 SD 卡或者 eMMC 那种并行总线、单线传输的思路完全不同,UFS 走的串行差分链路,类似 PC 上的 PCIe 或者 NVMe 的思路,但又结合了嵌入式设备的低功耗需求。
从分层结构上看,UFS 协议体系可以拆分四层:
- UniPhy:物理层,基于 MIPI 联盟的 M-PHY 规范,负责信号的收发、眼图、链路速率。
- UniPro:链路层,负责可靠传输、流量控制、多 lane 调度等。
- UTP:传输层,定义了 UPIU 封包格式,负责命令、数据、事件怎么封装和路由。
- UFS Command Set:应用层,定义主机发过来的命令集合,大部分来自 SCSI 命令体系。
所以你在协议文档里会同时看到 JEDEC 和 MIPI 的规范交叉引用。学的时候如果不搞明白层间关系,看到一半就会疑惑:怎么一会儿是 Line Control,一会儿是 INQUIRY 命令?其实是不同层的概念。
1.2 学习协议的正确顺序(别从第一页开始死磕)
很多人学协议的习惯是从 doc 第一页开始往下读,这对 UFS 来说几乎必废。我踩了弯路,后来总结出一个顺序:
第一步,先把物理层结构搞清楚。知道链路有几对差分线(lane)、每个 lane 方向、PWM 和 HS 两种速率档的概念。这是地基,后面理解链路初始化失败、速率掉档问题都依赖这里。
第二步,去看传输层协议(UTP)。重点抓 UPIU 的类型和头格式。你别管它是怎么发出去的,先知道一个命令从主机到设备是靠什么“信封”传输就行。
第三步,回到命令集。这时候你就会发现,UFS 本身不发明多少新命令,它大量套用 SCSI 块设备命令。熟悉 SCSI 的人学 UFS 会非常快,不熟的就补一下 INQUIRY、READ CAPACITY、READ/WRITE(10) 这些基础。
第四步,再回头补协议里零散的东西:UFS 描述符、属性和标志位。这些东西是设备“自报家门”和运行状态管理的手段,挺琐碎的,但在调试中至关重要。
这个顺序相当于先看路、再看车、再看货物、最后看收费站。反过来很容易被缩写淹没。
2. UniPhy 物理层:快慢两档的 M-PHY
2.1 PWM 模式与 HS 模式的关键参数
M-PHY 是串行差分接口,一条 lane 就是一组发送加一组接收。UFS3.1 最高支持两发两收,也就是常规说的两 lane,并行传输能力翻倍。
M-PHY 支持两类工作模式:PWM 模式和 HS 模式。它们设计意图差很远:
- PWM 模式是低速模式,适合链路训练、低功耗唤醒、初始建立连接。它的频率较低,功耗也低,用占空比编码信号。PWM-G1 到 G4 速率从 3Mbps 起步,最高能到 24Mbps 左右,主要用于控制信息交互阶段。
- HS 模式是高速模式,用于真正搬数据。HS-Gear 从 G1 到 G4,每个 Gear 速率翻倍。HS-G1 是 1.25Gbps,HS-G2 是 2.5Gbps,HS-G3 是 5Gbps,HS-G4 就是大约每 lane 接近 12Gbps。两条 lane 一起工作,总链路速率相当可观。
很多人在资料上看到的吞吐数字很漂亮,但必须搞清楚那是链路速率,不是真实用户吞吐。因为有编码开销、协议开销、命令排队、NAND 闪存写放大等因素,实测顺序读能到 1.8GB/s 以上就算很好了,顺序写取决于闪存架构,往往 400 到 700MB/s 之间。网上有些宣传测到 2.5GB/s 的都是理论极限场景,别拿那个来定你们的验收指标。
2.2 速率协商与两条 lane 是怎么工作的
设备上电以后,主机和 UFS 设备不会直接冲到最高的 HS-G4 速率,而是做速率协商(Link Startup)。这有点像两个人约见面,先远远喊一嗓子确定对方到了,再走近一点,最后才进入正题。
链路初始化的大致流程是:复位 -> PWM-G1 建立低速链路 -> 交换设备能力信息 -> 协商双方都支持的 Gear 和 lane 数量 -> 切换到高速模式。这个机制保证了无论设备端最高支持到多少,主机总能找到一个两边都能工作的速率等级。
两 lane 的调度机制不是简单的一发一收,它涉及链路层的多 lane 分发。你说一句话,哪几个字节走 lane0,哪几个走 lane1,是链路层调度决定的。所以如果你做硬件测试发现某一条 lane 信号有问题,系统可能不会直接挂,而是表现为速率降级、吞吐下跌、偶发 CRC 错误。查这条线就比较费劲。
2.3 物理层调试中最容易被忽略的点
调试 UFS 硬件链路时,我见过通电不认盘的案例,最后定位发现是复位引脚时序不对。很多平台对 UFS Reset 是有严格时序要求的,不能和系统上电同时拉高复位,必须在电源稳定后保持一段时间的有效电平再释放。时序不对,设备直接 soft reset 失败,主机端表现为找不到设备。
还有一点,PWM 模式下信号幅度比较小,容差范围也比 HS 模式紧张。你用示波器测的信号可能是 HS 眼图正常,但低速训练时误码率很高,导致链路起不来。这时候需要专门检查协议分析仪上 Link Startup 过程中 PWM 阶段的 CRC 错误。
样机阶段最推荐的做法是示波器和协议分析仪同时上。示波器查波形眼图,分析仪查报文层级,两个视角对照起来,才能区分是信号完整性还是协议状态机的问题。
3. UTP 传输层:UPIU 封包与命令流转
3.1 UPIU 长什么样
UPIU 是 UFS Protocol Information Unit,你可以把它理解成快递包裹。主机给设备发的所有命令、设备回报的所有状态、传输的数据,全部装在这个包裹里。不同功能的 UPIU 有不同的类型值,常见的有:
- COMMAND UPIU:主机发命令。
- DATA IN UPIU:设备往主机传数据。
- DATA OUT UPIU:主机往设备写数据。
- RESPONSE UPIU:设备响应命令。
- QUERY REQUEST/RESPONSE:读取或修改设备描述符、属性。
- TASK MANAGEMENT REQUEST:任务管理,比如 abort 命令。
- NOP OUT/IN:空操作,常用于链路探测。
头部信息包含传输类型、插槽号(Command Slot/SN)、LUN(逻辑单元号)、以及和命令相关的参数字段。具体的字段偏移不是必须背诵的,用到的时候翻规范查就行,但结构逻辑要清楚:后面跟的是命令描述块还是数据,还是响应状态。
3.2 命令从 Doorbell 到完成的完整路径
当主机软件想把一个写命令发给 UFS 设备,流程是这样的:
主机控制器把 UPIU 写到内存中的命令队列,然后往设备的寄存器(Doorbell)写一个 bit 通知“你该取活了”。设备硬件收到后,从队列里取走请求,执行相应操作,完成后通过中断或者寄存器状态报告主机。主机再按队列完成项记录释放资源。
这里最要紧的概念是命令插槽。每个命令队列有多个槽位,Doorbell 寄存器每个 bit 对应一个槽,软件写 1 表示提交命令,完成之后清零。AQLEN 这个属性决定了一个应用支持多少队列深度。你对队列深度管得不好,很大概率会影响性能,不是调高就一定快,要看设备端固件能不能接得住,太深了反而增加排队延迟。
3.3 为什么 NOP 命令都能拿来测链路
NOP OUT 这个 UPIU 很轻量,它不带数据负载、不带复杂参数,就是主机向设备问一句“你还在不在”。设备收到后回一个 NOP IN 响应。在实际调试中,这是个特别实用的探针工具。
举例,当你遇到 UFS 设备偶尔不响应某个命令,首先要区分是链路问题还是设备固件挂死。发一个 NOP,如果设备能正常回应,说明链路层还活着,命令层处理出了问题;如果连 NOP 都没回应,那就得好好查协议分析仪上的链路状态、电源和时钟了。这招在量产测试的异常处理里也常用,你可以快速确定隔离范围。
4. UFS 命令集:Flash 里真正执行的活
4.1 从 SCSI 体系继承过来的命令
UFS 命令集和 SCSI 走得很近。你会在 UFS 协议里看到 READ(10)、WRITE(10)、INQUIRY、TEST UNIT READY、UNMAP、SYNCHRONIZE CACHE 这些再眼熟不过的命令。
为什么 UFS 要套用 SCSI、而不是自己另搞一套?原因是UFS 的理念就是“把闪存盘做成一个低位块设备”,主机侧可以像访问 SCSI 磁盘那样访问 UFS。这让系统软件栈的适配变得很平滑,终端侧代码库、驱动框架、工具链都能直接复用,不需要为 UFS 单独养一套应用接口。
在实际代码里,你甚至可以直接用 sg 工具发 SCSI 命令给 UFS 设备做验证。比如你写一个测试脚本,先发 INQUIRY 拿设备基本信息,再发 READ CAPACITY(10) 看容量,接着发 READ(10) 指定 LBA 读数据,很快就能确定设备的基本通信是否正常。这个调试思路真的高效。
4.2 一次写操作的过程拆解
写一个逻辑块的完整过程,按命令视角看是这样的:
主机先构造一个写 UPIU,里面装满命令描述块——操作码、起始 LBA、块数量、以及数据缓冲地址。这个 UPIU 会被放入命令队列,主机通知设备取走。设备确认命令可执行后,主机会接着发 DATA OUT UPIU,把用户数据一包一包传过去。所有数据传完后,设备把数据写入闪存,再回一个 RESPONSE UPIU 告诉我们成功还是失败。
这里有一个新手非常容易疑惑的细节:设备在收完数据之后,是不是立刻就能保证写入成功?不一定。因为 UFS 设备内部有缓存机制,数据进了 DRAM 或 SRAM 缓存后就可能回状态,但距离真正落盘还有距离。如果你掉电了,设备端固件需要靠自身的掉电保护机制和映射表来恢复一致性。这也是为什么 UFS 设备有 write booster 这类加速特性:所谓加速,本质是把“写入完成的确认”提前,而真正的写入闪存动作在后台上。
4.3 有关 Production Area 和 WriteBooster 这类扩展特性
UFS3.1 里引入的 WriteBooster 功能,本质是主机可以配置一块专用的缓存缓冲区,把随机写变成顺序写到 SLC 缓存里,靠后台转储到 TLC。但这里有个坑:WriteBooster 缓存空间和用户 LBA 空间是分开管理的,主机需要通过协议里的划分配置来设置缓存大小。如果你没初始化对,写性能可能不升反降。
另外,协议里还有 Power Mode、Health Descriptor、FFU(固件更新功能)这些机制。FFU 做固件升级时,对厂商的固件镜像格式、版本管理策略、失败恢复流程都要求很严格。很多工程团队在这里栽过根:固件升级掉电后设备变砖,最后靠下载模式抢救。做量产前,建议把异常掉电升级的用例反复跑,特别是中断在镜像传输中间这个阶段。
5. 实际调试中的坑与排查套路
5.1 链路起不来的排查顺序
我见过最多的问题集中在设备不识别、识别后掉速率、以及运行一段时间后命令超时。这里给一套我常用的排查顺序:
第一步,查电源和时钟。UFS 设备有多个电源域,上电顺序错、电压不稳都可能直接导致链路起不来,别一上来就抓协议报文。
第二步,查复位时序和复位电平。如果复位期间有毛刺,或者释放时序不对,设备很容易停在初始化之前的某个状态。
第三步,查协议的 Link Startup。用协议分析仪抓链路训练过程,看设备是否回了正确的 ID_N 和参数交换报文。一旦在 PWM 阶段出现 CRC error,大概率是信号质量不好或者参考时钟问题。
第四步,查命令层的初始化流程。链路起来后,主机会发送一系列标准的 Query 和 INQUIRY 命令来了解设备,如果这个阶段某一环卡住,设备就停在“无法识别”的状态。你需要对照协议规范里的初始化序列一步一步排查主机软件是否漏步骤。
5.2 读一遍设备描述符是最基本的健步
从设备描述符可以读到厂商号、产品号、固件版本号、支持的规范版本、最大读写速度等信息。做兼容性测试时,这是个最直观的诊断手段。命令行下用工具发一个标准的 INQUIRY 或者 Read Device Descriptor 请求,几秒就能确认设备是否已经正常工作。我曾经遇到一个送测样品标称支持 HS-G4,读描述符发现支持的规范版本只有 3.0,说明产品用的是旧控制器或者被厂商做了限制。这种信息不看描述符,单靠实际测速根本猜不透。
有了描述符信息,再对照 product name 和 firmware revision 去维护一个设备数据库,量产调试里能省很多沟通成本。你一个月前测过一批固件有已知问题的盘,等再收到同类盘时可以立刻识别出是否需要特殊处理。
5.3 用逻辑分析仪抓总线 VS 直接看寄存器
调试 UFS,有条件就上协议分析仪。逻辑分析仪只能抓电平波形,UFS 的差分高速总线一压上逻辑分析仪,信号完整性就被干扰了,拿到波形也不好解协议。专业协议分析仪能把报文级别的内容直接解析出来,快速定位是哪一层错误。
但协议分析仪也不是万能的。它挂在链路上只是一个旁路监听,并不感知主机侧寄存器状态。很多问题表现为设备不回包,光看报文猜不出主机是不是真的发出了命令。这时配合主控厂商的调试工具看控制器寄存器状态,看看门铃寄存器、中断状态寄存器、队列状态,就能拼出完整现场。
两条腿走路:协议分析仪看设备视角,寄存器窗口看主机视角。两个视角对齐之后,几乎所有通信链路问题都能快速定位。
5.4 常见问题速查表(实战记录)
下面是我在实际调试中积累的几个高频问题,整理成表:
| 现象 | 常见根因 | 排查优先级 |
|---|---|---|
| 完全找不到设备 | 电源/复位时序异常、时钟未起振 | 最高,先于协议排查 |
| 上电偶尔能识别、偶尔不掉 | 复位释放时间处于临界区、链路训练偶发 CRC | 高,重点抓时序和 PWM 阶段波形 |
| 顺序读速率远低于标称 | Gear 协商不满、单 lane 工作、命令队列深度不足 | 中,查速率协商结果和实际队列利用率 |
| 写卡死、命令超时 | 设备固件资源池被占满、WriteBooster 区域配置异常 | 中,查任务管理请求和异常恢复流程 |
| 升级固件掉电后无法识别 | FFU 镜像异常、中断传输保护逻辑不健全 | 中,需厂商固件支持掉电恢复 |
| 长时间高负载出现偶发错误 | 信号链路余量不足、参考时钟抖动变大 | 低,需眼图余量测试定位 |
这张表只是经验汇总,具体到某个平台要结合控制器的勘误表来看。不同厂商的控制器在异常恢复策略、寄存器定义差异上很明显。
6. 给新人的几条实操建议
UFS3.1 协议学起来不像看一份产品说明书,它需要不停地在理论、代码、实测之间来回对照。给刚入行的朋友几点建议:
先搭一个能把命令发出去的调试环境。不用太高深的设备,买块现成的 UFS 测试卡或者基于现有手机平台的工具,先跑通 INQUIRY、READ CAPACITY、READ/WRITE 这几个基础命令。命令在链路里走一圈,你对协议的理解会立刻立体起来。
再准备一台支持 HS-G4 的样机做性能验证。把顺序读、顺序写、随机读、随机写、混合负载都跑一遍,记录掉速和发热。性能数据配合协议分析,能帮你更准确理解设备端策略对性能的影响。
然后是看异常。故意制造命令超时、热拔插、异常掉电、中断打断,观察设备怎么恢复。过程会有点痛苦,但这是深入理解 UFS 存储栈的必经之路。
最后,不懂的时候去翻 UFS 规范本身。网上很多二手资料会省略细节,或者用简化但不准确的话描述,传播广了反而容易误导。JEDEC 文档确实贵,但对做这个行业的人来说,它是必须有的工具书。二手文章只能帮你建立地图,地形的细节还得自己踩过一遍。