am335x uboot启动流程分析
基本指令含义
.globl _start
.globl指示告诉汇编器,_start这个符号要被链接器用到,所以要在目标文件的符号表中标记它是一个全局符号
b,bl
b是不带返回的跳转 bl带返回的跳转
.word
插入一个32-bit的数据队列。(与armasm中的DCD功能相同)
芯片到uboot启动流程 :ROM → MLO(SPL)→ uboot.img
启动脚本:/u-boot2011.09/arch/arm/cpu/armv7/omap-common/u-boot_spl.lds
MEMORY { .sram : ORIGIN = CONFIG_SPL_TEXT_BASE,\
LENGTH = CONFIG_SPL_MAX_SIZE }
MEMORY { .sdram : ORIGIN = CONFIG_SPL_BSS_START_ADDR, \
LENGTH = CONFIG_SPL_BSS_MAX_SIZE }
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
.text :
{
__start = .;
arch/arm/cpu/armv7/start.o(.text) //入口函数
*(.text*)
} >.sram
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(.rodata*)) } >.sram
. = ALIGN(4);
.data : { *(SORT_BY_ALIGNMENT(.data*)) } >.sram
. = ALIGN(4);
__image_copy_end = .;
_end = .;
.bss :
{
. = ALIGN(4);
__bss_start = .;
*(.bss*)
. = ALIGN(4);
__bss_end__ = .;
} >.sdram
}
分析文件arch/arm/cpu/armv7/start.S
1. 保存启动参数 arch/arm/cpu/armv7/ti81xx/lowlevel_init.S
bl save_boot_params
简化代码:
save_boot_params:
#ifdef CONFIG_SPL_BUILD
ldrr4, =ti81xx_boot_device
ldrr5, [r0, #BOOT_DEVICE_OFFSET]
andr5, r5, #BOOT_DEVICE_MASK
strr5, [r4]
#endif
bxlr
2. cpu初始化 cpu_init_crit
首先设置cpu工作模,把cpu的状态类型为SVC,然后执行cpu_init_crit,关闭mmu缓存,接着跳转到arch/arm/cpu/armv7/ti81xx/lowlevel_init.S 的lowlevel_init中,
lowlevel_init 主要调用 board\aplex\ecm_5206\evm.c s_init 函数
void s_init(void)
{
l2_cache_enable(); //二级缓存
__raw_writel(0xAAAA, WDT_WSPR);
while(__raw_readl(WDT_WWPS) != 0x0);
__raw_writel(0x5555, WDT_WSPR);
while(__raw_readl(WDT_WWPS) != 0x0);
pll_init(); //设置时钟
u32 regVal;
u32 uart_base = DEFAULT_UART_BASE;
enable_uart0_pin_mux(); //使能串口0
if (board_id == IA_BOARD) {
uart_base = UART3_BASE;
}
regVal = __raw_readl(uart_base + UART_SYSCFG_OFFSET);
regVal |= UART_RESET; //直接操作寄存器
__raw_writel(regVal, (uart_base + UART_SYSCFG_OFFSET) );
while ((__raw_readl(uart_base + UART_SYSSTS_OFFSET) &
UART_CLK_RUNNING_MASK) != UART_CLK_RUNNING_MASK);
/* Disable smart idle */
regVal = __raw_readl((uart_base + UART_SYSCFG_OFFSET));
regVal |= UART_SMART_IDLE_EN;
__raw_writel(regVal, (uart_base + UART_SYSCFG_OFFSET));
init_timer(); //实际上是timer2
preloader_console_init(); //控制台初始化
config_am335x_ddr(); //配置DDR
}
即:
1)使能二级缓存 l2_cache_enable();
2)关闭看门狗 __raw_writel(0x5555, WDT_WSPR);
3)设置外设时钟 pll_init()
PATH: board\aplex\ecm_5206中
mpu_pll_config(MPUPLL_M_720);
core_pll_config();
per_pll_config();
ddr_pll_config();
interface_clocks_enable();
power_domain_transition_enable();
per_clocks_enable();
使能RTC rtc32k_enable() /board/ti/am335x/evm.c
使能32K实时时钟时钟,准确是应该是32.768K
static void rtc32k_enable(void)
{
__raw_writel(0x83e70b13, (AM335X_RTC_BASE + RTC_KICK0_REG));
__raw_writel(0x95a4f1e0, (AM335X_RTC_BASE + RTC_KICK1_REG));
__raw_writel(0x48, (AM335X_RTC_BASE + RTC_OSC_REG));
}
4)使能串口 UART0
配置串口,主要用于打印中断,也可以配置UART3
if (board_id == IA_BOARD) {
uart_base = UART3_BASE;
}
6)初始化定时器Timer2 init_timer();
7)初始化控制台 preloader_console_init()
简化代码为: 实际上是串口配置 修改这里 可以修改串口控制台应该
/* This requires UART clocks to be enabled */
void preloader_console_init(void)
{
const char *u_boot_rev = U_BOOT_VERSION;
char rev_string_buffer[50];
gd = &gdata;
gd->bd = &bdata;
gd->flags |= GD_FLG_RELOC;
gd->baudrate = CONFIG_BAUDRATE; //串口波特率设置
serial_init(); //串口初始化
u_boot_rev = &u_boot_rev[7];
omap_rev_string(rev_string_buffer);
}
8)配置DDR config_am335x_ddr(void)
简化代码:
static void config_am335x_ddr(void) //ddr2初始化
{
int data_macro_0 = 0;
int data_macro_1 = 1;
enable_ddr_clocks();
config_vtp();
Cmd_Macro_Config();
Data_Macro_Config(data_macro_0);
Data_Macro_Config(data_macro_1);
__raw_writel(PHY_RANK0_DELAY, DATA0_RANK0_DELAYS_0);
__raw_writel(PHY_RANK0_DELAY, DATA1_RANK0_DELAYS_0);
__raw_writel(DDR_IOCTRL_VALUE, DDR_CMD0_IOCTRL);
__raw_writel(DDR_IOCTRL_VALUE, DDR_CMD1_IOCTRL);
__raw_writel(DDR_IOCTRL_VALUE, DDR_CMD2_IOCTRL);
__raw_writel(DDR_IOCTRL_VALUE, DDR_DATA0_IOCTRL);
__raw_writel(DDR_IOCTRL_VALUE, DDR_DATA1_IOCTRL);
__raw_writel(__raw_readl(DDR_IO_CTRL) & 0xefffffff, DDR_IO_CTRL);
__raw_writel(__raw_readl(DDR_CKE_CTRL) | 0x00000001, DDR_CKE_CTRL);
config_emif_ddr2();
}
到这里,整个cpu已经初始化完成,执行完成s_init之后返回到strt.S中,可以进行一些启动操作 比如配置状态灯等
3.板级操作初始化 include/configs/ecm_5206.h
返回start.S,执行
call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
ldr r0,=0x00000000
bl board_init_f //有返回的跳转到 board_init_f
1) 设置 internal RAM 内存空间的栈指针
2)接着跳转到/arch/arm/cpu/armv7/omap-common/spl.c 中
执行 C代码 board_init_f(ulong dummy),跳转到spl第二阶段
void board_init_f(ulong dummy)
{
debug(">>board_init_f()\n");
//重新指定
relocate_code(CONFIG_SPL_STACK, &gdata, CONFIG_SPL_TEXT_BASE);
}
4.代码重定位
1)stack_setup
2)copy_loop
3)clear_bss
5.返回转到spl第二阶段 ,调用函数 board_init_r
路径: /arch/arm/cpu/armv7/omap-common/spl.
简化代码为:
void board_init_r(gd_t *id, ulong dummy)
{
u32 boot_device;
timer_init();
i2c_init(CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE);
spl_board_init();
boot_device = omap_boot_device(); 获取启动选择
switch (boot_device) {
case BOOT_DEVICE_MMC1:
case BOOT_DEVICE_MMC2:
spl_mmc_load_image(); //EMMC启动
break;
case BOOT_DEVICE_NAND:
spl_nand_load_image(); //SD卡
break;
case BOOT_DEVICE_UART:
spl_ymodem_load_image(); //串口
break;
default: hang();
break;
}
jump_to_image_no_args();
}
流程概括为:
1)初始化定时器 timer_init();
2)i2c配置 i2c_init(CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE);
3)spl板级初始化 spl_board_init();
4)按照启动方式 加载 image jump_....
4)加载并跳转到img 代码也在 /arch/arm/cpu/armv7/omap-common/spl.c中
简化代码为:
static void jump_to_image_no_args(void)
{
typedef void (*image_entry_noargs_t)(void)__attribute__ ((noreturn));
image_entry_noargs_t image_entry =
(image_entry_noargs_t) spl_image.entry_point;
int *p = 0x80000000;
*p = omap_boot_device();
image_entry(); //跳转到image中
}
board_init_r 中完成 MLO(SPI)阶段的所有初始化,并跳转到 uboot.img 阶段
----------------------------uboot.img--------------------------------
6.uboot.img arch/arm/lib/board.c
在spl执行第一和第二阶段结束后,已经初始化运行平台,并根据boot_device选择方式加载了image,
image主要功能,加载kernel
接下来就是程序重新返回运行start.o,跳转回到board_init_f()
简化代码为:
void board_init_f(ulong bootflag)
{
bd_t *bd;
init_fnc_t **init_fnc_ptr;
gd_t *id;
ulong addr, addr_sp;
gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);
__asm__ __volatile__("": : :"memory");
memset((void *)gd, 0, sizeof(gd_t));
gd->mon_len = _bss_end_ofs;
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE;
addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;
addr -= (LOGBUFF_RESERVE);
i = getenv_r("pram", (char *)tmp, sizeof(tmp));
reg = (i > 0) ? simple_strtoul((const char *)tmp, NULL, 10) :
CONFIG_PRAM;
addr -= (reg << 10); /* size is in kB */
addr -= (4096 * 4);
addr &= ~(0x10000 - 1);
gd->tlb_addr = addr;
/* round down to next 4 kB limit */
addr &= ~(4096 - 1);
gd->fb_base = CONFIG_FB_ADDR;
#else
addr = lcd_setmem(addr);
gd->fb_base = addr;
addr -= gd->mon_len;
addr &= ~(4096 - 1);
addr_sp = addr - TOTAL_MALLOC_LEN;
addr_sp -= sizeof (bd_t);
bd = (bd_t *) addr_sp;
gd->bd = bd;
gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux
addr_sp -= sizeof (gd_t);
id = (gd_t *) addr_sp;
gd->irq_sp = addr_sp;
addr_sp -= (CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ);
addr_sp -= 12;
addr_sp &= ~0x07;
addr_sp += 128; /* leave 32 words for abort-stack */
gd->irq_sp = addr_sp;
post_bootmode_init();
post_run(NULL, POST_ROM | post_bootmode_get(0));
gd->bd->bi_baudrate = gd->baudrate;
dram_init_banksize();
display_dram_config();
gd->relocaddr = addr;
gd->start_addr_sp = addr_sp;
gd->reloc_off = addr - _TEXT_BASE;
memcpy(id, (void *)gd, sizeof(gd_t));
relocate_code(addr_sp, id, addr);
}
在 uboot.img 运行过程中,有两个非常重要的结构体:gd_t 和 bd_t 。
其中 gd_t :
global_data 数据结构的定义
位于:/arch/arm/include/asm/global_data.h 中。
其成员主要是一些全局的系统初始化参数。
其中 bd_t :
bd_info 数据结构的定义
位于:/arch/arm/include/asm/u-boot.h 中。
其成员是开发板的相关参数。
Image中重要结构体头文件 include/image.h
typedef struct bootm_headers { //boot头结构
image_header_t *legacy_hdr_os; /* image header pointer */
image_header_t legacy_hdr_os_copy; /* header copy */ ulong legacy_hdr_valid;
……
}
typedef struct image_header { //image 头结构体
……
__be32 ih_size; /* Image Data Size */
__be32 ih_load; /* Data Load Address
……
}
1. 启动脚本开始分析image u-boot2011.09/include/configs/ am335x_evm.h.20160426
#define CONFIG_BOOTCOMMAND \
"if mmc rescan; then " \ #如果是SD/MMC加载
"echo SD/MMC found on device ${mmc_dev};" \ # - - - @0
"if run loadbootenv; then " \ #加载loadbootenv - - - @1
"echo Loaded environment from ${bootenv};" \ #从bootenv加载- - - @2
"run importbootenv;" \ #运行 importbootenv
"fi;" \
"if test -n ${uenvcmd}; then " \
"echo Running uenvcmd ...;" \
"run uenvcmd;" \ #运行 uenvcmd
"fi;" \
"if run mmc_load_image; then " \
"run mmc_args;" \ #运行 mmc_args
"bootm ${kloadaddr};" \ #加载kloadaddr - - - - - @3
"fi;" \
"fi;" \
"run nand_boot;" \
@0
l "mmc_dev=0\0" \ #给mmc_dev赋值
"mmc_root=/dev/ram rw \0" \
"nand_root=ubi0:rootfs rw ubi.mtd=7,2048\0" \
"spi_root=/dev/mtdblock4 rw\0" \
@1 @2
l "bootenv=uEnv.txt\0" \
"loadbootenv=fatload mmc ${mmc_dev} ${loadaddr} ${bootenv}\0" \
"importbootenv=echo Importing environment from mmc ...; " \
"env import -t ${loadaddr} ${filesize}\0" \
"mmc_load_image=fatload mmc ${mmc_dev} ${kloadaddr} ${bootfile};" \
"fatload mmc ${mmc_dev} ${rdloadaddr} ${ramdisk}\0" \
@3
#define CONFIG_EXTRA_ENV_SETTINGS \
"bootfile=uImage\0" \
"ramdisk=ramdisk.gz\0" \
"loadaddr=0x82000000\0" \
"kloadaddr=0x80007fc0\0" \
u-boot,image中kernel 的加载函数
入口
1. common/cmd_botm,c
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
……
bootm_start_lmb();
//获取内核
os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,&images, &images.os.image_start, &images.os.image_len);
……
if (((images.os.type == IH_TYPE_KERNEL) ||
(images.os.type == IH_TYPE_MULTI)) &&
(images.os.os == IH_OS_LINUX)) { //使用IH_OS_LINUX
ret = boot_get_ramdisk (argc, argv, &images, IH_INITRD_ARCH,
&images.rd_start, &images.rd_end);
……
}
}
在 common/Cmd_bootms中,定义了IH_OS_LINUX ,并赋值为do_bootm_linux 这是最后跳转进入kernel的接口
static boot_os_fn *boot_os[] = {
#ifdef CONFIG_BOOTM_LINUX
[IH_OS_LINUX] = do_bootm_linux,
……
};
即:从这里跳转进入do_bootm_linux
2. arch/arm/lib/bootm.c
代码如下:arch/arm/lib/bootm.c
int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
{
bd_t *bd = gd->bd;
char *s;
int machid = bd->bi_arch_number;
void (*kernel_entry)(int zero, int arch, uint params);
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
show_boot_progress (15);
kernel_entry = (void (*)(int, int, uint))images->ep;
setup_start_tag (bd);
setup_serial_tag (¶ms);
announce_and_cleanup();
kernel_entry(0, machid, bd->bi_boot_params); //进入内核 不返回
}
参考文献:http://blog.chinaunix.net/uid-28458801-id-3486399.html