☰
UFS 3.1协议深度拆解:从M-PHY到UPIU,读懂嵌入式闪存高速传输
2026/10/2 13:15:28 网站建设 项目流程

入行嵌入式存储那几年,我啃得上头又踩坑最多的一份协议,就是UFS 3.1。官方JEDEC规范JESD220D.01,再叠加MIPI联盟的M-PHY、UniPro两本文档,少说上千页,全是英文,术语一个比一个抽象。但这玩意儿又绕不开,从旗舰手机到车载、AR/VR,UFS 3.1已经是当前中高端设备的默认闪存标准。所以我打算用这个系列,把UFS 3.1协议从零拆开讲,核心走“整体架构 -> 物理层和链路层 -> UPIU与命令层 -> 3.1新特性 -> 学习路径与排障”这条线,总共四讲的内容合并成一篇长文。适合刚接手UFS项目的人、做协议测试和FAE的兄弟,以及想搞清楚手机闪存为什么这么快的好奇党。读完你至少能看懂一份UFS trace,也能知道设备上不了高速、掉盘、掉速时该往哪层查。

1. 整体架构:把UFS 3.1拆成四层再学

1.1 为什么从eMMC切到UFS,接口这条路是怎么走到23.2Gbps的

先回答一个最常见的问题:eMMC用得好好的,为什么要换UFS?带宽差别是肉眼可见的。eMMC 5.1的HS400模式,8位并行总线,理论带宽大概400MB/s,工程上能跑到350MB/s已经算不错;而UFS 3.1的物理层采用串行差分lane,两个lane双向全双工,每个lane在HS-G4档位下速率约11.6Gbps,合计理论带宽23.2Gbps,换算过来约2.9GB/s。当然这是链路层的理论值,去掉协议开销、NAND介质读写速度、FTL映射消耗,商用量产芯片顺序读做到1900MB/s到2100MB/s很正常,已经是eMMC的五六倍。

更关键的是,UFS在架构上整个推倒重来了。eMMC是半双工并行总线,命令和数据分时复用,而且命令队列能力很弱,随机读写性能被严重限制。UFS借鉴了SSD那套思路:SCSI命令集、多LUN、最多32个命令槽位、乱序完成、全双工传输,所以不仅顺序快,随机读写的并发能力也完全不一样。用手机的人感知到的“装应用快、拍照连拍不掉速”,大头就是UFS带来的。3.1版本相比3.0还额外增加了Write Booster、HPB、深度睡眠、性能节流通知等特性,这些后面展开讲。

1.2 四层架构逐层拆:命令层、传输层、互连层、物理层

UFS 3.1协议标准文档看着吓人,本质就是一张四层结构表。理解分层之后再去看规范,会清楚很多。

协议层次由谁定义核心职责
命令层JEDECUFS命令集、SCSI命令映射、设备管理(Query请求)
传输层JEDECUFS Transport Protocol(UTP),把请求封装成UPIU报文
互连层MIPIUniPro,分段、重传、流控、多lane聚合和功率管理
物理层MIPIM-PHY,负责差分串行信号、速率档位、Hibern8低功耗链路状态

我建议在笔记本上把这个分层图画出来,遇到问题先定位“这是哪一层的事”。比如读写速度上不去,命令层查命令并发和队列,互连层查速率协商和重传计数,物理层查眼图和信号质量;设备睡眠后叫不醒,大概率是功率状态迁移出了问题,涉及链路层和命令层的配合。分层设计最大的好处是各层可以独立演进,M-PHY从HS-G1升到HS-G4的时候,命令层的CDB不用改,这也是协议能向后兼容的原因。

1.3 协议学习顺序:先画数据通路,再补状态机

读规范千万不要从第一页开始念。我的经验是先抓两条主线:一条是数据通路,看一次READ请求从主机应用层到UFS设备NAND是怎么流下去的,用到了哪些命令、报文和状态;另一条是设备管理通路,看Descriptor(描述符)、Attribute(属性)、Flag(标志)通过Query UPIU怎么读写。这两条线通了,再回头看M-PHY链路协商、电源状态机、刷新机制这些细节,就会觉得每一节都有它该待的位置。

这里先放一个整体的数据通路预览,后面第三讲会细节化:主机驱动构造SCSI命令 -> UFS传输层包装成Command UPIU -> UniPro拆包发给设备 -> 设备解析命令访问NAND -> 数据用Data In UPIU回给主机。开发调试时,只要一条命令走到哪一层断了,肉眼就能看出问题出在什么地方。

