某个项目使用的介质是 spinor
, 其 bootloader
需要从 flash
中加载 os
。
启动速度是一个关键指标,需要深入优化。其他部分的优化暂且略过,此篇主要记录对 nor
读速度的优化过程。
接到启动速度优化的任务之后, 首先是了解情况。
当前的 bootloader
实测读速度只有约 4M/s
。
为了加快速度已经尝试过
spinor
驱动改为使用四线读命令读取数据。速度并没有明显改善。待确认改动是否生效。spinor
驱动改为使用 dma
搬运数据。尚未修改成功。既然是要深入优化,那知道终点在哪还是很有必要的。
整个读取过程,数据主要是从 spinor
到达 soc
的 spi
控制器,再由 cpu
或 dma
搬运到 dram
中的目标位置。
spinor --> spi控制器 --> cpu/dma --> dram
先来考虑第一段的速度,这里比较好计算。针对当前的 soc
和 flash
的组合,从规格书可得到最高的 spi
时钟频率为 100M = 100 * 10^6
,且读数据可使用 4
线读取,即 soc
和 flash
之间有 4
根数据线在并行传输数据。那么简单算下 100 * 10^6 * 4bit = 400 * 10^6 bit/s = 47.68 MB/s
, 可知极限速度为 47.68 MB/s
。
当然较真一点,发送读命令给 flash
也需要时间,让我们来算下。
一般一个读命令需要 5 bytes
, 即 cmd + addr[3] + dummy
,所以实际的极限速度要考虑每发一次读命令后读取多少数据。读命令是单线传输的,数据是四线传输。假设发一次命令读 nbytes
数据,则命令和数据所占时间的比例为 5:(n/4)
, 那么实际用于传输数据的 clk
就只有 (n/4) / (5 + n/4) * 100M
。4
线传输的情况下每个 clk
可传输 4bit
,从 bit
换算成 byte
再除以 8
,于是速度公式为 (n/4) / (5 + n/4) * 100M * 4 / 8
, 这里要注意对于大小 1MB = 1024*1024 Byte
, 对于时钟 100M = 10^6
。
代入一些具体数据可得
每次读取bytes | 读速度 |
---|---|
64 | 36.33 MB/s |
256 | 44.23 MB/s |
1024 | 46.77 MB/s |
64k | 47.67 MB/s |
1M | 47.68 MB/s |
可以看出,如果每次读取数据量较小,那么发送读命令消耗的时间就不可忽视。每次读取的数据量越大,则读命令对速度造成的整体影响就越小。
后面的部分理论速度暂时没有很明确的计算方式,那暂时先知道完整的数据是这么流动的就可以了。
看了下驱动打印出来的确实是 80M
的 clk
和 4
线的读命令,虽然还没调到最高时钟 100M
,但当前 4M/s
也完全对不上,到底是谁出了问题呢?时钟不对?四线没配置成功?驱动存在 bug
? nor flash
物料的问题?
思考一下,要确认到底是谁的锅,最简单明了的方式还是量下波形,不管软件驱动上怎么写,控制器的寄存器怎么配,最终还是得反映在波形上才是最真实的传输效果。接上示波器或逻辑分析仪,看看 spi
线上的情况,是谁的问题就一目了然了。
首先量一下 spi clk
线,可以发现读数据的过程中,clk
的信号不是连续的,在有信号时其频率是正常的,但大部分时间 clk
线上却是没有信号的。再量量数据线,可以确认到确实使用了 4
线读。
问题很明显,spi
控制器是在间歇性读数据,所以虽然读 nor
的时候是 80M
的时钟频率进行读取,但把 spi
的空闲时间计算进去,均摊下来的总的速度就只有 4M/s
了。
那为什么 spi
控制器会间歇性读取而不是一直在读取呢? 这就涉及到刚刚所说的数据流了,spi
控制器本身的 fifo
是有限的,当从 spinor
读取的数据填满 fifo
之后,就必须等着 cpu/dma
把数据取走,腾出 fifo
空间来,才能继续发送指令从 nor
取数据。那么这段空闲时间,应该就是在等 cpu/dma
取数据了。
有了怀疑方向,那就得看下代码了。目前驱动中使用的是 cpu
来搬运数据,正常读取过程中,cpu
在执行以下代码
while 待读取数据计数值大于0
if (查询spi寄存器,判断到fifo中存在数据)
读取spi fifo寄存器数据,写到dram的buffer中
待读取数据计数值减1
如果是这里成为了瓶颈,那就有两个地方比较有嫌疑,一是读取 spi
寄存器,而是写 dram
。
做点实验确认下
实验一,尝试下把写 dram
的操作去掉,使用如下操作,并由读取前后的 log
时间戳来判断耗时。
while 待读取数据计数值大于0
if (查询spi寄存器,判断到fifo中存在数据)
读取spi fifo寄存器数据
待读取数据计数值减1
重新测试下,发现速度没有明显变化。
实验二,尝试下减少读 spi
寄存器的操作
while 待读取数据计数值大于0
读取spi fifo寄存器数据
待读取数据计数值减1
重新测试下,发现读速度翻倍了,达到了 8M/s
,看来果然是这里成为了瓶颈。没想到 cpu
读个 spi
寄存器竟然这么耗时。
cpu
太慢,那就指望 dma
了。
先来解决 dma
驱动异常问题,了解下情况,原来这个 dma
驱动的支持是从另一个分支上移植过来的,原本工作正常,到了这个分支就翻车了。
这就是一个找不同的问题了,先比较下两个分支的差异,再将可疑的地方 checkout/cherry-pick
到另一个分支来验证。很快找到了关键因素 dcache
。
这个分支上是默认打开了 dcache
的,可见旧文 记一个bootloader的cache问题,而这就导致了 dma
驱动工作异常。
简单点,关掉 dcache
试试,果然 dma
就正常了。测下速度,达到了 21M/s
。
再测试下不关 dcache
,在配置了 dma
描述符之后,刷一次 cache
再启动 dma
传输,也是正常的了。
21M/s
的速度,看来瓶颈还是在 dma
这里。此时可以尝试将 spi clk
从 80M
提高到 100M
,可以发现整体读速度没有变化,这也可以佐证当前瓶颈仍然不在 nor
的读取速度上面。
测个波形看看,果然 clk
线上还是间歇性的,不过空闲时间比之前少了很多。
dma
的速度能不能改进呢? 这就涉及到具体的芯片了,需要深究下 dma
控制器和 spi
控制器的配置。
优化 dma
和 spi
控制器的配置后,dma
从 spi
控制器取数据的速度,终于超过了 80M
时钟下的 spinor
读取速度,将 spi clk
修改为 100M
,测得读速度约 36M/s
。
前面说到,发送读命令给 flash
也需要时间,在 os
中受限于 buffer
大小等,可能会限制每次读取和处理的数据量,但对于 bootloader
来说则完全可以一口气将所需的数据读入,无需分段。
另外可查看驱动中是否有无用的清空 buffer
之类的操作,一并优化掉。
驱动逻辑和寄存器上无法优化之后,还想提速,那么可以试试提高时钟。
提高 cpu
时钟和 dma
时钟,提高后测得速度约 47M/s
,基本就是理论极限了。
但具体能否提高时钟还是得谨慎评估,单个板子可以正常运行,不意味能够稳定量产。
在读取速度无法进一步优化的情况下,要提高启动速度,那就得减少读入的数据量了。
可以评估下使用压缩的镜像来减少读入的数据量,只要多出的解压时间不长于节省掉的读取 flash
时间,那就是划算的。
是否压缩,选择哪种压缩算法,就跟 io
速度,cpu
解压速度直接相关了,最好经过实测确认,综合启动速度和 flash
占用来选择。
如果是带压缩的镜像,那启动速度就是读取时间+解压时间。
读取时主要是 io
操作,解压则主要是 cpu
操作。那么是否有可能实现边读取边解压,使得总的启动时间进一步缩短呢?这个待研究