在手机飞速发展的近10年,巨大的市场需求催生出移动存储的快速发展,人们需要越来越来越多的空间和越来越快的存储速度。下图是一个俗称存储8+256G的手机模块连接示意图。
图1手机存储连接
JEDEC在2011推出了UFS1.0协议标准,从2015开始手机厂家开始陆续商用UFS2.0的存储器件,Samsung,Toshiba,Hynix等传统存储器件率先推出自己的UFS存储器件。现在,UFS器件已经被各大手机厂商广泛采用,那么UFS具备哪些优势,有哪些特性,具体工作流程是什么,本文将逐一描述。
和传统eMMC相比,UFS采用了MPHY协议,这个是UFS底层物理信号传输协议,MPHY是一个MIPIAlliance推出的PHY协议的一种。这个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。
图2MPHY的底层链路传输示意图
通过驱动控制TX两个差分点TXDP/TXDN的电压,以及对端RX接收机RXDP/RXDN的阻抗,形成如下的4种差分线状态:
图3差分线组成的四种状态
图4UFS采用的TypeIIM-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。
图5MPHYHS-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,挡位。
图8Unipro定义的层次关系
Unipro定义了各层package的格式,从下往上看,Medium层传输的是差分信号,PHYAdaptor(L1.5)传输的是PACPframe,LA应用层传输的就是UPIU(UFSprotocolinformationunits),我们通用的读写请求就会被打包进UPIU里面,然后自上而下层层传递,最后通过差分信号传递给UFS器件,而器件里面拥有和HOST完全对等的层次关系和协议格式,从而实现数据通信。
DME(DeviceManagementEntity)对Unipro的所有层次进行控制和管理,提供各层次所有的寄存器的访问接口。这个接口通过UIC实现,Unipro各个层次的寄存器都是通过UIC来访问的。那么统一的UIC接口是如何实现各层次寄存器路由的呢,就是通过下面的LayerID实现的,通过寄存器address的bits【14:12】做掩码。
图9Unipro各层寄存器地址路由
所以可以直接从寄存器地址来判断这个寄存器是Unipro的哪一个层次的寄存器,比如,MPHY层的寄存器地址都是0xXX,而PA层的地址是0x15xx,DME层本身的地址是0xDxxx。
当然,MPHY的寄存器地址有点特殊,是因为MPHY里面有多个lane的时候,需要通过其他的mask来进行区分,目前是通过在最低位的0x0,0x1,0x4,0x5来进行区分的,分别表示TX的lane0,lane1,RX的lane0,lane1。
Unipro层是封装在UFS这个IP里面的,其中的寄存器也是CPU不能直接访问的。UFS整个模块对外的唯一接口就是UFSHCI(UFSHostControllerInterface)。CPU通过两条总线来实现对UFS的控制和数据交互,分别是APB和AXI总线。在下图中,左边的方框就是UFSHCI,通过APB总线访问,其地址空间通过ioremap在内核申请虚拟地址空间即可被CPU直接访问,右边方框则是数据部分,由dma_alloc接口进行申请,通过AXI进行访问。
图10UFSHCI经典数据结构
这个数据结构图覆盖了如下信息:
a.左框UFSHCI寄存器分布,分为6块,hostControllerCapability~VendorSpecific
b.UTPTransferRequest这一块的寄存器提供了指向UTRD(UTPTransferRequestDescriptor)List的指针地址
c.UTRD有0~31个,即32个,意味着UFS拥有32个slot,SOC层可以同时接受32个请求
d.32个UTRD是物理连续的,这点很重要,因为连续,可以直接根据slot序号直接获取到UTRD
e.UTRD里面包含了UPIU的地址,UPIU的size是固定的,否则AXI怎么能准确的把response信息填充到对应请求的ResponseUPIU里面呢
f.同样ResponseUPIU数据格式固定
g.如果UTRD里面有数据读写需求,那么需要填充PRDT(PhysicalRegionDescriptionTable)
h.PRDT指向的Databuffer不是连续的,而是离散状,因为PRDT是个表,所以这个表里面可以存放很多不连续的Databuffer地址,对应一个个的segment
i.1个Databuffer的size最大不能超过64K,这个是由芯片内部决定的,因为这个size涉及到一个数据包单元DATAOUT/INUPIU的长度,也是一次底层数据包传输的长度,但是PRDT表的个数可以自己确定,一般软件定义为128个,那么一个UTRD也就是一个请求可以最大传输64K*128=8M
j.在实际应用中一个读写请求如果size过大,如1000M,则会被上层切割,比如1M,所以底层的size是足够的
图11UFSHCI常用寄存器
上面的HCI寄存器cpu可以通过APB总线直接读写,而Unipro的寄存器则可以通过上面的4个UICCommand寄存器完成,分别是UICCMD,UCMDARG1,UCMDARG2,UCMDARG3。寄存器就是芯片的API接口,通过这4个UIC寄存器就可以实现对Unipro各层和MPHY的所有寄存器进行读写操作。
图12UFS初始化流程
函数
主要功能
ufs_qcom_probe
platform_driverprobe,高通平台驱动入口
ufshcd_pltfrm_init
申请IO空间,获取中断号,申请hba资源
ufshcd_init
完成数据结构初始化,注册中断,申请和配置UFScontroller所需要的DMA内存,初始化并使能UFScontroller,复位器件
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存放数据。
图13sys_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,减少往磁盘驱动下发的频度,提升综合性能。
图14bio->request数据流流程
图15request->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》