2. M-PHY与UniPro:物理通道和链路规矩怎么守

2.1 M-PHY的PWM和HS两大家族,速率档位怎么选

M-PHY是MIPI联盟定义的串行物理层,里面有两类工作模式。一类叫PWM模式,低速低功耗,用于链路上电初始化、模式切换过渡和低功耗场景;另一类叫HS高速模式,才是真正跑数据的地方。HS模式下有具体Gear档位,业界通常按这些值估算:

Gear档位每lane速率(约)典型用途
HS-G11.25 Gbps低速跑通、兼容调试
HS-G22.5 Gbps中速档,很少量产选用
HS-G35.8 GbpsUFS 2.1/3.0时代主流
HS-G411.6 GbpsUFS 3.1两lane合算23.2Gbps

实际芯片实现会有细微差别,不同厂商的手册有时标1.5Gbps、3Gbps,因为M-PHY标准允许一个误差范围,所以对齐速率的时候以对端链路协商结果和寄存器为准,不要拿笔算的数值跟仪器硬对。

为什么上电初始化一定要先跑PWM低速档?道理很简单,双方刚握手的时候,还没交换能力参数,用高速档信号去碰容易产生误码;PWM模式信号冗余和容错性好得多,先把能力集、Gear上限、供电能力都商量好,再做Power Mode Change切到HS-G4,成功率才高。我见过不少测试板在协议分析仪上反复出现链路训练失败,原因就是板卡的差分信号质量差,高速切换瞬间眼图闭嘴,只能降档到HS-G3甚至HS-G2跑,这是物理层经典问题,不是固件能完全救回来的。

2.2 UniPro在链路上干了什么:重传、聚合与功率管理

UniPro可以理解成UFS设备之间的“传输管家”。它负责把上层UFS协议产生的数据分段成一个个packet,加上序号和校验,放到M-PHY物理通道上发出去;对端收到后要确认,没收到或校验失败就重传,这就是UniPro L2层的ARQ机制。这套可靠传输机制保证了上层不用关心物理层偶发误码,但代价是链路效率不可能做到100%,工程调试时看到链路上有重传,不一定是坏事,只要重传比例不高就是健康状态。

UniPro还负责多lane聚合。UFS控制器可以接1条lane,也可以接2条lane,UniPro会把数据流拆到lane上并行发送,相当于把一条窄路改成双车道。这不是简单的复制,而是字节级条带化,考验信号同步。这也是为什么UFS 3.1标称23.2Gbps需要“两lane同时工作”才能达到的原因。链路功率管理方面,UniPro在物理层引入了Hibern8这种低功耗链路状态,链路空闲时可以快速进入省电模式,需要收发数据时再退出来,这和后面的设备级Deep Sleep不是一个东西,做调试时容易搞混。简单说链路可以睡、设备也可以睡,链路醒了不代表设备醒了。

2.3 实操:从日志看速率协商结果

实际产品上怎么验证链路跑到哪个档位?如果是Linux系统,把内核动态日志打开,能看到类似下面的输出:

echo 'file drivers/ufs/ufshcd.c +p' > /sys/kernel/debug/dynamic_debug/control dmesg | grep -i ufshcd

协商成功时,日志里通常能看到链路启动完成的消息,里面带了Gear档位和lane数量,比如“gear=4, lane=2”就说明协商到了HS-G4双通道。如果每次上电只能协商到gear=1或者lane=1,先别怀疑UFS芯片,重点查三件事:控制器的最高速率配置是否被限制、板卡差分走线阻抗和长度是否匹配、电源供电在高速切换瞬间有没有塌陷。UFS的差分线对阻抗有明确要求,PCB布线时应该按差分阻抗控制来做,并保证等长、少过孔、参考地完整。做测试板阶段最怕飞线乱飞,飞线一长,HS-G4基本只能靠运气。

3. UPIU与命令集:一次读写是怎么跑完的

3.1 一张UPIU报文长什么样

UFS传输层(UTP)把所有交互都封装成UPIU报文。你可以把它理解成快递面单:本地地址、对方地址、包裹内容、验货规则全写在里头。一个UPIU最基本的结构是12字节的基本头,后面跟着不同报文类型的附加字段,比如Command UPIU会附带SCSI命令描述块(CDB),Data In/Out UPIU会附带数据负载,Query UPIU会附带描述符或属性的读写作。

