ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

UFS 3.1 全双工总线与UPIU事务模型解析

UFS 3.1 全双工总线与UPIU事务模型解析

简介:JESD220E(UFS 3.1)是JEDEC于2020年1月发布的Universal Flash Storage官方协议标准文档,面向移动存储固件工程师、驱动开发者及存储系统学习者,用于系统掌握UFS 3.1的接口规范与实现细节。资源包内仅含1个PDF文件,大小约4.36MB,即完整的JESD220E标准正文,涵盖术语定义、架构模型、命令集与协议分层等章节,便于按目录检索查阅。目前已有5438人学习下载,在存储协议类资料中具备较高关注度。文档详细规定了全双工通信、多通道架构、命令队列、电源管理、CRC与ECC错误校验及低功耗状态等机制,并给出HS-G4接口、理论最高11.6Gbps传输速率与增强型NAND闪存支持等关键特性,读者可据此理解UFS相较eMMC在半双工限制上的突破,为固件开发、协议调试与性能优化提供权威依据。

1. UFS 3.1:为什么一条“全双工”总线能把存储性能翻倍

很多人第一次翻 JESD220E 这份 UFS 3.1 协议文档,都会被它的目录吓退——十五章、上百页、几十张时序图。但真正决定这颗存储芯片“快不快、稳不稳”的,其实就藏在几个核心机制里:全双工链路、命令队列、UPIU 事务模型、WriteBooster 缓存。搞懂这些,你再看手机里那颗 UFS 芯片,就不再是黑匣子。

UFS 3.1 常被说成“eMMC 的进阶版”,这个说法只对了一半。eMMC 是半双工,读写共用一组数据线,同一时刻只能朝一个方向跑;UFS 把链路拆成独立的发送和接收通道,读写可以同时进行,带宽直接翻番。但全双工只是表象,真正让它跑起来的是分层协议栈和一套基于 UPIU 的事务机制。这份 JESD220E 文档,就是把这套机制从电气层一路写到应用层的完整说明书。

2. 分层架构:UFS 为什么要把协议拆成四层

2.1 从 UIC 到 UAP:每一层各管什么

UFS 的协议栈不是随便分的,它对应的是“物理信号 → 链路可靠性 → 事务语义 → 命令语义”这条递进链。按 JESD220E 第 5 章的定义,自上而下大致是四层:

层次名称核心职责对应章节
应用层UAP(UFS Application Layer)SCSI 命令集、CDB 解析第 11 章
传输层UTP(UFS Transport Protocol)UPIU 封装、事务调度第 10 章
互联层UIC(UFS Interconnect)UniPro + M-PHY,链路与物理第 8、9 章
电气层Electrical时钟、复位、供电、信号电平第 6、7 章

这个分层的设计动机很直接:存储协议要同时面对“主机控制器厂商”和“闪存颗粒厂商”两类角色,如果所有细节揉在一起,任何一方改动都会牵连另一方。分层之后,M-PHY 的电气参数可以独立演进(比如从 HS-G3 到 HS-G4),而上层的 SCSI 命令语义保持稳定。常见的实现思路是,主机侧驱动只关心 UTP 层往上的 UPIU 格式,PHY 的均衡、终端电阻这些交给 UIC 层处理。

2.2 UniPro 在中间扮演的角色

UIC 层里最关键的是 MIPI UniPro。它本身不是 UFS 专有的,而是一套通用的链路层协议,UFS 借它来完成几件事:链路初始化、流量控制、错误检测与重传、电源状态管理。JESD220E 第 9 章明确列出了 UniPro 的可调参数,比如 PHY Adapter 的速率配置、Data Link Layer 的重传窗口、Network Layer 的地址映射。

为什么 UFS 不自己造一套链路层?因为 UniPro 已经被移动行业广泛验证,复用它能降低互操作风险。代价是 UFS 必须把 SCSI 的语义“翻译”成 UniPro 能承载的格式,这个翻译工作就落在 UTP 层。我一般会这么理解:UniPro 负责“把比特可靠地送过去”,UTP 负责“让对面知道这串比特是一个读命令还是一个写数据”。

2.3 分层带来的一个实际后果

分层不是免费的。每一层都要加头部、做校验、维护状态机,这些都会带来延迟。UFS 3.1 之所以能在分层的前提下做到低延迟,靠的是命令队列和 UPIU 的流水线化——主机可以连续下发多个命令,设备端并行处理,而不是等一个命令的响应回来再发下一个。这一点在第 10 章的事务模型里有完整体现,后面会展开。

3. UPIU 事务模型:一次读写到底经历了什么

3.1 UPIU 的基本结构

UTP 层的核心单位叫 UPIU(UFS Protocol Information Unit)。你可以把它理解成 UFS 世界里的“数据包”,每个 UPIU 固定 32 字节头部,后面可选跟数据段。JESD220E 第 10.6 节定义了基本头部格式,关键字段包括:

  • Transaction Type:标识这是命令、响应、数据还是查询
  • Flags:读写方向、任务属性
  • LUN:目标逻辑单元号
  • Task Tag:命令标签,用于匹配请求与响应
  • Data Segment Length:后续数据长度
  • Expected Data Transfer Length:预期传输长度

Task Tag 是理解整个事务模型的关键。主机每发一个命令就分配一个 Tag,设备返回响应时带上同一个 Tag,主机据此把响应和请求配对。这就是命令队列能工作的基础——多个命令可以同时在途,靠 Tag 区分。

3.2 一次读操作的完整时序

以 SCSI READ(10) 为例,一次读在 UPIU 层面的流转大致是:

Host Device |--- COMMAND UPIU (READ10) --->| 分配 Tag=N,解析 CDB |<-- DATA IN UPIU (Tag=N) -----| 返回数据段 |<-- DATA IN UPIU (Tag=N) -----| 继续返回 |<-- RESPONSE UPIU (Tag=N) ----| 状态 GOOD,事务结束

注意这里没有“READY TO TRANSFER”这一步,因为读操作的数据方向是设备到主机,设备可以直接推数据。写操作则不同:主机先发 COMMAND UPIU,设备如果缓冲区没准备好,会回一个 READY TO TRANSFER UPIU,主机收到后才开始发 DATA OUT UPIU。这个握手机制是为了防止主机把设备缓冲区冲垮。

3.3 命令队列与 Tag 的配合

UFS 3.1 支持命令队列,主机可以连续下发多个命令而不等待响应。设备端维护一个队列,按内部调度策略决定先处理哪个。JESD220E 第 13.4.3 节提到了 Inter-LU Priority,也就是不同逻辑单元之间的优先级。比如系统分区(LUN 0)的读请求可能优先于用户数据分区,保证开机和系统响应。

这里有个容易踩坑的地方:Tag 的数量是有限的,协议规定了最大队列深度。如果主机下发的命令数超过设备支持的 Tag 数,多出来的命令要么被拒绝,要么主机自己得排队。实际驱动里通常会根据设备返回的 Queue Depth 属性来限制并发数,这个值在描述符里可以读到。

3.4 查询请求走的是另一条路

不是所有交互都走 COMMAND UPIU。读设备描述符、读写属性、读写标志位,走的是 QUERY REQUEST / QUERY RESPONSE UPIU。这条路径不经过 SCSI 命令层,直接由 UTP 层处理,所以延迟更低。设备初始化时,主机就是靠一连串 Query 请求把设备的容量、LUN 配置、支持的功特性全部读出来的。JESD220E 第 10.7.8 和 10.7.9 节定义了 Query 请求和响应的完整格式,包括 Opcode、Descriptor ID、Index、Selector 这些字段。

4. 电源管理与低功耗:UFS 3.1 怎么做到又快又省

4.1 设备电源模式的状态机

UFS 设备不是只有“开”和“关”两个状态。JESD220E 第 7.4 节定义了一套电源模式,包括 Active、Idle、Sleep、PowerDown 等,逻辑单元(LU)还有独立的 Power Condition。设备可以根据主机下发的 START STOP UNIT 命令或者链路空闲时间,自动在模式之间迁移。

这套状态机的设计动机是:移动设备大部分时间存储是空闲的,如果一直保持全速链路,功耗吃不消。但频繁唤醒又会带来延迟。所以 UFS 把“链路状态”和“设备电源状态”分开管理——链路可以降到低速甚至休眠,设备内部的闪存管理(垃圾回收、磨损均衡)仍可以在后台低功耗运行。

4.2 低功耗模式与唤醒延迟的权衡

深度睡眠模式省电最多,但唤醒要重新做链路训练,延迟高。浅睡眠省电少,但唤醒快。协议没有规定主机必须用哪种,而是把选择权交给驱动策略。常见的实现思路是:屏幕熄灭后逐步降级到深睡,屏幕点亮前提前唤醒。这里的关键参数是 bRefClkGatingWaitTime 之类的时序值,主机必须按文档给的时序来,否则唤醒时链路可能起不来。

4.3 WriteBooster:UFS 3.1 新增的写加速机制

WriteBooster 是 UFS 3.1 相对 3.0 的一个重要增量,定义在第 13.4.17 节。它的思路是在设备里划出一块 SLC 缓存区,写入先落到 SLC(速度快、寿命短),后台再慢慢搬到 TLC/QLC 区域。主机可以通过配置属性来开关这个功能、设置缓存大小。

