UFS特性介绍
UFS发展史
在手机飞速发展的近 10 年,巨大的市场需求催生出移动存储的快速发展,人们需要越来越来越多的空间和越来越快的存储速度。下图是一个俗称存储 8+256G 的手机模块连接示意图。
图 1 手机存储连接
JEDEC在2011推出了UFS1.0协议标准,从2015开始手机厂家开始陆续商用UFS2.0的存储器件,Samsung,Toshiba,Hynix等传统存储器件率先推出自己的UFS存储器件。现在,UFS器件已经被各大手机厂商广泛采用,那么UFS具备哪些优势,有哪些特性,具体工作流程是什么,本文将逐一描述。
和传统eMMC相比,UFS采用了MPHY协议,这个是UFS底层物理信号传输协议,MPHY是一个MIPI Alliance推出的PHY协议的一种。这个MPHY的传输特点就是两个字,“快”和“稳”,MPHY通过串行+差分的形式保证了这一点,串行保证了“快”,串行信号能做到高频传输,因为没有并行信号的相互干扰,而“稳”则由差分保证,差分传输可以相互抵消共模信号干扰。
MPHY
如下图所示,信号具有单向,差分的特点,TXDP和TXDN分别表示发送端TX的差分管脚的positive和negative。目前UFS采用的是2lane(但是如果因为焊点等物理断开,只剩下一个lane,lane0,UFS也可以正常工作),类似于双向4车道,但是不能变道,也不能逆行,lane之间是相互独立工作的。下图属于1.5lane。需要注意的是这里一个通道就标注为1个lane,而UFS里面通常叫法是1个lane包含两个方向的数据方向,即TX->RX, RX->TX。
图 2 MPHY的底层链路传输示意图
通过驱动控制TX两个差分点TXDP/TXDN的电压,以及对端RX接收机RXDP/RXDN的阻抗,形成如下的4种差分线状态:
图 3 差分线组成的四种状态
同过控制上图4种line state的保持时间,可以实现TX和RX的状态机切换,以实现功耗和性能的平衡。从下面的状态机可以看到,可以将MPHY的功耗分为4个层次:POWERED,HIBERN8,STALL/SLEEP,BURST。手机在进行大量数据传输时,将处于HS-BURST状态,如果LINE被驱动成DIF-N,并且持续20UI(HS)的时间,那么就会进入STALL模式。这里的UI是Unit Interval,即在传输模式下的20个单位间隔。
图 4 UFS采用的TypeII M-TX状态机切换
在HS-BURST模式下,又分为1~4挡,和汽车的挡位一样,每个挡位对应不同的速度,MPHY对应的不同挡位的传输速率如下图所示。从UFS3.0增加了HS-G4,之前最高速挡只有到HS-G3。这里的传输是指1个lane的传输速率,实际上UFS有2个lane,带宽是应该是double一下的,目前各厂家基本采用rateB,那么理论上带宽11660.8Mbps * 2 = 2915.2MB/s,这里有个细节需要注意一下,为了避免连续的高电平或低电平出现,MPHY上面传输的数字都是经过特殊处理的,目前UFS采用的是8b/10b编码的方式,所以这里的有效理论带宽需要打8折,即2915.2MB/s * 0.8 = 2332.16MB/s。
图 5 MPHY HS-BURST各Gear速率
2332.16MB/s 这个带宽已经很高了,不过现在UFS3.0的器件已经测出2279.8MB/s的性能,已经很接近这个理论带宽了,当然器件的性能也会推动协议的演进,比如后面可能会推出HS-G5,8b/10b的编码方式变为效率更高的128b/130b。
图 6 某UFS3.0器件的androbench跑分数据
MPHY是个很复杂的模拟IP,目前市场基本被synopsys公司占领,各UFS器件厂家都从它那里购买这个IP,里面涉及到串行并行转换,编解码,信号放大,CDR时钟恢复,采样,以及上面的HS-G4还是HS-G1的挡位选择,那么MPHY是怎样被控制的呢?如下3个寄存器就是MPHY私有的寄存器,分别用来控制模式,rate,挡位。
图 7 UFS速度控制相关MPHY的寄存器
Unipro
和所有的OSI(Open Systems Interconnection) model一样,UFS采用了和OSI能够对应上的模型,即Unipro(Unified Protocol),Unipro和MPHY一样,也来源于MIPI alliance,目前UFS上面采用的是Unipro Version 1.8。
图 8 Unipro定义的层次关系
Unipro定义了各层package的格式,从下往上看,Medium层传输的是差分信号,PHY Adaptor(L1.5)传输的是PACP frame,LA应用层传输的就是UPIU(UFS protocol information units),我们通用的读写请求就会被打包进UPIU里面,然后自上而下层层传递,最后通过差分信号传递给UFS器件,而器件里面拥有和HOST完全对等的层次关系和协议格式,从而实现数据通信。
DME(Device Management Entity)对Unipro的所有层次进行控制和管理,提供各层次所有的寄存器的访问接口。这个接口通过UIC实现,Unipro各个层次的寄存器都是通过UIC来访问的。那么统一的UIC接口是如何实现各层次寄存器路由的呢,就是通过下面的LayerID实现的,通过寄存器address的bits【14:12】做掩码。
图 9 Unipro各层寄存器地址路由
所以可以直接从寄存器地址来判断这个寄存器是Unipro的哪一个层次的寄存器,比如,MPHY层的寄存器地址都是0xXX,而PA层的地址是0x15xx,DME层本身的地址是0xDxxx。
当然,MPHY的寄存器地址有点特殊,是因为MPHY里面有多个lane的时候,需要通过其他的mask来进行区分,目前是通过在最低位的0x0,0x1,0x4,0x5来进行区分的,分别表示TX的lane0,lane1,RX的lane0,lane1。
UFSHCI
Unipro层是封装在UFS这个IP里面的,其中的寄存器也是CPU不能直接访问的。UFS整个模块对外的唯一接口就是UFSHCI(UFS Host Controller Interface)。CPU通过两条总线来实现对UFS的控制和数据交互,分别是APB和AXI总线。在下图中,左边的方框就是UFSHCI,通过APB总线访问,其地址空间通过ioremap在内核申请虚拟地址空间即可被CPU直接访问,右边方框则是数据部分,由dma_alloc接口进行申请,通过AXI进行访问。
图 10 UFSHCI经典数据结构
这个数据结构图覆盖了如下信息:
a.左框UFSHCI寄存器分布,分为6块,host Controller Capability ~ Vendor Specific
b.UTP Transfer Request这一块的寄存器提供了指向UTRD (UTP Transfer Request Descriptor)List的指针地址
c.UTRD有0~31个,即32个,意味着UFS拥有32个slot,SOC层可以同时接受32个请求
d.32个UTRD是物理连续的,这点很重要,因为连续,可以直接根据slot序号直接获取到UTRD
e.UTRD里面包含了UPIU的地址,UPIU的size是固定的,否则AXI怎么能准确的把response信息填充到对应请求的Response UPIU里面呢
f.同样Response UPIU数据格式固定
g.如果UTRD里面有数据读写需求,那么需要填充PRDT(Physical Region Description Table)
h.PRDT指向的Data buffer不是连续的,而是离散状,因为PRDT是个表,所以这个表里面可以存放很多不连续的Data buffer地址,对应一个个的segment
i.1个Data buffer的size最大不能超过64K,这个是由芯片内部决定的,因为这个size涉及到一个数据包单元DATA OUT/IN UPIU的长度,也是一次底层数据包传输的长度,但是PRDT表的个数可以自己确定,一般软件定义为128个,那么一个UTRD也就是一个请求可以最大传输64K * 128 = 8M
j.在实际应用中一个读写请求如果size过大,如1000M,则会被上层切割,比如1M,所以底层的size是足够的
k.除了32个UTRD外还有8个UTMR,顾名思义,这个是任务管理请求,对上面的32个请求的状态进行查询,如果说32个UTRD理解为工人,这8个UTMD可以理解监工,查询是否工作完成,如果工作在预期时间内没有完成的也可以执行abort操作,然后重新下发请求或者进行复位器件等相关的异常处理。
图 11 UFSHCI常用寄存器
上面的HCI寄存器cpu可以通过APB总线直接读写,而Unipro的寄存器则可以通过上面的4个UIC Command寄存器完成,分别是UICCMD,UCMDARG1, UCMDARG2,UCMDARG3。寄存器就是芯片的API接口,通过这4个UIC寄存器就可以实现对Unipro各层和MPHY的所有寄存器进行读写操作。
初始化流程
图 12 UFS初始化流程
函数 |
主要功能 |
ufs_qcom_probe |
platform_driver probe,高通平台驱动入口 |
ufshcd_pltfrm_init |
申请IO空间,获取中断号,申请hba资源 |
ufshcd_init |
完成数据结构初始化,注册中断,申请和配置UFS controller所需要的DMA内存,初始化并使能UFS controller,复位器件 |
ufshcd_async_scan |
异步初始化函数 |
ufshcd_probe_hba |
Linkup建链,获取器件信息,注册scsi |
总之,和通用的驱动初始化流程一样,ufs驱动的初始化也主要包含资源申请,controller初始化和使能,建立链接,完成中断注册,注册中断上半部和中断下半部处理函数。
申请好的两端dma内存写进UFSHCI寄存器,controller最后通过这两个内存地址完成全部的数据交互过程,其原理就是base + index * unit 的索引方式:
UTRD,UCD的request,response,prdt物理地址数据结构填充:
数据传输路径
数据传输是驱动程序的本质目的,通过数据的传输,来完成作为存储介质的使命,read & write,在read流程中,ufs向应用程序提供数据,在write流程中,应用程序向ufs存放数据。
本节分三个阶段关注数据的流向,分别是:系统调用数据到bio的组成,bio到电梯队列request的组成,request到controller的数据传输。主要围绕buffer和存储地址两个数据流要素,根据读流程描述各阶段这两个数据流要素的数据结构。
图 13 sys_read->submit_bio数据流流程
如上图所示,用户态传入fd, buf, count的参数被传入到kiocb和iov_iter里面,kiocb用来传递磁盘地址,而iov_iter用来传递buf。
iov_iter用来从用户和内核之间传递数据用,通过迭代的方式可以实现物理地址或者虚拟地址不连续的描述:
_blkdev_direct_IO函数中从iocb传递磁盘地址bio->bi_sector,同时iov_iter传递给bio的bi_iter:
bio生成后,就plug到进程的plug_list里面,如果失败,则进电梯merge,如果依然失败,则只能等待新的request被释放出来,两次蓄流的目的是为了尽可能的让bio请求得到merge,减少往磁盘驱动下发的频度,提升综合性能。
图 14 bio->request数据流流程
当plug_list达到设定阈值或进程主动发起或设定时间已到或手机将要关机等,则会进行泄流。从elevator的queue里面取出一个request请求,下发到底层UFS驱动,这个流程从request转化为scsi协议包,最后封装在UFS的协议包UPIU包里面,于是就完成了一个请求的host内存准备,最后通过置这个请求对应的UFSHCI的doorbell寄存器中的mask,实现请求往驱动层的发送。
图 15 request->dma数据流流程
__blk_segment_map_sg实现bio到request里面的sglist的数据填充:bio里面的bv_page是将要进行读的buffer地址,这个地址被挂载到sg链表,sg操作将会对应UFS里面的PRDT,这时候的bv_page为DMA可以直接访问的物理地址。
对于存储器件的读写操作,通过传递磁盘地址和buffer,UFS根据磁盘地址读取到数据后填充到buffer,或UFS从buffer获取数据写入磁盘地址。写的流程和读原理类似,这里就不再描述了。
参考资料
[1]《mipi_M-PHY_specification_v4-1_JEDEC-LIAISON-DISCLOSURE》
[2]《JESD223D》
[3]《JESD220C》