UPIU Type值作用
Command UPIU0x01主机下发命令,里面带CDB
Response UPIU0x02设备回命令结果
Data In UPIU0x03设备向主机传数据
Data Out UPIU0x04主机向设备写数据
Task Management Request0x05任务管理,比如中止某个命令
Task Management Response0x06设备回任务管理结果
Ready to Transfer(RTT)0x07设备表示“缓冲区准备好了,发数据过来”
Query Request0x08主机读写设备管理信息
Query Response0x09设备回管理信息结果

理解RTT是理解UFS写路径的关键。设备端NAND写入不是随时都能接收数据,内部缓冲区、垃圾回收、写命令调度都有关系。所以主机写数据时不是一股脑把Data Out发出去,而是先发Write命令,设备准备好了之后回一个RTT,主机看到RTT再发Data Out UPIU。如果主机不等RTT就直接发数据,设备的接收缓冲区没准备好,多出来的那一堆数据就不知道往哪儿放,链路上会直接报错,严重的会触发命令超时。很多新手硬件工程师第一次抓UTP trace,看到读写流程和NVMe不一样会懵,实际上只要把RTT这个角色记住就顺了。

3.2 命令层:UFU命令集与SCSI命令的映射

UFS设备的命令层不是另起炉灶,而是把主机侧的命令映射到一组SCSI命令和UFS原生命令上。操作系统把UFS设备识别成一块SCSI磁盘,所以Linux下UFS设备节点是/dev/sda这种,而不是eMMC那种mmcblk0。日常操作里高频出现的命令就这几个:INQUIRY拿设备基本信息、READ CAPACITY(10)拿容量、READ(10) opcode 0x28、WRITE(10) opcode 0x2A、UNMAP opcode 0x42做Trim、SYNCHRONIZE CACHE(10)做缓存刷写、START STOP UNIT做电源状态管理。硬件端的固件收到这些CDB后,翻译成内部的NAND读写和FTL操作,地址从逻辑地址转成物理地址,这层逻辑属于厂商私有的Firmware,协议规范并不会约束。

这套设计的高明之处在于省掉了主机驱动层面的重造轮子。Linux的SCSI子系统、磁盘调度、命令超时处理、错误恢复机制,全部都可以复用。UFS协议要做的只是让设备在语义和行为上像一颗标准SCSI盘。调试时你用sg3_utils工具直接往UFS设备发SCSI命令,比如sg_inq查询设备型号,设备必须正确响应,这类命令就是排查“设备到底挂没挂、固件是否活着”的第一板斧。

3.3 实操:一次READ和一次WRITE的完整生命周期

读路径四步走:

  1. 主机发送Command UPIU,UPIU里带SCSI READ(10)命令,填写LBA、传输扇区数、任务标签。
  2. 设备解析命令,访问FTL映射拿到NAND物理地址,如果地址无效或超范围,直接回一个带Sense Key的Response UPIU。
  3. 设备把读出的数据封装成Data In UPIU,可以一次性传完,也可以分多个报文传完。
  4. 主机收到全部数据和Response UPIU,命令关闭;如果数据量很大,会在多个Data In之间反复,但每个命令始终用同一个任务标签关联。

写路径略有不同:

  1. 主机发Command UPIU,带WRITE(10)命令和期望传输长度。
  2. 设备准备缓冲区,回Ready to Transfer UPIU。
  3. 主机发Data Out UPIU,把数据按序发出。
  4. 设备写完后回Response UPIU,带上命令执行状态。

这中间最容易踩的坑是乱序完成。UFS支持最多32个并发任务,命令完成的返回顺序可以跟发出顺序不一样,驱动如果要按“谁先发谁先完成”去做状态机,必然翻车。正确做法是每个命令用task tag唯一标识,完成时按tag找对应请求处理。我做协议分析时见过很多软件在异常链路下tag复用、命令完成被错配到另一个请求上,时间一长就会积累出神秘的超时和IO错误,这种问题看协议trace很容易看出来,纯看应用层日志反而很难定位。

4. 三大新特性逐个拆:Write Booster、HPB与深度睡眠

4.1 Write Booster:把SLC缓存用到极致,但要小心掉速和掉电

UFS 3.1相比UFS 3.0,宣传最多的一项就是Write Booster,也就是三星叫的Turbo Write。原理和SSD上的SLC Cache一样:把一部分TLC或QLC NAND划分出来,以SLC模式去写。SLC单页只有1bit,写入电压判定简单,所以写速度可以做到TLC的两三倍;等系统空闲时,固件再把SLC缓存区里的数据搬移到普通TLC/QLC区,腾出缓存空间。用户实际感知到的,就是前几百兆大文件写入飞快,连续写入超过缓存容量后速度会掉回TLC原生水平。

