U-boot启动流程——eloader
2014-12-07 16:48
204 查看
近日研究了一段时间的Eloader和U-boot的代码,大致分析了他们的启动的过程,并且将心得记录下来。
本文所讲述的启动内核代码分为两个阶段,是已经将代码分成两个不同的bin文件了:eloader.bin和u-boot.bin文件,eloader是用来启动u-boot的,然后u-boot就是我们所熟悉的启动linux的内核了。
首先先讲解一下eloader,其实我也搞不太清楚这个eloader和u-boot到底为什么要分开,而且eloader有没有给u-boot什么参数,慢慢研究吧!
一句一条的分析,知道的就会进行解释,不知道只能我们共同探讨了:
1. 首先来研究我们这个芯片的启动流程吧:
之前很多博客中写到过很多ARM芯片中的启动的过程,是可能直接将nand中的4K代码直接通过硬件方式,也就是所说的steppingstone,直接copy到RAM 0~4K处,然后开始在0x00000000处执行,还有的是直接在Nor-flash中执行,因为直接将nor-flash映射到0x00000000处直接执行。
但是此处的执行方式有一点点区别,这里并没有steppingstone,这里是将Serial Flash中0x3F0000~0x3FFFFF直接映射到0xFFFF0000~0xFFFFFFFF,然后程序就可以从0xFFFF0000开始执行了,就我自己的理解,就是在Serial Flash上直接运行的,因为我们这里所用的Serial Flash是直接在片上的,然而SDRAM不是在片上的,需要对它进行初始化之后才能使用,所以这里我认为就是直接在Serial
Flash上直接进行运行的,而且对于CPU来说,Serial Flash上0x3F0000~0x3FFFFF,就是0xFFFF0000~0xFFFFFFFF。
那这样我们大家都同意程序运行的第一条程序是在0xFFFF0000处开始运行的,而且我会在后面会进行验证,此时我会将eloader.bin文件通过烧写器写到板子Serial flash0x3F0000处,也就是说程序运行的第一条程序就是运行eloader.bin的第一条指令。
2. 现在来分析源代码:
后面也有很多关于协处理器的操作的。
然后通过下面指令就跳转到main函数中:
3. main函数
3.1 load_timing_table():
然后将board_id写入到寄存器中:
0xd8390000~0xd839ffff属于PMU里面的,然后0xd839341c是控制PMU 中MCBOOT (keep the address for system recover)
3.2利用timing_table.bin读出来的值对SDRAM进行初始化init_sdram_timing():
这两句是设定clock enable register0和clock enable register1,0xd8130000~0xd813ffff是属于PMC(Power Management Controller)寄存器组的。
下面这一句完全不懂是什么?并且没有找到相关的技术手册:
3.3通过读出来的数据改变cpu的频率switch_cpu_freq();
3.4 handle_stm()这个函数完全不知道干了什么;
3.5 memory_auto_tune();这里是与MIU寄存器相关的设定
3.6 pmc_writel(pmc_readl(0x250) | (1<<2), 0x250):
这里是使能UART1 clock,是属于PMC的一部分。
3.7
初始化UART1 的GPIO口:
第一句是pin-sharing selection register。
后面几句是设置UART1 TXD/RXD Signale GPIO Enable和UART1RXD/TXD Signal pull-up/down control0
3.8 串口初始化serial_init() -> serial_setbrg():
这里有一个很重要的结构体,就是pUart_Reg,它将关于串口寄存器的值都包含进来了:
3.9 将u-boot.bin从Serial Flash拷贝到SDRAM中去load_os_image():
3.9.1
初始化SPI sf_init():
3.9.2
将存储在Serial Flash中的u-boot.bin拷贝到SDRAM 0x3F80000处sf_read(OS_FLASH_OFFSET, OS_MEM_ADDR, OS_IMAGE_SIZE):
3.10 直接跳转到0x3F80000,进入到u-boot.bin中的第一条指令处:
这里这个寄存器有两种mode:car mode和normal mode
这个我不知道是干嘛用的?
3.11 验证eloader的代码运行地址
在这条代码之前加入:
本文所讲述的启动内核代码分为两个阶段,是已经将代码分成两个不同的bin文件了:eloader.bin和u-boot.bin文件,eloader是用来启动u-boot的,然后u-boot就是我们所熟悉的启动linux的内核了。
首先先讲解一下eloader,其实我也搞不太清楚这个eloader和u-boot到底为什么要分开,而且eloader有没有给u-boot什么参数,慢慢研究吧!
一句一条的分析,知道的就会进行解释,不知道只能我们共同探讨了:
1. 首先来研究我们这个芯片的启动流程吧:
之前很多博客中写到过很多ARM芯片中的启动的过程,是可能直接将nand中的4K代码直接通过硬件方式,也就是所说的steppingstone,直接copy到RAM 0~4K处,然后开始在0x00000000处执行,还有的是直接在Nor-flash中执行,因为直接将nor-flash映射到0x00000000处直接执行。
但是此处的执行方式有一点点区别,这里并没有steppingstone,这里是将Serial Flash中0x3F0000~0x3FFFFF直接映射到0xFFFF0000~0xFFFFFFFF,然后程序就可以从0xFFFF0000开始执行了,就我自己的理解,就是在Serial Flash上直接运行的,因为我们这里所用的Serial Flash是直接在片上的,然而SDRAM不是在片上的,需要对它进行初始化之后才能使用,所以这里我认为就是直接在Serial
Flash上直接进行运行的,而且对于CPU来说,Serial Flash上0x3F0000~0x3FFFFF,就是0xFFFF0000~0xFFFFFFFF。
那这样我们大家都同意程序运行的第一条程序是在0xFFFF0000处开始运行的,而且我会在后面会进行验证,此时我会将eloader.bin文件通过烧写器写到板子Serial flash0x3F0000处,也就是说程序运行的第一条程序就是运行eloader.bin的第一条指令。
2. 现在来分析源代码:
_TEXT_BASE: .word TEXT_BASE这里的TEXT_BASE在/Makefile中有定义
# entry point of text section TEXT_BASE=0x0
_armboot_start: .word _start /* * These are defined in the board-specific linker script. */ _bss_start: .word __bss_start _bss_end: .word _end上面程序中的几个参数是在/Nonsec_bl1.lds中有定义:
SECTIONS { . = 0x0; . = ALIGN(4); .text : { start.o (.text) *(.text) } . = ALIGN(4); .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) } . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); .got : { *(.got) } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) } . = ALIGN(4); _end = .; }然后继续往下分析,首先将CPU设置为SVC32 mode,然CPU处于一个比较强大的模式,可以操控更多的资源
mrs r0, cpsr bic r0, r0, #0x1f orr r0, r0, #0xd3 msr cpsr,r0
mrc p15, 0, r0, c0, c0, 5 @get cpu id and r0, r0, #0x3
后面也有很多关于协处理器的操作的。
ldr r1, =REG_RESET_STATUS ldr r3, [r1] tst r3, #0x10 @check if AP reset from suspend to memory status(STM) bne continue ldr r1, =REG_MCSBOOT_BASE add r1, r1, r0, lsl #2 ldr r2, [r1] cmp r2, #0 @ check if it's context restore movne pc, r2这里关于寄存器的设定,我可以找到它的寄存器在哪里,但是不知道作用是什么?
relocate: @ relocate itself to RAM adr r0, _start @ r0 <- current position of code ldr r1, _TEXT_BASE @ test if we run from flash or RAM cmp r0, r1 @ don't reloc during debug beq stack_setup这里不会进行relocate,因为此时_start=_TEXT_BASE=0,它会直接跳转到stack_setup去
stack_setup: ldr r0, _stack_start @ upper 128 KiB: relocated uboot sub sp, r0, #12 @ leave 3 words for abort-stack bic sp, sp, #7 @ 8-byte alignment for ABI compliance mov r0, sp ldr r1, =STACK_SIZE sub r0, r0, r1 cps #18 @ change to irq mode mov sp, r0 cps #19关于_start_start和STACK_SIZE会在Start.S中有定义:
#define STACK_SIZE 0x10000
_stack_start: .word 0x80000这里为了调用C函数,设置简单的栈。可以同cps对cpsr寄存器进行设置,相当于用到了mrs指令。
clear_bss: ldr r0, _bss_start @ find start of bss segment ldr r1, _bss_end @ stop here mov r2, #0x00000000 @ clear value clbss_l: str r2, [r0] @ clear BSS location cmp r0, r1 @ are we at the end yet add r0, r0, #4 @ increment clear index pointer bne clbss_l @ keep clearing till at end这里是为了初始化_bss段,bss段是存储的未初始化静态变量的,已经初始化的静态变量会存放在.data段中。
然后通过下面指令就跳转到main函数中:
ldr pc, _start_armboot @ jump to C code _start_armboot: .word main相当于直接将,main函数的地址给到pc寄存器中,就会跳转到main函数了。
3. main函数
3.1 load_timing_table():
board_id = *(unsigned int*)BOARD_INFO_ADDR; cpu_freq = *(unsigned int*)(BOARD_INFO_ADDR+4); timing_tbl_size = *(unsigned int*)(BOARD_INFO_ADDR+8); timing_tbl = (sdram_timing_t*)(BOARD_INFO_ADDR+0xc);这里是直接在Serial flash中读入一些板级数据和初始化sdram的寄存器的地址和值?之前有人问过我,为什么这里还没有对SDRAM进行初始化,就可以读出SDRAM的值,这里不是SDRAM,我们将timing_table.bin烧录到的是Serial Flash0x3F8000中,不是SDRAM中。
然后将board_id写入到寄存器中:
writel(board_id, (void*)0xd839341c);
0xd8390000~0xd839ffff属于PMU里面的,然后0xd839341c是控制PMU 中MCBOOT (keep the address for system recover)
3.2利用timing_table.bin读出来的值对SDRAM进行初始化init_sdram_timing():
size = timing_tbl_size; for (i=0; i<size; i++) { <span style="white-space:pre"> </span>if ((timing_tbl[i].address < 0xd839d200) || (timing_tbl[i].address > 0xd839d2ff)) continue; writel(timing_tbl[i].value, timing_tbl[i].address); if (timing_tbl[i].delay) { delay(timing_tbl[i].delay); } }这里都是涉及到底层的SDRAM寄存器的操作,有对MPLL,DLL和MIU一些寄存器的设定。
/* enable peripheral clock */ writel(0xffffffff, (void *) 0xd8130250); writel(0xffffffff, (void *) 0xd8130254);
这两句是设定clock enable register0和clock enable register1,0xd8130000~0xd813ffff是属于PMC(Power Management Controller)寄存器组的。
下面这一句完全不懂是什么?并且没有找到相关的技术手册:
/* now AXI fabric decoder not correct, * close remap register this will open * when final AXI fabric OK */ writel(0, (void *)0xd8120500);
3.3通过读出来的数据改变cpu的频率switch_cpu_freq();
3.4 handle_stm()这个函数完全不知道干了什么;
3.5 memory_auto_tune();这里是与MIU寄存器相关的设定
3.6 pmc_writel(pmc_readl(0x250) | (1<<2), 0x250):
这里是使能UART1 clock,是属于PMC的一部分。
3.7
初始化UART1 的GPIO口:
elite_gpio_pinmux_set(0); elite_gpio_func_en(93, 0); //TXD elite_gpio_func_en(94, 0); //RXD elite_gpio_pullupdown_ctrl0_set(93, ELITE_PINCONF_PULL_UP); //TXD elite_gpio_pullupdown_ctrl0_set(94, ELITE_PINCONF_PULL_DOWN); //RXD
第一句是pin-sharing selection register。
后面几句是设置UART1 TXD/RXD Signale GPIO Enable和UART1RXD/TXD Signal pull-up/down control0
3.8 串口初始化serial_init() -> serial_setbrg():
这里有一个很重要的结构体,就是pUart_Reg,它将关于串口寄存器的值都包含进来了:
typedef struct _UART_REG_ { volatile unsigned long URTDR; /* Transmit data register */ volatile unsigned long URRDR; /* Receive data register */ volatile unsigned long URDIV; /* UART clock divisor & Bard rate divisor */ volatile unsigned long URLCR; /* Line control register */ volatile unsigned long URICR; /* IrDA control register */ volatile unsigned long URIER; /* Interrupt enable register */ volatile unsigned long URISR; /* Interrupt status register */ volatile unsigned long URUSR; /* Status register */ volatile unsigned long URFCR; /* Fifo control register */ volatile unsigned long URFIDX; /* Fifo index register */ volatile unsigned long URBKR; /* Break signal count value register */ volatile unsigned long URTOD; /* UART time out divisor register */ volatile unsigned long Resv30_FFF[0x3F4]; /* 0x0030 - 0x0FFF Reserved */ volatile unsigned char TXFifo[32]; /* 0x1000 - 0x101F 16x8 bit asynchronous Fifo */ volatile unsigned char RXFifo[32]; /* 0x1020 - 0x103F 16x10 bit asynchronous Fifo */ } UART_REG, *PUART_REG;然后在serial_setbrg()这个函数里面就是这个结构体的值赋值就好了。
3.9 将u-boot.bin从Serial Flash拷贝到SDRAM中去load_os_image():
3.9.1
初始化SPI sf_init():
reg_base = ELITE_SPI_FLASH_REG_BASE; writeb(0x00, (void *)(reg_base + SPI_RD_WR_CTRL)); phy_flash_base = 0xffffffff - SPI_FLASH_SIZE + 1; for (i = 0; i < 32; i++) { if ((SPI_FLASH_SIZE >> i) == 0x8000) phy_flash_size = i; } writel(phy_flash_base | (phy_flash_size << 8), (void *)(reg_base + CHIP_SEL_0_CFG)); writel(0x00000000, (void *)(reg_base + SPI_IF_CFG));需要对SPI相关的寄存器进行操作,这里有人就会问为什么前面不需要初始化Serial Flash就可以用到里面的内容,因为这里是初始化SPI总线,是要将Serial Flash 与SDRAM联系起来或是传递数据用的。
3.9.2
将存储在Serial Flash中的u-boot.bin拷贝到SDRAM 0x3F80000处sf_read(OS_FLASH_OFFSET, OS_MEM_ADDR, OS_IMAGE_SIZE):
writel(0XFFFFFFFF, (void *)(reg_base + SPI_ERROR_STATUS)); writel(readl((void *)(reg_base + SPI_IF_CFG))|(1 << 6), (void *)(reg_base + SPI_IF_CFG)); while (len > 0) { if (len > 32) burst_size = 32; else burst_size = len; writeb(0x3, (void*)(reg_base + SPI_PROG_CMD_WBF)); writeb((offset>>16) & 0xff, (void*)(reg_base + SPI_PROG_CMD_WBF+1)); writeb((offset>>8) & 0xff, (void*)(reg_base + SPI_PROG_CMD_WBF+2)); writeb(offset & 0xff, (void*)(reg_base + SPI_PROG_CMD_WBF+3)); writel((0x4<<24)|(burst_size<<16)|(chip_index<<1)|0x1, (void*)(reg_base + SPI_PROG_CMD_CTRL)); while(readl((void *)(reg_base + SPI_ERROR_STATUS)) & (1 << 31)); /* wait data ready */ while (readl((void*)(reg_base + SPI_PROG_CMD_CTRL)) & (1 << 0)); for (i = 0; i < burst_size; i++) *(unsigned char*)(buf + i) = readb((void*)(reg_base + SPI_PROG_CMD_RBF+i)); len -= burst_size; offset += burst_size; buf += burst_size; } writel(readl((void *)(reg_base + SPI_IF_CFG)) & ~(1 << 6), (void *)(reg_base + SPI_IF_CFG));首先会传递命令0x03到SPI_PROG_CMD_WBF中,告诉它我要进行读命令了,然后继续传递地址到此命令中去,然后在从SPI_PROG_CMD_RBF中读出刚刚写进去的地址中的内容,一次性读取4个字节的内容。然后将这些值传递到SDRAM 0x3F80000处,进而完成了u-boot.bin的拷贝工作。
3.10 直接跳转到0x3F80000,进入到u-boot.bin中的第一条指令处:
#define OS_MEM_ADDR 0x3f80000 typedef void (*bl2)(void); bl2 bl2_entry; bl2_entry = (bl2*)OS_MEM_ADDR; bl2_entry();首先定义了一个函数指针,然后将这个指针的值赋予0x3F80000,然后直接调用这个指针函数,就相当于直接跳转到这个指针处,也就是0x3F80000。然后在此之前会遇到几行代码:
__asm__ volatile("dsb"); __asm__ volatile("isb");这个是内嵌到C语言中的汇编代码,dsb值指数据同步隔离,isb是指指令同步隔离,意思等待之前的数据和指令全部执行完毕之后,才能进行后面的操作;
/* turn off car mode */ *(volatile unsigned int*)REG_CAR_SETTING = 1;
这里这个寄存器有两种mode:car mode和normal mode
这个我不知道是干嘛用的?
3.11 验证eloader的代码运行地址
ldr pc, _start_armboot @ jump to C code
在这条代码之前加入:
mov r0, pc将pc当前的值,给到r0处,这时候r0就是调用main()函数传递进来的第一个参数,在main()函数中加入:
void main(unsigned int temp) { printf("Miles: temp = %x\n", temp); }这样就会将pc的值传递到temp,并打印出来,结果为0xFFFF0108,说明是直接运行在Serial Flash上的。
相关文章推荐
- Spring boot启动运行流程
- u-boot启动流程分析(1)_平台相关部分
- u-boot_2013.01启动流程分析(三)(for exynos4412)
- uboot启动流程详解(1)-_start
- [IMX6Q]u-boot启动kernel流程
- u-boot启动流程分析(1)_平台相关部分
- [SPRD][uboot]展讯平台启动流程介绍
- u-boot系统启动流程
- U-Boot启动流程(Linux内核)的分析(二转)
- x210v3开发板u-boot-2012.10移植之补充篇---uboot启动流程详解
- 移植u-boot-2012-10到tiny210v2(一)-----基本芯片介绍和启动流程介绍
- spring boot 启动流程
- U-BOOT的启动流程及移植
- 【Spring Boot】SpringBoot-启动流程分析
- u-boot 启动流程(mips)
- IMX6 uboot的启动流程
- TQ2440-U-BOOT-1.1.6启动流程及常用功能浅析
- arm linux 启动流程之 ppcboot
- 【学习总结】ARM cotex-a8 下 u-boot启动流程
- 2014.4新版uboot启动流程分析