[IMX][UBoot] 08.启动流程 (4) - 平台后期初始化阶段 - board_init_r

发布于:2025-07-05 ⋅ 阅读:(23) ⋅ 点赞:(0)

文章链接

UBoot 启动流程 (1) - 基本流程

UBoot 启动流程 (2) - 平台前期初始化阶段 - board_init_f

UBoot 启动流程 (3) - UBoot 程序重定位 - relocate_code

UBoot 启动流程 (4) - 平台后期初始化阶段 - board_init_r

UBoot 启动流程 (5) - UBoot 运行阶段 - main_loop

UBoot 启动流程 (6) - bootz 命令启动 Linux


目录

1.初始化 Trace - initr_trace()

2.标记 uboot 重定位完成 - initr_reloc()

3.使能数据缓存 D-Cache - initr_caches()

4.重定位部分 gd 成员 - initr_reloc_global_data()

5.初始化 malloc - initr_malloc()

6.创建启动阶段信息的副本 - bootstage_relocate()

7.记录当前启动阶段 - initr_bootstage()

8.外设初始化 - board_init()

9.STDIO 初始化 - stdio_init_tables()

10.串口初始化 - initr_serial()

11.打印 uboot 已在内存中运行的调试信息 - initr_announce()

12.初始化 eMMC - initr_mmc()

13.初始化环境变量 - initr_env()

14.添加其他 stdio 设备 - stdio_add_devices()

15.初始化跳转表 - initr_jumptable()

16.初始化控制台 - console_init_r()

17.中断初始化 - interrupt_init()

18.中断使能 - initr_enable_interrupts()

19.从环境变量中获取 MAC 地址 - initr_ethaddr()

20.设置启动模式 - board_late_init()

21.初始化以太网 - initr_net()

22.启动命令行或 Linux 内核 - run_main_loop()


// 函数调用栈
reset()
    |--> _main()
        |--> board_init_f() // 初始化核心硬件,如内存、串口等
            |--> relocate_code() // 将 uboot 从内存的低地址处拷贝至内存的高地址中
                |--> board_init_r() // 初始化剩余复杂外设,如显示、以太网等
                    |--> run_main_loop() // 启动 uboot 命令行

重定位完成后,此时已在内存中为 malloc() 预留了空间,硬件资源不再受限,例如,栈的大小为 16MB、可以使用堆内存等,因此可以初始化以太网、显示等复杂外设,这部分工作由 board_init_r() 完成:

