跳到主要内容
版本:2.9.0

BSP 开发指南

简介

本文主要介绍了 BSP 编译、系统启动和烧录流程。通过该文档,用户可以不依赖 Buildroot 构建,自定义 U-Boot、OpenSBI、Kernel,快速输出 Bootloader 和 Bootfs,构建新的 Linux 系统。

启动流程及镜像介绍

启动流程遵循 RISC-V 官方规范。

<-------------( M-mode )----------><----------( S-mode )------->
+------------+ +--------------+ +----------+ +----------+
| U-Boot SPL |-->| SBI firmware |--->| U-Boot |-->| OS |
+------------+ +--------------+ +----------+ +----------+

以 A210 芯片为例,完整的启动流程和对应的镜像和资源如下图所示。 启动流程

SDK 提供镜像中存储分区布局与镜像文件分配说明如下图所示。

  1. emmc_boot-loader.img 包含的固件可启动 U-Boot。
  2. U-Boot 继续从 emmc-boot_a.img 中加载 Kernel,进入系统。

分区和镜像

编译

编译操作

执行以下命令,生成镜像。编译完成后镜像输出至 build_bsp/Release 目录下,镜像资源内容与预编译镜像相同,具体资源介绍请参考快速上手

# 获取编译脚本
wget http://developer.zhcomputing.com/downloads/release/zhihesdk/v2.9.0/build.sh -O build.sh && chmod +x ./build.sh

# 下载Builldroot相关资源
./build.sh -s buildroot

# 下载发布的镜像,使用里面已经构建好的rootfs来测试重新编译的BSP
./build.sh -s buildroot-image

# 重新编译BSP,生成启动和烧写镜像
./build.sh -b bsp a210

说明:

  • 关于基础开发环境的搭建,请参考快速上手

  • 执行build.sh -s 脚本后下载资源列表如下:

    资源说明
    images发布的预编译镜像目录
    zhihesdkSDK 扩展的驱动和库目录
    kernel/opensib/u-boot源代码
    buildroot并不编译,但其中的分区表将被使用

build_bsp 函数介绍

执行./build.sh -b bsp a210 命令后会调用 build.sh 中 build_bsp 函数。