这个特性不是默认全部开启的。主机需要通过配置描述符显式使能Write Booster,设备也有相关属性和描述符暴露缓存大小、类型(固定SLC还是动态SLC)、当前是否处于忙状态。调试时最烦的是“写入掉速”,用户说设备越写越慢,实际上是Write Booster缓存被写满了、后台搬移又来不及腾地方,速度就崩了。遇到这种问题,先查温度有没有触发节流,再查缓存水位,不要一上来就怀疑NAND颗粒品质。

另外提一个关键风险:掉电时SLC缓存里还有未搬移到TLC的数据,这些数据的映射信息如果还没落盘,固件上电恢复时要去扫描和重建映射,会导致掉电后启动速度变慢,严重时可能丢数据。所以商用产品通常不会让缓存装满到100%就强制开启强制刷盘,或者通过刷新周期机制把脏数据控制在一定水位内,这个属于固件策略的博弈。

4.2 HPB:主机帮设备缓存映射表,随机读提升的原理

UFS设备内部维护一张逻辑地址到物理地址的映射表,每次读一个随机LBA,固件都要去查表,而这张表本身存在NAND里,需要先读到控制器SRAM再查。随机读频繁时,查表开销占了大头,延迟就上去了。HPB(Host Performance Booster)的思路很直接:既然主机内存大带宽高,那把映射表的一部分放在主机侧缓存起来,由主机在每次读命令里直接把物理地址告诉设备,设备就不用回查映射了。

协议上,设备会把逻辑地址空间划分成一个个HPB Region,主机通过HPB相关命令获取每个Region的映射信息并缓存。下次读这个Region里的数据时,主机在命令里带上已缓存好的物理地址,设备验一下就能直接用。这个特性是真能改善随机读延迟和功耗的,因为设备端省掉的不只是查表时间,还有把映射信息读进SRAM的NAND访问功耗。

不过HPB有两个关键点要注意。一是缓存失效问题,设备在内部分配了新的物理块给这个LBA,原来主机缓存的物理地址就过期了,设备必须在响应里明确告诉主机“你这份映射失效了,重新拉一次”,主机侧拿不到正确提示就会读错数据。二是主机内存开销,移动设备内存本来就紧张,缓存映射表太激进反而伤及整体性能,所以实际落地方案都是只缓存部分热区域的映射,并配合淘汰策略。调试HPB问题时,我最常看的两类现象是:随机读性能没有任何提升,多半是设备没使能HPB或者主机没发HPB Read;随机读出现偶发错误,多半是映射失效同步出了问题,需要用trace确认失效通知是否正常。

4.3 电源状态机与Deep Sleep:省电和唤醒的平衡

UFS设备从上电开始,在Active、Idle、Pre-Sleep、Sleep、Deep Sleep这几个状态之间迁移。Active状态耗电最高但能直接跑命令;Idle状态命令可以立刻执行;Sleep状态唤醒延迟略高;Deep Sleep是最深度的设备级省电状态,唤醒时需要更多准备时间。UFS 3.1把Deep Sleep的设计补齐,让设备在待机场景下能压到极低功耗,对手机待机时间帮助明显。

主机是通过SCSI的START STOP UNIT命令来控制设备状态迁移的,命令里的Power Condition字段指定目标状态。设备状态会通过属性(Attribute)汇报给主机,比如当前电源模式。调试低功耗问题要特别注意两个陷阱:一个是设备和链路两个低功耗状态要配合好,链路进了Hibern8,不代表设备就一定在Deep Sleep;另一个是唤醒时序,主机在Deep Sleep唤醒后不能马上发命令,要给设备留足重建上下文的时间,否则命令会直接超时。我遇到过一台设备“休眠唤醒后找不到盘”,最后定位就是固件在Deep Sleep退出时和UniPro链路重新初始化打架,数据通路竞争导致命令没有得到及时响应,典型的跨层配合问题,单查任何一层都看不出来。

4.4 性能节流通知与Refresh:不显眼但很实用的机制

这两个特性在营销材料上不怎么提,但工程上非常有价值。性能节流通知(Performance Throttling Notification)解决的是“设备为什么变慢”的信息不对称问题。当设备温度过高、电压跌落或者其他原因导致固件主动降速时,设备会通过属性和异步通知把节流状态告诉主机。主机知道这是受控降速,就不会误判成设备故障去跑一堆错误恢复流程,用户体验端也可以弹出提示让用户避免持续重负载。做压力测试时,看到性能曲线跳水,第一件事就是读这两个节流状态属性,确认是不是热保护触发。

