第2章 UFS协议栈架构:物理层、链路层、传输层、应用层分层介绍

做UFS驱动开发,第一件事儿就是把协议栈的四层结构刻在脑子里。你想想看,从Host端发一个读写命令到UFS Device,中间要穿过物理层、链路层、传输层,最后到应用层——每一层都有自己的规矩和坑。我最早啃协议时,觉得分层太啰嗦,后来踩过几次物理层时序不准的雷,才明白这四层划分其实非常务实。

一句话总结:UFS协议栈 = 物理层(管信号) + 链路层(管帧) + 传输层(管事务) + 应用层(管命令)。向下是硬件,向上是软件,驱动主要跟后三层打交道。

2.1 物理层——信号怎么跑

物理层是最底下的那层,它定义了两个芯片怎么通过差分线(D0/D1)把0和1送过去。用的是M-PHY规范,工作模式有HS(高速)和PWM(低速)两种。我习惯把物理层看成一条“数字高速公路”——车道宽度、限速、转道规则全由这层定。

  • M-PHY:串行接口,1-lane或2-lane,每lane速率从GEAR1到GEAR4(HS-GEAR4约11.6 Gbps)。
  • UniPro v1.8/v2.0:负责物理层之上的链路管理,但物理层直接用了它的电气特性。
  • 电压域:VCC(主供电)、VCCQ(I/O供电)。

个人提醒:调驱动时如果物理层训练失败(比如初始化超时),先量M-PHY的差分电压和时钟。我遇到过一次PCB走线过长导致HS-GEAR3信号眼图闭合,差点以为是PHY芯片挂了。

2.2 链路层——帧怎么组装

链路层坐在物理层上面,它负责把上层的数据包拆成一个个帧(Packet),加上帧头、CRC,然后交给物理层发出去。说白了就是“打包发货”。UniPro协议里的L2子层干的就是这事。

链路层的关键要素:

  1. 帧格式:每个帧最大256字节(包含9字节头+247字节数据载荷)。
  2. 流控机制:发送端要等接收端的ACK才能发下一帧,不然就重传。UFS用的是基于信用(Credit)的流控。
  3. 错误恢复:CRC校验失败后自动重传,最多重试3次。

踩坑实录:我以前调试某款UFS 2.1芯片时,发现链路层CRC错误率奇高。查了半天,原来是Host端的UniPro配置把重试次数设成了0,等于关掉了链路层纠错。嗯,这种配置项在驱动初始化时很容易被忽略。

链路层帧头字段
字段 大小(位) 说明
设备ID / 端点ID 8 区分Host还是Device
序列号 8 用于ACK搭配、去重
数据长度 11 有效载荷字节数(0~247)
帧类型 2 数据帧 / 控制帧 / 管理帧
CRC校验 16 帧头+载荷的校验值

2.3 传输层——事务怎么调度

传输层把多个链路层帧组合成“事务”(Transaction)。一个事务可以包含一个或多个数据帧、命令帧、响应帧。UFS的传输层协议叫UTP(UFS Transport Protocol),本质上是一个高层级的事务管理单元。

我记传输层就记三个东西:请求(Request)、响应(Response)、数据(Data)。驱动给Device发个READ命令,传输层先生成一个命令帧,然后发数据帧(如果是写),最后收响应帧。这三部分构成一个完整事务。

  • 任务管理功能:比如 ABORT 一个挂起的命令。
  • 多命令并发:通过Tag从0到31,Host可以同时发多个事务。

我个人习惯在传输层dump一份完整的UFS Protocol Information Units(PIUs),很多诡异问题(比如命令超时)查PIU就能找出是哪个阶段卡住了。

2.4 应用层——命令怎么执行

应用层是最靠近业务逻辑的,它定义了两组核心命令:一组是SCSI命令(INQUIRY、READ/WRITE 10/16等),另一组是UFS独有的Query Request(用来读写设备描述符、标志、属性)。我经常跟新人说:应用层就是你写驱动时调用的那个接口层——你发一个scsi_cmd,应用层包装一下再往下丢。

应用层的关键对象:

  • 命令描述符块(CDB):SCSI命令的标准载体,最长16字节(有变长扩展)。
  • 上行/下行数据:通过传输层送到Device。
  • 事件通知:Device可以异步上报状态变化(比如温度过高)。

注意:应用层有个“命令超时”陷阱。UFS规范允许Device对某个命令延迟响应(最多拖几百毫秒),但Host驱动如果设的timeout太短,就会误判为Device死掉。我曾在某块SSD上遇到过一次,Query Request 请求描述符时Device回应慢了200ms,驱动直接重置链路,导致整个UFS控制器复位。

2.5 UFS协议栈完整视图

下面这张图把四层串起来,标注了每层对应的核心协议和主要动作。你可以把它贴在工位上,写代码时瞄一眼就知道自己正处在哪一层。

UFS 协议栈四层架构 物理层 M-PHY · GEAR1 ~ GEAR4 · 差分信号 · 电压域 链路层 UniPro L2 · 帧打包/解包 · CRC校验 · 信用流控 传输层 UTP · 事务组包(Request/Data/Response) · 多Tag并发 应用层 SCSI命令 · Query Request · 描述符管理 · 事件通知 Host → Device 命令/数据流 Device → Host 响应/状态流

2.6 层间协同与数据流动

实际工作中,你敲一个scsi_execute(),执行流程会经过四层:应用层把CDB打包 → 传输层封装成UTP事务 → 链路层切成帧并加CRC → 物理层逐位发送。回包路径相反。

有个容易被忽略的点——电源管理。UFS支持多种电源状态(Active / Sleep / Deep Sleep),状态切换是由应用层通过Query Request发起的,但底层物理层和链路层也要跟着调压、调速。我曾在项目里遇到睡眠唤醒后物理层重新训练失败,查了两天才发现是链路层在睡眠前没有flush掉Credit队列。

给新手一个小建议:读UFS驱动时别盯着一个层看,要顺着一个命令的完整生命周期走一遍。从应用层入口到物理层出HCI(Host Controller Interface),每一层做什么、什么时候出错、报什么错误码,心里有数了再调bug就快得多。