当前位置: 首页 > 工具软件 > magic-boot > 使用案例 >

U-Boot启动Linux内核的简单实现

锺星腾
2023-12-01

64位ARM Linux内核启动的环境要求

在64位ARM处理器上,Linux内核启动前,对设备的环境要求主要有以下几点:

  • 内存(DDR)已初始化完成,禁用MMU,关闭数据缓存(dcache);

  • 蔽屏CPU中断,关闭指令缓存(icache);

  • 禁用驱动的DMA操作,防止Linux内核在启动过程中内存被IO设备访问;

除此之外, Linux内核对64位ARM处理器的状态(例如异常级别,Exception Level)有一些细致的要求,这里不再探究,详细的说明请参考官方文档

U-Boot启动Linux内核的参数

u-boot提供了多个命令用以启动Linux内核,如bootmbooti等。这些命令可以带有一些参数,在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增加简单的Linux内核启动命令

U-Boot提供了简单的执行裸应用的命令,go;对于2021.07版本的u-boot,该命令的实现代码为cmd/boot.c。参考该命令,笔者增加了goraw命令,在跳转之前关闭了中断,并禁用了dcacheicache。主要的修改如下:

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),使其与bootmbooti等命令动态修改后的设备树文件保持一致。笔者因未进行这一操作,造成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内核!经笔者实际测试,若将禁用dcacheicache的操作删除(这样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
 类似资料: