您的位置:首页 > 其它

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. 现在来分析源代码:

_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上的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: