milkv-duo启动流程分析:手动构建fip.bin [1/2]

发布时间 2023-10-15 16:15:24作者: 可爱无辜猫猫头

Milk-V Duo是一个基于CV1800B芯片的超紧凑嵌入式开发平台。它可以运行Linux和RTOS,为专业人士、工业ODM、AIoT爱好者、DIY爱好者和创作者提供了一个可靠、低成本和高性能的平台。在这样一个平台上跑上buildroot就变得意义非凡。

构建riscv64-unknown-linux-musl编译工具链

直接下载官方工具链

MilkV Duo官方给了我们工具链的下载地址,但是由于存sdk的仓库内容被清空,不再被维护,并且下载地址被隐藏在release里边,太难找了,所以我就假装我没找到官方的工具链,看看软件源里有没有riscv64的编译器。

尝试自己编译T-head Gcc

使用

apt search riscv64

一搜,还真有结果。

binutils-riscv64-linux-gnu/jammy-updates,jammy-security 2.38-4ubuntu2.3 amd64
  GNU binary utilities, for riscv64-linux-gnu target

gcc-riscv64-linux-gnu/jammy 4:11.2.0--1ubuntu1 amd64
  GNU C compiler for the riscv64 architecture

于是安装了这俩,然后做了点测试,发现它并不支持平头哥c906fdv架构,所以我们还是需要使用官方给出的编译链。但是由于我假装看不见官方的release下载地址,就抱着play的心态,尝试自己构建一个工具链。riscv64是我们的目标架构,unknown是vendor,即厂商名,这里被指定为unknown。linux就是OS平台,musl是一种新的c库。既然官方用musl,那我也跟着用吧。

平头哥魔改过的包含c906fdv处理器的gcc源代码在T-head-Semi/gcc,我们把它clone下来

git clone https://github.com/T-head-Semi/gcc.git

下载编译binutils

Binutils是一组开源的二进制工具集合,用于创建、操作和分析可执行文件、目标文件和库文件。它由一系列工具组成,包括汇编器(as)、链接器(ld)、目标文件工具(objdump、readelf等)和调试器(gdb)等。这些工具被广泛用于开发和维护各种操作系统、编译器和软件工具链。Binutils可以用于多种体系结构和操作系统,包括x86、ARM、MIPS、PowerPC等,支持多种操作系统如Linux、Windows、macOS等。它们提供了一些核心功能,如将汇编代码转换为可执行文件、将目标文件链接为可执行文件、提取和修改目标文件的符号和节(sections)信息,以及执行调试操作。通过使用Binutils,开发人员可以进行底层的二进制文件操作和分析,包括反汇编、符号表查看、调试信息提取等。它们对于编译器开发、操作系统开发、嵌入式系统开发等方面非常有用,也是构建软件工具链的重要组成部分。

在编译GCC(GNU Compiler Collection)之前,需要准备Binutils,原因如下:

  • 链接器(ld):GCC在编译过程中需要使用链接器将编译后的目标文件(.o文件)链接成最终的可执行文件。链接器负责解析符号引用、符号重定位以及生成最终可执行文件的各个节(sections)。Binutils中的ld是一个功能强大的链接器,被GCC用作默认的链接器。

  • 汇编器(as):GCC在编译源代码时,会将源代码翻译成汇编代码。然后,汇编器将汇编代码转换成相应的机器码,并生成目标文件。Binutils中的as是GCC默认使用的汇编器。

  • 目标文件工具(objdump、readelf等):在编译过程中,可能需要查看、分析和调试目标文件的内容。Binutils提供了一些工具,如objdump和readelf,用于查看目标文件的符号表、节表、重定位信息等。这些工具对于调试编译器生成的目标文件或库文件非常有用。

因此,为了编译GCC,需要安装并准备Binutils,以确保编译器能够正确地使用其中的链接器、汇编器和目标文件工具。Binutils提供了一套强大的二进制工具,为GCC的编译过程提供了必要的支持。

我们不可以直接使用apt安装的binutils,因为默认的binutils提供的ld的生成的目标文件是x86架构的。如果直接安装binutils-riscv64-linux-gnu,我们还需要手动把头文件复制一遍,把二进制文件也改名一遍,这样做很麻烦,所以我们应当直接make -j64&&make install,这样还能顺便把头文件都给我们塞好。

首先我们把binutils的代码wget下来,然后创建一个build目录,专门用于编译,再创建一个目录,用于存放install的结果。

# 进入binutils目录
cd binutils
# 创建build目录
mkdir build
cd build
# 配置binutils,prefix是install的时候生成文件的存放地址
../configure --prefix=/opt/riscv --target=riscv64-unknown-linux-musl
make -j64&&make install

这样之后,/opt/riscv目录下就出现了bin、include等不少文件。

编译交叉gcc

同样,我们进入平头哥gcc的目录,创建一个build目录,然后配置

../configure --prefix=/opt/riscv --target=riscv64-unknown-linux-musl --with-sysroot=/opt/riscv
make all-gcc
make install-gcc

在编译GCC时,--with-sysroot选项用于指定系统根目录(sysroot)。系统根目录是一个包含完整系统文件层次结构的目录,通常用于交叉编译环境中。

设置--with-sysroot选项的目的是告诉GCC编译器在指定的系统根目录下查找头文件、库文件和其他系统资源,以便正确地构建和链接目标程序。这在交叉编译环境中特别重要,其中编译器和目标平台的操作系统不匹配。

通过将--with-sysroot选项设置为系统根目录的路径,GCC将使用该路径作为基准,查找和引用目标平台所需的系统文件。这包括标准C库、头文件、共享库、链接器脚本等。使用--with-sysroot选项可以确保编译器能够正确地定位和使用目标平台的系统资源。

在交叉编译环境中,你还需要设置其他相关的选项,如--target选项指定目标平台体系结构,以及--prefix选项指定安装位置等。这些选项的设置将根据你的具体需求和目标平台而有所变化。

需要注意的是,--with-sysroot选项并不适用于本地编译,因为本地编译时GCC可以直接使用主机操作系统的系统资源。它主要用于交叉编译环境,以确保编译器在正确的系统根目录下查找和使用目标平台的系统文件。

在编译的过程中,我们遇到了报错,提示缺少/opt/riscv/usr/include的头文件。因此,我们还需要编译musl

编译musl

去官网下载musl的代码,然后编译。

mkdir build
cd build
../configure --prefix=/opt/riscv
make
make install

此时继续编译gcc,仍然会遇到报错,提示缺少/opt/riscv/usr/include的头文件。我们把/opt/riscv/include下的文件直接复制过去就行。
然后再次make install-gcc

此时就完成了工具链的编译

手动合成fip.bin和boot.sd

为了扔进buildroot,先要摸透每个流程。

编译u-boot

官方sdk给出了魔改过的uboot,有关的代码也scatter在不知道什么地方。根据目前的探查,我们需要把build目录下的

  • build/boards/cv180x/cv1800b_milkv_duo_sd/u-boot/cvi_board_init.c复制到u-boot/board/cvitek/目录下
  • build/boards/cv180x/cv1800b_milkv_duo_sd/u-boot/cvitek.h复制到u-boot/include/cvitek/目录下
  • build/boards/cv180x/cv1800b_milkv_duo_sd/u-boot/cvitek_cv1800b_milkv_duo_sd_defconfig复制到u-boot/configs目录下

生成cvi_board_memmap.hcvipart.himgs.h

执行make -j64,报错,提示没有cvi_board_memmap.h。进入build目录,找到scripts/mmap_conv.py,执行

./build/scripts/mmap_conv.py --type h ./build/boards/cv180x/cv1800b_milkv_duo_sd/memmap.py u-boot/include/cvi_board_memmap.h

继续生成cvipart.h

python3 build/tools/common/image_tool/mkcvipart.py build/boards/cv180x/cv1800b_milkv_duo_sd/partition/partition_sd.xml u-boot/include

继续生成imgs.h

python3 build/tools/common/image_tool/mk_imgHeader.py build/boards/cv180x/cv1800b_milkv_duo_sd/partition/partition_sd.xml u-boot/include

这个cvi_board_memmap.h在后续开发中也很重要,要记住位置。

继续编译u-boot

执行make -j64 CROSS_COMPILE=riscv64-unknown-linux-gnu-,编译时发现汇编器不认csrr寄存器。

board/cvitek/cv180x/board.c: Assembler messages:
board/cvitek/cv180x/board.c:327: Error: unrecognized opcode `csrr a5,0xc01', extension `zicsr' required

所以我们进入arch/riscv/Makefile,做出以下改动

ARCH_EX = _zicsr_zifencei

ARCH_FLAGS = -march=$(ARCH_BASE)$(ARCH_A)$(ARCH_C)$(ARCH_EX) -mabi=$(ABI) \
	     -mcmodel=$(CMODEL)

再次编译,发现又报错了

