定制 SoC 的 U-Boot SPL image type 类型

开发板 SoC 系统上电启动时,从 boot rom 开始运行、并加载后续程序;被加载的通常是 U-Boot SPL,它运行并加载其他后续的程序。按先后顺序,一般是这样的:boot rom --> U-Boot SPL --> sbi --> U-Boot --> Linux Kernel --> .. 其中,U-Boot SPL 是 Secondary Program Loader 的缩写,这个名字很明确地说明了它的启动时机。当然,除了最开始的 boot rom 是固化的,从第二步开始,只要符合 SoC 的设计约定,可以替换成其他程序。

是否需要 SPL 自定义类型

对于新研发的 SoC 来说,需要实现 SPL 对板子的支持。

如果通用的代码基本能满足 SoC 板子的要求,那么对于为数不多的额外设置,可以在 spl_board_init_f 等处添加适当的回调,满足 boot rom 以及其他的要求即可。

但如果板子相关要求较多,配置复杂,或者其他一些实际的需求,比如 boot rom 需要验证后续程序是否符合要求,等等,那还是需要自定义打包类型,从而把相关功能以及实现代码归拢在一起,保持 U-Boot 代码结构,方便项目的开发和维护。以下记录了为 SoC 定制 SPL 镜像类型的过程。U-Boot 源码基于 2021-10 版本。

Image Types

镜像程序的类型定义。除了多种标准类型外,还包括了很多厂商自定义的类型。所以需要这样修改,大概是这样的:

文件:include/image.h

         IH_TYPE_SOCFPGAIMAGE_V1,        /* Altera SOCFPGA A10 Preloader */
         IH_TYPE_MTKIMAGE,               /* MediaTek BootROM loadable Image */
         IH_TYPE_IMX8MIMAGE,             /* Freescale IMX8MBoot Image    */
         IH_TYPE_IMX8IMAGE,              /* Freescale IMX8Boot Image     */
         IH_TYPE_COPRO,                  /* Coprocessor Image for remoteproc*/
         IH_TYPE_SUNXI_EGON,             /* Allwinner eGON Boot Image */
+        IH_TYPE_XXX,                    /* XXX Boot Image */

         IH_TYPE_COUNT,                  /* Number of image types */
 };

 /*
  * Compression Types

表格里类型相关的入口

类型相关的名字定义,用于查找和调试打印。是这样定义的:

/*
 * Translation table for entries of a specific type; used by
 * get_table_entry_id() and get_table_entry_name().
 */
typedef struct table_entry {
        int     id;
        char    *sname;         /* short (input) name to find table entry */
        char    *lname;         /* long (output) name to print for messages */
} table_entry_t;

所以大概这样修改:

文件:common/image.c

         {       IH_TYPE_FIRMWARE_IVT, "firmware_ivt", "Firmware with HABv4 IVT" },
         {       IH_TYPE_PMMC,        "pmmc",        "TI Power Management Micro-Controller Firmware",},
         {       IH_TYPE_STM32IMAGE, "stm32image", "STMicroelectronics STM32 Image" },
         {       IH_TYPE_MTKIMAGE,   "mtk_image",   "MediaTek BootROM loadable Image" },
         {       IH_TYPE_COPRO, "copro", "Coprocessor Image"},
         {       IH_TYPE_SUNXI_EGON, "sunxi_egon",  "Allwinner eGON Boot Image" },
+        {       IH_TYPE_XXX,    "xxx",     "XXX Boot Image" },
         {       -1,                 "",           "",                   },
 };

 static const table_entry_t uimage_comp[] = {
         {       IH_COMP_NONE,   "none",         "uncompressed",         },
         {       IH_COMP_BZIP2,  "bzip2",        "bzip2 compressed",     },

打包镜像文件的实现

镜像文件的打包格式需要按照 SoC 里 boot rom 的要求来实现,包括各种参数的设置、验证,等等。

按照 U-Boot 的开发格式,代码文件放在这里:tools/xxx.c。代码就不贴了,这里说一下大概的实现要求和过程:

定义文件头格式:对于自定义的镜像格式,定义文件头是有必要的。其中需要包括版本信息、实际文件大小、CRC 校验值、加载目标地址、硬件相关的设置参数,等等。当然,还有一些本身的特殊标志值。这些都是 boot rom 在加载时所需要知道的。

定义一些回调函数:

  1. 检查文件头是否完整: xxx_verify_header
  2. 打印文件头以及镜像文件相关信息:xxx_print_header
  3. 设置文件头:xxx_set_header。包括计算并设置每一个字段。有一个有说特别说一下,Boot rom 在跳转过来以后,需要知道从哪里开始运行。考虑到文件头在镜像文件起始的位置,一种做法是文件头里指明运行起始地址;也可以不指明,从默认起始地址开始,这时此处需要硬编码一条跳转指令,跳过文件头,打包程序需要实现这个逻辑。
  4. 检查文件头类型:xxx_check_image_type,确保加载镜像文件符合规范;
  5. 其他 ……

把这些信息统一注册(具体的结构格式,可以进一步参考 struct image_type_params 的定义,在文件 tools/imagetool.h 里):

U_BOOT_IMAGE_TYPE(
        XXX,
        "XXXBoot Image",
        sizeof(struct xxx_file_head),
        NULL,
        xxx_check_params,
        xxx_verify_header,
        xxx_print_header,
        xxx_set_header,
        NULL,
        xxx_check_image_type,
        NULL,
        xxx_vrec_header
);

并修改文件 tools/Makefile,加入编译:

 # common objs for dumpimage and mkimage
 dumpimage-mkimage-objs := aisimage.o \
     ...
     ...
     $(ROCKCHIP_OBS) \
     socfpgaimage.o \
     sunxi_egon.o \
+    xxx.o \
     lib/crc16.o \
     lib/hash-checksum.o \
     lib/sha1.o \

生成镜像文件

有了打包工具,接下来是使用。

修改文件:scripts/Makefile.spl

ifdef CONFIG_TARGET_XXX
INPUTS-y        += $(obj)/u-boot-spl-xxx.bin
endif

MKIMAGEFLAGS_u-boot-spl-xxx.bin = -T xxx -e $(CONFIG_SPL_TEXT_BASE)

$(obj)/u-boot-spl-xxx.bin: $(obj)/u-boot-spl.bin FORCE
        $(call if_changed,mkimage)

如果把这里的依赖关系展开来看,实际调用了 ./tools/mkimage 打包工具,通过 -T 选项指定新实现的类型,以及 -e 选项指定代码入口。这样,当启用 CONFIG_TARGET_XXX 时,在编译生成默认的 u-boot-spl.bin 后,会进一步打包并生成成 u-boot-spl-xxx.bin

这两个文件的关系: file head + u-boot-spl.bin => u-boot-spl-xxx.bin,即:

+-------------------+  -
| file head ... ... |   \
+-------------------+    \
|                   |     \
|                   |      >>>  u-boot-spl-xxx.bin
| u-boot-spl.bin    |     /
|                   |    /
|                   |   /
+-------------------+  -

Read More: