SPL 阶段
Artinchip 平台上的 SPL(Secondary Program Loader) 是第一级引导程序(FSBL, First Stage Boot Loader), 同时也是第二级程序加载器。
BROM -> SPL -> U-Boot -> Kernel
SPL 运行在 SRAM 中,其最重要的任务有两个:
- 完成 DDR,并且使能 Cache
- 加载和验证 U-Boot
在一些启动速度优化的方案中,也可以直接从 SPL 启动 Kernel。 本章节描述不同启动介质的 SPL 处理流程,以及安全启动的相关处理。
1. RISCV SPL
SPL BSS 的配置
CONFIG_SPL_TEXT_BASE=0x103100
CONFIG_SPL_SIZE_LIMIT=0x10000
#define CONFIG_SPL_MAX_SIZE (CONFIG_SPL_SIZE_LIMIT)
#define CONFIG_SPL_STACK (D211_SRAM_BASE + D211_SRAM_SIZE)
#define CONFIG_SPL_BSS_START_ADDR (CONFIG_SPL_TEXT_BASE + CONFIG_SPL_MAX_SIZE)
#define CONFIG_SPL_BSS_MAX_SIZE 0x00002000 /* 8 KiB */
即 BSS 从 0x113100 开始,后续还有 SPL STACK 的空间,HEAP 的空间。
对于 spl.aic 文件,在 spl.bin 的后面还存放着其他的资源数据,如果该数据需要在 SPL 阶段使用, 则需要注意,在 SPL 运行时,资源数据的区域与 BSS 区域不能重合,不然会有数据错误。
2. 启动流程
理解spl的启动流程,关键是设备树,设备驱动模型。关于设备树,请查看设备树相关章节,设备驱动模型的介绍如下:
2.1. uboot 设备驱动框架模型
> uclass <–> uclass_driver <–> udevice <–> driver <–> hardware
uclass表示管理某一个类别下的所有device;
uclass_driver表示对应uclass的ops集合。
2.2. uboot 设备驱动框架搭建的过程
- 创建udevice
- 应用uclass如果没有则匹配生成uclass
- udevice和uclass绑定
- uclass_driver和uclass绑定
- driver和udevice绑定
- device_probe执行,会触发uclass_driver调用driver函数
2.3. SPL RISCV 的启动整体流程
_start // arch/riscv/cpu/start.S
|-> save_boot_params // arch/riscv/mach-artinchip/lowlevel_init.S
| // BROM 跳转到 SPL 执行的时候,传递了一些参数,这里首先需要将这些参数保存起来
|
|-> csrw MODE_PREFIX(ie), zero // Disable irq
|-> li t1, CONFIG_SPL_STACK // 设置sp寄存器
|-> jal board_init_f_alloc_reserve // common/init/board_init.c
| // 预留初始 HEAP 的空间
| // 预留 GD 全局变量的空间
|
|-> jal board_init_f_init_reserve
| // common/init/board_init.c, init gd area
| // 此时 gd 在 SPL STACK 中。
|
|-> jal icache_enable // arch/riscv/cpu/c906/cache.c 使能指令高速缓存
|-> jal dcache_enable // 使能数据高速缓存
|
|-> jal debug_uart_init // drivers/serial/ns16550.c
| // 初始化调试串口,如果使能
|
|-> board_init_f // arch/riscv/lib/spl.c
| |-> spl_early_init() // common/spl/spl.c
| |-> spl_common_init(setup_malloc = true) // common/spl/spl.c
| |-> fdtdec_setup(); // lib/fdtdec.c 获取dtb的地址,并验证合法性
| | // 只对带有“u-boot,dm-pre-reloc”属性节点进行解析,初始化驱动模型的根节点,扫描设备树创建udevice,uclass
| |-> dm_init_and_scan(!CONFIG_IS_ENABLED(OF_PLATDATA)); // drivers/core/root.c
| |-> dm_init(); // driver model, initiate virtual root driver
| | |-> INIT_LIST_HEAD(DM_UCLASS_ROOT_NON_CONST); // 初始化uclass链表
| | |-> device_bind_by_name()
| | | | // drivers/core/device.c
| | | | // 加载"root_driver"name, gd->dm_root
| | | |-> lists_driver_lookup_name()
| | | | |-> ll_entry_start(struct driver, driver); // 获取driver table起始位置
| | | | |-> ll_entry_count(struct driver, driver); // 获取driver table长度
| | | | // drivers/core/lists.c
| | | | // 采用 U_BOOT_DRIVER(name) 声明的 driver,从driver table中获取struct driver数据
| | | |
| | | | // 初始化udevice 与对应的uclass,driver绑定
| | | |-> device_bind_common(); // drivers/core/device.c
| | | |-> uclass_get(&uc)
| | | | |-> uclass_find(id); // 判断对应的uclass是否存在
| | | | |-> uclass_add(id, ucp); // 如果不存在就创建
| | | | |-> lists_uclass_lookup(id); // 获取uclass_driver结构体数据
| | | |-> uclass_bind_device(dev) // uclass绑定udevice drivers/core/uclass.c
| | | |-> drv->bind(dev) // driver绑定udevice
| | | |-> parent->driver->child_post_bind(dev)
| | | |-> uc->uc_drv->post_bind(dev)
| | |
| | |-> device_probe(gd->dm_root) // drivers/core/device.c
| | |-> uclass_resolve_seq(dev) // 通过dtb解析获得设备差异数据
| | |-> uclass_pre_probe_device(dev); // probe前操作
| | |-> drv->probe(dev); // 执行driver的probe操作
| | |-> uclass_post_probe_device(dev); // probe后操作
| |
| |-> dm_scan(pre_reloc_only);
| | // 扫描和绑定由 U_BOOT_DEVICE 声明的驱动。
| | // 一般用在 SPL OF_PLATDATA 的情况
| |-> dm_scan_plat(pre_reloc_only);
| | |-> lists_bind_drivers(DM_ROOT_NON_CONST, pre_reloc_only);
| | |-> bind_drivers_pass(parent, pre_reloc_only);
| | |-> device_bind_by_name();
| |
| |-> dm_extended_scan(pre_reloc_only);
| | |-> dm_scan_fdt(pre_reloc_only); // 扫描设备树并与设备驱动建立联系
| | | |-> dm_scan_fdt_node(gd->dm_root, ofnode_root(), pre_reloc_only); //扫描设备树并绑定root节点下的设备
| | | |-> ofnode_first_subnode(parent_node) // 获取设备树的第一个子节点
| | | |-> ofnode_next_subnode(node) // 遍历所有的子节点
| | | |-> ofnode_is_enabled(node) // 判断设备树的子节点是否使能
| | | |-> lists_bind_fdt(parent, node, NULL, pre_reloc_only); // 绑定设备树节点,创建新的udevicd drivers/core/lists.c
| | | |-> ofnode_get_property(node, "compatible", &compat_length); // 获取compatible
| | | |-> driver_check_compatible() // 和driver比较compatible值
| | | |-> device_bind_with_driver_data() // 创建一个设备并绑定到driver drivers/core/device.c
| | | |-> device_bind_common() // 创建初始化udevice 与对应的uclass,driver绑定
| | |
| | | // /chosen /clocks /firmware 一些节点本身不是设备,但包含一些设备,遍历其包含的设备
| | |-> dm_scan_fdt_ofnode_path(nodes[i], pre_reloc_only);
| | |-> ofnode_path(path); // 找到节点下包含的设备
| | |-> dm_scan_fdt_node(gd->dm_root, node, pre_reloc_only);
| |
| |-> dm_scan_other(pre_reloc_only);
| | // 扫描使用者自定义的节点 nothing
|
|-> spl_clear_bss // arch/riscv/cpu/start.S
|-> spl_relocate_stack_gd // 切换stack 和 gd 到dram空间
|-> board_init_r() // common/spl/spl.c
|-> spl_set_bd() // board data info
| // 设置完 bd 之后,才能 enable d-cache
|-> mem_malloc_init()
| // init heap
| // - CONFIG_SYS_SPL_MALLOC_START
| // - CONFIG_SYS_SPL_MALLOC_SIZE>
|
|-> spl_init
| |-> spl_common_init
| // 由于前面已经调用了 spl_early_init,
| // 这里不再调用 spl_common_init
|
|-> timer_init(); // lib/time.c nothing
|-> spl_board_init(); // arch/riscv/mach-artinchip/spl.c nothing
|
|-> initr_watchdog // enable watchdog,如果使能
|-> dram_init_banksize(); // 如果使能
|-> board_boot_order() // common/spl/spl.c
| |-> spl_boot_device(); // arch/riscv/mach-artinchip/spl.c
| |-> aic_get_boot_device(); // arch/riscv/mach-artinchip/boot_param.c
| // 从 boot param 中获取启动介质信息
|
|-> boot_from_devices(spl_boot_list)
| |-> spl_ll_find_loader() // 根据boot device找到spl_load_image指针
| | // 这里可能是各种介质的 load image 函数
| | // SPL_LOAD_IMAGE_METHOD() 定义的 Loader
| | // 可能是 MMC/SPI/BROM/...
| |
| |-> spl_load_image // 以emmc启动为例
| |-> spl_mmc_load_image // common/spl/spl_mmc.c
| |-> spl_mmc_load // 具体可看后面的流程
|
|-> spl_perform_fixups // vendor hook,用于修改device-tree传递参数
|-> spl_board_prepare_for_boot // vendor hook, 可不实现
|-> jump_to_image_no_args // 跳转到u-boot执行
3. MMC 加载
SPL 从 MMC 加载 U-Boot 的处理过程。
程序编码的时候,针对 MMC 设备添加了对应的加载程序支持,如 spl_mmc.c 中,通过使用宏:
SPL_LOAD_IMAGE_METHOD(“MMC1”, 0, BOOT_DEVICE_MMC1, spl_mmc_load_image);
将 spl_mmc_load_image
函数添加到 .u_boot_list_2_spl_image_loader_* 段。
在 SPL 初始化过程中,通过 boot_from_devices(spl_boot_list)
函数调用,检查当前项目 所支持的 SPL 读取的存储介质类型,然后依次检查是否存在对应的程序加载器。
board_init_r() // common/spl/spl.c
|-> boot_from_devices(spl_boot_list)
|-> spl_ll_find_loader() // 根据boot device找到spl_load_image指针
// 这里可能是各种介质的 load image 函数
// SPL_LOAD_IMAGE_METHOD() 定义的 Loader
// 可能是 MMC/SPI/BROM/...
找到 SPL MMC Loader 之后,从项目配置的指定 Sector 读取数据。
- CONFIG_SYS_MMCSD_RAW_MODE_U_BOOT_SECTOR
boot_from_devices(spl_boot_list); // common/spl/spl.c
|-> spl_ll_find_loader() // 根据boot device找到spl_load_image指针
| // 此处通过遍历固件的 .u_boot_list_2_spl_image_loader_* 段
| // 找到当前支持的存储介质,然后逐个尝试
|
|-> spl_load_image(loader);
|-> loader->load_image(spl_image, &bootdev);
spl_mmc_load_image(); // common/spl/spl_mmc.c
|-> spl_mmc_load();
|
+-------------+
|
spl_mmc_load();
|-> spl_mmc_find_device(&mmc, bootdev->boot_device);
| |-> mmc_initialize
| |-> mmc_probe
| |-> uclass_get(UCLASS_MMC, &uc)
| |-> device_probe(dev)
| |-> uclass_resolve_seq(dev)
| |-> pinctrl_select_state(dev, "default")
| | |-> pinctrl_select_state_full(dev, "default")
| | | |-> state = dev_read_stringlist_search(dev,
| | | | "pinctrl-names", "default");
| | | |-> dev_read_prop(dev, propname, &size)
| | | | // snprintf(propname, sizeof(propname),
| | | | // "pinctrl-%d", state)
| | | |
| | | |-> pinctrl_config_one(config)
| | | |-> ops = pinctrl_get_ops(pctldev)
| | | |-> ops->set_state(pctldev, config)
| | |
| | |-> pinctrl_select_state_simple(dev)
| | |-> uclass_get_device_by_seq(UCLASS_PINCTRL, 0, &pctldev)
| | |-> ops=pinctrl_get_ops(pctldev)
| | | // #define pinctrl_get_ops(dev)
| | | // ((struct pinctrl_ops *)(dev)->driver->ops)
| | |
| | |-> ops->set_state_simple(pctldev, dev)
| |
| |-> power_domain_on(&powerdomain)
| |-> uclass_pre_probe_device(dev)
| |-> clk_set_defaults(dev)
| | |-> clk_set_default_parents(dev)
| | |-> clk_set_default_rates(dev)
| |
| |-> drv->probe(dev)
| |-> uclass_post_probe_device(dev)
|
|-> mmc_init
|-> boot_mode=spl_boot_mode(bootdev->boot_device)
|-> mmc_load_image_raw_sector
|-> header=spl_get_load_buffer(-sizeof(*header), bd->blksz)
| // header位于load_addr偏移-head_size处
|
|-> blk_dread(bd, sector, 1, header)
| // 读取一个sector的u-boot image header
|
|-> mmc_load_legacy(spl_image, mmc, sector, header)
| |-> spl_parse_image_header(spl_image, header)
| // 解析u-boot image header信息,得到u-boot的addr和size信息
|
|-> blk_dread(bd, sector, cnt, load_addr)
// 读取完整的u-boot image,包括header,注意load_addr是向前偏移过的地址
4. SPI NAND 加载
官方版本的 SPL 并不支持从 SPI NAND 启动,Artinchip 增加了从 SPI NAND 的 MTD 分区 和 UBI 加载 U-Boot 的支持。
common/spl/spl_spi_nand.c 中注册了两个不同的 SPL 程序加载器。
#ifdef CONFIG_SPL_UBI
/* Use priorty 0 to override other SPI device when this device is enabled. */
SPL_LOAD_IMAGE_METHOD("SPINAND_UBI", 0, BOOT_DEVICE_SPI, spl_ubi_load_image);
#else
SPL_LOAD_IMAGE_METHOD("SPINAND", 0, BOOT_DEVICE_SPI, spl_spi_nand_load_image);
#endif