milk-v duo原厂Linux_SDK编译流程

本文主要介绍 milk-v duo原厂Linux_SDK 编译生成 fip.bin 流程。
原厂相关的简单介绍链接:CV18(1x/0x) 裸烧与非裸烧升级使用手册 — CvitekBareandNon-BareProcessorBurningUpgradeOperationGuide master 文档

1、编译前准备

新版本 SDK 通过 build_milkv.sh 脚本来实现一键编译。

MILKV_BOARD_CONFIG=${MILKV_BOARD_DIR}/boardconfig-${MILKV_BOARD}.sh

function prepare_env()
{
  source ${MILKV_BOARD_CONFIG}

  source build/${MV_BUILD_ENV} > /dev/null 2>&1
  defconfig ${MV_BOARD_LINK} > /dev/null 2>&1

  echo "OUTPUT_DIR: ${OUTPUT_DIR}"  # @build/milkvsetup.sh
}

其中: - ${MV_BOARD_CPU}=cv1800b - ${MV_VENDOR}=milkv - ${MILKV_BOARD_DIR}=milkv - ${MILKV_BOARD}=milkv-duo - ${MV_BUILD_ENV}=milkvsetup.sh - ${MV_BOARD_LINK}=cv1800b_milkv_duo_sd - OUTPUT_DIR: /home/share/samba/risc-v/duo-buildroot-sdk/install/soc_cv1800b_milkv_duo_sd

这些变量后续都会用到

README.md 中也有介绍分步编译介绍

export MILKV_BOARD=milkv-duo
source milkv/boardconfig-milkv-duo.sh

source build/milkvsetup.sh
defconfig cv1800b_milkv_duo_sd
clean_all
build_all
pack_sd_image

生成的固件位置: install/soc_cv1800b_milkv_duo_sd/milkv-duo.img。build_milkv.sh 也是一步一步调用这些函数。

2、编译入口

总的编译命令,可以不调用 clean_all 函数,不然每次都要全编译。

function milkv_duo_build()
{
  # clean old img
  old_image_count=`ls ${OUTPUT_DIR}/*.img* | wc -l`
  if [ ${old_image_count} -ge 0 ]; then
    pushd ${OUTPUT_DIR}
    rm -rf *.img*
    popd
  fi

  clean_all
  build_all
  if [ $? -eq 0 ]; then
    print_info "Build board ${MILKV_BOARD} success!"
  else
    print_err "Build board ${MILKV_BOARD} failed!"
    exit 1
  fi
}

重点看一下 build_all 函数,位于 build/milkvsetup.sh 文件中

function build_all()
{
  # build bsp
  build_uboot || return $?
  build_kernel || return $?
  build_middleware || return $?
  pack_access_guard_turnkey_app || return $?
  pack_ipc_turnkey_app || return $?
  pack_boot || return $?
  pack_cfg || return $?
  pack_rootfs || return $?
  pack_data
  pack_system || return $?
  copy_tools
  pack_upgrade
}

根据上面函数,先调用 build_uboot 函数。

3、uboot编译

  1. build_uboot 函数在 build/cvisetup.sh 文件中定义
function build_uboot()
{(
  print_notice "Run ${FUNCNAME[0]}() function"
  _build_uboot_env
  _build_opensbi_env
  _link_uboot_logo

  cd "$BUILD_PATH" || return
  [[ "$CHIP_ARCH" == CV182X ]] || [[ "$CHIP_ARCH" == CV183X ]] && \
    cp -f "$OUTPUT_DIR"/fip_pre/fip_pre_${ATF_KEY_SEL}.bin \
    "$OUTPUT_DIR"/fip_pre/fip_pre.bin

  make u-boot
)}
  1. 查看 build/Makefile 文件中 203 行 u-boot 依赖 u-boot-dep
u-boot: u-boot-dep

继续找 u-boot-dep 。。。(文件真多啊。。。) 根据 build/.config 中定义了 CONFIG_FIP_V2=y 找到 scripts/fip_v2.mk 文件 40 行

