10. UFS介绍
10.1 UFS简介
电脑有三大件——CPU、内存和硬盘。CPU用于计算和控制,内存用于临时存储程序运行时所需的数据(掉电后数据丢失),而硬盘用于长久保存数据(掉电后不丢失)。
我们每天使用的手机,本质上就是一个移动的小型计算机,同样有三大件——CPU/GPU、内存和存储设备。其中存储设备相当于电脑的硬盘,用于长久保存手机上的数据,比如视频、照片、音乐、操作系统等。
UFS(Universal Flash Storage,通用闪存存储)有两层意思,一层是指一种移动存储接口协议,类似SATA、PCIe/NVMe;另一层是指使用该协议的移动存储设备。后文出现UFS,大家应该根据上下文理解具体为哪层含义。
为什么说UFS是手机存储的未来?无他,快!可以通过下表感受以下。
| UFS版本 | 2.0/2.1/2.2 | 3.0/3.1 | 4.0 |
| 单通道最大接口速率/(Mb/s) | 5836.8 | 11673.6 | 23347.2 |
| 单向最大通道数 | 2 | 2 | 2 |
| 单向最大有效带宽(MB/s) | 1081 | 2163 | 4326 |
注:表中最大有效带宽不包括协议开销和8/10b编码开销,这是一种二进制速率。
UFS协议是JEDEC(www.jedec.org)组织指定的,三星、海力士、铠侠等公司力捧。我们可以看到,UFS协议一直在大踏步朝着更高更快的目标前进。
UFS为什么能那么快?
1)它在数据信号传输上使用的是差分串行传输。这是UFS快的基础。所有的高速传输总西安,如SATA、SAS、PCIe等,都是串行差分型号。串行,可以使用更快的时钟(时钟信息可以嵌在数据流中);差分信号,即用两根信号线上的电平差表示0或者1。与单端信号传输相比,差分信号抗干扰能力强,能提供更宽的带宽(跑得更快)。打个比方,假设用两根信号线上电平差表示0和1,差值大于0表示1,差值小于0表示0。如果传输过程中存在干扰,两根线上加了近乎同样大小的干扰电平,两者相减,差值几乎不变。但对单端信号传输来说,就很容易受干扰,比如0 ~ 1V表示0,1 ~ 3V表示1,一个本来是0.8V的电压,加入干扰,变成1.5V,相当于0变成1,数据就出错了。因为具有抗干扰能力强的特性,所以差分串行信号线可以用更快的速度进行数据传输,从而提供更宽的带宽。
UFS的前辈是eMMC。eMMC使用的是并行数据传输方式。并行最大的问题是速度上不去,因为一旦时钟上去,干扰就变大,信号完整性无法保证。另外并口需要更多的数据传输线。
2)UFS和PCIe一样,支持多通道数据传输,目前,一个方向上最多支持两个通道。多通道选项可以让UFS在成本、功耗和性能之间做取舍。
3)UFS采用全双工工作模式,就是读写可以并行。它的前辈eMMC是半双工,读写不能同时进行,两个的对比如下图所示。
UFS和eMMC工作模式对比
要让UFS速度快,底层硬件基础设施要跟上层数据传输协议的配合。就好比我们想要速度快,除了需要一条又宽敞又平坦的高速公路,还需要一辆可高速行驶的汽车。你如果仅有一辆拖拉机,那么即使上了高速公路也跑不快。
UFS上层协议怎样来充分发挥底层硬件速度快的优势呢?
UFS支持命令队列,也就是主机可以同时发很多个命令,然后UFS设备支持并行和乱序执行,谁先完成谁先返回状态。这种命令处理方式称为异步命令处理。而它的前辈eMMC,早期版本是不支持命令队列的,命令一个一个执行或者一包一包(每个包里面含有若干个命令)执行,前面命令没有执行完成,后面的命令是不能发下去的。这种命令处理方式称为同步命令队列。
我们来比较一下“全双工+异步命令处理”和“半双工+同步命令处理”两者命令处理方式的执行效率。
1. 半双工+同步
如下图所示,主机发了一个写命令W1给设备,主机把数据写到设备。同步命令处理方式下,命令是被串行处理的,所以在发读命令R2之前,必须等前一个写m命令W1处理完成。同样,在发送写命令W3之前,必须等命令R2处理完成。
同步命令处理流程示例
2. 全双工+异步
由于支持命令队列,主机以下可以发若干个命令给设备,如下图所示,主机同发一个写命令W1和一个读命令R2给设备。设备可以并行处理这两个命令。由于协议支持全双工操作,主机传输写命令W1的数据给设备的同时,设备也可以把读命令R2的数据返回给主机。后面命令R3、R4、R5的处理方式与此类似。
异步命令处理流程示例
再形象一点,我们以搬运货物为例来比较以下eMMC和UFS命令执行方式,如下图所示。
eMMC和UFS命令处理方式对比
现在的手机,应用非常丰富,用户可能要一边看新闻,一边听歌,一边聊微信,多线程操作。由于全双工和命令队列的存在,UFS处理命令的效率大大提高,给用户极好的体验。
前面我们拿UFS和eMMC做了对比,但什么是eMMC?eMMC(Embedded MultiMedia Card)和UFS一样,也是JEDEC制定的一种移动存储协议。eMMC最新标准是2015年发布的eMMC5.1,支持的最高速度是400MB/s。JEDEC已经有了UFS,应该不会再发布新的eMMC标准。毕竟,并行传输的eMMC受限于物理信号,速度想要有质的飞跃是不现实的。
UFS正在慢慢取代eMMC成为主流移动存储协议,UFS最终取代eMMC也是必然。下图所示是eMMC和UFS的应用趋势变化。
eMMC和UFS的应用趋势变化(来源:CFM闪存市场)
不过,UFS一统天下的道路上还有一个拦路虎,那就是NVMe。有人说,NVMe不是SSD的协议标准吗?没错,不过,现在苹果手机中的存储协议是PCIe+NVMe而不是UFS。在短期内,UFS和NVMe会分别在安卓和苹果手机中存在。长期来说,UFS和NVMe是二分天下,还是合二为一,暂未可知。最终可能会两者并存,UFS会借鉴NVMe的优点,毕竟NVMe走在前面。
给大家看看嵌入式UFS的实物图,如下图所示。
UFS设备实物图
嵌入式UFS为BGA形态,一般尺寸为11.5mm × 13mm × 1mm(或0.8mm),如大拇指的指甲盖大小。但麻雀虽小,五脏俱全。UFS存储芯片内部,如下图封装了UFS控制器和闪存阵列,和SSD结构很相似。不过和SSD相比,由于它的容量更小,因此闪存Die比较少,闪存的通道数也少。另外,出于功耗和成本考虑,UFS芯片一般是不带DRAM。
UFS设备框架图
10.2 UFS协议栈
任何一种接口或者协议,都是由一个完整的协议栈组成的,UFS也不例外。UFS的协议栈如下图所示。
UFS协议栈
UFS定义了一个完整的协议栈,从上到下依次为应用层、传输层、数据链路层和物理层。UFS所使用MIPI(Mobile Industry Proceor Interface,移动产业处理器接口)联盟的Unipro作为数据链路层,使用MIPI的M-PHY作为物理层,两者合起来称为UFS互连层(UFS INterConnect Layer, UIC)。UFS主机(比如手机、平板电脑等设备)和UFS存储设备两侧都需要实现UFS协议栈。
目前,UFS主要使用的命令是简化的SCSI命令(基于SBC和SPC),这是由INCITS T10组织定义的。关于SCSI相关协议,可以参看相应的协议规范。
UFS协议栈的四层中有三层是别人的:命令层是T10的,数据链路层和物理层是MIPI的,只有传输层是JEDEC自己的。
UFS至今已经有多个版本(本章都是基于UFS4.0之前的版本撰写的),每个版本使用的Unipro和M-PHY版本也不尽相同,如下表所示。
UFS版本和对应的各层版本
10.2.1 应用层
应用层包括UFS命令集、设备管理器(Device Manager)和任务管理器(Task Manager)。应用层是整个协议栈的最高层,所有的命令或者请求都来源于该层。它是最高统帅,所有的战术和策略都是它制定的,真正去冲锋陷阵的是将军和士兵(应用层下面的各层)。
1. 命令集
如前所述,UFS主要使用简化的SCSI命令,具体如下表所示。
UFS使用的SCSI命令集
2. 设备管理器
顾名思义,设备管理器用于管理UFS设备。设备管理器有两个功能:一是处理设备级操作,二是管理设备级配置。前者包括管理设备功耗、设置数据传输相关参数、使能/禁止设备后台操作(Background Operation)以及其他设备相关操作。后者通过维护和存储一系列描述符,通过Query请求修改或获取设备的配置信息。
如下图所示,设备管理器既可以通过下层的传输层为自己服务(通过UDM_SAP接口),通过Query命令去处理设备级操作和修改获取设备配置信息,也可以绕过传输层,直接通过UIC提供的服务接口UIO_SAP去管理与控制互连层。
设备管理器可以通过互连层提供的接口(UIO_SAP),使用一系列的原语(Primitive)直接控制并操作互连层。这些原语包括重启设备、重启互连层、让物理层进入和退出休眠模式(Hibernate)等。
设备管理器通过UIO_SAP和UDM_SAP接口与互连层、传输层交互
总之,设备管理器既可以走常规渠道(通过传输层,以数据包UPIU的形式传输数据),也可以走快速通道(发送互连层能理解的命令,以原语的形式)管理和操作设备。
3. 任务管理器
任务管理器用于管理命令队列中的命令。比如任务管理可以发Abort命令,中止之前发下去的命令。它也可以清空命令队列中的所有命令。任务管理器的具体功能如下表所示。
任务管理器功能表
当某个命令超时时,系统可能发Abort命令把这个命令中止。
10.2.2 传输层
传输层为它上面的应用层服务。当传输层接收到应用层的命令或者请求后,会生成UPIU(UFS Protocol Information Unit,UFS协议信息单元),把命令块或者请求封装成固定格式的数据结构,然后交由下层传到接收端的传输层。和命令相关的数据、状态、也有相应的UPIU。UPIU是主机和设备进行信息交换的基本数据单元,上层命令或者数据都是通过此类数据包封装起来,然后传输到接收端的。
10.2.3 互连层
互连层包括MIPI Unipro和M-PHY,它们分别充当UF数据链路层和物理层的角色。数据链路层负责主机和设备的连接,物理层负责传输实实在在的物理信号。
Unipro其实不仅定义了数据链路层,还是一个比较完整的协议栈,如下图所示。
Unipro协议栈
传输层(L4)支持多设备之间的双向连接,但UFS只支持CPort0;网络层(L3)支持通过设备ID寻址多达128个设备,但由于UFS是点到点传输,所以不需要网络层;数据链路层(L2)支持流控、CRC生成和校验、重传机制等,UFS利用UniPro的数据链路层作为主机和设备之间的通信提供可靠的连接;物理适配层(L1.5)对物理层做了抽象,使UniPro适配不同的具体物理接口。
物理层(M-PHY)使用8/10b编码、差分信号以串行的方式进行数据传输。数据传输分高速(Hgh Speed)和低速(Low Speed)两种模式,每种模式下又有几种不同的速度档(Gear)。当线上没有数据传输时,M-PHY会进入省电模式,其中高速模式下对应的省电模式是STALL,低速模式下对应的省电模式为SLEEP,而最为省电的状态为HIBERNATE。
这里以下图所示为例来解释一下M-PHY连接的一些术语。
M-PHY通道示例
LINE(线路):发送模块和相应接收模块之间点对点的互联信号线,包括两根差分信号线。
Lane(通道):单向、单信号、物理传输通道,用于点A到点B的信息传输。由上图可以看到,通道包括发送模块和其对应的接收模块,以及它们之间的连接信号线(Line)。UFS在每个方向可以有2条Lane,由于相反方向必须具有跟它同样多的Lane,所以UFS事实上可以有4条Lane(每个方向各2条)。我们平时说UFS有2条Lane,其实是指单方向的。
SUB-LINK(子链接):由统一方向的所有Lane组成的连接。两个具有不同方向的子连接给UFS提供了双向数据传输功能。
LINK(连接):由所有子连接组成的点A(比如主机)和点B(比如设备)之间的连接。
下图所示是2通道的UFS主机和设备连接的示意图,具体信号线如下表所示。
主机和设备互连示意图
UFS信号线
应用层和传输层之间是CPort接口,PA层(物理适配层)和PHY层使用的RMMI接口。
10.3 UPIU
UFS协议中数据包称为UPIU,它是固定格式的数据结构,用于传输应用层发来的命令或者请求,以及跟它们关联的数据或者状态信息。UFS中的UPIU类似于SATA中的FIS和PCIe的TLP。
UFS采用“客户-服务器”结构(或者说从主从的命令结构),UFS主机(客户)发送命令或者请求给UFS设备(服务器),UFS设备执行命令并返回命令状态(Response)。注意:UFS所有的数据传输都是主机发起的,设备不能主动发起数据的传输,但跟PCIe是不同的(PCIe设备可以主动向主机传输数据)。
一个命令或者请求的执行包含下面几个阶段,如下图所示
UFS命令执行过程包含的几个阶段
图中所示几个阶段说明如下。
命令阶段:主机发起命令或请求给设备,这是“因”。
数据阶段:传输跟数据关联的命令(比如读写命令),这个过程肯定会涉及数据的传输;有些命令不涉及数据的传输,就没有这个阶段。所以这个阶段并不是总存在,跟具体命令和请求相关。
响应阶段:设备执行完命令,必须给主机返回命令执行状态信息。这个是“果”,是必不可少的。在PCIe中,有Posted和Non-posted的TLP,对于前者,命令执行者无须返回命令执行状态给命令发起者;对于后者,命令执行者必须返回状态给命令发起者。对UFS来说,它的命令总是Non-posted,即设备必须返回命令状态给主机。
无论是命令执行过程中的哪个阶段,UFS主机和设备间都是通过UPIU进行信息交互的。
- UFS主机通过命令或者请求UPIU发命令请求给设备。
- UFS主机或者设备通过UPIU传输数据。
- UFS设备通过UPIU返回命令状态信息给主机。
10.3.1 UPIU事务
下面我们看看UFS中都有哪些UPIU事务。
1. 命令或者请求UPIU
应用层包括UFS命令、设备管理器和任务管理器3个模块,传输层根据不同模块发来的命令或者请求分别产生不同类型的UPIU。
UFS命令模块发送简化版本的SCSI命令,当传输层收到命令请求后,它会生成COMMAND UPIU,把命令封装起来。
应用层通过任务管理器来管理任务队列,比如中止或查询命令队列中的命令。当传输层收到来自任务管理器的请求后,它会生成TASK MANAGEMENT REQUEST UPIU,把请求封装起来。
UFS通过设备管理器来管理UFS设备,比如设置、查询配置UFS设备。当传输层收到来自设备管理器发来的请求后,它会生成QUERY REQUEST UPIU,把请求封装起来。
把命令请求类UPIU归总到下表中。
命令请求类UPIU
| 应用层模块 | 对应的UPIU | 传输方向 | 作用 |
| SCSI命令 | COMMAND UPIU | 主机到设备 | 主机发送SCSI命令给设备 |
| 任务管理器 | TASK MANAGEMENT REQUEST UPIU | 主机到设备 | 主机管理命令队列中的命令,比如中止或者查询设备命令队列中的命令 |
| 设备管理器 | QUERY REQUEST UPIU | 主机到设备 | 主机通过设备管理器查询、配置、管理设备 |
2.数据传输相关UPIU
当主机发送了类似读命令这样的与数据相关的命令给设备之后,设备需要通过DATA IN UPIU向主机传输数据。
当主机发送了类似写命令这样的与数据相关的命令给设备之后,主机需要通过DATA OUT UPIU向设备传输数据。
UFS主机在向设备写数据的时候,会考虑设备这个时候能不能接收数据(因为此时设备可能没有足够的内存空间接收主机数据),它在向设备发了写命令之后,不会立刻把数据传输给设备,而是在那里等设备的反馈。当设备准备好接收数据并确定能接收多少数据时,设备通过READY TO TRANSFER UPIU(RTT)告知主机。当主机收到该RTT后,才开始根据RTT的信息传输数据。RTT中包含该次传输多少数据的信息,主机根据RTT提供的信息进行传输。所以,主机只有在收到设备的RTT后才能发DATA OUT UPIU。
注意,读命令不需要这种机制。因为设备从闪存中获得数据后,是设备控制数据的传输。对主机来说,它在发读命令之前,已经准备好足够的空间来接收数据,所以不存在主机没有空间接收数据的情况。
我们把数据传输类UPIU归总在下表中。
数据传输类UPIU
| 数据传呼相关的UPIU | 传输方向 | 作用 |
| DATA IN UPIU | 设备到主机 | 设备传输数据给主机 |
| DATA OUT UPIU | 主机到设备 | 主机写数据到设备,主机只有收到RTT后才能往设备写数据 |
| READY TO TRANSFER UPIU | 主机到设备 | 同步,处理写命令时,设备告诉主机可以传输数据已经传多少数据 |
3. 响应UPIU
前面看到,主机有3中请求——SCSI命令、任务管理器发出的任务管理请求(Task Management Request),以及设备管理器发出的查询请求(Query Request)。针对不同的命令或者请求,设备在执行完相应的任务后,分别返回对应的状态UPIU给主机。
我们把命令响应类UPIU归总在下表中。
命令响应类UPIU
| 设备响应UPIU | 传输方向 | 作用 |
| RESPONSE UPIU | 设备到主机 | 设备返回命令执行状态 |
| TASK MANAGEMENT RESPONSE UPIU | 设备到主机 | 设备返回任务管理请求执行状态 |
| QUERY RESPONSE UPIU | 设备到主机 | 设备返回设备管理器的查询请求执行状态 |
4. 其它UPIU
除了以上常规的UPIU,还有其它UPIU,这里进行简单介绍。
设备上电后,主机检测是否与之连接,此时会发NOP OUT UPIU给设备。这如同我们平时想看与某台电脑能否连接上会发一个ping命令。
当设备收到NOP OUT UPIU后,会返回NOP IN UPIU。主机收到该UPIU后,确认与设备连接,然后就可以进行后续操作了。
最后一个UPIU就是REJECT UPIU。当设备收到一个无效的UPIU(或者无法识别的UPIU)时,它会发REJECT UPIU拒绝无效的UPIU。
我们把其它类UPIU归总到下表中
其它类UPIU
| 辅助UPIU | 传输方向 | 作用 |
| NOP OUT UPIU | 主机到设备 | 主机ping命令,查询主机是否与设备相连 |
| NOP IN UPIU | 设备到主机 | 设备响应NOP OUT UPIU,主机收到该UPIU后,确认主机与设备相连 |
REJECT UPIU | 设备到主机 | 设备收到无效的UPIU,发送UPIU给主机 |
5. 读写命令中UPIU交互的例子
前面我们都是单独看每一个UPIU,现在我们以读写命令为例,看看它们是如何组合完成命令处理的。
首先是一个“主机往设备读取96KB数据”的例子,如下图所示。
读命令处理过程中UPIU交互的例子
首先,主机发送读96KB数据的命令给设备,然后设备执行命令,并分3批把数据返回给主机,最后返回命令执行状态给主机。
下面是一个“主机往设备写64KB数据”的例子,如下图所示。
写命令处理过程中UPIU交互的例子
主机发送写64KB数据的命令个设备,然后在那里等设备响应。很快,设备说,你可以传24KB数据下来了,于是主机写24KB数据给设备。接着,设备又来说可以继续传32KB数据,主机照做。最后,设备通知可以把最后8KB数据也传过来,主机于是写最后8KB数据。最后,主机收到设备命令执行完成的响应。我们看到,主机必须收到RTT后才能启动数据传输。
10.3.2 UPIU格式
上面我们介绍了UFS的各个UPIU事务,下面我们具体看一下UPIU的内容。UPIU是有固定格式的数据包,分析数据包格式有助于我们深入理解UPIU以及整个UFS协议。
每个UPIU都有一个12Byte的Header,再加上跟每个UPIU相关的域,如下图所示。一个UPIU(包括Header)最小为32Byte,最大为65600Byte。
UPIU内容组成
我们看通用的Header,具体如下表所示。
UPIU Header格式
我们看看其中的一些域:
1)Tranaction Type(事务类型):用啦标识该UPIU是前面介绍的12个UPIU事务中的哪一个。
2)Flags(标识):只对命令和其响应的UPIU有用,指定命令的属性。
- Flags.R:如果该位置起,说明该命令需要设备向主机传输数据(比如读命令)。
- Flags.W:如果该位置起,说明该命令需要主机向设备传输数据(比如写命令)。
- Flags.CP:表示命令的优先级。1为高优先级,0表示没有优先级。注意,该位只适用于简单命令(具有Simple属性的命令)。
- Flags.ATTR:命令属性域。UFS命令有Simple、Ordered和Head of Queue属性,具体如下。
- Flags.ATTR=00h:表示命令为Simple command,就是一般的命令。设备收到这样的命令无效进行特别处理,一般谁先到谁先执行。这是系统中最常见的命令属性。
- Flags.ATTR=01h:表示命令为Ordered command。设备收到这样的命令,应该把该命令之前的命令都处理完,才处理该命令,后面的命令也必须等该命令处理完成后才能执行。
- Flags.ATTR=10h:表示是命令为Head of Queue Command。设备收到该命令后,将其放到命令队列的头部,立即执行。
3)LUN(Logical Unit Number,逻辑单元编号):指定该UPIU的目标LU。UFS上层协议来自SCSI,它继承了LU的概念,即把物理存储空间划分成若干个逻辑空间,每个逻辑空间都从LBA 0开始,用LUN标识。主机在发命令中指定该命令发给哪个LU。
4)Task Tag(任务标签):UFS支持命令队列,主机可以同时发送很多个命令给设备。为区分这些命令,主机需要为每个命令贴上标签。跟这个命令相关的数据UPIU和状态UPIU,都具有跟这个命令UPIU一样的标签。比如对读命令来说,COMMAND UPIU、所有的DATA IN UPIU和RESPONSE UPIU都具有同一个标签。
5)Command Set Type(命令集类型):UFS预期有三类命令,即简化的SCSI命令、UFS原生命令和用户自定义命令。目前,UFS的命令都是从别人家(SCSI)借来的,自己一个命令也没有制定。如果用户无自定义命令,该域就是0(SCSI命令)。
6)Initiator ID(身份编号):UPIU发起者身份编号。手机系统中一般一个主机连接一个UFS设备,所以主机ID一般为0.
7)Response(响应):设备告知主机命令是否执行成功。
8)Status(状态):设备返回的命令执行的状态。UFS有下表所示状态。
| 状态码 | 响应 | 说明 |
| 00h | GOOD | 表明命令成功执行 |
| 02h | CHECK CONDITION | 此状态表示设备以完成命令,但有错误,或者需要其它操作来处理结果。出现此状态时,将在响应UPIU中返回最后处理的命令的有效sense数据 |
| 08h | BUSY | 此状态表示逻辑单元忙。当逻辑单元无法接收命令时,将返回此状态。主机稍后发送命令是标准的恢复操作。 |
| 28h | TASK SET FULL | 此状态表示由于缺少资源(如任务队列已满或命令执行所需的内存暂时不可用),逻辑单元无法处理命令 |
9)Query Function,Task Manag Function(查询和任务管理功能):任务管理器包括中止任务、中止任务集、清除任务集、重置LU、查询任务、查询任务集等功能。设备管理器包括标准的查询读、用户自定义查询读、标准的查询写、用户自定义查询写等功能。
10)Device Information(设备信息):该域往往跟该命令无关,属于设备夹带的私活。因为UFS主机和设备是主从关系,如果UFS主机没有向设备发命令,UFS设备是不能主动向主机报告设备状况的。如果UFS设备上有特殊事件发生,它可以趁返回RESPONSE UPIU的时候把事件告诉主机。所以该域只对REPONSE UPIU有效。
11)Total EHS Length(EHS总长)和Data Segment Length(数据段总长):这两部分见名知义,很好理解。
10.4 逻辑单元
NVMe里面有命名空间的概念,就是把SSD物理空间划分成若干个逻辑地址空间。UFS中也有这个特性。用户可以把UFS设备的物理存储空间分配成若干个独立的逻辑地址空间(这就是“分区”的概念),我们把这些独立的逻辑地址空间称为LU(Logical Unit)。由前文可知,在每个UPIU的Header中都有一个LUN域,这就是标识与该UPIU关联的命令的目标逻辑单元。每个LUN的地址空间是独立的,主机在发命令给设备的时候,需通过UPIU Header的LUN域指定目标逻辑单元。
每个LU都是独立的,“独立”表现在以下几个方面。
- 逻辑地址空间是独立的,都是从LBA 0开始的。
- 逻辑块大小可以不同,可以为4KB,8KB等。
- 可以有不同的安全属性,比如可以设置不同的写保护属性。
- 每个LU可以有自己的命令队列。
- 不同的LU可以存储不同的数据,比如有的LU存储系统启动代码,有的LU存储普通的应用数据,有的LU存储用户特殊数据。
最新的UFS协议中可以有最多32个普通LU和4个核心LU(4个Well known LU,即众所周知的LU),如下图所示。
UFS中支持的LU
普通LU的逻辑块大小至少是4KB,但PRMB LU逻辑块大小为256Byte。至于什么是RPMB LU,下一节讲。
普通LU是用来存储用户数据的,这没必要展开。下面主要介绍4个核心LU。
1)Report LUNS LU:Report LUNS LU主要用来代表设备向主机汇报设备LU清单。主机想知道设备LU的支持情况,就需要发命令给该LU。UFS中有一个命令Report LUNS(和该LU名字一样)用来访问Report LUNS。
2)UFS Devcice LU:UFS设备的法人。当UFS主机不针对某个具体LU,而是对整个UFS发命令的时候,UFS Devcice LU就成为该命令接收的对象,比如格式化UFS设备(FORMAT UNIT命令)、切换UFS设备的功耗模式(START STOP UNIT命令)等时。
3)BOOT LU:顾名思义,就是用来存储启动代码的LU。不过,BOOT LU本身是不存储启动代码的,它只是一个虚拟的LU,启动代码在物理上是存储在普通LU上的。有两个BOOT LU——BOOT LU A和BOOT LU B,它们可以用来存储不同的启动代码(比如一个新,一个旧),但在启动过程中,只有一个是活跃的。32个普通LU中的任意一个都可以配成BOOT LU A或者BOOT LU B。
主机启动时,首先应该通过设备管理器发送查询请求给设备,获取一个名为bBootLunEn的属性,该属性标识当前活跃的BOOT LU,如下表所示。如果bBootLunEn=01,说明BOOT LU A是活跃的,因此主机会从与BOOT LU A对应的普通LU上读取启动代码完成系统的启动;如果bBootLunEn=02,则表明BOOT LU B是活跃的。
bBootLunEn属性表
| bBootLunEn | 描述 |
| 00h | BOOT LU A = 禁止 BOOT LU B = 禁止 |
| 01h | BOOT LU A = 使能 BOOT LU B = 禁止 |
| 02h | BOOT LU A = 禁止 BOOT LU B = 使能 |
| 其它 | 保留 |
值得一提的是,BOOT LU不是必须有的。如果系统的启动代码不是存储在UFS设备上,那么BOOT LU就不需要了,因此bBootLunEn=0.
4)RPMB LU:在UFS里有这样一个LU,主机往该LU写数据时,UFS设备会校验数据的合法性,只有特定的主机才能写入;同时。主机在读取数据时,也有对应校验机制,保证主机读取到的数据是从该LU上读的数据,而不是攻击者伪造的数据。这个LU就是RPMB LU。
下一节会专门讲解RPMB。
4个核心LU分工明确,分别执行不同的任务。下面对它们能接收的命令进行总结,如下表所示。
核心LU能接收命令列表
| 核心LU | W-LUN | UPIU头中的LUN域 | 命令名称 |
| Report LUNS | 01h | 81h | INQUIRY、REQUET SENE、TEST UNIT READY、REPORT LUNS |
| UFS Device | 50h | D0h | INQUIRY、REQUET SENE、TEST UNIT READY、START STOP UNIT、FORMAT UNIT |
| BOOT | 30h | B0h | INQUIRY、REQUET SENE、TEST UNIT READY、READ(6)、READ(10)、READ(16) |
| RPMB | 44h | C4h | INQUIRY、REQUET SENE、TEST UNIT READY、SECURITY、PROTOCOL IN、SECURITY PROTOCOL OUT |
需要注意的是,写BOOT LU和RPMB LU时,不支持缓存操作,也就是说,数据必须写到闪存以后这条写命令才算完成。而写一般LU,大多都是支持缓存操作的,即主机数据写到设备的内部缓存,设备就会将命令完成状态返给主机。
10.5 RPMB
UFS主机通过认证(authenticated)的方式访问RPMB LU。图10-19展示了RPMB数据写的过程。
主机和设备写RPMB数据流程
对上图所示流程说明如下。
1)UFS主机和UFS设备共享密钥,该密钥在UFS设备出厂时就保存在UFS设备。
2)UFS主机在发送主机数据给UFS设备前,会用该密钥和哈希算法生成消息认证码(Message Authentication Code,MAC)。
3)UFS主机把主机数据连同MAC一起发给UFS设备。
4)UFS设备把收到的主机数据和共享密钥在本地重新计算并生成MAC,然后把计算出的MAC和收到的MAC做对比,如果一致,则认证成功,写入到闪存;否则,拒绝该笔数据的写入。
UFS使用HMAC(Hash-based Message Authentication Code,基于Hash的消息认证)SHA-256算法生成消息认证码。HMAC运算利用Hash算法,以一个密钥和一个消息为输入,生成一个消息摘要作为输出。关于HMAC具体算法可参考https://en.wikipediia.org/wiki/HMAC。
消息认证码本质是Hash值。Hash的一个特点是,即使只改变原数据中的1位,两者的Hash值也是截然不同的。如果恶意攻击者在数据传输过程中篡改了用户数据,那么UFS设备根据收到的数据和接收到的MAC不一致,认证通不过,数据就不会写入UFS设备。
上述前提是共享密钥不能被恶意攻击者获取,否则,恶意攻击者完全可以模拟主机行为;用自己的恶意数据和共享密钥生成MAC,然后把恶意数据和与其对应的MAC发送给UFS设备。UFS设备胡认证成功,恶意数据被写入。所以,请保管好你的密码。
但是,恶意攻击者是狡猾的,即使他没有办法获得你的密钥,他还是有办法对你进行攻击的。比如,恶意攻击者可能监听到UFS主机和UFS设备之间某次数据传输,得到“用户数据+MAC”,然后他就可以重复发送该“用户数据+MAC”给UFS设备,由于“用户数据+MAC”是合法的,所以认证会通过,UFS设备就会接收该数据并写到闪存。这就是重放攻击——Replay Attack,示意图如下图。
重放攻击的概念
RPMB(Replay Protection Memory Block,重放保护存储块)的名字暗示了RPMB是能抵御重放攻击的。那么RPMB是怎么对付重放攻击的呢?
UFS维护了一个写计数(Write Counter),它的初始值为0。UFS设备每次成功处理完一个RPMB写命令,写计数加1.主机在往设备写入数据前,获得该计数。然后把用户数据和该计数一起做MAC计算。这样,即使恶意攻击者窃听到某次合法的“用户数据+MAC”,再往设备写入时,由于写计数发生变化,它无法生成写计数改变之后的MAC值,因此就无法一直重复往设备写入某次合法的“用户数据+MAC”。
下面回到UFS RPMB协议上来。
UFS中,RPMB LU最小逻辑空间为128KB,最大为16MB。它的逻辑块大小为256Byte(普通LU逻辑块大小一般为4KB)。应用层不是通过普通的读写命令去读/写RPMB上的数据,而是通过SERURITY PROTOCOL OUT/IN命令来访问RPMB的。下表给出了普通LU和RPMB LU的对比。
RPMB LU和普通LU对比表
| 对比项 | 逻辑空间大小 | 逻辑块大小 | 访问命令 | 安全性 | 存储数据类型 |
| RPMB LU | 128KB ~ 16MB | 256Byte | Security Protocol OUT/IN | 数据认证 | 防篡改的重要数据 |
| 普通 LU | 无限制 | 一般为4KB | Read/Write | 无 | 普通用户数据 |
UFS主机在访问设备RPMB时,可通过下表所示不同消息类型完成不同功能。
RPMB消息类型列表
| 请求(Request) | 响应(Response) | 说明 |
| 认证密钥写请求 | 认证密钥写响应 | 用于写认证密钥 |
| 写计数读请求 | 写计数读响应 | 用于读取写计数 |
| 认证数据写请求 | 认证数据写响应 | 用于写认证数据 |
| 认证数据读请求 | 认证数据读响应 | 用于读取认证数据 |
| 操作结果读请求 | 无 | 读取操作结果 |
| 安全写保护配置块写请求 | 安全写保护配置块写响应 | 用于写安全写保护配置块 |
| 安全写保护配置块读请求 | 安全写保护配置块读响应 | 用于读安全写保护配置块 |
每条消息包含一条或者若干条消息数据帧。消息数据帧大小是512Byte,具体如下图所示。
从下图中我们可看到:
- 认证密钥(key)是32B。
- UFS使用SHA-256计算MAC,也就是说任意长度的数据产生的MAC值总是256位,即MAC大小为32Byte。
- 逻辑块大小为256Byte。
- 写计数(Write Counter)大小为4字节,当该值涨到0xFFFF FFFF时,它就会保持不动,不会继续增长了。
- Addres:RPMB的逻辑地址,同LBA,大小为2字节,最多表示65536个逻辑块,每个逻辑块大小为256Byte,因此RPMB逻辑空间最大为16MB。
- Block Count,逻辑块数,即指定读写多少个逻辑块。
- Result,RPMB操作结果(状态)。
RPMB消息数据帧格式
下面以“主机写认证数据”为例来帮助大家理解上面的消息。
主机命令层通过SECURTY PROTOCOL OUT命令把用户数据和对应的MAC发送给设备,然后通过SECURTY PROTOCOL OUT请求获取前面数据的写结果,最后通过SECURTY PROTOCOL IN读取写结果。写结果中包含写的写计数,这样下次主机就可以利用新的写计数计算MAC了·。注意,只有本次写认证数据成功,设备擦灰递增该计数。下图所示为写RPMB的流程。
写RPMB数据的流程
从上图中我们可以看到,和普通数据读写相比,RPMB数据读写过程非常繁琐,比如读写一次需要发若干条命令,主机和设备交互太多。另外,数据传输效率也不高,512Byte的数据帧真正有效的数据只有256Byte,其它都是元数据。基于此,UFS4.0提出了Advanced RPMB,采用4KB的数据块,提升了RPMB访问效率,简化了读写访问流程。UFS4.0规范已发布,自行参考。
RPMB提供了认证访问方式和抵御重放攻击的机制,保证了存储在RPMB LU上的数据的安全。因此,用户可以把一些敏感和重要的信息写在RPMB上。在实际应用中,它通常用于存储一些有防止非法篡改需求的数据,例如手机上指纹支付相关的公钥、序列号等敏感信息。
10.6 UFS低功耗简介
UFS作为移动存储设备,对功耗要求很高,尤其是对低功耗的要求,因为移动存储设备大多数时候都处于空闲状态。本节将简单介绍一下UFS设备的低功耗管理。
前面提到,UFS协议采用MIPI的M-PHY作为物理层和UniPro作为数据链路层。M-PHY有高速模式(High Speed Mode,HS-MODE)和低速模式(Low Speed Mode, LS-MODE)。其中,高速模式下,M-PHY有2种状态——HS-BURST和STALL,其中HS-BURST表示高速数据传输状态,STALL表示没有数据传输时链路所处的一种省电状态。低速模式下,M-PHY有3种状态——LINE-CFG、SLEEP和PWM-BURST。其中PWM-BURST表示低速数据传输状态,SLEEP表示没有数据传输时链路所处的一种省电状态。
当链路上没有数据传输时,M-PHY会自动(无需主机或者设备上层干预)切换到STALL或者SLEEP状态,以达到省电的目的。HS-BURST和STALL、PWM-BURST和SLEEP之间的状态切换时间为纳秒级别。除此之外,M-PHY还有一种更加省电的状态——HIBERN8(Hbernate,休眠状态)。UFS主机和UFS设备不可能一直交互数据,总有闲下来的时候。和STALL和SLEEP状态不同的是,进入HIBERN8状态需要主机的干预;当UFS主机一段时间没有访问UFS设备时,它会发命令让彼此(主机和设备)的链路进入休眠状态,即HIBERN8状态。
那UFS主机如何通知M-PHY切换到休眠状态呢?
前面提到,设备管理器可以略过传输层,直接管理与控制互连层(UIC,即UniPro和M-PHY)。如
下图所示,主机设备管理器可以通过原语直接与UFS互连层通信。除了图中所示的重启互连层/设备的原语外,UFS还包括让UIC进入和退出休眠状态的原语——DME_HIBERNATE_EXIT(退出休眠状态)。
当设备收到主机发来的Hibernate进入命令时,它就明白在一段时间内主机不会访问设备,因此,在这段时间内,UFS设备可以进入低功耗模式;比如把当前UFS设备的软硬件上下文保存到闪存,然后切断设备绝大多数模块的电源以达到省电的目的;当UFS设备收到Hibernate退出命令时,UFS重新给这些模块上电,并把软硬件上下文加载回到控制器内存继续运行。
注意,UFS设备进入低功耗的时候,不是切换对所有模块的供电(否则,设备就没有办法接收到HIbernate退出命令),而是切断主要模块的供电,比如闪存模块的供电以及UFS控制器绝大多数模块的供电。为做到这一点,这就要求控制器采用Power Domain的设计,即控制器主要模块处于不同的Power Domain中,它们的供电是独立的,关闭某个模块的供电,不会影响到其它模块的供电。
以下图所示为例,某个控制器有3个Power Domain(不用纠结每个Power Domain里具体的模块是什么),当UFS设备进入低功耗的时候,它把耗电大户Power Domain3(比如CPU、RAM、LDPC模块等)的供电切断,是否切断Power Domain2的供电是可选的,保持Power Domain 1不断电(用于响应主机发来的Hibernate退出命令)。
Power Domain的概念
前面提到,UFS设备进入低功耗状态的时候,需要把设备软硬件上下文保存到闪存,退出的时候再把它们加载回来。相关数据表明,在典型手机应用中,一个UFS设备每天接收的Hibernate命令有几十万甚至上百万个,如果每次进入低功耗的时候都要写闪存,会加速闪存磨损,从而影响UFS设备寿命。另外,与访问内存相比,访问闪存是比较耗时的操作,如果把上下文保存到闪存,会影响Hibernate进入和退出的延时,这一方面会影响系统功耗(UFS设备如果能快点进入低功耗状态,南那么整个移动设备也可以尽快进入低功耗状态),另一方面会影响用户体验。UFS设备低功耗处理追求的是“快进快出”,小于1ms的低功耗退出延时也是业界追求的目标。为此,现在的控制器一般都提供比较大的保持内存(Retention RAM,如上图 Power Domain 2中所示),它可以在比较低的功耗保持数据。UFS设备进入低功耗的时候,上下文数据可以保存在保持内存,从而减少或者避免对闪存的访问,这对UFS设备寿命和用户体验来说都是一个好选择。
10.7 WriteBooster
10.8 HPB