基于全志哪吒tina-d1-h开发版分析。
前言
以全志哪吒tina-d1-h开发版D1-H 芯片介绍为例,uboot中使用bootm启动uImage
uImage制作命令:
mkimage -A riscv -O linux -T kernel -C gzip -a 0x40080000 -e 0x40080000 -n 'RISCV OpenWrt Linux-5.4.61' -d out/d1-h-nezha/compile_dir/target/linux-d1-h-nezha/Image.gz out/d1-h-nezha/d1-h-nezha-uImage
bootm启动uImage:
boot_normal=sunxi_flash read 45000000 ${boot_partition};bootm 45000000
bootm地址和-a指定的地址不同,从0x4500_0000开始提取64byte的头部,然后把头部去掉,在copy到-a指定的load地址(load地址在uboot中根据BASE_ADDRESS和KERNEL_OFFSET重新计算为0x4000_0000)。
uboot启动kernel
images->ep指向0x40000000物理地址;boot_hart启动CPU ID(cpu不支持超线程);ft_addr表示DTB物理地址,uboot会将command line更新到dts根节点下的chosen的bootargs属性值中,以及根据ddr的起始地址和大小更新dts根节点下的memory节点内容(起始地址和大小)。
kernel = (void (*)(ulong, void *))images->ep;
kernel(gd->arch.boot_hart, images->ft_addr);
kernel启动
kernel启动地址0x40000000,对应_start入口地址,执行一些初始化操作后调用到_start_kernel汇编函数,函数的输入参数为启动cpu ID和DTB物理地址。_start_kernel主要工作包括:屏蔽所有中断,清bss段,setup_vm创建页表,切到虚拟地址空间运行,配置C运行环境,解析dtb,start_kernel。
代码路径:arch/riscv/kernel/head.S
屏蔽所有中断
csrw CSR_SIE, zero
csrw CSR_SIP, zero
清bss段
la a3, __bss_start
la a4, __bss_stop
ble a4, a3, clear_bss_done
clear_bss:
REG_S zero, (a3)
add a3, a3, RISCV_SZPTR
blt a3, a4, clear_bss
setup_vm创建页表
参考平台使用3级页表,在分析setup_vm创建页表之前需要先熟悉下risc-v的内存布局。
Kernel space
KERN_VIRT_SIZE: 0x20_0000_0000 //128GB
User space
TASK_SIZE: 0x40_0000_0000 //256GB
配置参数
CONFIG_PAGE_OFFSET: 0xFFFF_FFE0_0000_0000
CONFIG_VA_BITS=39
setup_vm()创建页表,主要创建early_pg_dir和trampoline_pg_dir两张页表。
early_pg_dir页表是一张大小为4KB的数组,每一个页表项占8Bytes,存放在.init.data段,格式为early_pg_dir[512]。early_pg_dir页表的生命周期会持续到setup_vm_final()函数创建swapper_pg_dir页表,每一个页表项的结构体如下:(参考:玄铁C910用户手册_v13.pdf 虚拟内存管理章节)
early_pg_dir页表地址需要写入到MMU地址转换寄存器(SATP)中,SV39 SATP格式如下:
setup_vm()函数执行完成后,early_pg_dir页表全貌如下图:
1. 固定映射区(FIXADDR_START)建立映射,kernel启动过程中将DTB物理地址映射到固定映射区中。
2. kernel加载地址到PAGE_OFFSET建立映射,将虚拟地址[PAGE_OFFSET, PAGE_OFFSET+image size]区间映射到物理地址[0x4000000, 0x40000000 + image size]区间。
trampoline_pg_dir页表是一张大小为4KB的数组,每一个页表项占8Bytes,存放在.bss段,格式为trampoline_pg_dir[512]。setup_vm()函数执行完成后,trampoline_pg_dir页表全貌如下图:
trampoline_pg_dir页表仅建立虚拟地址[PAGE_OFFSET, PAGE_OFFSET + 2MB]区间到物理地址[0x40000000, 0x40000000 + 2MB]区间的映射。
切到虚拟地址空间运行
汇编函数relocate,输入参数为early_pg_dir,该函数执行后,系统就进入到虚拟地址运行。该函数主要工作如下:
1. 将函数返回地址ra(return address)寄存器中的值修改为虚拟地址
2. 将((trampoline_pg_dir >> 12) | SATP_MODE写入SATP寄存器,并刷新TLB sfence.vma
3. 修改部分通用寄存器到虚拟地址
4. 更新页表,将((early_pg_dir >> 12) | SATP_MODE写入SATP寄存器,并刷新TLB sfence.vma,该页表覆盖整个kernel,可支持系统运行到paging_init(),在setup_vm_final()函数中会map所有的ddr memory
配置C环境
la tp, init_task
sw zero, TASK_TI_CPU(tp) // 初始化thread_info.cpu=0
la sp, init_thread_union + THREAD_SIZE
解析DTB
DTB早期解析实现函数early_init_dt_scan(),主要工作包括command line解析,获取address、size格式,添加内存到memblock
start_kernel
start_kernel会初始化所有的资源,如启动cpu初始化,建立运行页表,内存初始化,进程调度初始化,启动多核,加载所有的module等,此处指分析运行页表的建立。
swapper_pg_dir页表是一张大小为4KB的数组,每一个页表项占8Bytes,存放在.bss段,格式为swapper_pg_dir[512]。
setup_vm_final()函数建立swapper_pg_dir页表,swapper_pg_dir页表是一张大小为4KB的数组,每一个页表项占8Bytes,存放在.bss段,格式为swapper_pg_dir[512]。该函数执行完成后,会映射memblock中的所有memory空间,然后清除固定映射区,swapper_pg_dir页表全貌如下图:
vmlinux内存布局
根据vmlinux内存布局,可知道页表建立过程中,各个页表的存放位置如下:
early_pg_dir[512]: 0xffffffe000023000 存放在.init.data段,4KB
early_pmd[512]: 0xffffffe000022000 存放在.init.data段,4KB
fixmap_pmd[512]: 0xffffffe000936000 存放在.bss段,4kB
trampoline_pmd[512]: 0xffffffe000937000 存放在.bss段,4kB
fixmap_pte[512]: 0xffffffe000938000 存放在.bss段,4kB
trampoline_pg_dir[512]: 0xffffffe000939000 存放在.bss段,4kB
swapper_pg_dir[512]: 0xffffffe00093a000 存放在.bss段,4kB