// arch/arm/lib/crt0.S
ENTRY(_main)
    ...
    mov r0, r9                  /* gd_t */
    ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
    ldr pc, =board_init_r       /* this is auto-relocated! */
  • mov r0, r9 将 R9 中保存的 gd 地址存入 R0,即将 gd 作为第一个参数传递给 board_init_r();

  • ldr r1, [r9, #GD_RELOCADDR] 将重定位后的 uboot 起始地址存入 R1,作为 board_init_r() 的第二个参数;

  • ldr pc, =board_init_r 调用 board_init_r() 初始化剩余复杂外设,并启动 uboot 命令行或 Linux 内核;

board_init_r() 和 board_init_f() 类似,需要执行初始化的函数保存在数组 init_sequence_r 中:

// common/board_r.c
init_fnc_t init_sequence_r[] = {
    initr_trace,
    initr_reloc,
    ...
    /* Light up LED2 */
    imx6_light_up_led2,

    run_main_loop,
};

board_init_r() 通过 initcall_run_list() 依次调用 init_sequence_r 中的初始化函数:

// common/board_r.c
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
    ...
    // 依次调用 init_sequence_r 中的初始化函数
    if (initcall_run_list(init_sequence_r))

1.初始化 Trace - initr_trace()

在 uboot 中使用 trace 需要配置 CONFIG_TRACE 宏 (默认不使用 trace)

board_init_f() 进行平台早期初始化时,在内存中为 trace 预留了 16MB 大小的空间:

// common/board_f.c
static int reserve_trace(void)
{
#ifdef CONFIG_TRACE
    // 在内存中为 trace 预留空间
    gd->relocaddr -= CONFIG_TRACE_BUFFER_SIZE;
    // 为 trace 分配内存,大小为 CONFIG_TRACE_BUFFER_SIZE = 16 << 20 = 16MB
    gd->trace_buff = map_sysmem(gd->relocaddr, CONFIG_TRACE_BUFFER_SIZE);
    debug("Reserving %dk for trace data at: %08lx\n",
          CONFIG_TRACE_BUFFER_SIZE >> 10, gd->relocaddr);
#endif

    return 0;
}

在 board_init_r() 阶段中调用 trace_init() 初始化 trace:

// common/board_r.c
static int initr_trace(void)
{
#ifdef CONFIG_TRACE
    trace_init(gd->trace_buff, CONFIG_TRACE_BUFFER_SIZE); // 初始化 trace
#endif

    return 0;
}
--------------------------------------------------------

// lib/trace.c
int __attribute__((no_instrument_function)) trace_init(void *buff,
        size_t buff_size) // buff_size = 16MB
{
    ulong func_count = gd->mon_len / FUNC_SITE_SIZE; // 计算可跟踪的函数个数
    size_t needed; // 保存 trace 所需的内存
    ...
    // 初始化 trace header
    hdr = (struct trace_hdr *)buff;
    ...
    hdr->func_count = func_count; // 可以跟踪多少个函数
    hdr->call_accum = (uintptr_t *)(hdr + 1);

    /* Use any remaining space for the timed function trace */
    hdr->ftrace = (struct trace_call *)(buff + needed);
    hdr->ftrace_size = (buff_size - needed) / sizeof(*hdr->ftrace);
    add_textbase(); // 设置代码段的基地址

    puts("trace: enabled\n");
    hdr->depth_limit = 15; // 限制最大调用深度
    trace_enabled = 1; // 标记 trace 已使能
    trace_inited = 1; // 标记 trace 初始化已完成
    return 0;
}

2.标记 uboot 重定位完成 - initr_reloc()

设置 gd->flags,标记 uboot 程序重定位已完成:

// common/board_r.c
static int initr_reloc(void)
{
    /* tell others: relocation done */
    gd->flags |= GD_FLG_RELOC | GD_FLG_FULL_MALLOC_INIT;

    return 0;
}

GD_FLG_RELOC 中的 FLG 是 Flag 的缩写,表示这个宏是一个标志位

3.使能数据缓存 D-Cache - initr_caches()

指令缓存 I-Cache 在 cpu_init_cp15() 初始化 CP15 协处理器时已经使能

initr_caches() 负责使能数据缓存 D-Cache,具体操作由 enable_caches() 完成:

// common/board_r.c
static int initr_caches(void)
{
    /* Enable caches */
    enable_caches(); // 使能数据缓存 D-Cache

    return 0;
}
----------------------------------------------------

void enable_caches(void)
{
    // 设置缓存的写入策略: Write-Through or Write-Back
#if defined(CONFIG_SYS_ARM_CACHE_WRITETHROUGH)
    enum dcache_option option = DCACHE_WRITETHROUGH;
#else
    enum dcache_option option = DCACHE_WRITEBACK;
#endif
    /* Avoid random hang when download by usb */
    invalidate_dcache_all(); // 清除目前所有已缓存的 D-Cache

    /* Enable D-cache. I-cache is already enabled in start.S */
    dcache_enable(); // 使能 D-Cache

    // 配置 OCRAM 和 ROM 的写入策略:Write-Through or Write-Back
    /* Enable caching on OCRAM and ROM */
    mmu_set_region_dcache_behaviour(ROMCP_ARB_BASE_ADDR,
                    ROMCP_ARB_END_ADDR,
                    option);
    mmu_set_region_dcache_behaviour(IRAM_BASE_ADDR,
                    IRAM_SIZE,
                    option);
}

ARM SoC 可以配置向缓存写入数据的策略:

  • Write-Through:写通,数据同时写入缓存和主存,可以保证数据一致性,但性能较差;

  • Write-Back:回写,数据先写入缓存,当缓存被替换时才写回主存,性能较高,但需要处理一致性问题;

4.重定位部分 gd 成员 - initr_reloc_global_data()

计算 uboot 代码段 .text 的长度,重定位环境变量的保存地址、设备树的地址:

// common/board_r.c
static int initr_reloc_global_data(void)
{
    ...
    monitor_flash_len = _end - __image_copy_start; // 计算代码段的长度
    ...
    /*
     * Some systems need to relocate the env_addr pointer early because the
     * location it points to will get invalidated before env_relocate is
     * called.  One example is on systems that might use a L2 or L3 cache
     * in SRAM mode and initialize that cache from SRAM mode back to being
     * a cache in cpu_init_r.
     */
    gd->env_addr += gd->relocaddr - CONFIG_SYS_MONITOR_BASE; // 环境变量保存地址重定位
    ...
    /*
    * The fdt_blob needs to be moved to new relocation address
    * incase of FDT blob is embedded with in image
    */
    gd->fdt_blob += gd->reloc_off; // 设备树地址重定位
    ...

    return 0;
}

5.初始化 malloc - initr_malloc()

  • 设置 malloc() 可分配内存的起始地址 malloc_start;

  • 调用 map_sysmem() 将物理地址直接转换为虚拟地址;

  • 调用 mem_malloc_init() 初始化 malloc() 所使用的内存;

// common/board_r.c
static int initr_malloc(void)
{
    ulong malloc_start;

#ifdef CONFIG_SYS_MALLOC_F_LEN
    debug("Pre-reloc malloc() used %#lx bytes (%ld KB)\n", gd->malloc_ptr,
          gd->malloc_ptr / 1024);
#endif
    /* The malloc area is immediately below the monitor copy in DRAM */
    malloc_start = gd->relocaddr - TOTAL_MALLOC_LEN; // 设置 malloc() 分配内存的起始地址
    mem_malloc_init((ulong)map_sysmem(malloc_start, TOTAL_MALLOC_LEN),
            TOTAL_MALLOC_LEN); // 初始化 malloc() 所使用的内存
    return 0;
}

6.创建启动阶段信息的副本 - bootstage_relocate()

启动阶段的相关信息保存在结构体 record 中,启动信息中包含了各个阶段的开始时间、名称等信息:

// common/bootstage.c
struct bootstage_record {
    ulong time_us;
    uint32_t start_us; // 当前阶段的开始时间
    const char *name; // 当前阶段的名称
    int flags;      /* see enum bootstage_flags */
    enum bootstage_id id;
};
-----------------------------------------------------

// uboot/common/bootstage.c
static struct bootstage_record record[BOOTSTAGE_ID_COUNT] = { {1} };

uboot 程序重定位后,为了防止原始数据被覆盖,需要在内存中重新为其分配空间 (创建副本):

// common/bootstage.c
int bootstage_relocate(void)
{
    ...
    /*
     * Duplicate all strings.  They may point to an old location in the
     * program .text section that can eventually get trashed.
     */
    for (i = 0; i < BOOTSTAGE_ID_COUNT; i++) // 在内存中重新分配空间
        if (record[i].name) // 未使用 malloc() 分配内存,因此新的数据在堆中分配空间
            record[i].name = strdup(record[i].name);

    return 0;
}

7.记录当前启动阶段 - initr_bootstage()

记录当前启动阶段 board_init_r() 的相关信息:

// common/board_r.c
static int initr_bootstage(void)
{
    /* We cannot do this before initr_dm() */
    bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R, "board_init_r");
        |--> bootstage_add_record(id, name, flags, timer_get_boot_us())
            |--> rec->time_us = mark; // 当前时间戳
            |--> rec->name = name; // 当前阶段的名称
            |--> rec->flags = flags;
            |--> rec->id = id; // 当前阶段的编号

    return 0;
}

