[IMX][UBoot] 08.启动流程 (4) - 平台后期初始化阶段 - board_init_r
文章链接
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 = 16MBgd->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);
#endifreturn 0;
}
在 board_init_r() 阶段中调用 trace_init() 初始化 trace:
// common/board_r.c
static int initr_trace(void)
{
#ifdef CONFIG_TRACEtrace_init(gd->trace_buff, CONFIG_TRACE_BUFFER_SIZE); // 初始化 trace
#endifreturn 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 headerhdr = (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-Cachereturn 0;
}
----------------------------------------------------void enable_caches(void)
{// 设置缓存的写入策略: Write-Through or Write-Back
#if defined(CONFIG_SYS_ARM_CACHE_WRITETHROUGH)enum dcache_option option = DCACHE_WRITETHROUGH;
#elseenum 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_LENdebug("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_MXCsetup_i2c(0, CONFIG_SYS_I2C_SPEED, 0x7f, &i2c_pad_info1); // I2C 初始化
#endif#ifdef CONFIG_FEC_MXCsetup_fec(CONFIG_FEC_ENET_DEV); // 网口初始化
#endif#ifdef CONFIG_USB_EHCI_MX6setup_usb(); // USB 初始化
#endif#ifdef CONFIG_FSL_QSPIboard_qspi_init(); // QSPI 初始化
#endif#ifdef CONFIG_NAND_MXSsetup_gpmi_nand(); // NAND Flash 初始化
#endifreturn 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_UARTvoid (*loop)(int);
#endifstruct 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_RELOCif (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;
#endifdev->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(); // 环境变量重定位elseset_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.hEXPORT_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_QUIETstdio_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_ETH1eth_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");elsesetenv("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 复位
#endifreturn 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