文章链接
UBoot 启动流程 (2) - 平台前期初始化阶段 - board_init_f
UBoot 启动流程 (3) - UBoot 程序重定位 - relocate_code
UBoot 启动流程 (4) - 平台后期初始化阶段 - board_init_r
UBoot 启动流程 (5) - UBoot 运行阶段 - main_loop
UBoot 启动流程 (6) - bootz 命令启动 Linux
目录
2.标记 uboot 重定位完成 - initr_reloc()
3.使能数据缓存 D-Cache - initr_caches()
4.重定位部分 gd 成员 - initr_reloc_global_data()
6.创建启动阶段信息的副本 - bootstage_relocate()
7.记录当前启动阶段 - initr_bootstage()
9.STDIO 初始化 - stdio_init_tables()
11.打印 uboot 已在内存中运行的调试信息 - initr_announce()
14.添加其他 stdio 设备 - stdio_add_devices()
18.中断使能 - initr_enable_interrupts()
19.从环境变量中获取 MAC 地址 - initr_ethaddr()
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();
}