8.外设初始化 - board_init()

初始化开发板上的外设,例如 I2C、SPI、USB 等:

  • 获取启动参数的保存地址;

  • 设置 LED 与 IO 引脚的功能复用与电气特性;

  • 调用 iox74lv_init() 初始化 74LV595 驱动芯片 (该芯片用于驱动数码管或继电器);

  • 调用 setup_i2c() 初始化 I2C 模块;

  • 调用 setup_fec() 初始化网口;

  • 调用 setup_usb() 初始化 USB 模块;

  • 调用 board_qspi_init() 初始化 QSPI 模块;

  • 调用 setup_gpmi_nand() 初始化 NAND Flash;

// board/freescale/mx6ullevk/mx6ullevk.c
int board_init(void)
{
    /* Address of boot parameters */
    gd->bd->bi_boot_params = PHYS_SDRAM + 0x100; // 获取启动参数的地址
    // 配置 LED 引脚
    imx_iomux_v3_setup_multiple_pads(leds_pads, ARRAY_SIZE(leds_pads));
    // 配置 IO 引脚
    imx_iomux_v3_setup_multiple_pads(iox_pads, ARRAY_SIZE(iox_pads));

    iox74lv_init(); // 初始化 74LV595 驱动芯片

#ifdef CONFIG_SYS_I2C_MXC
    setup_i2c(0, CONFIG_SYS_I2C_SPEED, 0x7f, &i2c_pad_info1); // I2C 初始化
#endif

#ifdef  CONFIG_FEC_MXC
    setup_fec(CONFIG_FEC_ENET_DEV); // 网口初始化
#endif

#ifdef CONFIG_USB_EHCI_MX6
    setup_usb(); // USB 初始化
#endif

#ifdef CONFIG_FSL_QSPI
    board_qspi_init(); // QSPI 初始化
#endif

#ifdef CONFIG_NAND_MXS
    setup_gpmi_nand(); // NAND Flash 初始化
#endif

    return 0;
}

