在64位ARM处理器上,Linux内核启动前,对设备的环境要求主要有以下几点:
内存(DDR)已初始化完成,禁用MMU,关闭数据缓存(dcache);
蔽屏CPU中断,关闭指令缓存(icache);
禁用驱动的DMA操作,防止Linux内核在启动过程中内存被IO设备访问;
除此之外, Linux内核对64位ARM处理器的状态(例如异常级别,Exception Level)有一些细致的要求,这里不再探究,详细的说明请参考官方文档。
u-boot提供了多个命令用以启动Linux内核,如bootm和booti
等。这些命令可以带有一些参数,在64位ARM设备上,要求的参数配置寄存器如下:
- Primary CPU general-purpose register settings:
- x0 = physical address of device tree blob (dtb) in system RAM.
- x1 = 0 (reserved for future use)
- x2 = 0 (reserved for future use)
- x3 = 0 (reserved for future use)
注意到寄存器x0
存放了设备树二进制文件(DTB)的物理地址,在执行bootm
等命令以启动Linux内核时,因内核命令行参数bootargs
不能直接传递,u-boot会在跳转到Linux内核之前修改DTB,添加当前环境变量中定义的bootargs
参数。了解了Linux内核在64位ARM设备上启动的一些要求之后,是否可以编写一个简单的Linux内核启动命令,尝试启动Linux内核呢?
U-Boot提供了简单的执行裸应用的命令,go;对于2021.07
版本的u-boot,该命令的实现代码为cmd/boot.c
。参考该命令,笔者增加了goraw
命令,在跳转之前关闭了中断,并禁用了dcache
和icache
。主要的修改如下:
diff --git a/cmd/boot.c b/cmd/boot.c
index b84c0ed8..f36a746e 100644
--- a/cmd/boot.c
+++ b/cmd/boot.c
@@ -10,6 +10,8 @@
#include <common.h>
#include <command.h>
#include <net.h>
+#include <cpu_func.h>
+#include <irq_func.h>
#ifdef CONFIG_CMD_GO
@@ -53,6 +55,62 @@ U_BOOT_CMD(
" passing 'arg' as arguments"
);
+typedef ulong (* goraw_func)(ulong, ulong, ulong, ulong);
+static int do_goraw(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
+{
+ ulong addr, rval;
+ goraw_func raw_entry;
+ unsigned long regs[4];
+
+ if (argc < 2)
+ return CMD_RET_USAGE;
+ addr = simple_strtoul(argv[1], NULL, 16);
+ printf("## Starting application at 0x%08lX ...\n", addr);
+
+ /* disable interrupts */
+ disable_interrupts();
+
+ /* disable instruction cache */
+ icache_disable();
+ invalidate_icache_all();
+
+ /* disable data cache */
+ dcache_disable();
+ invalidate_dcache_all();
+
+ /* initialize raw entry argument array */
+ regs[0] = regs[1] = 0;
+ regs[2] = regs[3] = 0;
+
+ /*
+ * check the number of arguments,
+ * translate them into numbers:
+ */
+ if (argc >= 3)
+ regs[0] = simple_strtoul(argv[2], NULL, 16);
+ if (argc >= 4)
+ regs[1] = simple_strtoul(argv[3], NULL, 16);
+ if (argc >= 5)
+ regs[2] = simple_strtoul(argv[4], NULL, 16);
+ if (argc >= 6)
+ regs[3] = simple_strtoul(argv[5], NULL, 16);
+
+ raw_entry = (goraw_func) addr;
+ rval = raw_entry(regs[0], regs[1], regs[2], regs[3]);
+ printf("## Application terminated with: %lx\n", rval);
+
+ /* enable interrupts: */
+ enable_interrupts();
+ return 0;
+}
+
+U_BOOT_CMD(
+ goraw, CONFIG_SYS_MAXARGS, 1, do_goraw,
+ "start application at address 'addr', with CACHE/IRQ disabled",
+ "addr [arg ...]\n - start application at address 'addr'\n"
+ " passing 'arg' as arguments"
+);
+
#endif
这是一个尝试,因为goraw
命令在跳转之前没有关闭u-boot的驱动,不能确定不会有异步的DMA操作在不知情的情况下访问内存。另一个重要的步骤是,修改Linux内核源码中相应的设备树源文件(DTS),使其与bootm
和booti
等命令动态修改后的设备树文件保持一致。笔者因未进行这一操作,造成Linux内核启动异常,花费了一些时间调试。
goraw
命令启动Linux内核64位ARM的Linux内核镜像不支持自解压功能,这是与32位ARM Linux内核不同的;也是通常64位Linux内核文件比较大的原因。内核镜像文件的头部有固定的结构,对内核启动时的加载地址作了要求:
u32 code0; /* Executable code */
u32 code1; /* Executable code */
u64 text_offset; /* Image load offset, little endian */
u64 image_size; /* Effective Image size, little endian */
u64 flags; /* kernel flags, little endian */
u64 res2 = 0; /* reserved */
u64 res3 = 0; /* reserved */
u64 res4 = 0; /* reserved */
u32 magic = 0x644d5241; /* Magic number, little endian, "ARM\x64" */
u32 res5; /* reserved (used for PE COFF offset) */
反汇编vmlinux
可以得到以下数据(以下仅保留相关调试结果):
Disassembly of section .head.text:
ffffffc010080000 <_text>:
ffffffc010080000: 14224000 b ffffffc010910000 <stext>
ffffffc010080004: 00000000 .word 0x00000000
ffffffc010080008: 00080000 .word 0x00080000
ffffffc01008000c: 00000000 .word 0x00000000
ffffffc010080010: 009f8000 .word 0x009f8000
ffffffc010080014: 00000000 .word 0x00000000
ffffffc010080018: 0000000a .word 0x0000000a
...
ffffffc010080038: 644d5241 .word 0x644d5241
ffffffc01008003c: 00000000 .word 0x00000000
注意到对应的text_offset
值为0x00080000
。参考该值,笔者在调试设备上,最终选择了Linux内核的加载地址为0x8080000
,设备树的加载地址为0x9000000
;通过goraw
命令,完整的启动Linux操作如下:
U-Boot 2021.07-g4955deac (Aug 29 2021 - 20:46:36 +0800)
U-Boot> tftp 0x8080000 Image
TFTP from server 10.10.8.100; our IP address is 10.10.8.151
Filename 'Image'.
Load address: 0x8080000
Loading: ################################################## 9.4 MiB
3.4 MiB/s
done
Bytes transferred = 9906184 (972808 hex)
U-Boot> tftp 0x9000000 dtb.dtb
TFTP from server 10.10.8.100; our IP address is 10.10.8.151
Filename 'dtb.dtb'.
Load address: 0x9000000
Loading: ################################################## 27.9 KiB
1.1 MiB/s
done
Bytes transferred = 28571 (6f9b hex)
U-Boot> goraw 0x8080000 0x9000000
## Starting application at 0x08080000 ...
[ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x410fd034]
[ 0.000000] Linux version 5.4.123+ (yejq@ubuntu) (gcc version 7.5.0 (Linaro GCC 7.5-2019.12)) #1 SMP Sat Aug 29 20:55:01 CST 2021
通过新增的goraw
命令可以正常启动Linux内核!经笔者实际测试,若将禁用dcache
和icache
的操作删除(这样goraw
在功能上等同于go
命令),Linux内核不能正常启动。此外,让笔者费解的是,对于32位ARM和64位ARM处理器,u-boot源码中的disable_interrupts
函数的实现都为空,难道不需要禁用中断(参考arch/arm/lib/interrupts_XX.c
源码)?这个问题留待以后解答。
0000000000083408 <enable_interrupts>:
83408: d65f03c0 ret
000000000008340c <disable_interrupts>:
8340c: 52800000 mov w0, #0x0 // #0
83410: d65f03c0 ret