Refresh机制则是一个数据保鲜的后台任务。NAND长期存放或大量读操作之后,电荷会发生漂移、出现读干扰,弱块数据可能出错。UFS 1.x时代就有类似思路,3.1继续优化了周期刷新和事件触发刷新。设备会在空闲时扫描并刷新有风险的数据块,相当于给数据“续命”。和Write Booster的后台搬移一样,Refresh不能和前台读写抢资源,否则会引发延迟毛刺,这也是固件调度里最考验功力的部分之一。

5. 从零啃UFS 3.1协议:资料地图与问题速查

5.1 中文环境怎么啃英文规范:资料地图

UFS 3.1的中文系统性资料确实少,但信息源很好找,只是需要分清优先级。第一优先级永远是JEDEC的JESD220D.01规范,它就是UFS协议本体,重点读命令集、UPIU、描述符/属性/标志、设备管理这几章。第二优先级是JESD223D,UFS主机控制器接口规范,做驱动和硬件设计的人必须看,它规定了主机控制器寄存器、命令门铃、中断机制。第三优先级是MIPI联盟的UniPro和M-PHY规范,不需要整本刷,只看链路启动、速率切换、流控和Hibern8相关章节就够了。

中文资料更多得靠间接路线:Linux内核drivers/ufs目录下的驱动源码是最好的一手中文阅读材料,代码里的注释和日志字符串会帮你把抽象协议和板子上发生的事对应起来;主流闪存厂商的应用笔记和白皮书,以及行业公众号对UFS的特性拆解,能补足概念层面的讲解。我的建议是别试图背规范,而是先让开发板跑起来,抓一份正常读写的trace,再回头看规范里对应的报文结构,效率高很多。

5.2 协议问题速查:症状、原因和排查手段

症状常见原因排查手段
协商只能到HS-G1没有更高档高速信号质量差、控制器档位限制、对端能力不支持抓协议分析仪看眼图和协商日志,检查差分阻抗和电源
频繁命令超时或复位UniPro重传过多、供电波动、命令tag冲突读取UniPro重传计数,降低速率验证,查驱动tag分配
大文件写入掉速Write Booster缓存满、温度触发节流、碎片过多检查节流属性、缓存水位,做全盘Trim后再测
深度睡眠唤醒后掉盘Deep Sleep退出时序不对、链路Hibern8和命令层状态没同步抓唤醒时序,确认START STOP UNIT和链路初始化完成再下发命令
随机读性能没提升HPB未使能、映射缓存没有命中、设备固件不支持确认HPB使能位和主机缓存大小,抓HPB Read的物理地址字段

这个表基本覆盖了我在项目里遇到过的绝大多数协议层问题。注意所有“性能问题”都不要只看平均数值,要分场景去复现,顺序写、顺序读、随机写、随机读、小队列深度、大队列深度,每一种负载对应的瓶颈完全不一样。

5.3 现场调试:内核节点和直觉判断

做完上电时序分析、UPIU分析之后,在Linux环境里最实用的一套调试组合:用sg3_utils给UFS设备发SCSI命令做基本健康检查,用fio做性能基准测试,用内核动态调试和trace抓UFS驱动日志,用协议分析仪或SoC内嵌trace抓UPIU细节。这些工具能覆盖90%的日常排查。

以Linux为例,UFS设备在系统里以SCSI设备呈现,先sg_scan看设备路径,再用sg_inq看设备基本信息,确认设备是否能正常响应INQUIRY;接下来用fio跑随机读场景,同时打开ufshcd动态日志,观察有没有发送大量重试或任务管理请求。如果怀疑链路协商有问题,看dmesg里的链路启动消息,确认Gear档位;如果链路正常但命令在某些LBA区域反复失败,目标区域很可能是坏块或映射表损坏,进一步用UNMAP和全盘读写去缩小范围。

最后说几句自己的体会

UFS 3.1协议文档确实厚,但真正干起活来,核心就是三层逻辑:链路协商、UPIU数据通路、设备状态管理。我在实际项目里最深的体会是,遇到疑难问题先判层,再去想具体协议字段。如果一上来就在应用日志里瞎猜,往往会把信号完整性问题看成固件bug,把固件调度问题误判成颗粒品质不行。这个系列四篇讲下来,我自己对协议的理解也重新捋了一遍。最后给一个小建议:把四层架构图和UPIU类型表打印出来贴在工位上,抓trace前先问自己一句“我现在在哪一层”,真的能省掉大量无效调试时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询