
把一颗UFS闪存焊到主板上示波器探针搭上差分数据线的瞬间你会看到密密麻麻的高速翻转波形。这些波形背后就是UFS协议栈——从最底层的M-PHY物理层到中间负责可靠传输的UniPro层再到最上层真正说“闪存语言”的UTP层。很多人在项目里搞UFS最头疼的也是这里资料一搜一大把但没人告诉你数据到底是怎么在这几层之间流下去的。这篇就把这层窗户纸捅破。我按数据实际经过的顺序从M-PHY走到UTP层把每一层干什么、为什么这么设计、有哪些坑一次性讲明白。适合正在调UFS驱动的嵌入式工程师也适合想认真搞懂手机闪存原理但被协议规范绕晕的初学者。读完之后你再去看JEDEC或者MIPI的原始规范至少不会迷路。1. UFS为什么要做成分层协议而不是一根线直连1.1 串行差分链路取代并行总线的必然性UFS之前手机上最常见的闪存接口是eMMC。eMMC走的是并行总线数据线最多8根最高速率模式下理论带宽大概400MB/s左右。并行总线到了这个速度基本到头了——线间串扰、时钟偏斜、PCB布线难度都在指数级上升想再往上提速率代价高得离谱。手机里寸土寸金不可能给闪存接口留出一大块布线的空间。所以UFS选择了和PCIe、SATA类似的思路串行差分高速链路。一对差分线上同时传数据和时钟靠接收端的CDR时钟数据恢复电路把时钟从信号里抠出来不需要单独的并行时钟线。这样引脚数量少了速率却可以做到十几Gbps级别而且抗干扰能力更强。M-PHY就是这么来的它是MIPI联盟定义的物理层标准负责把比特流变成真正的电压信号。1.2 分层不是给人看的是为了让每一层能独立演进如果只是“串行总线”还不够UFS真正的复杂之处在于它把协议分成了清晰的三层UTP层UFS Transport Protocol负责封装命令、数据和状态让上层可以用SCSI命令读写闪存。UniPro层负责把UTP层丢下来的数据包稳稳地送到对端包含分帧、校验、重传、流量控制。M-PHY层负责最终的物理信号传输。为什么非要拆三层因为闪存控制器的逻辑复杂度远高于一个个字节的搬移。SSD领域的NVMe也走类似路径——应用层只关心命令语义底层传输协议只关心可靠传输。如果所有功能揉在一起改任何一处都会牵一发动全身。分层之后M-PHY只管信号UniPro只管链路UTP只管命令语义将来某一层要升级比如从HS-G3提速到HS-G4其他层不需要重新设计。我见过很多工程师刚开始看UFS协议栈时第一反应是“这么多层不是脱裤子放屁吗”。真正调过一轮问题就明白了没有分层你根本没法定位一个随机坏帧到底出在物理层还是链路层。2. M-PHY物理层从差分引脚到Gear速率的底层语言2.1 Gear、通道和差分对M-PHY在UFS场景下一条lane就是一组收发差分对。UFS设备最多支持2条lane每条lane内部是独立的发送差分对和接收差分对支持全双工。这也是UFS比eMMC占优的地方读和写可以同时在两条差分对上跑。关键是Gear。Gear决定了一条lane上能跑多快的速率类似PCIe的Gen1/Gen2/Gen3概念Gear单lane速率对应典型UFS版本HS-G11.25 GbpsUFS 2.0HS-G22.5 GbpsUFS 2.0/2.1HS-G35.8 GbpsUFS 2.1/3.0HS-G411.6 GbpsUFS 3.1注意这些速率是“线速率”不是用户能直接拿到的吞吐。M-PHY在HS模式下有编码开销8b/9b编码实际可用带宽要打一个八分之九的折扣。两条lane的HS-G4加起来线速率是23.2Gbps编码后有效数据速率大约2.58GB/s这还没算协议头、校验和帧间隔的开销。这个账我们后面专门算。2.2 PWM模式和HS模式的取舍很多人以为UFS永远跑在HS高速模式下这是错的。链路两端会根据场景在PWM模式和HS模式之间切换。PWM模式是单端低摆幅信号速率低但功耗也低很多HS模式是差分高速信号速率高但需要维持收发端的模拟电路在更高功耗状态。手机平时待机时闪存链路完全没必要保持高速。系统休眠时链路会切到Hibern8这种低功耗状态唤醒后如果只是小流量IO也可能先用PWM模式顶着负载上来了再切回HS。这个切换由UniPro层管理但对上层是透明的。我在实际项目中踩过一个经典坑系统从suspend恢复后第一次顺序读感觉特别慢一测只有几十MB/s。后来才发现链路还在PWM模式没切回HS。驱动里可以强制把最大Gear配上去但不能图快一刀切因为强制HS会导致待机功耗明显上涨。正确做法是配置好模式切换的阈值让链路在连续IO时能快速升到HS空闲时又能降下来。2.3 HS burst内部的结构M-PHY在HS模式下不是一条线慢慢淌数据而是“一段一段”地发。每个burst之前有preamble接收端用这段时间完成CDR锁定和增益调整之后才是真正的同步数据。burst结束会有一个marker信号。做协议分析仪抓包的时候如果你看到的波形是“一串高速信号、一片安静、又一串高速信号”不用怀疑那就是HS burst的节奏。这个特性对功耗极其重要。如果链路始终连续发高速信号光物理层就能把手机电池抽干。burst机制让链路的占空比可以压得很低空闲时马上进入低功耗状态。3. UniPro链路层把比特流整理成可靠传输的“中间商”3.1 UniPro到底在传输层和物理层之间干了什么如果M-PHY只是“把0和1发出去”那UniPro就是“确保对方收到的确实是这个0和这个1”的中间商。它介于UTP和M-PHY之间负责把上层数据包切成固定格式的帧加上CRC校验通过应答和重传机制保证可靠性同时管理多个逻辑通道的流量控制。很多从PCIe/NVMe转过来的人会把UniPro类比成PCIe的数据链路层。这个类比基本靠谱但也有差异UniPro对功耗管理的介入更深因为它要配合M-PHY的多种低功耗模式。UniPro内部又分好几层子层物理适配层负责和M-PHY打交道数据链路层负责可靠传输网络层和传输层负责寻址和分段。如果只为了理解数据流你可以把它当成一个带重传功能的“可靠管道”。3.2 链路训练是启动的第一步UniPro链路上电后第一个动作是链路训练UFS规范里叫Link Startup。链路两端要互相确认对方支持什么Gear、支持几条lane、当前线路质量怎么样然后双方握手最终确定一个双方都能接受的最大速率。这个协商过程和PCIe的链路训练高度相似。链路训练不通过的典型案例是主机支持HS-G4设备也声称支持HS-G4但双方链路训练后速率降到HS-G3甚至更低有时根本没反应。原因常常不是什么协议错误而是PCB走线太长、差分阻抗不对、参考时钟抖动偏大。我后面专门写调试的时候再展开。3.3 重传机制可靠传输的代价UniPro的数据帧里带CRC接收端校验失败就会发NAK发送端收到NAK后重传。这个机制保证了UTP层完全不需要操心“数据会不会传错”。但可靠是要付出代价的。一旦链路上出现校验错误重传会触发额外的开销吞吐瞬间掉一截。如果信号质量差到一定程度链路还会反复重训、降速性能表现会变得极其难看。这也是为什么很多“协议层”问题最后查根因都是信号完整性问题。4. UTP层与UPIU一条写命令在协议栈里的完整旅程4.1 UPIU是UFS协议层的“信封”UFS的应用层把SCSI命令比如READ/WRITE 10、16、UNMAP通过UTP层封装成一个个UPIUUFS Protocol Information Unit。UPIU就是UFS协议层的通用信封所有在主机和设备之间交换的命令、数据、状态都装在这里面。UPIU的结构你可以简单理解成三部分头部、命令描述符/数据、填充。头部里有传输类型这个是Command、Data Out还是Response等等、LUN、任务标签、初始化器ID等字段。任务标签特别重要它把多个并发命令区分开让设备可以同时处理很多条命令这就是UFS多命令队列的基础。4.2 一条写数据命令的完整旅程我把最典型的写操作从头到尾捋一遍这是理解数据流最有效的方式。第一步主机的UFS驱动收到来自文件系统的写请求后构造一条SCSI WRITE命令通过UTP层封装成Command UPIU。这个UPIU里包含了起始逻辑块地址、长度、以及要写入的数据缓冲区的描述信息。Command UPIU交给UniPro层UniPro给它加上帧头、CRC切成适合M-PHY传输的符号序列从差分线上发出去。第二步设备端的UniPro层收到帧做CRC校验解出UPIU交给UFS设备控制器。控制器解析Command UPIU后发现是一条写命令先查一下内部状态准备好接收数据然后回一个Ready to Transfer UPIU。这个UPIU的意思是我准备好了你可以把数据发过来了顺带告诉你期望的传输起始位置和数据长度。第三步主机收到Ready to Transfer UPIU之后才开始真正发送用户数据。数据会被拆成若干个Data Out UPIU一个接一个通过UniPro和M-PHY发往设备。Data Out UPIU里装的就是你要写入闪存的真实内容。第四步设备把所有Data Out UPIU收齐数据放到缓冲里之后还要经过内部FTL映射、ECC校验、NAND编程最终真正落到存储介质里。这个步骤耗时最长但协议栈层面是看不到的。第五步设备完成写入后发Response UPIU给主机里面带状态码告诉主机“写成功了”。整个旅程下来用户态一个fwrite到了协议层要经历Command、Ready to Transfer、多个Data Out、Response这么复杂的交互。如果是不带缓存直接写你甚至会感觉协议开销不小但正因为有这套握手机制才能保证掉电时数据的一致性有据可查。4.3 命令队列让“多封信”并行处理UFS之所以比eMMC快命令队列功不可没。eMMC同一时间只能处理一条命令写数据时总线空闲状态很多UFS的UTP层通过任务标签支持多个outstanding命令设备可以一边准备数据、一边写NAND、一边回状态。这就有点像流水线而不是一个一个排队做。我调优时经常观察的一个指标就是队列深度。队列深度太浅SSD/UFS内部的调度器发挥不出来太深又会让延迟增加。具体设备有一个最优值得实测。5. 理论带宽与现实吞吐UFS性能数字是怎么缩水的5.1 从线速率到实际带宽的账每次看到UFS 3.1标称23.2Gbps、很多人以为能跑2.9GB/s我都很无奈。23.2Gbps只是两条lane的物理线速率实际链路是层层“剥皮”的环节速率(GB/s)说明线速率合计 2x HS-G42.923.2Gbps / 8物理编码开销(8b/9b)2.582.9 x 8/9加上协议头、校验、帧间隔约2.3~2.4UniPro头、CRC、UPIU头真实应用层顺序读吞吐1.8~2.1与设备固件、FTL性能有关能跑到理论值七成已经算很好了。你去看很多旗舰机的拆解评测连续读取1500~1900MB/s是比较常见的数字就是这个原因。如果厂商标称2000MB/s以上通常需要超大块连续读写而且还得保证链路一直在HS-G4不掉速。5.2 实际吞吐还和什么有关除了协议开销闪存介质本身也会拖后腿。NAND写入前要擦除垃圾回收会占时间写入放大导致实际写入的数据比主机要求的更多随机小IO场景下命令握手和FTL寻址开销占比太高速度远低于顺序大吞吐。我之前做过一个对比测试同一颗UFS 3.1芯片顺序写可以到1.7GB/s但4K随机写只有不到200MB/s。不是协议栈有毛病而是闪存物理特性决定的。分析性能问题时千万别一上来怀疑UFS协议栈先确认测试条件是不是合理。5.3 Gear降级是性能杀手链路训练降级是实际项目里最烦的问题之一。设计支持HS-G4但板子layout没做好或者使用的UFS器件个别批次体质一般链路训练后稳定在HS-G3理论带宽直接砍半。更隐蔽的是产品前期测试全是HS-G3没人发现等到竞品都标HS-G4你的速度起不来才回来查。这个问题的核心是让链路能在更高Gear上工作不只是驱动配寄存器还要确保物理层的信号完整性足够。后续调试部分我再详细说。6. 调试UFS协议栈的实战笔记链路训练、抓包与功耗坑6.1 链路训练失败的排查思路如果UFS链路训练失败或者训练后速率上不去我的排查顺序一般是固定的从信号源头往协议层走。第一是参考时钟。UFS有独立的REF_CLK输入通常要求几十MHz级别的稳定时钟如果时钟幅度不够、相位噪声太大高速训练直接失败。示波器先看REF_CLK的波形干净不干净看有没有毛刺。第二是电源纹波。M-PHY在HS模式下对电源噪声极其敏感尤其是高速收发器供电的纹波。用示波器测靠近UFS芯片的供电引脚如果在差分数据翻转时电源被拉出明显纹波优先处理去耦电容和电源层。第三是走线。差分对要保持等长阻抗按PCB叠层控制过孔和换层尽量少。这些是高速信号的常识但在UFS上更容易被忽略因为很多板子是从eMMC方案直接改过来的走线习惯没跟上。如果这三样查不出问题再用驱动把最大Gear限制到HS-G3做A/B对比大概率能缩小范围。不要一上来就怀疑UFS控制器的问题概率太低。6.2 用协议分析仪抓包时要看什么调UFS如果有条件一定要用协议分析仪别只靠驱动打印日志。分析仪能抓到M-PHY的波形、UniPro的帧、UPIU的原始内容。抓包第一步确认链路训练Gear。分析仪上会直接显示当前链路速率如果设备协商成了HS-G3而代码里明明配置的是HS-G4立刻就知道链路训练出了问题。第二步看UPIU层。按任务标签过滤把一个写命令的Command UPIU、Ready to Transfer、Data Out、Response整个过程串起来能看到每一步的耗时。如果从Ready to Transfer发出到第一个Data Out回来间隔太长说明主机侧驱动或者DMA配置有问题如果Data Out发完到Response回来间隔太长说明设备内部NAND写入慢或者垃圾回收卡住了。第三步看UniPro层的CRC错误计数。如果这个数字一直在涨说明信号质量有问题迟早会链路重训。别等到性能崩了才想起来看。6.3 低功耗切换导致的第一笔IO延迟还有一个很常见的性能“假故障”设备从待机唤醒后的第一笔IO特别慢测得几十毫秒甚至上百毫秒。有人以为是UFS芯片坏了其实是链路从Hibern8唤醒、再做Gear协商、再回到HS模式整套流程本来就需要时间。解决办法是合理设置电源管理策略不允许链路过快进入低功耗状态。UFS驱动的电源管理参数要按实际使用场景调比如系统允许的唤醒延迟是多少、链路空闲多久才允许降速。这个参数调太激进日常延迟很难看调太保守功耗又上去了。手机厂商花了大量时间在权衡这个。6.4 板级信号质量才是很多协议异常的根因最后说一个我自己的体会UFS协议栈出现莫名其妙的超时、重传、链路降级不要总往软件上想。协议栈的可靠性设计非常完善很多异常其实是M-PHY信号质量不好触发的。在实验室做眼图测试时重点看HS burst期间的信号张开度和抖动。眼图张开不够、抖动过大就意味着链路随时可能出位错误。很多项目在温升、量产批次变化后开始出现UFS读写超时都是信号裕量不够导致的。这个问题如果在样机阶段不好好查到了量产阶段再想改layout成本和周期都伤不起。调UFS这么多年最大的心得是一定要先把协议栈的分层模型刻在脑子里。遇到问题先判断它属于哪一层——是物理信号、链路传输、还是上层命令语义思路就清晰一半。很多人卡住是因为一上来就在最上层的驱动里找原因完全忽略了M-PHY物理层和UniPro链路层才是最容易出鬼的地方。