Boot Assist Module(BAM)

对于绝大多数 mpu/cpu 来说,上电之后 pc 都会进到一个约定俗成的地址,这个地址一般由芯片设计厂商设置,但是也有支持设备集成厂商修改的 mpu/cpu,如 ppc 的 e200z7 就可通过 p_rstbase[0:29] 设置上电地址。

对于一般的板级设备开发,使用 cpu/mpu 默认的地址就好了;但是也有比较高级的设备,它在设备集成的时候内建了一个 boot 程序,cpu 上电后进入到约定地址,即 boot 程序地址,然后再由 boot 程序加载操作系统。这就是当前 pc 机的做法,而对于简单的嵌入式设备一般是不会内建 boot 程序的。

BAM(Boot Assist Module)就是这样的一个 boot 程序,我在 QEMU 中实现 mpc5675 的时候所遇到的一个问题就是无法确定加载程序的 entry 地址,而在其他固定上电地址的板级设备中则可确定 entry 地址为默认上电地址。

1、分析

参考 e500 中获取 entry 地址的方式,在qemu/hw/ppc/e500.c:ppce500_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
if (bios_name == NULL) {
    if (machine->kernel_filename) {
        bios_name = machine->kernel_filename;
    } else {
        bios_name = "u-boot.e500";
    }
}
filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_nam
bios_size = load_elf(filename, NULL, NULL, &bios_entry, &loadaddr, NULL,
                     1, PPC_ELF_MACHINE, 0, 0);
if (bios_size < 0) {
    /*
     * Hrm. No ELF image? Try a uImage, maybe someone is giving us an
     * ePAPR compliant kernel
     */
    kernel_size = load_uimage(filename, &bios_entry, &loadaddr, NULL,
                              NULL, NULL);
    if (kernel_size < 0) {
        fprintf(stderr, "qemu: could not load firmware '%s'\n", filename);
        exit(1);
    }
}
g_free(filenam
/* Reserve space for dtb */
dt_base = (loadaddr + bios_size + DTC_LOAD_PAD) & ~DTC_PAD_MA
dt_size = ppce500_prep_device_tree(machine, params, dt_base,
                                   initrd_base, initrd_size,
                                   kernel_base, kernel_size);
if (dt_size < 0) {
    fprintf(stderr, "couldn't load device tree\n");
    exit(1);
}
assert(dt_size < DTB_MAX_SIZ
boot_info = env->load_info;
boot_info->entry = bios_entry;
boot_info->dt_base = dt_base;
boot_info->dt_size = dt_size;

e500 通过 elf 文件加载时读取 elf 文件头文件信息,获取 entry。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static void ppce500_cpu_reset(void *opaque)
{
    PowerPCCPU *cpu = opaque;
    CPUState *cs = CPU(cpu);
    CPUPPCState *env = &cpu->env;
    struct boot_info *bi = env->load_info;

    cpu_reset(cs);

    /* Set initial guest state. */
    cs->halted = 0;
    env->gpr[1] = (16<<20) - 8;
    env->gpr[3] = bi->dt_base;
    env->gpr[4] = 0;
    env->gpr[5] = 0;
    env->gpr[6] = EPAPR_MAGIC;
    env->gpr[7] = mmubooke_initial_mapsize(env);
    env->gpr[8] = 0;
    env->gpr[9] = 0;
    env->nip = bi->entry;
    mmubooke_create_initial_mapping(env);
}

在 cpu_reset 之后,手动设置env->nip = bi->entry。但是在加载 bin 类型文件时却没有办法读取 entry 信息。

2、BAM 实现

查看 BAM 实现,看 BAM 如何找到程序 entry,是否采用指定到固定地址的方式。

2.1、概述

BAM 在一块只读内存上,它包含了 VLE 指定代码,会根据设备的不同启动模式选择执行。

BAM 通过以下两个协议下载代码到 SRAM,并且执行它:

  • FlexCAN
  • LINFlexD-UART

BAM 的目的是通过一个串口下载代码到 SRAM,下载完成后进行检验确保下载数据的完整性。

2.2、启动模式

mpc5675 支持两种启动模式:

  • SC(Single Chip)设备从 flash main array 的第一个可执行的段启动;
  • SBL(Serial Boot)设备从 FlexCAN 和 LINFlex 下载同时运行它。

如果没有可用的启动方式,那么就会进入“Static mode”。

2.3、内存映射

BAM 代码在 8KB 的 ROM 上,映射地址为 0xFFFF_C000。

BAM 将代码加载到 SRAM 上,起始地址为 0x4000_0100。

2.4 、进入启动模式

BAM 会读取 SSCM_STATUS[BMODE] 位判断进入那个模式

  • 000 Reserved
  • 001 FlexCAN
  • 010 LINFlexD
  • 011 Single Chip

2.5、内存映射

用户代码加载到板子上之后,会在板子 FLASH 上添加 RCHW 和 Start address。调试时直接打印内存 0x0 0x4 可看到。

valid boot identifier 为 0x5A,后面的 0x6c 为 application start address。该地址即为我想要寻找的 entry 地址。

3、解决方法

在板级设备中添加 BAM。