U-Boot分析、移植及使用

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中系统一般的分区结构,如下图:

![](https://blog-image-1257972744.cos.ap-chongqing.myqcloud.com/hceng/blog_image/170326/1.jpg)

分区主要分为四个分区: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_bss

    ldr 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来启动内核。这里总结画个图来记录下,中间的顺序可能有点小差异,但大致没问题:

![](https://blog-image-1257972744.cos.ap-chongqing.myqcloud.com/hceng/blog_image/170326/2.jpg)
# 3.U-Boot移植 # 待补充。 # 4.U-Boot使用 # 记录下平时使用U-Boot烧写内核/根文件系统和挂载根文件系统,这里假设如下:

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 %}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值