Skip to content

peiyuanix/baremetal-lichee-rv

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Bare Metal Lichee RV

项目实现了什么?

Lichee RV 裸机编程,将自己写的裸机程序烧录至 SD 卡,实现上电启动。

实现这一步之后,就可以继续完善自己的裸机程序,写自己的 SPL,Bootloader,Kernel 等。

当然,写自己的 Bootloader 和 Kernel 或许并不一定需要依赖自己的 SPL,使用固件中通用的 OpenSBI,U-Boot 也可以做,但是让板子从头到尾都运行自己写的代码也是一件很有趣的事情。

D1 SoC 启动流程

D1 这款 SoC 启动流程为:

  • 上电之后 pc 寄存器重置为 reset vector: 0x0,并开始加载指令执行,这个地址对应于 SoC 内置 BROM

  • BROM 检查 FEL 引脚状态。如果用户按了 FEL 按钮,则跳转到 FEL 模块并执行,这种模式下可以使用 xfel 工具操作 Soc;

  • 否则 BROM 会:

    • 寻找启动设备;
    • 在启动设备的特定位置,检查是否存在 eGON.BT0 标记来判断是否存在 BOOT0
    • 加载 BOOT0SRAM: 0x20000,并对 BOOT0 进行完整性校验,然后执行;

    如果未找到设备、未找到 BOOT0,或者校验不通过,则跳转到 FEL 模块执行;

对于 Lichee RV 板子来说:

  • 启动设备也就是 SD 卡;
  • 可以把裸机程序作为 BOOT0;
  • 关键在于按照规范生成 BOOT0,并写到 SD 卡的指定位置;

这样一来,Lichee RV 上电之后,就会从 SD 卡加载自己写入的 BOOT0,完成校验并执行。

BOOT0 规范

BOOT0 存储位置

SD 卡的第 16 个扇区,也就是 0x2000 处。

BOOT0 格式

  • BOOT0 区域由 header 和 boot0 程序组成,先是 header,然后是 boot0 程序,header 和 boot0 都需要适当对齐,因此可能存在一些 padding;
  • 具体来说,BOOT0 包括:
    • header
    • boot0 程序
    • 为对齐而填充的 padding

header 定义如下:

typedef struct __attribute__((packed)) boot_file_head
{
	u32  jump_instruction;   // one intruction jumping to real code
	u8   magic[8];           // ="eGON.BT0", not C-style string.
	u32  check_sum;          // generated by PC
	u32  length;             // generated by PC
	u32  pub_head_size;      // the size of boot_file_head_t
	u8   pub_head_vsn[4];    // the version of boot_file_head_t
	u8   file_head_vsn[4];   // the version of boot0_file_head_t or boot1_file_head_t
	u8   Boot_vsn[4];        // Boot version
	u8   eGON_vsn[4];        // eGON version
	u8   platform[8];        // platform information
} boot_file_head_t;

boot_file_head_t 中,

  • jump_instruction 是一条跳转指令,用于跳转到 boot0 程序。
    这是因为 BROM 加载 BOOT0 时,并非只加载程序代码,而是第 16 扇区起始处开始读取 header 和 boot0 程序,并放置到 SRAM 起始处 0x20000,校验通过后从 0x20000 开始执行,由于此处是一个 header,所以起始处有一个跳转指令,跳过 header 内容,至 boot0 程序继续执行。
  • magic[8] 即为 eGON.BT0,BROM 会初步检查是否存在此标记来判断是否存在 BOOT0;
  • check_sum 是整个 BOOT0 区域的校验和,BROM 会计算校验和是否正确来校验 BOOT0 的合法性。校验和生成逻辑为:
    // STEP-1:初始校验和为 0
    check_sum = 0
    
    // STEP-2: 计算校验和之前,赋给 BOOT0.header 中的 check_sum 一个给定的初值
    BOOT0.header.check_sum = 0x5F0A6C39
    
    // STEP-3: 将 BOOT0 视为 u32 数组,依次读取其中每一个 u32_val
    //         BOOT0 的长度由 BOOT0.header.length 确定  
    for u32_val in BOOT0:
      // 将 u32_val 加到 check_sum 上
      check_sum += u32_val
    
    // STEP-4: 此处得到正确的 check_sum  
    BOOT0.header.check_sum = check_sum
  • length 存储整个 BOOT0 区域的长度,包括 header、boot0、padding,上述校验和计算所涵盖的 BOOT0 范围即由 length 限定;
  • 其它字段似乎并不是很重要;