common/command.c:586:20: error: conflicting types for ‘cmd_process’ due to enum/integer mismatch; have ‘enum command_ret_t(int,  int,  char * const*, int *, ulong *)’ {aka ‘enum command_ret_t(int,  int,  char * const*, int *, long unsigned int *)’} [-Werror=enum-int-mismatch]
  586 | enum command_ret_t cmd_process(int flag, int argc, char *const argv[],
      |                    ^~~~~~~~~~~
In file included from common/command.c:13:
include/command.h:234:5: note: previous declaration of ‘cmd_process’ with type ‘int(int,  int,  char * const*, int *, long unsigned int *)’
  234 | int cmd_process(int flag, int argc, char *const argv[], int *repeatable,
      |     ^~~~~~~~~~~

找到common/command.c:586,把返回值类型改成int,问题解决。继续编译,又遇到

riscv64-buildroot-linux-musl-ld.bfd: warning: u-boot has a LOAD segment with RWX permissions
  OBJCOPY u-boot.srec
  OBJCOPY u-boot-nodtb.bin
  SYM     u-boot.sym
make[2]: *** No rule to make target 'arch/riscv/dts/_.dtb', needed by 'dtbs'.  Stop.
make[1]: *** [dts/Makefile:45: arch-dtbs] Error 2
make: *** [Makefile:1145: dts/dt.dtb] Error 2
make: *** Waiting for unfinished jobs....

我们首先把build/boards/下的设备树都复制到uboot/arch/riscv/dts

  • build/boards/default/dts/cv180x/cv180x_base.dtsi
  • build/boards/default/dts/cv180x_riscv/cv180x_base_riscv.dtsi
  • build/boards/default/dts/cv180x/cv180x_asic_qfn.dtsi
  • build/boards/default/dts/cv180x/cv180x_asic_sd.dtsi
  • build/boards/default/dts/cv180x/cv180x_default_memmap.dtsi
  • build/boards/cv180x/cv1800b_milkv_duo_sd/dts_riscv/cv1800b_milkv_duo_sd.dts

然后在u-boot-2021.10/arch/riscv/dts/Makefile中,修改:

dtb-$(CONFIG_TARGET_CVITEK) += cv1800b_milkv_duo_sd.dtb

然后在u-boot-2021.10/dts/Makefile中,取消注释,修改:

DTB := arch/$(ARCH)/dts/$(DEVICE_TREE).dtb

同时,进入make menuconfig,设置CONFIG_DEFAULT_DEVICE_TREE=cv1800b_milkv_duo_sd

再次尝试编译,编译通过

LD      u-boot
riscv64-unknown-linux-gnu-ld.bfd: warning: u-boot has a LOAD segment with RWX permissions
  OBJCOPY u-boot.srec
  OBJCOPY u-boot-nodtb.bin
  SYM     u-boot.sym
  CAT     u-boot-dtb.bin
  COPY    u-boot.bin
  LD      u-boot.elf

编译opensbi

根据:

opensbi: export CROSS_COMPILE=$(CONFIG_CROSS_COMPILE_SDK)
opensbi: u-boot-build
  $(call print_target)
  ${Q}$(MAKE) -j${NPROC} -C ${OPENSBI_PATH} PLATFORM=generic \
      FW_PAYLOAD_PATH=${UBOOT_PATH}/${UBOOT_OUTPUT_FOLDER}/u-boot-raw.bin \
      FW_FDT_PATH=${UBOOT_PATH}/${UBOOT_OUTPUT_FOLDER}/arch/riscv/dts/${CHIP}_${BOARD}.dtb

翻译成人话,我们推测,我们应该执行:

make -j64 CROSS_COMPILE=riscv64-buildroot-linux-musl- PLATFORM=generic \
  FW_PAYLOAD_PATH=../u-boot-2021.10/u-boot.bin \
  FW_FDT_PATH=../u-boot-2021.10/arch/riscv/dts/cv1800b_milkv_duo_sd.dtb

编译后,又报错:

Error: unrecognized opcode `fence.i', extension `zifencei' required

修改Makefile

ifndef PLATFORM_RISCV_ISA
  ifneq ($(PLATFORM_RISCV_TOOLCHAIN_DEFAULT), 1)
    PLATFORM_RISCV_ISA = rv$(PLATFORM_RISCV_XLEN)imafdc_zicsr_zifencei
  else
    PLATFORM_RISCV_ISA = $(OPENSBI_CC_ISA)
  endif
endif

此时编译通过,生成

 OBJCOPY   platform/generic/firmware/payloads/test.bin
 OBJCOPY   platform/generic/firmware/fw_dynamic.bin
 OBJCOPY   platform/generic/firmware/fw_jump.bin
 OBJCOPY   platform/generic/firmware/fw_payload.bin