为什么需要它?因为 TLC/QLC 的编程速度慢,直接写会拖累突发写入性能。WriteBooster 用 SLC 做缓冲,把“写快”和“存得多”解耦。代价是 SLC 缓存区容量有限,写满之后性能会回落,所以主机驱动需要根据写入量动态调整策略。这个机制和主机侧的 F2FS、ext4 文件系统配合时,效果差异很大,属于典型的“协议给了工具,怎么用看实现”。

5. 避坑与排查:UFS 调试中最容易翻车的几个点

5.1 链路起不来,先查参考时钟

现象:上电后主机读不到设备,Query 请求超时。原因:参考时钟频率或时序不满足 JESD220E 第 6.4 节的要求,M-PHY 无法完成初始化。解决:用示波器量 REF_CLK 的频率、占空比和抖动,对照文档里的 HS Gear Rates 表确认。很多翻车案例是时钟源选错档位,或者 PCB 走线太长导致边沿变缓。

5.2 读写偶发 CRC 错误

现象:大文件传输时偶发数据校验失败,重试后恢复。原因:信号完整性有问题,或者 UniPro 的重传窗口配置过小。解决:先查 PCB 阻抗和终端电阻是否匹配文档第 8.1 节的 Termination 要求;再确认 Data Link Layer 的重传参数是否按设备能力配置。偶发 CRC 往往是电气问题,不要一上来就怀疑协议实现。

5.3 命令队列深度设太大导致超时

现象:高并发场景下部分命令超时,降低并发后正常。原因:主机下发的并发命令数超过了设备实际支持的 Tag 数,或者设备内部缓冲区不足。解决:读设备的 Queue Depth 属性,按实际值限制主机队列深度。不要照搬参考驱动的默认值,不同颗粒和主控的组合能力不一样。

5.4 WriteBooster 开启后写入反而变慢

现象:打开 WriteBooster 后,持续大文件写入的后期速度骤降。原因:SLC 缓存区写满,后台搬移跟不上,触发限流。解决:根据工作负载调整缓存大小,或者在持续写入场景下关闭 WriteBooster,让数据直接写 TLC。这个取舍没有标准答案,得按实际业务测。

5.5 低功耗唤醒后设备无响应

现象:系统休眠再唤醒,存储设备偶尔失联。原因:唤醒时序不满足文档要求,或者电源斜坡时间不够。解决:对照第 7.2 节的 Power up ramp 和第 7.3 节的 Power off ramp,检查供电时序。唤醒失败很多时候是电源管理 IC 的斜坡太慢,不是 UFS 本身的问题。

6. 从协议到实现:怎么用这份文档验证你的设计

JESD220E 不是一本读一遍就完的书,它更像一本查得到的字典。真正做 UFS 驱动或硬件设计的人,用法通常是“按场景查章节”:调电气看第 6、8 章,调事务看第 10 章,调电源看第 7 章,调性能看第 13 章。但光查还不够,得建立一套验证方法。

我一般会建议按这个顺序过一遍:先确认设备能枚举(Query 读描述符成功),再确认基本读写正确(SCSI READ/WRITE 往返数据一致),然后压命令队列看并发稳定性,最后才调电源和性能特性。这个顺序的好处是,每一层的问题都能被隔离出来,不会出现“读写不对但不知道是链路问题还是命令问题”的尴尬。

文档里还有几个容易被忽略但很实用的部分。第 14 章的描述符、标志和属性,是主机了解设备能力的唯一入口,设备支持什么特性、容量多大、LUN 怎么划分,全在这里。第 12 章的 RPMB(重放保护内存块)和安全模式,涉及数据防篡改,做安全启动和密钥存储时绕不开。第 13.6 节的 PSA(Production State Awareness)则是产线烧录场景的必备知识,处理不好会导致出厂设备首次启动异常。

一个设计取舍值得单独说:UFS 3.1 把很多优化能力做成了“可选特性”,比如 WriteBooster、动态容量、温度事件通知。协议只定义接口,不强制实现。这意味着同样标称 UFS 3.1 的两颗芯片,实际能力可能差很多。选型时不能只看版本号,得逐条对照设备返回的属性位,确认你要用的特性真的被支持。这也是为什么读懂描述符和属性表,比背协议目录更有价值。

从那以后我每次拿到一颗新的 UFS 器件,都会先把它的描述符和属性完整 dump 一遍,和 JESD220E 第 14 章逐项对照,再决定驱动怎么配。这个习惯帮我避开了不少“协议支持但器件没实现”的坑。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进