ARMv8 Uboot支持MMU和Cache说明
1. 背景介绍
由于Uboot是第一次在我司平台方案上支持,因此存在很多不完善的地方,在启动过程中,客户反馈Uboot在内核解压(gzip压缩内核)这一块耗时过长,影响系统开机时间,需要澄清内核解压耗时原因和解决这个问题。
针对这个问题,由于我司没有硬件解压IP,因此解压都是靠CPU进行软解的,因此主要排查方向存在以下几点:
-
CPU主频以及相关访问DDR的总线频率、DDR频率。
-
CPU Cache,特别是Dcache。
经过排查和确认,上述第一点是没有问题的,那主要原因就是cache导致的了,排查代码,发现是uboot阶段没有支持cache特性,因此在uboot阶段支持cache,在进行对比实验,确认相关信息。
Uboot版本:uboot-2021.04-y
2. Uboot支持MMU和Cache
2.1 MMU和Cache关系
2.1.1 MMU介绍
MMU是Memory Management Unit的缩写,中文名是内存管理单元,它是中央处理器(CPU)中用来管理虚拟存储器、物理存储器的控制线路,同时也负责虚拟地址映射为物理地址,以及提供硬件机制的内存访问授权,多用户多进程操作系统。
其功能主要有:
- 完成虚拟地址到物理地址的转化
- 对相应的地址空间的访问权限控制
- 与操作系统的内存管理程序一起协作对内存进行管理
Uboot主要使用了上述第1、2点功能
2.1.2 Cache介绍
Cache是处理器内部的一个高速缓存单元,为了应对处理器和外部存储设备的速度不匹配而设立的,其速度比内存的读写速度要快好多,接近处理器的工作速度,一般处理器从内存中读取数据到cache中,到下次再用到数据时,会先去cache中查找,如果cache中存在的话就不会去访问内存了,用以提高系统性能。一般在各个阶段跳转前,都需要把MMU和Dcache关闭,以免发生不可预料的错误。如preloader跳转bootloader, bootloader跳转kernel等。
Cache分Dcache和Icache两种,在ARM体系架构中,Icache不需要MMU支持,但Dcache需要依赖MMU支持,因此开Dcache需要把MMU打开。
2.2 Uboot MMU的使用
2.2.1 ARM mmu页表翻译介绍
页表翻译,就是指将一个虚拟地址通过查找页表信息,转换成对应的物理地址,这个任务主要是mmu完成的,但mmu完成该项工作,需要我们配置有些相关ARM system相关寄存器才可以,本小节主要结合我司方案Uboot实际使用情况介绍mmu页表转换过程。
ARM64架构下,把页表称为转换表(translation table),最多支持5级,如下所示:
但实际情况,我们都是使用了4级,因为uboot和内核目前配置的最大虚拟地址都是48bit的,且没有支持large page特性,因此Minus one代表那一级没有使用到。
ARM64架构,支持页长度为4KB、16KB、64KB。
页长度和虚拟地址宽度决定了页转换表的级数,如下图所示:
ARM64架构下,把转换表的表项称为描述符,使用64bit位长描述符格式(即表项占用8Byte),描述符格式如下图所示:
从图可以看出,描述符可以表示块描述符(Block)或者表描述符(table),各级转换表中描述符说明如下:
-
第0级转换表中,只有表描述符,存放着下一级转换表所在的地址
-
块描述符只会出现在第1、2级转换表中,块描述符存放一个内存块的起始地址
-
表描述符可以存在第1、2级转换表中,表描述符和块描述符靠BIT1来进行区分,BIT1为1,表示表描述符,BIT1为0,则是块描述符
-
在第3级转换中,只存在页描述符
-
当BIT0为0时,表示无效描述符,表示该描述符是无效的
-
块描述符和页描述符,除了确定对应描述内存的基地址信息外,还有描述了该块内存信息的属性信息,如读写、执行、安全访问等,具体可以看ARM手册了解
描述符格式说明如下图所示(虚拟地址宽度<48Bit情况下):
- 无效描述符格式
- 块描述符格式
- 表描述符格式
总结如下图所示:
2.2.2 ARM MMU在Uboot的相关信息
ARMv8在uboot下,我司的方案是使用了ARM64模式,因此最大寻址范围是2^64Byte,理论上虚拟地址宽度最大可以到64bit,但由于很多方案并不需要这么大的虚拟地址范围,且也没有这么大的物理内存需求,因此也支持其他的虚拟地址宽度,如下图所示:
在uboot中,根据memmap信息,会自动确认虚拟地址宽度,具体函数如下:
//arch/arm/cpu/armv8/cache_v8.c
u64 get_tcr(int el, u64 *pips, u64 *pva_bits)
{
u64 max_addr = 0;
u64 ips, va_bits;
u64 tcr;
int i;
/* Find the largest address we need to support */
for (i = 0; mem_map[i].size || mem_map[i].attrs; i++)
max_addr = max(max_addr, mem_map[i].virt + mem_map[i].size);
/* Calculate the maximum physical (and thus virtual) address */
if (max_addr > (1ULL << 44)) {
ips = 5;
va_bits = 48;
} else if (max_addr > (1ULL << 42)) {
ips = 4;
va_bits = 44;
} else if (max_addr > (1ULL << 40)) {
ips = 3;
va_bits = 42;
} else if (max_addr > (1ULL << 36)) {
ips = 2;
va_bits = 40;
} else if (max_addr > (1ULL << 32)) {
ips = 1;
va_bits = 36;
} else {
ips = 0;
va_bits = 32;
}
if (el == 1) {
tcr = TCR_EL1_RSVD | (ips << 32) | TCR_EPD1_DISABLE;
} else if (el == 2) {
tcr = TCR_EL2_RSVD | (ips << 16);
} else {
tcr = TCR_EL3_RSVD | (ips << 16);
}
/* PTWs cacheable, inner/outer WBWA and inner shareable */
tcr |= TCR_TG0_4K | TCR_SHARED_INNER | TCR_ORGN_WBWA | TCR_IRGN_WBWA;
tcr |= TCR_T0SZ(va_bits);
if (pips)
*pips = ips;
if (pva_bits)
*pva_bits = va_bits;
return tcr;
}
从上述代码可知,uboot支持最大的寻址访问为2^48 Byte,虚拟地址宽度最大支持48bit,Uboot支持的页长度为4KB。
由于我司在uboot的memmap如下所示:
static struct mm_region g12a_mem_map[] = {
{
/* RAM */
.virt = 0x40000000UL,
.phys = 0x40000000UL,
.size = 0xC0000000UL, /* 3GB */
.attrs = PTE_BLOCK_MEMTYPE(MT_NORMAL) |
PTE_BLOCK_INNER_SHARE
}, {
/* IO SPACE */
.virt = 0x30000000UL,
.phys = 0x30000000UL,
.size = 0x10000000UL, /* 256MB */
.attrs = PTE_BLOCK_MEMTYPE(MT_DEVICE_NGNRNE) |
PTE_BLOCK_NON_SHARE |
PTE_BLOCK_PXN | PTE_BLOCK_UXN
}, {
/* List terminator */
0,
}
};
struct mm_region *mem_map = g12a_mem_map;
因此在我司方案上,uboot目前使用的虚拟地址有效位宽为32bit(随着映射内存的增大而改变)。
Uboot支持mmu大概流程:
_main
board_init_f
setup_dest_addr
arch_reserve_mmu
...
board_init_r
initr_caches
enable_caches
icache_enable
dcache_enable
mmu_setup
...
Uboot地址翻译过程如下:
log信息
U-Boot 2021.04-gdd07439d-dirty (Dec 10 2021 - 09:40:25 +0800) d9_ref
Model: Semidrive kunlun D9 REF Board
DRAM:dd reserve_round_4k
TLB table from 13fff0000 to 13fff8000 //TTBL0基地址:13fff0000
reserve_uboot
Reserving 32832k for malloc() at: 13df3f000
3.9 GiB
setup_reloc
Relocation Offset is: df94f000
Relocating to 13ff4f000, new gd at 13df3edd0, sp at 13df1bc40
board_init_r
initr_trace
从虚拟地址,获取到第一级转换表的index为1,可以拿到一级转换表的表项(描述符)为:0x4000711,如下所示:
=> md 000000013fff0008
13fff0008: 40000711 00000000 80000711 00000000 ...@............
13fff0018: c0000711 00000000 00000711 00000001 ................
13fff0028: 00000000 00000000 00000000 00000000 ................
13fff0038: 00000000 00000000 00000000 00000000 ................
13fff0048: 00000000 00000000 00000000 00000000 ................
从该描述符可以确认是块描述符,根据块描述符的含义,可以确认块描述符描述的内存基地址为0x40000000;再从虚拟地址的bit0-bit29获取物理地址的偏移,可以翻译出对应的虚拟地址:0x606023d8。
疑问:
为什么uboot需要使用块描述符而不使用表描述符?
个人见解如下:
-
Uboot不需要像OS那样,需要对内存进行精细化管理
-
使用块描述符映射相对简单
3.性能对比
在打开cache后,解压的性能测试如下:
MMC read: dev # 0, block # 40, count 49152 ... 49152 blocks read: OK
## Loading kernel from FIT Image at 5e000000 ...
Using 'conf@2' configuration
Trying 'kernel' kernel subimage
Description: scmap1_ref Linux kernel
Type: Kernel Image
Compression: gzip compressed
Data Start: 0x5e0000e8
Data Size: 9604718 Bytes = 9.2 MiB
Architecture: AArch64
OS: Linux
Load Address: 0x5c480000
Entry Point: 0x5c480000
Hash algo: crc32
Hash value: d9dd977c
Hash algo: sha1
Hash value: 424e0624a9c5f846db303640147f2ef6a66cb291
Verifying Hash Integrity ... crc32+ sha1+ OK
## Flattened Device Tree blob at 47400000
Booting using the fdt blob at 0x47400000
Uncompressing Kernel Image
take time:345 ms
而关闭cache,解压性能如下:
MMC read: dev # 0, block # 40, count 49152 ... 49152 blocks read: OK
## Loading kernel from FIT Image at 5e000000 ...
Using 'conf@2' configuration
Trying 'kernel' kernel subimage
Description: scmap1_ref Linux kernel
Type: Kernel Image
Compression: gzip compressed
Data Start: 0x5e0000e8
Data Size: 9604718 Bytes = 9.2 MiB
Architecture: AArch64
OS: Linux
Load Address: 0x5c480000
Entry Point: 0x5c480000
Hash algo: crc32
Hash value: d9dd977c
Hash algo: sha1
Hash value: 424e0624a9c5f846db303640147f2ef6a66cb291
Verifying Hash Integrity ... crc32+ sha1+ OK
## Flattened Device Tree blob at 47400000
Booting using the fdt blob at 0x47400000
Uncompressing Kernel Image
take time:19246 ms
可以看出,打开Dcache后,对解压性能有巨大的影响。