Edit online

启动流程

15 Jul 2024
Read time: 7 minute(s)

理解 SPL 的启动流程,关键是设备树,设备驱动模型。关于设备树,请查看设备树相关章节,设备驱动模型的介绍如下:

uboot 设备驱动框架模型

uclass <–> uclass_driver <–> udevice <–> driver <–> hardware

uclass 表示管理某一个类别下的所有 device;

uclass_driver 表示对应 uclass 的 ops 集合。

uboot 设备驱动框架搭建的过程

  1. 创建 udevice

  2. 应用 uclass 如果没有则匹配生成 uclass

  3. udevice 和 uclass 绑定

  4. uclass_driver 和 uclass 绑定

  5. driver 和 udevice 绑定

  6. device_probe 执行,会触发 driver 调用 driver 函数

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); // 获取 table 起始位置
|               |   |   |   |-> ll_entry_count(struct driver, driver); // 获取 table 长度
|               |   |   |   // drivers/core/lists.c
|               |   |   |   // 采用 U_BOOT_DRIVER(name) 声明的 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); // 获取 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()  // 根据 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,用于修改 tree 传递参数
    |-> spl_board_prepare_for_boot  // vendor hook, 可不实现
    |-> jump_to_image_no_args   // 跳转到 boot 执行