ifeq (${CONFIG_FIP_V1},y)
include scripts/fip_v1.mk
else ifeq (${CONFIG_FIP_V2},y)
include scripts/fip_v2.mk
else
$(error no fip version)
endif

flp_v2.mk

u-boot-dep: fsbl-build ${OUTPUT_DIR}/elf
  $(call print_target)
ifeq ($(call qstrip,${CONFIG_ARCH}),riscv)
  ${Q}cp ${OPENSBI_PATH}/build/platform/generic/firmware/fw_payload.bin ${OUTPUT_DIR}/fw_payload_uboot.bin
  ${Q}cp ${OPENSBI_PATH}/build/platform/generic/firmware/fw_payload.elf ${OUTPUT_DIR}/elf/fw_payload_uboot.elf
endif
  1. u-boot-dep 又依赖 fsbl-build fsbl-build 定义在 scripts/fip_v2.mk 文件 28 行
ifeq ($(call qstrip,${CONFIG_ARCH}),riscv)
fsbl-build: opensbi
endif

ifeq (${CONFIG_ENABLE_FREERTOS},y)
fsbl-build: rtos
fsbl%: export BLCP_2ND_PATH=${FREERTOS_PATH}/cvitek/install/bin/cvirtos.bin
fsbl%: export RTOS_DUMP_PRINT_ENABLE=$(CONFIG_ENABLE_RTOS_DUMP_PRINT)
fsbl%: export RTOS_DUMP_PRINT_SZ_IDX=$(CONFIG_DUMP_PRINT_SZ_IDX)
fsbl%: export RTOS_FAST_IMAGE_TYPE=${CONFIG_FAST_IMAGE_TYPE}
fsbl%: export RTOS_ENABLE_FREERTOS=${CONFIG_ENABLE_FREERTOS}
endif
fsbl%: export FSBL_SECURE_BOOT_SUPPORT=${CONFIG_FSBL_SECURE_BOOT_SUPPORT}
fsbl%: export ARCH=$(call qstrip,${CONFIG_ARCH})
fsbl%: export OD_CLK_SEL=${CONFIG_OD_CLK_SEL}
fsbl%: export VC_CLK_OVERDRIVE=${CONFIG_VC_CLK_OVERDRIVE}

fsbl-build: u-boot-build memory-map
  $(call print_target)
  ${Q}mkdir -p ${FSBL_PATH}/build
  ${Q}ln -snrf -t ${FSBL_PATH}/build ${CVI_BOARD_MEMMAP_H_PATH}
  ${Q}$(MAKE) -j${NPROC} -C ${FSBL_PATH} O=${FSBL_OUTPUT_PATH} BLCP_2ND_PATH=${BLCP_2ND_PATH} \
    LOADER_2ND_PATH=${UBOOT_PATH}/${UBOOT_OUTPUT_FOLDER}/u-boot-raw.bin
  ${Q}cp ${FSBL_OUTPUT_PATH}/fip.bin ${OUTPUT_DIR}/

又依赖 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

又依赖 u-boot-build

u-boot-build: memory-map
u-boot-build: u-boot-dts
u-boot-build: ${UBOOT_PATH}/${UBOOT_OUTPUT_FOLDER} ${UBOOT_CVIPART_DEP} ${UBOOT_OUTPUT_CONFIG_PATH}
  $(call print_target)
  ${Q}ln -snrf ${CVI_BOARD_MEMMAP_H_PATH} ${UBOOT_PATH}/include/
  ${Q}rm -f ${UBOOT_CVI_BOARD_INIT_PATH}
  ${Q}ln -s ${BUILD_PATH}/boards/${CHIP_ARCH_L}/${PROJECT_FULLNAME}/u-boot/cvi_board_init.c ${UBOOT_CVI_BOARD_INIT_PATH}
  ${Q}rm -f ${UBOOT_CVITEK_PATH}
  ${Q}ln -s ${BUILD_PATH}/boards/${CHIP_ARCH_L}/${PROJECT_FULLNAME}/u-boot/cvitek.h ${UBOOT_CVITEK_PATH}
  ${Q}$(MAKE) -j${NPROC} -C ${UBOOT_PATH} olddefconfig
  ${Q}$(MAKE) -j${NPROC} -C ${UBOOT_PATH} all
  ${Q}cat ${UBOOT_PATH}/${UBOOT_OUTPUT_FOLDER}/u-boot.bin > ${UBOOT_PATH}/${UBOOT_OUTPUT_FOLDER}/u-boot-raw.bin

然后依赖 u-boot-dts

u-boot-dts:
  $(call print_target)
ifeq ($(UBOOT_SRC), u-boot-2021.10)
# U-boot doesn't has arch/arm64
ifeq ($(ARCH), arm64)
  ${Q}find ${BUILD_PATH}/boards/${CHIP_ARCH_L} \
    \( -path "*linux/*.dts*" -o -path "*dts_${ARCH}/*.dts*" \) \
    -exec cp {} ${UBOOT_PATH}/arch/arm/dts/ \;
  ${Q}find ${DTS_DEFATUL_PATHS} -name *.dts* -exec cp {} ${UBOOT_PATH}/arch/arm/dts/ \;
else
  ${Q}find ${BUILD_PATH}/boards/${CHIP_ARCH_L} \
    \( -path "*linux/*.dts*" -o -path "*dts_${ARCH}/*.dts*" \) \
    -exec cp {} ${UBOOT_PATH}/arch/${ARCH}/dts/ \;
  ${Q}find ${DTS_DEFATUL_PATHS} -name *.dts* -exec cp {} ${UBOOT_PATH}/arch/${ARCH}/dts/ \;
endif
endif

最终执行的命令为:

find /home/share/samba/risc-v/duo-buildroot-sdk/build/boards/cv180x \
  \( -path "*linux/*.dts*" -o -path "*dts_riscv/*.dts*" \) \
  -exec cp {} /home/share/samba/risc-v/duo-buildroot-sdk/u-boot-2021.10/arch/riscv/dts/ \;
find /home/share/samba/risc-v/duo-buildroot-sdk/build/boards/default/dts/cv180x /home/share/samba/risc-v/duo-buildroot-sdk/build/boards/default/dts/cv180x_riscv -name *.dts* -exec cp {} /home/share/samba/risc-v/duo-buildroot-sdk/u-boot-2021.10/arch/riscv/dts/ \;

/home/share/samba/risc-v/duo-buildroot-sdk/u-boot-2021.10/arch/riscv/dts 目录下执行 tree 命令,目录下 cv180x 和 cv1801c 开头的文件都是从新 copy 过来的。我们理论上用的是 cv1800b 相关的 cv1800b_milkv_duo_sd.dts 这个文件。

