博通机顶盒平台启动时间优化(一)CFE启动时间优化
博通机顶盒
BCM97583
平台上,默认不做裁剪和优化的情况下,从上电CFE启动到进入
Linux
命令行,这个过程大约需要4.48S。通过一系列功能裁剪和代码优化,使得从上电到进入
Linux
命令行在1S以内。
环境:
- 硬件平台:
BCM97583
CFE
版本:bcm97583 cfe v3.7
Linux
版本:stblinux-3.3-4.0
(linux 3.3.8
的定制版本)
启动时间优化分为两部分:
优化的核心是分析启动过程中的时间消耗,分析中细化并落实到到各个阶段的各项功能消耗的具体时间(这个过程也叫做break down),在此基础上对这些功能逐个进行优化(optimization),通过这两步操作,从而在整体上达到启动时间大幅减少的效果。
本文详细描述
CFE
启动时间优化过程,从原来的2677ms缩短到652ms。
下一篇着重描述
linux
启动过程的优化。
1. 串口日志分析工具
CFE
是博通的专用
bootloader
,由于是启动引导程序,应用程序调试的环境还不具备,不能进行软件模拟,也没有
gdb
,所以调试功能比较有限,唯一能做的就是通过串口在各个阶段开始和结束时通过打印输出信息来计算所耗费的时间。
启动时间优化的第一步就是抓取日志,以分析启动过程中每一部分消耗的时间。
推荐使用
SecureCRT
或
grabserial
来抓取日志,这两个工具都带有记录日志时间戳的功能。
SecureCRT
SecureCRT
基于Windows
,操作直观,设置也很方便。根据SecureCRT
的帮助文档,其记录的时间能够精确到毫秒(ms)级别。
抓取日志到达时间的设置请参考文档:
SecureCRT使用之自动记录日志功能
grabserial
grabserial
是用Python
开发的基于Linux
的命令行工具,用于串口日志的记录和时间分析。
安装和使用说明请参考文档:Grabserial
相比之下,
SecureCRT
记录日志比较方便,但
Grabserial
记录的时间更精确,这里采用
Grabserial
,直接从
Grabserial
官方下载
2016-09-30
的版本
v1.9.4
。
2. CFE启动时间分析
默认情况下,在启动的第一阶段会打印很多芯片或内存相关的测试信息,这些信息一般没有什么作用,生产中通过编译时设置
CFG_PRODUCTION_FSBL ?= 0
来关闭这些打印信息。
以下是关闭测试输出后完整的启动
log
信息:
完整的启动log信息
设置
CFG_PRODUCTION_FSBL ?= 0
后的打印信息太少,需要在启动的各个阶段开始和结束处添加额外的打印信息来统计各个阶段花费的时间,以下是添加打印后的启动
log
,并附加了一些注解:
[0.000001 0.000001]
[0.453170 0.453169] BCM75840001
[0.000861 0.000861]
[0.000962 0.000101] A
*[0.505464 0.504502] M00
*[0.526440 0.020976] XC
*[0.583520 0.057080] RZS
[0.584834 0.001314] L2=1
[0.585469 0.000635] LLMB=1
[0.586124 0.000655] BrCfg=E30FB7C6
[0.587157 0.001033] #@
[0.594957 0.007800] set cpu speed
*[0.721160 0.126203]
[0.721219 0.000059]
[0.721256 0.000037] BCM97583A1 CFE v3.7, Endian Mode: Little
[0.724848 0.003592] Build Date: Tue Nov 22 18:10:22 CST 2016 (ygu@fs-ygu.corp.ad.broadcom.com)
[0.731710 0.006862] Copyright (C) Broadcom Corporation.
[0.735099 0.003389]
[0.735178 0.000079]
[0.735226 0.000048] nand read disturb
*[1.465000 0.729774]
[1.465077 0.000077] CPU speed: 742MHz
[1.467367 0.002290] DDR Frequency: 928 MHz
[1.470071 0.002704] DDR Mode: DDR3
[1.472826 0.002755] Total memory(MEMC 0): 512MB
[1.474949 0.002123] MEMC 0 DDR Width: 16
[1.477617 0.002668] Boot Device: NAND
[1.479828 0.002211] Total flash: 512MB
[1.482542 0.002714] RTS VERSION: Initial RTS Version
[1.486150 0.003608] ETH0_PHY: INT
[1.488365 0.002215] ETH0_MDIO_MODE: 1
[1.490519 0.002154] ETH0_SPEED: 100
[1.493132 0.002613] ETH0_PHYADDR: 1
[1.495420 0.002288]
[1.495470 0.000050] Initializing USB.
* [1.527454 0.031984] USB: Locating Class 09 Vendor 0000 Product 0000: USB Root Hub
[1.532507 0.005053]
[1.533027 0.000520] CFE initialized.
[1.534594 0.001567] Starting splash screen.
[1.603471 0.068877] Found splash image - Width = 427 Height = 343
[1.607809 0.004338] Non Interlaced Replace list 043e8680 0c800000Interlaced Replace list 043e8c20 0c8005a0
*[1.626344 0.018535] splash end
[1.627179 0.000835] Executing STARTUP...Loader:elf Filesys:raw Dev:flash0.kernel File: Options:root=/dev/mtdblock0 rootfstype=squashfs quiet
[1.638027 0.010848] Loading: 0x80001000/5989376 0x805b7400/110224 Entry address is 0x8045f360
[2.678788 1.040761] Starting program at 0x8045f360
[2.681771 0.002983]
[2.682318 0.000547] Linux version 3.3.8-4.0 (ygu@fs-ygu.corp.ad.broadcom.com) (gcc version 4.5.4 (Broadcom stbgcc-4.5.4-2.9) ) #4 SMP Tue Nov 22 17:22:09 CST 2016
...
从上面
log
中可见,
CFE
的第一条信息
BCM75840001
到最后一条信息
Starting program at 0x8045f360
共耗时2678.788ms:
[0.000001 0.000001]
[0.453170 0.453169] BCM75840001
...
[2.678788 1.040761] Starting program at 0x8045f360
...
在
CFE
跳转到
linux
前,所有耗时超过10ms的地方都在每行的开始做了’*’来标记,主要耗时的地方总结如下:
序号 |
功能名称 |
用时(ms) |
1
AVS (avs_start)
504.502
2
Shmoo (memsys_init + run_shmoo)
20.976
3
复制ssbl
57.08
4
设置CPU速率 (GetMIPSFreq)
126.203
5
扫描nand建立坏块表 (READ_DISTURB)
729.774
6
初始化USB
32.034
7
读取图片并显示 Splash
91.750
8
读取并解压缩kernel文件
1040.761
-
1 ~ 8用时
2603.080
-
其余用时
75.708
-
CFE总计用时
2678.788
在整个CFE的3594.550ms中,1~8项占用了3522.299ms,尤其是第8项,用时1959.605ms。显然,如果能将这8项进行优化,整个过程将取得不错的效果。
为了对启动时间进行大幅缩减,有如下几个措施:
- 裁剪功能和模块
- 动态计算参数改为静态编码
- 用BBT建立坏块表而不是扫描整个nand读取坏块标记
- 改进nand的读写速率
- 尝试用非压缩kernel(评估解压缩kernel消耗的时间和读取非压缩kernel的时间)
- 关闭打印输出,减少串口占用时间
接下来将根据以上的这几个措施,对CFE启动时间实施优化。
3. CFE启动时间优化
3.1 裁剪功能和模块
- 关闭AVS, USB,Network和Splash
在
Makefile
中通过如下设置关闭:
CFG_AVS_ENABLE ?= 0
...
CFG_USB ?= 0
CFG_ENET ?= 0
...
dram scramble
用于将内存加扰,防黑客攻击。如果打开这项功能,则内存中的所有数据在读写时都会通过内存加扰器进行处理。
shmoo
用于内存初始化时进行一些测试,根据当前的环境调整内存参数。
可以根据需要,决定是否需要启用dram scramble和shmoo功能,从优化启动时间的角度,我们这里关闭
dram scramble
。(在我所用的板子上关闭shmoo会引起内存不稳定导致板子重启,所以没有关闭)
reset.s
中调用
init_serial
初始化串口,在进入
ssbl
以后再次调用
init_serial
初始化串口,这里可以去掉对串口的第二次初始化。
board_test_wraparound
函数用于检测DDR配置是否产生了
wrap around
,正确配置DDR后这个函数显得比较冗余,可以去掉这个函数。
3.2 动态计算参数改为静态编码
CFE启动时通过GetMIPSFreq函数来获取CPU在1/8秒内的计数值,用于CFE自己的时间系统计算(例如用于CFE的Ticks和睡眠时间的计算)。
可以在系统正常启动时记录通过GetMIPSFreq设置的数值,并将这个值硬编码给cfe_cpu_speed从而裁剪这部分不需要的时间,如下:
void set_cpu_speed(void)
{
#if 0
cfe_cpu_speed = (unsigned int)( GetMIPSFreq() * 27 * 1000 / 0x337F98 * CPUCFG_CYCLESPERCPUTICK) * 1000;
#else
cfe_cpu_speed = 742504000;
#endif
}
3.3 用BBT建立坏块表而不是扫描整个nand读取坏块标记
在
BCM97584SFF
参考板上,
nand
大小为
512MB
,块大小为
128KB
,页大小为
2KB
,因此总共有4096块。
由于一些设计上的原因,主要是出于软件开源
license
的考虑,CFE坏块并没有用BBT进行管理,而是在每次开机启动第一次访问
nand
时,读取每一块的第一和第二页来获取该块的坏块状态,通过扫描整个’nand’的所有块在内存中建立坏块数组进行管理。
这里暂不考虑
license
的问题,直接查找
nand
尾部的
BBT
(
BBT
由linux
进行管理和维护),这样就只需要读取少数几块就可以创建坏块表,而不用扫描整个’nand’。
在
dev_nandflash.c
中,添加两个函数:
nand_search_bbt
用于从nand
尾部向前查找BBT
位置
nand_read_bbt
用于解析BBT
数据
详细代码如下:
static int32_t nand_search_bbt(nandflashpart_t* part);
/*
* search bbt block with pattern 'Bbt0' or '1tbB'
*
* if bbt exist, return bbt block index;
* if bbt doesn't exist, return -1;
*
*/
static int32_t nand_search_bbt(nandflashpart_t* part)
{
nandflashdev_t* softc = part->fp_dev;
uint32_t num_blocks = softc->fd_probe.flash_size/softc->fd_probe.flash_block_size;
/* not implemented yet, return the last block by default */
int32_t start_block = num_blocks - 1; /* 这里需要从倒数第一块开始,往前查找有'Bbt0'或`1tbB`的块,这里暂时直接使用倒数第一块 (默认为这块,如果有坏块,需要往前调整) */
return start_block;
}
/* The number of bits used per block in the bbt bit map on the device */
static uint8_t *bbt = NULL; /* pointer for bbt bit map in ram */
/*
* update memory bbt bit map from flash
*
*
*/
static uint32_t nand_read_bbt(nandflashpart_t* part, uint32_t bbt_block)
{
nandflashdev_t* softc = part->fp_dev;
uint32_t block_size = softc->fd_probe.flash_block_size;
uint32_t num_blocks = softc->fd_probe.flash_size/softc->fd_probe.flash_block_size;
uint32_t flash_base_address = PHYS_TO_K1b(softc->fd_probe.flash_phys);
uint32_t len, totlen, from, marker_len, offset = 0;
uint32_t i, j, res;
uint32_t act = 0;
uint8_t *buf; /* internal page buffer */
uint8_t reserved_block_code = 0; /* reserved code */
uint8_t msk = (uint8_t)((1 << NAND_BBT_NRBITS) - 1); /* mask for block status */
/* 分配1页数据用于存放nand读取结果 */
buf = (uint8_t *)KMALLOC(NAND_INTERNAL_PAGE_BUFFER_SIZE, 4);
if (buf == NULL)
{
xprintf("Out of memory!
");
return -1;
}
/* 计算存放BBT需要的字节数 */
totlen = (NAND_BBT_NRBITS * num_blocks) >> 3; /* number of bytes required for BBT */
/* 分配内存存放BBT */
bbt = (uint8_t *)KMALLOC(totlen, 4);
if (buf == NULL)
{
xprintf("Out of memory!
");
return -1;
}
memset(bbt, 0, totlen);
from = flash_base_address + bbt_block * block_size;
marker_len = 5; /* 'Bbt0' + BBT version at the beginning of BBT block */
while (totlen)
{
len = min(totlen, NAND_INTERNAL_PAGE_BUFFER_SIZE);
if (marker_len)
{
/*
* In case the BBT marker is not in the OOB area it
* will be just in the first page.
*/
len -= marker_len;
offset = marker_len;
marker_len = 0;
}
res = nand_read_page(from, buf, offset, len);
/* analyze data */
for (i=0; ifor (j=0; j<8; j+=NAND_BBT_NRBITS, act+=2) /* update bit map by byte */
{
uint8_t tmp = (dat >> j) & msk;
if (tmp == msk)
{
/* bbt[act>>3] |= NAND_BLOCK_GOOD << (act & 0x06); */
softc->fp_blk_status[act>>1].block_status = NAND_BLOCK_STATUS_GOOD;
continue;
}
/* mark all other blocks bad in CFE */
softc->fp_blk_status[act>>1].block_status = NAND_BLOCK_STATUS_BAD;
/* check reserved block */
if (reserved_block_code && (tmp == reserved_block_code))
{
xprintf("Find reserved block at %d
", act>>1);
bbt[act>>3] |= NAND_BLOCK_RESERVED << (act & 0x06);
/* update block status here! */
continue;
}
xprintf("Find bad block at %d
", act>>1);
/* Factory marked bad or worn out? */
if (tmp == 0)
{
bbt[act>>3] |= /* 0x03 */ NAND_BLOCK_FACTORY_BAD << (act & 0x06);
}
else
{
bbt[act>>3] |= /* 0x01 */ NAND_BLOCK_WEAROUT << (act & 0x06);
}
}
}
offset = 0;
totlen -= len;
from += NAND_INTERNAL_PAGE_BUFFER_SIZE; /* fixed reading shift is internal page size */
}
KFREE(buf);
buf = NULL;
return 0;
}
在nand_create_block_status_array先检查BBT并解析,如果没有找到BBT才启用原有方式在内存中创建坏块数组:
static int32_t nand_create_block_status_array ( nandflashpart_t* part )
{
...
int32_t bbt_block;
...
xprintf("Search BBT...
");
bbt_block = nand_search_bbt(part); /* 尝试查找BBT表 */
if (bbt_block != -1) /* 找到BBT并解析 */
{
xprintf("Find BBT at block %d
", bbt_block);
nand_read_bbt(part, bbt_block);
}
else /* 没有找到BBT,则采用原有方式通过扫描整个nand创建坏块数组 */
{
xprintf("Can't find BBT
");
xprintf("Update nand block status...
");
/* find the bad blocks */
for ( i = 0; i < num_blocks; i++ )
{
if ( nand_get_block_status( (flash_base_address + i * block_size), block_size, page_size, bbi_map ) == 1 )
{
softc->fp_blk_status[i].block_status = NAND_BLOCK_STATUS_BAD;
}
else
{
softc->fp_blk_status[i].block_status = NAND_BLOCK_STATUS_GOOD;
}
}
}
...
return 0;
}
3.4 改进nand读写速率
规格书上在
nand
控制器部分有描述其支持
108MHz (9.26ns)
和
216MHz (4.63ns)
两种时钟频率,且复位后默认设置为前者。
实验测试结果表明:
+ 对于
108MHz
时钟频率,读取一块大约需要
20~30ms
+ 对于
216MHz
时钟频率,读取一块大约需要
15~20ms
为什么控制器时钟提高了1倍,读取性能却没有提高1倍呢?这里主要是因为nand读取除了控制器操作外,从控制器缓存读取到内存也要花费时间,另外操作command后需要等待执行也需要时间,所以总体性能大概可以提高1/4以上。
在
reset.s
中复位上电后随即设置
nand
的时钟频率为
216MHz
:
...
romInit:
.set noreorder
/* 以下设置nand控制器的时钟为216MHz */
li a0, PHYS_TO_K1(BCHP_PHYSICAL_OFFSET + BCHP_NAND_TIMING_2)
lw a1, 0(a0)
li a2, (BCHP_NAND_TIMING_2_CLK_SELECT_CLK_216<0(a0)
nop
...
从上电CFE启动至跳转到内核,读取nand的地方主要有:
fsbl
复制ssbl
到内存(ssbl
约250KB左右,占用2个block)
ssbl
初始化读取环境变量(环境变量小于128KB,占用1个block)
ssbl
初始化读取网卡地址(网卡地址小于128KB,占用1个block)
ssbl
超找并读取BBT
(查找并读取BBT
需要读取2个左右block)
ssbl
读取splash
图像并显示(优化后不再读取splash图像)
ssbl
读取kernel
文件并解压缩(默认kernel
文件大约3200KB,占用25个block)
初步估计读取以上31个左右的block可以减少240ms左右(
108MHz
时钟时按照25ms计算,
216MHz
时按照
17.5ms
计算)
关于nand读写性能优化还有待进一步挖掘的地方,nand控制器有一些设置时序的寄存器,目前都采用默认值,没有单独进行配置,可以根据规格书上具体的数值对这些参数进行配置以达到较高的效率。
网上也有一篇关于博通nand速度优化的文章值得推荐:
NAND速度优化探索
3.5 尝试用非压缩kernel
默认启动采用压缩后的kernel,所以需要一边读取一边解压缩文件,除了读取占用时间外,解压缩也需要时间。可以尝试用非压缩kernel文件,这样在启动时就只需要读取文件,从而免去解压缩的步骤了。
启动非压缩文件同压缩文件相比,虽然免去了解压缩的步骤,但是需要从nand读取的文件增大了,所以需要评估时间上的消耗。
以下是两种情况的时间消耗比较:
- |
压缩kernel |
非压缩kernel |
大小 (Bytes)
3,250,519
7,527,065
加载启动时间 (ms)
1040.761(1)
595.109(2)
(1)1040.761是没有优化nand速度时读取并加载的时间
(2)595.109ms是经过nand速度优化后读取并加载的时间
这里时间的提升还是比较明显的,但是占用nand空间增加了,典型的空间换时间型优化。(
进一步分析表明,在启动压缩kernel时,由于是一边读取一边解压缩,CFE会多次读取kernel所在分区的第一块进行解析,浪费了较多时间)
3.6 初步结论
通过以上一些优化,虽然还有不少打印信息,但是CFE启动时间已经大幅缩短(
点这里查看初步优化后的log),主要耗时如下:
序号 |
功能名称 |
优化前用时(ms) |
优化后用时(ms) |
备注 |
1
AVS (avs_start)
504.502
0.277
实际应该为0,打印占用时间
2
Shmoo (memsys_init + run_shmoo)
20.976
21.065
时间增加0.089ms, 基本无变化
3
复制ssbl
57.08
29.668
时间减少27.412ms
4
设置CPU速率 (GetMIPSFreq)
126.203
1.171
实际应该为0,打印占用时间
5
扫描nand建立坏块表 (READ_DISTURB)
729.774
21.016
时间减少702.362ms
6
初始化USB
32.034
0
模块关闭
7
读取图片并显示 Splash
91.750
0
模块关闭
8
读取并解压缩kernel文件
1040.761
595.109
减少445.652ms
-
1 ~ 8用时
2603.080
668.306
时间减少了1934.774ms
-
其余用时
75.708
159.137
时间增加了83.429ms(1)
-
CFE总计用时
2678.788
827.443
总体时间减少了1851.345ms
(1)这部分增加的时间可能跟打印信息(如坏块信息等)输出有关,关闭打印信息后应该有所减少。
3.7 去掉打印信息
为了进一步优化时间,这里选择去掉额外的打印信息,仅保留必要的信息(上电后的第一条和离开前的最后一条)用于统计CFE启动占用的时间,最终的log信息如下:
[0.000001 0.000001]
[0.368464 0.368463] BCM75840001
[0.648537 0.648537]
[0.648718 0.000181] Starting program at 0x8045f360
[0.651475 0.002757]
[0.651856 0.000381] Linux version 3.3.8-4.0 (ygu@fs-ygu.corp.ad.broadcom.com) (gcc version 4.5.4 (Broadcom stbgcc-4.5.4-2.9) ) #4 SMP Tue Nov 22 17:22:09 CST 2016
[0.664210 0.012354] Fetching vars from bootloader... found 13 vars.
[0.668455 0.004245] Options: moca=0 sata=1 pcie=0 usb=1
[0.671466 0.003011] Using 512 MB + 0 MB RAM (from CFE)
[0.674986 0.003520] bootconsole [early0] enabled
[0.677584 0.002598] CPU revision is: 0002a065 (Broadcom BMIPS4380)
[0.681301 0.003717] FPU revision is: 00130001
[0.684052 0.002751] Determined physical RAM map:
[0.686868 0.002816] memory: 10000000 @ 00000000 (usable)
[0.689637 0.002769] memory: 10000000 @ 20000000 (usable)
[1.344329 0.654692] No PHY detected, not registering interface:1
[1.797709 0.453380] starting pid 429, tty '': '/etc/init.d/rcS'
[1.900459 0.102750] Mounting virtual filesystems
[1.969279 0.068820] Starting mdev
[2.171144 0.201865] * WARNING: THIS STB CONTAINS GPLv3 SOFTWARE
[2.174998 0.003854] * GPLv3 programs must be removed in order to enable security.
[2.180103 0.005105] * See: http:
[2.185750 0.005647] Configuring eth0 interface
[2.414136 0.228386] Configuring lo interface
[2.446222 0.032086] Starting network services
[2.455873 0.009651] starting pid 458, tty '': '/bin/cttyhack /bin/sh -l'
[2.482691 0.026818] #
从CFE上电后的第一条打印
BCM75840001
到CFE跳转到
linux
前的最后一条打印
Starting program at 0x8045f360
,中间总共耗时
648.718ms
(按照进入
linux
的第一条打印看,CFE中总计用时
651.856ms
)
4. 结论
经过优化CFE在启动过程中消耗的时间从2678ms
缩短到652ms
。
从CFE启动各个部分占用的时间看,I/O仍然占用了绝大部分时间,包括串口打印输出信息和从外设(nand)中获取数据。
对于串口,可以通过尽量避免打印来减少其输出占用时间;对于nand数据读取,由于目前芯片nand控制器时序采用默认设置,严格按照nand规格书中定义的时序来设置寄存器可以进一步提高nand读取效率,从而提高I/O效率。
5. patch
本篇基于
bcm97584 cfe v3.7
版本的patch链接:
bcm97584_cfe_v3.7-fastboot.patch
下一篇详细描述
linux
启动过程的优化。