9.STDIO 初始化 - stdio_init_tables()

重定位设备名称 stdio_names,并初始化标准 I/O 使用的设备链表:

// common/stdio.c
int stdio_init_tables(void)
{
#if defined(CONFIG_NEEDS_MANUAL_RELOC)
    /* already relocated for current ARM implementation */
    ulong relocation_offset = gd->reloc_off;
    int i;

    /* relocate device name pointers */
    for (i = 0; i < (sizeof (stdio_names) / sizeof (char *)); ++i) {
        stdio_names[i] = (char *) (((ulong) stdio_names[i]) +
                        relocation_offset);
    }
#endif /* CONFIG_NEEDS_MANUAL_RELOC */

    /* Initialize the list */
    INIT_LIST_HEAD(&(devs.list)); // 初始化标准 I/O 设备链表

    return 0;
}

注意:这里的设备指的是 stdio 使用的设备,如显示器、串口等

10.串口初始化 - initr_serial()

串口设备类型 serial_device 提供了统一的接口,包括初始化方法 start()、输出方法 puts() 等:

// include/serial.h
struct serial_device {
    /* enough bytes to match alignment of following func pointer */
    char    name[16];

    int (*start)(void);
    int (*stop)(void);
    void    (*setbrg)(void);
    int (*getc)(void);
    int (*tstc)(void);
    void    (*putc)(const char c);
    void    (*puts)(const char *s);
#if CONFIG_POST & CONFIG_SYS_POST_UART
    void    (*loop)(int);
#endif
    struct serial_device    *next;
};

IMX 的串口设备为 mxc_serial_drv:

// drivers/serial/serial_mxc.c
static struct serial_device mxc_serial_drv = {
    .name   = "mxc_serial",
    .start  = mxc_serial_init,
    .stop   = NULL,
    .setbrg = mxc_serial_setbrg,
    .putc   = mxc_serial_putc,
    .puts   = default_serial_puts,
    .getc   = mxc_serial_getc,
    .tstc   = mxc_serial_tstc,
};

initr_serial() 调用 serial_initialize() 初始化所有已编译的串口设备:

// common/board_r.c
static int initr_serial(void)
{
    serial_initialize();
    return 0;
}
---------------------------------------------------