$ tree
.
|-- Makefile
|-- ae350-u-boot.dtsi
|-- ae350_32.dts
|-- ae350_64.dts
|-- binman.dtsi
|-- cv1800b_milkv_duo_sd.dts
|-- cv1800b_sophpi_duo_sd.dts
|-- cv1800b_wdmb_0008a_spinor.dts
|-- cv1800b_wevb_0008a_spinor.dts
|-- cv1800c_wevb_0009a_spinor.dts
|-- cv1801b_wevb_0008a_spinor.dts
|-- cv1801c_wdmb_0009a_spinor.dts
|-- cv1801c_wevb_0009a_spinand.dts
|-- cv1801c_wevb_0009a_spinor.dts
|-- cv180x_asic_bga.dtsi
|-- cv180x_asic_qfn.dtsi
|-- cv180x_asic_sd.dtsi
|-- cv180x_asic_spinand.dtsi
|-- cv180x_asic_spinor.dtsi
|-- cv180x_base.dtsi
|-- cv180x_base_riscv.dtsi
|-- cv180x_default_memmap.dtsi
|-- cv180x_fpga.dts
|-- cv180x_palladium.dts
|-- cv180zb_wdmb_0008a_spinor.dts
|-- cv180zb_wevb_0008a_spinor.dts
|-- fu540-c000-u-boot.dtsi
|-- fu540-c000.dtsi
|-- fu540-hifive-unleashed-a00-ddr.dtsi
|-- fu740-c000-u-boot.dtsi
|-- fu740-c000.dtsi
|-- fu740-hifive-unmatched-a00-ddr.dtsi
|-- hifive-unleashed-a00-u-boot.dtsi
|-- hifive-unleashed-a00.dts
|-- hifive-unmatched-a00-u-boot.dtsi
|-- hifive-unmatched-a00.dts
|-- k210-maix-bit.dts
|-- k210.dtsi
|-- microchip-mpfs-icicle-kit-u-boot.dtsi
|-- microchip-mpfs-icicle-kit.dts
|-- openpiton-riscv64.dts
`-- qemu-virt.dts
  • 执行到这里,才真正开始编译 u-boot,主要是以下几行命令
${Q}$(MAKE) -j${NPROC} -C ${UBOOT_PATH} olddefconfig
  ${Q}$(MAKE) -j${NPROC} -C ${UBOOT_PATH} all
  ${Q}cat ${UBOOT_PATH}/${UBOOT_OUTPUT_FOLDER}/u-boot.bin > ${UBOOT_PATH}/${UBOOT_OUTPUT_FOLDER}/u-boot-raw.bin

最终产物在 /home/share/samba/risc-v/duo-buildroot-sdk/u-boot-2021.10/build/cv1800b_milkv_duo_sd/u-boot-raw.bin

4、opensbi 编译

有了 u-boot 之后,开始编译 opensbi

为了兼容不同的运行需求,OpenSBI 支持三种类型的 Firmware,分别为: - dynamic:从上一级 Boot Stage 获取下一级 Boot Stage 的入口信息,以 struct fw_dynamic_info 结构体通过 a2 寄存器传递。 - jump:假设下一级 Boot Stage Entry 为固定地址,直接跳转过去运行。 - payload:在 jump 的基础上,直接打包进来下一级 Boot Stage 的 Binary。下一级通常是 Bootloader 或 OS,比如 U-Boot,Linux。

相关编译脚本位置: build/scripts/fip_v2.mk

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

根据以上脚本获知,milk-v duo 上采用的是 payload 模式,payload 文件为 u-boot-raw.bin,就是上面我们编译出来的文件。 FW_FDT_PATH 为设备树路径,为 /home/share/samba/risc-v/duo-buildroot-sdk/u-boot-2021.10/build/cv1800b_milkv_duo_sd/arch/riscv/dts/cv1800b_milkv_duo_sd.dtb

编译完成后, 产物在 /home/share/samba/risc-v/duo-buildroot-sdk/opensbi/build/platform/generic/firmware 目录下。

5、fsbl编译

编译完了u-boot 和 opensbi,继续往回推,来到了 fsbl-build。(中间还有一个 rtos 编译,暂时不支持,略过先)。 FSBL 是 First Stage Boot Loader 的缩写。

fsbl-build: u-boot-build memory-map
  $(call print_target)
  ${Q}mkdir -p ${FSBL_PATH}/build
  ${Q}ln -snrf -t ${FSBL_PATH}/build ${CVI_BOARD_MEMMAP_H_PATH}
  ${Q}$(MAKE) -j${NPROC} -C ${FSBL_PATH} O=${FSBL_OUTPUT_PATH} BLCP_2ND_PATH=${BLCP_2ND_PATH} \
    LOADER_2ND_PATH=${UBOOT_PATH}/${UBOOT_OUTPUT_FOLDER}/u-boot-raw.bin
  ${Q}cp ${FSBL_OUTPUT_PATH}/fip.bin ${OUTPUT_DIR}/

做种执行的编译命令为

make -j8 -C /home/share/samba/risc-v/duo-buildroot-sdk/fsbl O=/home/share/samba/risc-v/duo-buildroot-sdk/fsbl/build/cv1800b_milkv_duo_sd BLCP_2ND_PATH=/home/share/samba/risc-v/duo-buildroot-sdk/freertos/cvitek/install/bin/cvirtos.bin \
  LOADER_2ND_PATH=/home/share/samba/risc-v/duo-buildroot-sdk/u-boot-2021.10/build/cv1800b_milkv_duo_sd/u-boot-raw.bin

主要来看看 fsbl 编译流程,从 fsbl/Makefile 文件开始

all: fip bl2 blmacros

include ${MAKE_HELPERS_DIRECTORY}fip.mk

依次找到 fip 在 fsbl/make_helpers/fip.mk 文件

gen-chip-conf:
  $(print_target)
  ${Q}./plat/${CHIP_ARCH}/chip_conf.py ${CHIP_CONF_PATH}

macro_to_env = ${NM} '${BLMACROS_ELF}' | awk '/DEF_${1}/ { rc = 1; print "${1}=0x" $1 } END { exit !rc }' >> ${BUILD_PLAT}/blmacros.env

blmacros-env: blmacros
  $(print_target)
  ${Q}> ${BUILD_PLAT}/blmacros.env  # clear .env first
  ${Q}$(call macro_to_env,MONITOR_RUNADDR)
  ${Q}$(call macro_to_env,BLCP_2ND_RUNADDR)

fip: fip-all

fip-dep: bl2 blmacros-env gen-chip-conf

fip-simple: fip-dep
  $(print_target)
  ${Q}echo "  [GEN] fip.bin"
  ${Q}${FIPTOOL} -v genfip \
    '${BUILD_PLAT}/fip.bin' \
    --CHIP_CONF='${CHIP_CONF_PATH}' \
    --NOR_INFO='${NOR_INFO}' \
    --NAND_INFO='${NAND_INFO}'\
    --BL2='${BUILD_PLAT}/bl2.bin'
  ${Q}echo "  [LS] " $(ls -l '${BUILD_PLAT}/fip.bin')

fip-all: fip-dep
  $(print_target)
  ${Q}echo "  [GEN] fip.bin"
  ${Q}. ${BUILD_PLAT}/blmacros.env && \
  ${FIPTOOL} -v genfip \
    '${BUILD_PLAT}/fip.bin' \
    --MONITOR_RUNADDR="${MONITOR_RUNADDR}" \
    --BLCP_2ND_RUNADDR="${BLCP_2ND_RUNADDR}" \
    --CHIP_CONF='${CHIP_CONF_PATH}' \
    --NOR_INFO='${NOR_INFO}' \
    --NAND_INFO='${NAND_INFO}'\
    --BL2='${BUILD_PLAT}/bl2.bin' \
    --BLCP_IMG_RUNADDR=${BLCP_IMG_RUNADDR} \
    --BLCP_PARAM_LOADADDR=${BLCP_PARAM_LOADADDR} \
    --BLCP=${BLCP_PATH} \
    --DDR_PARAM='${DDR_PARAM_TEST_PATH}' \
    --BLCP_2ND='${BLCP_2ND_PATH}' \
    --MONITOR='${MONITOR_PATH}' \
    --LOADER_2ND='${LOADER_2ND_PATH}' \
    --compress='${FIP_COMPRESS}'
  ${Q}echo "  [LS] " $(ls -l '${BUILD_PLAT}/fip.bin')

通过一堆复杂的操作。。。生成了一个 bl2.bin 文件。(正常情况下我们开发者也不会去修改这个玩意。。。)

主要关心 fip-all 这个操作。。。用于生成 fip.bin 文件。

6、fip.bin文件合成

查阅官方有限的资料,获知 fip.bin 为内含bootloader+uboot 的文件

为啥要这么干?官方的文档里面是这么说的:
由于原生u-boot编译出u-boot.bin 无法直接刻录到FLASH中。我们采取ARM Trusted Firmware Design中的Firmware Image Package (FIP)方式,将uboot.bin 封装在 FIP.bin里面。
https://doc.sophgo.com/cvitek-develop-docs/master/docs_latest_release/CV180x_CV181x/zh/01.software/OSDRV/U-boot_Porting_Development_Guide/build/html/3_U-boot_Transplant.html

通过 chatgpt 查询 fip 后得知:

FIP(Flexible Image Processor)文件是指存储在ARM架构的处理器设备上的固件映像文件。它主要用于引导和配置设备。FIP文件通常包含以下内容: 1. Trusted Firmware-A(TF-A):TF-A是ARM架构设备上的一个开源的可信固件。它负责设备的引导、安全检查和启动加载程序的执行。 2. U-Boot:U-Boot是一个开源的引导加载程序(bootloader),用于启动设备。它提供了引导设备的功能,并提供了配置和管理设备硬件的选项。 3. ARM Trusted Firmware(ATF):ATF是一组用于安全引导的固件,用于验证和启动设备上的其他软件组件(如操作系统)。 4. 其他组件:FIP文件还可以包含其他固件组件,如设备树(Device Tree)文件、TEE固件(如OP-TEE)、加密密钥和配置参数等。 FIP文件在ARM架构设备上很常见,它们提供了引导和初始化处理器的必要组件。这些文件由设备制造商提供,并根据特定设备、硬件配置和需求进行定制。

按照前面的分析,fip.bin 在 fsbl 最后阶段合成

fip-all: fip-dep
  $(print_target)
  ${Q}echo "  [GEN] fip.bin"
  ${Q}. ${BUILD_PLAT}/blmacros.env && \
  ${FIPTOOL} -v genfip \
    '${BUILD_PLAT}/fip.bin' \
    --MONITOR_RUNADDR="${MONITOR_RUNADDR}" \
    --BLCP_2ND_RUNADDR="${BLCP_2ND_RUNADDR}" \
    --CHIP_CONF='${CHIP_CONF_PATH}' \
    --NOR_INFO='${NOR_INFO}' \
    --NAND_INFO='${NAND_INFO}'\
    --BL2='${BUILD_PLAT}/bl2.bin' \
    --BLCP_IMG_RUNADDR=${BLCP_IMG_RUNADDR} \
    --BLCP_PARAM_LOADADDR=${BLCP_PARAM_LOADADDR} \
    --BLCP=${BLCP_PATH} \
    --DDR_PARAM='${DDR_PARAM_TEST_PATH}' \
    --BLCP_2ND='${BLCP_2ND_PATH}' \
    --MONITOR='${MONITOR_PATH}' \
    --LOADER_2ND='${LOADER_2ND_PATH}' \
    --compress='${FIP_COMPRESS}'
  ${Q}echo "  [LS] " $(ls -l '${BUILD_PLAT}/fip.bin')

转换成实际执行命令为:

. /home/share/samba/risc-v/duo-buildroot-sdk/fsbl/build/cv1800b_milkv_duo_sd/blmacros.env && \
./plat/cv180x/fiptool.py -v genfip \
  '/home/share/samba/risc-v/duo-buildroot-sdk/fsbl/build/cv1800b_milkv_duo_sd/fip.bin' \
  --MONITOR_RUNADDR="${MONITOR_RUNADDR}" \
  --BLCP_2ND_RUNADDR="${BLCP_2ND_RUNADDR}" \
  --CHIP_CONF='/home/share/samba/risc-v/duo-buildroot-sdk/fsbl/build/cv1800b_milkv_duo_sd/chip_conf.bin' \
  --NOR_INFO='FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF' \
  --NAND_INFO='00000000'\
  --BL2='/home/share/samba/risc-v/duo-buildroot-sdk/fsbl/build/cv1800b_milkv_duo_sd/bl2.bin' \
  --BLCP_IMG_RUNADDR=0x05200200 \
  --BLCP_PARAM_LOADADDR=0 \
  --BLCP=test/empty.bin \
  --DDR_PARAM='test/cv181x/ddr_param.bin' \
  --BLCP_2ND='/home/share/samba/risc-v/duo-buildroot-sdk/freertos/cvitek/install/bin/cvirtos.bin' \
  --MONITOR='../opensbi/build/platform/generic/firmware/fw_dynamic.bin' \
  --LOADER_2ND='/home/share/samba/risc-v/duo-buildroot-sdk/u-boot-2021.10/build/cv1800b_milkv_duo_sd/u-boot-raw.bin' \
  --compress='lzma'

最后分析一下启动流程