build_bsp() {
if [ -z "$1" ];then
echo "${TIP_BR_DEFCONFIG_LIST}"
return 0
fi

# 编译环境变量配置
export CROSS_COMPILE=riscv64-unknown-linux-gnu-
export ARCH=riscv
export PLATFORM=generic

SOC_NAME=$1

# 资源准备
# ZHIHESDK_TGZ:包含二级制发布的引导固件和非Kernel编译的驱动
# BR2IMAGE_TGZ:预编译的bootroot镜像,提供rootfs用于系统启动的测试
if [ "${SOC_NAME}" = "a210" ]; then
ZHIHESDK_TGZ=zhihesdk-local-a210_evb.tar.gz
BR2IMAGE_TGZ=br2image-${VERSION_NAME}-a210_evb.tar.gz
UBOOT_DEFCONFIG=a210_evb_defconfig
KERNEL_DEFCONFIG=a210_evb_defconfig
GPT_TYPE=gpt32g
elif [ "${SOC_NAME}" = "a200" ]; then
ZHIHESDK_TGZ=zhihesdk-local-a200_evb.tar.gz
BR2IMAGE_TGZ=br2image-${VERSION_NAME}-a200_evb.tar.gz
UBOOT_DEFCONFIG=a200_evb_defconfig
KERNEL_DEFCONFIG=th1520_defconfig
GPT_TYPE=gpt32g
fi

echo ">>> 1. Cleanup Release"
rm -fr build_bsp/Release
mkdir -p build_bsp/Release
echo " Done"

# 解压zhihesdk,主要使用boot目录中的二进制固件
echo ">>> 2. Prepare zhihesdk: ${ZHIHESDK_TGZ}"
if hash_fail zhihesdk/${ZHIHESDK_TGZ} build_bsp/.zhihesdk.hash; then
rm -fr build_bsp/zhihesdk/
mkdir -p build_bsp/zhihesdk/
tar -xf zhihesdk/${ZHIHESDK_TGZ} -C build_bsp/zhihesdk
echo " Done"
else
echo " Hash OK"
fi

# 解压预编译镜像,主要使用emmc-system_a.img文件
echo ">>> 3. Prepare test rootfs: ${BR2IMAGE_TGZ}"
if hash_fail images/${BR2IMAGE_TGZ} build_bsp/.br2image.hash; then
rm -fr build_bsp/diskimage
tar -xf images/${BR2IMAGE_TGZ} -C build_bsp/
echo " Done"
else
echo " Hash OK"
fi

# 编译U-Boot
echo ">>> 4. Build U-Boot: ${UBOOT_DEFCONFIG}"
if [ -z "$2" ] || [ "$2" = "u-boot" ] || [ ! -e u-boot/u-boot.img ]; then
make -C u-boot ${UBOOT_DEFCONFIG}
make -C u-boot -j
make -C u-boot u-boot.img
make -C u-boot envtools
echo " Done"
else
echo " Ignore"
fi

# 编译OpenSBI
echo ">>> 5. Build OpenSBI"
if [ -z "$2" ] || [ "$2" = "opensbi" ] || [ ! -e opensbi/build/platform/generic/firmware/fw_dynamic.bin ]; then
make -C opensbi -j
echo " Done"
else
echo " Ignore"
fi

# 编译Kernel
echo ">>> 6. Build Linux Kernel: ${KERNEL_DEFCONFIG}"
if [ -z "$2" ] || [ "$2" = "kernel" ] || [ ! -e kernel/arch/riscv/boot/Image ]; then
make -C kernel ${KERNEL_DEFCONFIG}
LOCALVERSION= make -C kernel -j
echo " Done"
else
echo " Ignore"
fi

# 生成分区表
# Generate GPT(emmc-gpt_primary.img)
echo ">>> 7. Create GPT (emmc-gpt_primary.img)"
rm -fr build_bsp/gpt
mkdir -p build_bsp/gpt
cp buildroot/board/zhihe/common/images/${GPT_TYPE}/* build_bsp/gpt/
gen_gpt_image build_bsp/gpt
cp build_bsp/gpt/gpt.img build_bsp/Release/emmc-gpt_primary.img
cp build_bsp/gpt/output.yml build_bsp/Release/
cp u-boot/board/zhihe/common/script/fastboot_images.* build_bsp/Release/
echo " Done"

# 生成U-Boot环境变量镜像
# Generate emmc-uboot_env.img
echo ">>> 8. Create U-Boot ENV (emmc-uboot_env.img)"
UBOOT_ENV_SIZE=$(grep CONFIG_ENV_SIZE u-boot/.config | cut -d'=' -f2)
u-boot/tools/mkenvimage -s ${UBOOT_ENV_SIZE} -o build_bsp/Release/emmc-uboot_env.img u-boot/u-boot-initial-env
echo " Done"

# 生成ITB包,SPL解析ITB并把所有内容加载到DDR中
# Generate fit_target/riscv-boot.itb
echo ">>> 9. Create Fit image (riscv-boot.itb)"
gen_fit_image ${SOC_NAME}

# 把引导固件、SPL、ITB打包为一个镜像文件
# Generate loader image(emmc_boot-loader.img & fastboot image)
echo ">>> 10. Create Loader image(emmc_boot-loader.img)"
gen_loader_image ${SOC_NAME}
cp build_bsp/loader_target/btz-with-uboot-rvbl.bin build_bsp/Release/emmc_boot-loader.img
cp build_bsp/loader_target/spl-with-fit-rvbl.bin build_bsp/Release/
cp build_bsp/zhihesdk/rootfs/boot/bootzero-rvbl.bin build_bsp/Release 2>/dev/null || true
echo " Done"

# 生成Bootfs,包含Image,dtb,initramfs
# Generate boofs image(emmc-boot_a.img)
echo ">>> 11. Create BootFS (emmc-boot_a.img)"
gen_bootfs_image
echo " Done"

# 复制预编译镜像中的rootfs
# app_a和data为空镜像,主要用于格式化分区
# Prepare Sample rootfs
echo ">>> 12. Prepare Sample rootfs"
cp build_bsp/diskimage/emmc-app_a.img build_bsp/Release
cp build_bsp/diskimage/emmc-system_a.img build_bsp/Release
cp build_bsp/diskimage/emmc-data.img build_bsp/Release
echo " Done"
}

烧录

烧录分为两个阶段。具体流程如下图所示。

  1. 由 Bootrom 实现,通过 Fastboot 命令加载并运行 U-Boot。
  2. 由 U-Boot 实现,利用 U-Boot 启动后提供的 Fastboot 功能,完成后续镜像烧录。

烧写流程

以 Linux 烧录脚本为例介绍,该脚本与 Windows 的批处理脚本流程一致。

#!/bin/sh
FAIL="###### Images flashing failed ######"
if [ -n "$1" ]; then
device="-s $1"
fi
# 阶段判断,若是bootrom阶段,需要加载uboot
if fastboot ${device} getvar product 2>&1 | grep -q "product: a2"; then
echo "###### Start flash images ######"
else
echo "###### Start the flashing tool"
# A200没有bootzero阶段
if [ -e bootzero-rvbl.bin ]; then
fastboot ${device} flash ram bootzero-rvbl.bin || { echo $FAIL; exit 1; }
fastboot ${device} reboot
fastboot ${device} flash ram spl-with-fit-rvbl.bin || { echo $FAIL; exit 1; }
fastboot ${device} reboot
else
fastboot ${device} flash ram spl-with-fit-rvbl.bin || { echo $FAIL; exit 1; }
fastboot ${device} reboot
fi
echo "###### Wait for the flashing tool to be ready"
sleep 5
fi
# 以下是逐个烧写eMMC每个分区的镜像
echo "###### Flash gpt"
fastboot ${device} flash gpt emmc-gpt_primary.img || { echo $FAIL; exit 1; }
echo "###### Flash loader"
fastboot ${device} flash mmc0boot0 emmc_boot-loader.img || { echo $FAIL; exit 1; }
echo "###### Flash partition uboot_env"
fastboot ${device} flash uboot_env emmc-uboot_env.img || { echo $FAIL; exit 1; }
echo "###### Flash partition boot"
fastboot ${device} flash boot emmc-boot_a.img || { echo $FAIL; exit 1; }
echo "###### Flash partition system"
fastboot ${device} flash system emmc-system_a.img || { echo $FAIL; exit 1; }
echo "###### Flash partition app"
fastboot ${device} flash app emmc-app_a.img || { echo $FAIL; exit 1; }
echo "###### Flash partition data"
fastboot ${device} flash data emmc-data.img || { echo $FAIL; exit 1; }
echo "###### Images flashed success ######"

启动 Kernel

U-Boot 启动 Kernel

加载过程代码在 u-boot/include/configs/a210-evb.h 中。ext4load 命令从 Bootfs 中加载 dtb、kernel、initrd 固件到DDR,调用 booti 启动 Kernel。

#ifdef CONFIG_RISCV_SMODE
#define BOOT_FIT \
"loadfdt=ext4load ${boot_device} ${dtb_addr} ${fdt_file}\0" \
"loadkernel=ext4load ${boot_device} ${kernel_addr} ${kernel_file}\0" \
"loadinitrd=ext4load ${boot_device} ${initrd_addr} ${initrd_file}; setenv initrd_size $filesize\0" \
"load_image=run loadkernel; run loadinitrd\0" \
"bootcmd=run select_slot; run load_image; bootaon; bootslave; run set_bargs_pre; setenv bootargs ${barg_pre}; booti $kernel_addr $initrd_addr:$initrd_size $dtb_addr;\0" \
"altbootcmd=run rollback; run rollback_finish; reset;\0"
#else
#define BOOT_FIT
#endif

SDK 使用的 Rootfs 采用 Overlay 方式,需要加载 initrd。如果直接使用 ext4,可以修改 booti 参数跳过 initrd。

booti $kernel_addr - $dtb_addr;

加载设备树(DTB 文件)

RISC-V 的启动流程需要加载 OpenSBI,而 OpenSBI 需要对所有的 CPU 核进行初始化,所以 SDK 采用了把 CPU 的初始化定义在 Kernel 的 DTB 文件中。这样 CPU 完全可以有 Kernel 来配置,但也引入了一些特殊处理,需要注意。

  • 启动镜像中的 ITB 包(FIT 包)中的 DTB 文件是 Kernel 输出的。
  • 为了解决 Bootfs 单独更新,可能导致 DTB 和 Kenel 不匹配问题,FIT 包加载后,会再次从 Bootfs 加载 DTB 文件。

设备树加载

切换设备树

如果 Kernel 中编译了多个设备树文件,需要临时修改测试可进入 uboot,修改环境变量。

env set dtb_file a210-dev.dtb
env save
reset # 由于覆盖加载在SPL阶段,必须重启生效