// drivers/serial/serial.c
void serial_initialize(void)
{
    amirix_serial_initialize();
    arc_serial_initialize();
    ...
    mxc_serial_initialize(); // 初始化 IMX 系列的串口
    ...

IMX 的串口设备初始化函数为 mxc_serial_initialize():

// uboot/drivers/serial/serial_mxc.c
void mxc_serial_initialize(void)
{
    serial_register(&mxc_serial_drv); // 注册串口设备
}

serial_register() 检查是否需要对串口设备中的函数进行重定位,然后将传入的串口设备与全局串口设备绑定:

// drivers/serial/serial.c
void serial_register(struct serial_device *dev)
{
    // 重定位串口设备的成员函数
#ifdef CONFIG_NEEDS_MANUAL_RELOC
    if (dev->start)
        dev->start += gd->reloc_off;
    if (dev->stop)
        dev->stop += gd->reloc_off;
    if (dev->setbrg)
        dev->setbrg += gd->reloc_off;
    if (dev->getc)
        dev->getc += gd->reloc_off;
    if (dev->tstc)
        dev->tstc += gd->reloc_off;
    if (dev->putc)
        dev->putc += gd->reloc_off;
    if (dev->puts)
        dev->puts += gd->reloc_off;
#endif

    dev->next = serial_devices; // 将串口设备加入设备链表
    serial_devices = dev; // 绑定全局串口设备
}

11.打印 uboot 已在内存中运行的调试信息 - initr_announce()

打印调试信息,指示 uboot 程序目前已在内存中运行:

// common/board_r.c
static int initr_announce(void)
{
    debug("Now running in RAM - U-Boot at: %08lx\n", gd->relocaddr);
    return 0;
}

12.初始化 eMMC - initr_mmc()

initr_mmc() 调用 mmc_initialize() 初始化所有 SD/eMMC 设备,并将其加入设备链表:

// common/board_r.c
static int initr_mmc(void)
{
    puts("MMC:   ");
    mmc_initialize(gd->bd); // 初始化 SD/eMMC 设备
    return 0;
}
----------------------------------

// drivers/mmc/mmc.c
int mmc_initialize(bd_t *bis)
{
    static int initialized = 0;
    int ret;
    if (initialized)    /* Avoid initializing mmc multiple times */
        return 0;
    initialized = 1; // 标记 SD/eMMC 设备已初始化

    INIT_LIST_HEAD (&mmc_devices); // 初始化 SD/eMMC 设备链表
    cur_dev_num = 0; // 设置当前 SD/eMMC 设备的编号

    ret = mmc_probe(bis);
        |--> board_mmc_init() // 初始化 SD/eMMC 设备的引脚、初始化 SD/eMMC 控制器
    if (ret)
        return ret;
    ...

    do_preinit(); // 简单测试每个 SD/eMMC 设备是否可以正常工作
    return 0;
}

IMX6ULL 有 FSL_SDHC:0 和 FSL_SDHC:1 两个 SD/eMMC 设备,分别为 SD 卡和 eMMC 存储器

13.初始化环境变量 - initr_env()

环境变量重定位,获取设备树的地址以及 Linux 内核的加载地址:

// common/board_r.c
static int initr_env(void)
{
    /* initialize environment */
    if (should_load_env()) // 检查环境变量是否需要重定位
        env_relocate(); // 环境变量重定位
    else
        set_default_env(NULL);
#ifdef CONFIG_OF_CONTROL // 是否启用设备树
    setenv_addr("fdtcontroladdr", gd->fdt_blob); // 将设备树的地址加入环境变量
#endif

    /* Initialize from environment */
    load_addr = getenv_ulong("loadaddr", 16, load_addr); // 获取 Linux 内核的加载地址
    ...
    return 0;
}

14.添加其他 stdio 设备 - stdio_add_devices()

初始化时一般将串口作为 stdio 的默认设备,但 stdio 也可以使用其他设备,如终端、显示器、键盘等

IMX6ULL 在这里调用 drv_video_init() 将 LCD 加入 stdio 设备:

// common/stdio.c
int stdio_add_devices(void)
{
    ...
# if defined(CONFIG_LCD)
    drv_lcd_init (); // 初始化 LCD
# endif
    ...

    return 0;
}

drv_lcd_init() 初始化 LCD 使用的内存,并调用 stdio_register() 将 LCD 注册为 stdio 设备:

// common/lcd.c
int drv_lcd_init(void)
{
    struct stdio_dev lcddev;
    int rc;

    lcd_base = map_sysmem(gd->fb_base, 0); // 为 LCD 分配内存

    lcd_init(lcd_base); // 初始化 LCD 的显存、终端等

    /* Device initialization */
    memset(&lcddev, 0, sizeof(lcddev));

    strcpy(lcddev.name, "lcd"); // 设置 LCD 的设备名称
    lcddev.ext   = 0;           /* No extensions */
    lcddev.flags = DEV_FLAGS_OUTPUT;    /* Output only */
    lcddev.putc  = lcd_stub_putc;       /* 'putc' function */
    lcddev.puts  = lcd_stub_puts;       /* 'puts' function */

    rc = stdio_register(&lcddev); // 将 LCD 注册为 stdio 设备

    return (rc == 0) ? 1 : rc;
}

15.初始化跳转表 - initr_jumptable()

跳转表 jumptable 中使用 EXPORT_FUNC() 宏定义了一系列函数:

// include/exports.h
struct jt_funcs {
#define EXPORT_FUNC(impl, res, func, ...) res(*func)(__VA_ARGS__);
#include <_exports.h>
#undef EXPORT_FUNC
};
-------------------------------------------------------------------

// include/_exports.h
    EXPORT_FUNC(get_version, unsigned long, get_version, void)
    EXPORT_FUNC(getc, int, getc, void)
    ...
    EXPORT_FUNC(printf, int, printf, const char*, ...)
    ...
    EXPORT_FUNC(getenv, char  *, getenv, const char*)
    EXPORT_FUNC(setenv, int, setenv, const char *, const char *)
    ...

EXPORT_FUNC() 宏的第一个参数不使用,第二个参数为返回类型,第三个参数为函数名,剩下的为传参,其展开形式如下:

EXPORT_FUNC(getc, int, getc, void)
// 宏展开如下:
int(* getc)(void);

通过 gd->jt 可以调用这些函数,例如 gd->jt->getenv()

initr_jumptable() 调用 jumptable_init() 为这些函数分配内存:

// common/board_r.c
static int initr_jumptable(void)
{
    jumptable_init(); // 初始化跳转表
    return 0;
}
------------------------------------------

// common/exports.c
void jumptable_init(void)
{
    gd->jt = malloc(sizeof(struct jt_funcs)); // 为跳转表分配内存
#include <_exports.h> // 包含相关函数的声明
}

16.初始化控制台 - console_init_r()

初始化输入 IN、输出 OUT、错误输出 ERR 所使用的控制台/终端 (IMX6ULL 使用串口作为 I/O 设备):

// common/console.c
int console_init_r(void)
{
    struct stdio_dev *inputdev = NULL, *outputdev = NULL;
    int i;
    struct list_head *list = stdio_get_list();
    struct list_head *pos;
    struct stdio_dev *dev;
    ...

    /* Scan devices looking for input and output devices */
    list_for_each(pos, list) { // 绑定 I/O 设备:串口
        dev = list_entry(pos, struct stdio_dev, list); // 查找 stdio 设备
        // 绑定输入设备:串口
        if ((dev->flags & DEV_FLAGS_INPUT) && (inputdev == NULL)) {
            inputdev = dev;
        } 
        // 绑定输出设备:串口
        if ((dev->flags & DEV_FLAGS_OUTPUT) && (outputdev == NULL)) {
            outputdev = dev;
        }
        if(inputdev && outputdev)
            break;
    }

    /* Initializes output console first */
    if (outputdev != NULL) { // 设置普通输出和错误输出所使用的文件
        console_setfile(stdout, outputdev);
        console_setfile(stderr, outputdev);
        ...
    }

    /* Initializes input console */
    if (inputdev != NULL) { // 设置输入所使用的文件
        console_setfile(stdin, inputdev);
        ...
    }

#ifndef CONFIG_SYS_CONSOLE_INFO_QUIET
    stdio_print_current_devices(); // 打印当前的控制台类型
#endif /* CONFIG_SYS_CONSOLE_INFO_QUIET */

    /* Setting environment variables */
    for (i = 0; i < 3; i++) { // 设置环境变量
        setenv(stdio_names[i], stdio_devices[i]->name);
    }
    // 标记初始化已完成
    gd->flags |= GD_FLG_DEVINIT;    /* device initialization completed */
    ...
    // 将之前缓存在 Buffer 中的数据输出
    print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT2_EVERYTHING_BUT_SERIAL);
    return 0;
}