项目内容

项目包含一个示例裸机程序和一个 BOOT0 生成工具。

C 语言版本在 c-language 分支。

示例裸机程序 - GPIO 点灯

程序文件

  • yuan_spl.s : 该程序将 PB_0 引脚设置为 OUTPUT 模式,并将其状态设为 1,如果连接 LED 灯,就能看到灯亮起
  • yuan_spl.ld : 链接脚本,其中指定了程序加载地址,在 BOOT0 header 之后,即 0x20030

编译并提取 flat binary

# mkdir -p bin
riscv64-linux-gnu-as yuan_spl.s -o bin/yuan_spl.o
riscv64-linux-gnu-ld -T yuan_spl.ld bin/yuan_spl.o -o bin/yuan_spl.elf
riscv64-linux-gnu-objcopy -O binary -S bin/yuan_spl.elf bin/yuan_spl.bin

BOOT0 生成工具

mksunxiboot.c 文件可单独编译成可执行文件,它可以读取 flat binary,生成 BOOT0 header,并将 header 和 flat binary 拼装起来输出为可写入的 BOOT0 数据。

# mkdir -p tools
# gcc -o tools/mksunxiboot mksunxiboot.c
./tools/mksunxiboot bin/yuan_spl.bin bin/BOOT0.bin

将生成的 BOOT0 写入 SD 卡第 16 扇区,即可上电执行:

sudo dd if=bin/BOOT0.bin of=${your_sdcard} bs=512 seek=16 conv=sync

烧录结果与 BOOT0 内容

使用 xxd 查看 SD 卡第16扇区的内容,即为 BOOT0 的内容,可以看到 eGON.BT0 标记。

riscv-server:~$ sudo xxd -s 8192 -l 512 /dev/sdb

00002000: 6f00 0003 6547 4f4e 2e42 5430 2b5d 86ea  o...eGON.BT0+]..
00002010: 0002 0000 3000 0000 0000 0000 0000 0000  ....0...........
00002020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002030: b701 0002 9b02 1000 9392 0202 9382 12ff  ................
00002040: 23a8 5102 9302 1000 23a0 5104 6f00 0000  #.Q.....#.Q.o...
00002050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002060: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002090: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000020a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000020b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000020c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000020d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000020e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000020f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002100: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002110: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002120: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002130: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002140: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002150: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002160: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002170: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002180: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00002190: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000021a0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000021b0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000021c0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000021d0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000021e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
000021f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

使用 objdump 反编译 BOOT0 ,可以看到起始位置的 jump 指令能够正确跳过 header,至 0x30 处执行 BOOT0 程序代码。

riscv-server:~$ riscv64-linux-gnu-objdump -b binary -m riscv:rv64 -D bin/BOOT0.bin

bin/BOOT0.bin:     file format binary


Disassembly of section .data:

0000000000000000 <.data>:
   0:   0300006f                j       0x30
   4:   4765                    li      a4,25
   6:   422e4e4f                fnmadd.d        ft8,ft8,ft2,fs0,rmm
   a:   3054                    fld     fa3,160(s0)
   c:   ea865d2b                0xea865d2b
  10:   0200                    addi    s0,sp,256
  12:   0000                    unimp
  14:   0030                    addi    a2,sp,8
        ...
  2e:   0000                    unimp
  30:   020001b7                lui     gp,0x2000
  34:   0010029b                addiw   t0,zero,1
  38:   02029293                slli    t0,t0,0x20
  3c:   ff128293                addi    t0,t0,-15
  40:   0251a823                sw      t0,48(gp) # 0x2000030
  44:   00100293                li      t0,1
  48:   0451a023                sw      t0,64(gp)
  4c:   0000006f                j       0x4c
        ...

参考资料