CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2017/03/26/U-Boot分析、移植及使用/#more
该系列主要是为驱动学习提供基石,从0到Qt。本文是该系列的第二篇:对U-Boot进行分析及移植.
1.U-Boot结构分析
Bootloader一般可以分成两个阶段:
- 第一个阶段通常使用汇编进行必要的硬件初始化,例如:关看门狗、关中断、设置时钟频率、RAM初始化等。除此之外,还要将第二阶段的代码复制到RAM中,设置好栈,然后跳到第二阶段,该阶段主要是与SOC平台相关。
- 第二阶段一般是用c语言编写,该阶段主要和开发板板载资源相关。该阶段主要初始化后续的硬件,处理内存映射、以及将内核和根文件系统从Flash“搬运”到RAM中运行,然后传入启动参数,最后启动引导内核,完成使命。
Linux中系统一般的分区结构,如下图:
分区主要分为四个分区:Bootloader、Boot parameters、Kernel、Root filesystem。其中Boot parameters是待传入内核的参数,Kernel即内核、Root filesystem是根文件系统。Bootloader在第一个分区(假设该储存介质为Nand Flash),则在Nand Flash的地址为0x0000 0000,S3C2440上电后,硬件自动把Nand Flash上前4k代码复杂到自己内部RAM中运行。该4k代码先要初始化一些必要的硬件,如看门狗,中断等,然后准备好SRAM,并将后续的代码复制到SDRAM中,再跳到SDRAM中运行,完成Bootloader第一阶段。此时在SDRAM中的第二阶段代码开始工作,首先还是初始化硬件,处理内存映射,把Nand Flash上内核和根文件系统“搬运”到SDRAM准备运行,然后根据用户通过串口传入的启动参数放在Boot parameters,然后内核启动,完成使命。内核启动后,开始去Boot parameters寻找启动参数,根据启动参数做出相应的操作,如:控制台输出、串口波特率、根文件系统位置。
2.U-Boot源码分析
这里根据u-boot-1.1.6的源码进行分析,,主要工作有:
- 第一阶段的硬件初始化(CPU模式、看门狗、中断)
- 准备SDRAM
- 设置堆栈
- 搬运FLASH中的代码到SDRAM
- 清除.bss段并跳转至第二阶段
- 第二阶段的硬件初始化(时钟、串口)
- 内存映射
- 根据传入参数启动内核
2.1第一阶段
该阶段的源码在/arch/cpu/arm920t/start.S
中:首先进行硬件初始化,包括设置CPU模式,关闭看门狗、关闭中断。这里把时钟配置放在了第二阶段。
- 设置CPU模式
把CPU设置为SVC模式,将当前程序状态计数器CPSR的值保存到r0中,利用bic指令清除r0的0x1f并存入r0中,再利用orr指令或运算上0xd3并存入r0,最后r0写回CPSR,这一过程就把CPSR设置为SVC模式:
{% codeblock lang:asm [start.S]%}
reset:
/* set the cpu to SVC32 mode*/
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xd3
msr cpsr,r0
{% endcodeblock %} - 设置看门狗和关闭中断
根据S3C2440的datasheet,将0x00写入pWTCON对应寄存器的地址,完成看门狗的关闭。中断部分也类似,将INTMSK寄存器设置为0xffff ffff即可:
{% codeblock lang:asm [start.S]%}
/* turn off the watchdog */
define pWTCON 0x53000000
define INTMOD 0X4A000004
define INTMSK 0x4A000008 /* Interupt-Controller base addresses */
define INTSUBMSK 0x4A00001C
define CLKDIVN 0x4C000014 /* clock divisor register */
ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0]
/* turn off the int */
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0]
{% endcodeblock %}
- 准备SDRAM
start.S中没有初始化SDRAM,而是先跳转到函数cpu_init_crit中,再跳转至lowlevel_init.S中进行初始化SDRAM。adr是一个位置无关指令,将_start的地址读取到r0中,上电后,Flash中的前4k就被拷到S3C2440内部RAM,则这里r0=0。然后再用ldr指令将_TEXT_BASE这个地址(来自链接脚本,代表运行地址)的值放在r1中。然后比较r0和r1的值,如果是相等的,那么证明这个时候代码已经位于了其应该位于的地方,这个时候就不再需要初始化SDRAM去重定位了。如果r1和r0的值不相等,那么将直接跳转cpu_init_crit中。
{% codeblock lang:asm [start.S]%}
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 */
blne cpu_init_crit
{% endcodeblock %}
{% codeblock lang:asm [start.S]%}
cpu_init_crit:
/flush v4 I/D caches/
mov r0, #0
mcr p15, 0, r0, c7, c7, 0 /* flush v3/v4 cache /
mcr p15, 0, r0, c8, c7, 0 / flush v4 TLB */
/*disable MMU stuff and caches*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
orr r0, r0, #0x00000002 @ set bit 2 (A) Align
orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
mcr p15, 0, r0, c1, c0, 0
/*
* before relocating, we have to setup RAM timing
* because memory timing is board-dependend, you will
* find a lowlevel_init.S in your board directory.
*/
mov ip, lr
bl lowlevel_init
mov lr, ip
{% endcodeblock %}
- 设置栈
通常为了保存调用C语言时传递参数和保存CPU运行时的现场,需要设置栈,根据定义_TEXT_BASED为代码段的开始地址,然后在代码段的下面留出一段内存实现malloc和全局参数,再留出一点给中断模式的栈和abort异常的栈,剩下的就是栈了:
{% codeblock lang:asm [start.S]%}
/* Set up the stack /
stack_setup:
ldr r0, _TEXT_BASE / upper 128 KiB: relocated uboot /
sub r0, r0, #CFG_MALLOC_LEN / malloc area /
sub r0, r0, #CFG_GBL_DATA_SIZE / bdinfo */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
{% endcodeblock %}
-
搬运FLASH中的代码到SDRAM
现在将Bootloader的全部代码复制到RAM中运行:
{% codeblock lang:asm [lowlevel_init.S]%}
relocate: /* relocate U-Boot 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 clear_bssldr r2, _armboot_start
ldr r3, _bss_start
sub r2, r3, r2 /* r2 <- size of armboot */bl CopyCode2Ram /* r0: source, r1: dest, r2: size */
copy_loop:
ldmia r0!, {r3-r10} /* copy from source address [r0] /
stmia r1!, {r3-r10} / copy to target address [r1] /
cmp r0, r2 / until source end addreee [r2] */
ble copy_loop
{% endcodeblock %}
- 清除.bss段并跳转至第二阶段
bss段部分保存有静态变量、没有初始化或初始化值为0的全局变量,为了减小体积和防止程序在读取它们时,读取的时上次的值,所以需要先清0:
{% codeblock lang:asm [start.S]%}
clear_bss:
ldr r0, _bss_start /* find start of bss segment /
ldr r1, _bss_end / stop here /
mov r2, #0x00000000 / clear */
clbss_l:str r2, [r0] /* clear loop… */
add r0, r0, #4
cmp r0, r1
ble clbss_l
ldr pc, _start_armboot
{% endcodeblock %}
2.2第二阶段##
第二阶段的代码在lib_arm/board.c中的start_armboot()函数开始,在内部调用各自函数进行设置:
-
设置时钟
{% codeblock lang:c [smdk2410.c]%}
int board_init (void)
{
S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();/* to reduce PLL lock time, adjust the LOCKTIME register */
clk_power->LOCKTIME = 0xFFFFFF;/* configure MPLL */
clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);/* some delay between MPLL and UPLL */
delay (4000);/* configure UPLL */
clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);/* some delay between MPLL and UPLL */
delay (8000);/* set up the I/O ports */
gpio->GPACON = 0x007FFFFF;
gpio->GPBCON = 0x00044555;
gpio->GPBUP = 0x000007FF;
gpio->GPCCON = 0xAAAAAAAA;
gpio->GPCUP = 0x0000FFFF;
gpio->GPDCON = 0xAAAAAAAA;
gpio->GPDUP = 0x0000FFFF;
gpio->GPECON = 0xAAAAAAAA;
gpio->GPEUP = 0x0000FFFF;
gpio->GPFCON = 0x000055AA;
gpio->GPFUP = 0x000000FF;
gpio->GPGCON = 0xFF95FFBA;
gpio->GPGUP = 0x0000FFFF;
gpio->GPHCON = 0x002AFAAA;
gpio->GPHUP = 0x000007FF;/* arch number of SMDK2410-Board */
gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;/* adress of boot parameters */
gd->bd->bi_boot_params = 0x30000100;icache_enable();
dcache_enable();return 0;
}
{% endcodeblock %} -
初始化串口
{% codeblock lang:c [serial.c]%}
int serial_init (void)
{
serial_setbrg ();return (0);
}
/*
-
Read a single byte from the serial port. Returns 1 on success, 0
-
otherwise. When the function is succesfull, the character read is
-
written into its argument c.
*/
int serial_getc (void)
{
S3C24X0_UART * const uart = S3C24X0_GetBase_UART(UART_NR);/* wait for character to arrive */
while (!(uart->UTRSTAT & 0x1));return uart->URXH & 0xff;
}
{% endcodeblock %}
-
监内存映射
在smdk2410.c里的函数dram_init中设置的内存起始地址为0x3000 0000,大小为0x4000 0000:
{% codeblock lang:c [smdk2410.c]%}
int dram_init (void)
{
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;return 0;
}
{% endcodeblock %}
至此,U-Boot的启动过程基本分析完了,在启动U-Boot后,通过lib_arm/armlinux.c中的do_boot_linux来启动内核。这里总结画个图来记录下,中间的顺序可能有点小差异,但大致没问题:
Ubuntu中待作为nfs挂载的路径:
/work/hceng/nfs_rootfs
Ubuntu IP:192.168.1.141
开发板 IP:192.168.1.142
网关 IP:192.168.1.1
这里要提前在Ubuntu虚拟机中进行NFS配置,参考上一篇文章中的:2.3安装、 配置网络服务;
启动U-Boot后,迅速按任意键进入U-Boot的下载模式,再输入"q"退出菜单。
-
设置开发板ip和nfs挂载的服务ip:
{% codeblock lang:shell %}
setenv serverip 192.168.1.141 //设置服务器ubuntu ip
setenv ipaddr 192.168.1.142 //设置开发板ip
saveenv
{% endcodeblock %} -
设置u-boot各分区大小(内核也要相应设置匹配,在Linux-3.4.2内核中,文件在arch/arm/mach-s3c24xx/common-smdk.c中):
{% codeblock lang:shell %}
setenv mtdparts mtdparts=nandflash0:256k@0(bootloader),128k(params),4m(kernel),-(root)
saveenv
{% endcodeblock %} -
下载、擦除、烧写内核:
{% codeblock lang:shell %}
nfs 30000000 192.168.1.141:/work/hceng/nfs_rootfs/uImage
nand erase kernel
nand write.jffs2 30000000 kernel
{% endcodeblock %} -
下载、擦除、烧写根文件系统:
{% codeblock lang:shell %}
nfs 30000000 192.168.1.141:/work/hceng/nfs_rootfs/fs_mini_mdev_new.yaffs2
nand erase root
nand write.yaffs 30000000 4a0000 $(filesize)
{% endcodeblock %} -
根文件系统采用nand启动:
{% codeblock lang:shell %}
set bootargs noinitrd root=/dev/mtdblock3 rootfstype=yaffs2 init=/linuxrc console=ttySAC0,115200
saveenv
{% endcodeblock %}
启动后,对应的NFS挂载:
{% codeblock lang:shell %}
mount -t nfs -o nolock 192.168.1.141:/work/hceng/nfs_rootfs /tmp
{% endcodeblock %} -
根文件系统采用nfs启动:
{% codeblock lang:shell %}
set bootargs root=/dev/nfs nfsroot=192.168.1.141:/work/hceng/nfs_rootfs ip=192.168.1.142:192.168.1.141:192.168.1.1:255.255.255.0::eth0:off init=/linuxrc console=ttySAC0,115200
saveenv
{% endcodeblock %}
最后启动:
{% codeblock lang:shell %}
boot
{% endcodeblock %}