17.中断初始化 - interrupt_init()

通过内联汇编向 CPSR 寄存器写值,设置中断栈的起始地址:

// arch/arm/lib/interrupts.c
int interrupt_init (void)
{
    unsigned long cpsr;

    /*
     * setup up stacks if necessary
     */
    IRQ_STACK_START = gd->irq_sp - 4;
    IRQ_STACK_START_IN = gd->irq_sp + 8;
    FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;

    // 设置 CPSR 寄存器,设置中断栈的地址
    __asm__ __volatile__("mrs %0, cpsr\n"
                 : "=r" (cpsr)
                 :
                 : "memory");

    __asm__ __volatile__("msr cpsr_c, %0\n"
                 "mov sp, %1\n"
                 :
                 : "r" (IRQ_MODE | I_BIT | F_BIT | (cpsr & ~FIQ_MODE)),
                   "r" (IRQ_STACK_START)
                 : "memory");

    __asm__ __volatile__("msr cpsr_c, %0\n"
                 "mov sp, %1\n"
                 :
                 : "r" (FIQ_MODE | I_BIT | F_BIT | (cpsr & ~IRQ_MODE)),
                   "r" (FIQ_STACK_START)
                 : "memory");

    __asm__ __volatile__("msr cpsr_c, %0"
                 :
                 : "r" (cpsr)
                 : "memory");

    return arch_interrupt_init(); // 空函数
}

18.中断使能 - initr_enable_interrupts()

通过内联汇编配置 CPSR 寄存器,使能 IRQ 和 FIQ:

// common/board_r.c
static int initr_enable_interrupts(void)
{
    enable_interrupts();
    return 0;
}
--------------------------------------------

// arch/arm/lib/interrupts.c
void enable_interrupts (void)
{
    unsigned long temp;
    // 中断 IRQ 和 FIQ
    __asm__ __volatile__("mrs %0, cpsr\n"
                 "bic %0, %0, #0x80\n"
                 "msr cpsr_c, %0"
                 : "=r" (temp)
                 :
                 : "memory");
}

19.从环境变量中获取 MAC 地址 - initr_ethaddr()

从环境变量中获取 MAC 地址,保存至 bd->bi_enetaddr 中:

// common/board_r.c
static int initr_ethaddr(void)
{
    bd_t *bd = gd->bd;

    /* kept around for legacy kernels only ... ignore the next section */
    eth_getenv_enetaddr("ethaddr", bd->bi_enetaddr); // 从环境变量中读取 MAC 地址
#ifdef CONFIG_HAS_ETH1
    eth_getenv_enetaddr("eth1addr", bd->bi_enet1addr);
#endif
#ifdef CONFIG_HAS_ETH2
    ...
    return 0;
}

20.设置启动模式 - board_late_init()

设置开发板的启动模式和启动信息,设置 LCD 屏幕的型号:

// board/freescale/mx6ullevk/mx6ullevk.c
int board_late_init(void)
{
    // 设置启动模式、说明信息
    add_board_boot_modes(board_boot_modes); 
    // 设置开发板名称
    setenv("board_name", "EVK"); 
    // 设置开发板的版本
    if (is_mx6ull_9x9_evk())
        setenv("board_rev", "9X9");
    else
        setenv("board_rev", "14X14");

#ifdef CONFIG_ENV_IS_IN_MMC // 环境变量是否存储在 eMMC 设备中
    board_late_mmc_env_init();
#endif
    // 复位看门狗
    set_wdog_reset((struct wdog_regs *)WDOG1_BASE_ADDR);
    // 设置 LCD 屏幕型号
    select_display_dev();

    return 0;
}

21.初始化以太网 - initr_net()

初始化 MAC 控制器并复位 PHY:

// common/board_r.c
static int initr_net(void)
{
    puts("Net:   ");
    eth_initialize(); // 初始化 MAC 控制器
#if defined(CONFIG_RESET_PHY_R)
    debug("Reset Ethernet PHY\n");
    reset_phy(); // PHY 复位
#endif
    return 0;
}

22.启动命令行或 Linux 内核 - run_main_loop()

run_main_loop() 是一个死循环,不断调用 main_loop():

// common/board_r.c
static int run_main_loop(void)
{
    ...
    /* main_loop() can return to retry autoboot, if so just run it again */
    for (;;)
        main_loop();
    return 0;
}

在 main_loop() 中进行倒计时,并检查倒计时结束前是否有按键按下:

  • 若没有按键按下,则会启动 Linux 内核;

  • 若检测到按键按下,则会启动 uboot 命令行;

// common/main.c
void main_loop(void)
{
    const char *s;
    // 记录当前启动阶段
    bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");
    ...
    // 初始化命令行
    cli_init();
    ...
    // 设置定时时长并读取启动命令
    s = bootdelay_process();
    ...
    // 检查倒计时是否结束
    // 如果倒计时结束则会启动 Linux 内核
    // 如果在倒计时结束前检测到按键输入,则会返回并启动 uboot 命令行
    autoboot_command(s);
    // 启动 uboot 命令行
    cli_loop();
}

下一篇:UBoot 启动流程 (5) - UBoot 运行阶段 - main_loop