http://blog.youkuaiyun.com/yangxingbo0311/article/details/7333525
一个可以执行的Image都会有一个入口点并且只有一个入口点,不管是WinCE还是Linux,都有唯一的一个入口,通常这个入口被放在Rom(flash)的0X0地址。例如在Uboot中:
输入:VIM /data/u-boot-1.1.6/cpu/s3c24xx/start.S
可见如下代码:
可见代码:
.globl_start
_start:
但是这个_start 入口又在何处呢?这个工作在linux中主要是由另一个关键的文件来实现。即链接器脚本文件。以后缀名.lds的文件。
gcc等编译器内置有缺省的链接器脚本。如果采用内置的缺省脚本,则生成的目标代码需要操作系统才能加载运行。为了能在嵌入式系统上直接运行,需要编写自己的链接生成脚本文件。编写链接脚本,首先要对目标文件的格式有一定的了解。GNU编译器生成的目标文件缺省为elf格式。elf文件由若干段(section)组成,如不特殊指明,又源程序生成的目标代码中包含如下的段:
.text(正文段)包含程序的指令代码;
.data(数据段)包含固定的数据,如常量、字符串等;
.bss(未初始化数据段)包含未初始化的变量、数组等。
C++源程序的目标代码中还包括.fini(析构函数代码)和.init(构造函数代码)等。
先来看看uboot内的u-boot.lds文件:
输入指令:VIM vim /data/u-boot-1.1.6/board/samsung/smdk2416/u-boot.lds
可得:
即:
/*
2 * (C) Copyright 2002
3 * Gary Jennejohn, DENX Software Engineering, <gj@denx.de>
4 *
5 * See file CREDITS for list of people who contributed to this
6 * project.
7 *
8 * This program is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU General Public License as
10 * published by the Free Software Foundation; either version 2 of
11 * the License, or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
21 * MA 02111-1307 USA
22 */
23
24 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
25 OUTPUT_ARCH(arm)
26 ENTRY(_start)
27 SECTIONS
28 {
29 . = 0x00000000;
30 . = ALIGN(4);
31 .text :
32 {
33 cpu/s3c24xx/start.o (.text)
34 cpu/s3c24xx/s3c2416/cpu_init.o (.text)
35 cpu/s3c24xx/onenand_cp.o (.text)
36 cpu/s3c24xx/nand_cp.o (.text)
37 cpu/s3c24xx/movi.o (.text)
38 *(.text)
39 }
40 . = ALIGN(4);
41 .rodata : { *(.rodata) }
42 . = ALIGN(4);
43 .data : { *(.data) }
44 . = ALIGN(4);
45 .got : { *(.got) }
46
47 . = .;
48 __u_boot_cmd_start = .;
49 .u_boot_cmd : { *(.u_boot_cmd) }
50 __u_boot_cmd_end = .;
51
52 . = ALIGN(4);
53 .mmudata : { *(.mmudata) }
54
55 . = ALIGN(4);
56 __bss_start = .;
57 .bss : { *(.bss) }
58 _end = .;
59 }
对以上代码做好好的分析:
额,好像一开始就看这个比较复杂了,先来看看GUN官网上的形式完整的描述:
SECTIOS{
...
secname start BLOCK(align)(NOLOAD):AT(ldadr)
{contents}>region:phdr = fill
...
}
24 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")指定输出可执行文件是elf格式,32位ARM指令,小端
25 OUTPUT_ARCH(arm)指定输出可执行文件的平台为ARM
26 ENTRY(_start)
其中,ENTRY(_start)指明程序的入口点为_start标号。=0x00000000指明目标代码的起始地址为0x00000000。这一段地址可以是SDRAM的起始地址;
. = ALIGN(4);表示以四字节对齐
.text :
{
cpu/s3c24xx/start.o (.text) 代码的第一个代码部分,指明start.S是入口程序代码,被放到代码段的开头
cpu/s3c24xx/s3c2416/cpu_init.o (.text)
cpu/s3c24xx/onenand_cp.o (.text)
cpu/s3c24xx/nand_cp.o (.text)
cpu/s3c24xx/movi.o (.text)
*(.text)
}
40 . = ALIGN(4); 以四字节对齐
41 .rodata : { *(.rodata) } 指定只读数据段,RO段
42 . = ALIGN(4);
43 .data : { *(.data) }指定读/写数据段,RW段,表示从0x00000000开始放置所有目标文件的代码段
44 . = ALIGN(4);
45 .got : { *(.got) }指定got段,got段式是uboot自定义的一个段,非标准段。
46
47 . = .;
48 __u_boot_cmd_start = .; 把__u_boot_cmd_start赋值为当前位置,即起始位置
49 .u_boot_cmd : { *(.u_boot_cmd) }指定u_boot_cmd段,uboot把所有的uboot命令放在该段
50 __u_boot_cmd_end = .;把__u_boot_cmd_end赋值为当前位置,即结束位置
51
52 . = ALIGN(4);
53 .mmudata : { *(.mmudata) }
54
55 . = ALIGN(4);
56 __bss_start = .;把__bss_start赋值为当前位置,即bss段的开始位置
57 .bss : { *(.bss) }指定bss段
58 _end = .;把_end赋值为当前位置,即bss段的结束位置。
接下来就是start.S了。。本文源码来源于u-boot-1.1.6。
源码的分析参考网上的诸多博客的整理。如http://home.eeworld.com.cn/my/space.php?uid=135723&do=blog&id=25548。http://www.51hei.com/mcu/1132.html等。
都说bootloader分为两个阶段。。四极管也来看看这两个阶段都做了什么事情。
一、阶段1
阶段1通常包括以下步骤(以执行的先后顺序)
1、一些基本硬件初始化工作
2、为加载映像2准备RAM空间(RAM足够的情况下可以省略)
3、把映像2拷贝到RAM空间
4、跳转到映像2的入口点(一般是C入口点)
一般阶段1都会有如下具体工作:
1、定义ARM各个运行模式
2 、定义ARM各个运行模式堆栈的大小
3、根据处理器工作状态确定编译方式
4、初始化异常中断向量表
5、禁止看门狗
6、屏蔽中断
7、屏蔽子中断
8、设置时钟,初始化使能SDRAM
9、为各操作模式设置堆栈指针,将系统模式置为监管模式(SVC)并设置SP
10、将映像2拷贝到SDRAM的指定处。。。通过跳转进入阶段2
现在看源码分析:
对于UBOOT的relocate代码的深入理解。可以看看http://www.51hei.com/mcu/1131.html的解析。。我也把他记下来,以便以后自己复习。Uboot start.S分析
四极管 2012-3-8
/*
* cpu/s3c24xx/start.S
*
* U-Boot - Startup Code for S3C24XX
*
* Copyright (c) 2006, Samsung Electronics
* All rights reserved.
*
* Based on cpu/arm926ejs/start.S
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* $Id: start.S,v 1.8 2008/05/23 00:26:34 eyryu Exp $
*/
/*
* mod by sc.suh@samsung.com
*
* Our U-Boot Memory Map
* (offset)
* -------------------------- 0x04000000
* | Stack (512KB) |
* -------------------------- 0x03f80000
* | Heap (1MB) |
* -------------------------- 0x03e80000
* | IRQ Stack (4KB) | <------------------------ if exists
* -------------------------- 0x03e70000
* | FIQ Stack (4KB) | <------------------------ if exists
* -------------------------- 0x03e60000
* | GBL (128B) |
* -------------------------- 0x03exxxxx
* | BSS and Reserved |
* -------------------------- 0x03e40000
* | U-Boot (256KB) |
* -------------------------- 0x03e00000
*/
#include <config.h>
#include <version.h>
#ifdef CONFIG_ENABLE_MMU
#include <asm/proc/domain.h>
#endif
#ifndef CONFIG_ENABLE_MMU
#ifndef CFG_PHY_UBOOT_BASE
#define CFG_PHY_UBOOT_BASE CFG_UBOOT_BASE
#endif
#endif
#define no_compile 0
/********************************************************************
*
* Jump vector table as in table 3.1 in [1]
*
*******************************************************************/
.globl _start
_start: /*系统复位位置,各个异常向量对应的跳转代码,ARM中规定了异常向量的地址*/
b reset //复位 0x0
ldr pc, _undefined_instruction //未定义的指令异常 0x4
ldr pc, _software_interrupt //软件中断异常
ldr pc, _prefetch_abort//预取指令0xC
ldr pc, _data_abort//数据0x10
ldr pc, _not_used //未使用0x14
ldr pc, _irq//慢速中断异常0x18
ldr pc, _fiq//快速中断异常0x1C
/*.word伪操作作用于分配一段字内单元(分配的单元都是字对齐的),并用伪操作中的expr初始化。.long于.int作用与//相同*/
//以下.word 的含义如下:
.Word为GNU ARM汇编特有的伪操作,为分配一段字节内容单元(分配的单元为字对齐的),可以使用.word把标识符作为常量使用,如_irq:.word irq即把fiq存入内存变量_irq中,也即是把fiq放到地址_irq中。
_undefined_instruction:
.word undefined_instruction
_software_interrupt:
.word software_interrupt
_prefetch_abort:
.word prefetch_abort
_data_abort:
.word data_abort
_not_used:
.word not_used
_irq:
.word irq
_fiq:
.word fiq /*定义fiq变量到_fiq地址里面去,_fiq地址就是上面对应的,比如_fiq是0x0000 001C*/
.balignl 16,0xdeadbeef
/*对于这句话的理解内容就比较多了。。。。在Nunca Muer to 的空间里面有详细的对比分析。。为了以后难找,我还是把那段话敲出来。。。
先要弄明白.balignl的意思,这个其实应该算是一个伪操作符,伪操作符的意思就是机器码里,并没有一个汇编指令与其对应,是编译器来实现其功能的,.balignl是.balign的变体,.balign的意思是,在以当前地址开始,地址计数器必须是以第一个参数为整数倍的地址为尾,在前面记录一个字节长度的信息,信息内容为第二个参数。
.balignl 8,0xde
它的意思就是在以当前地址开始,在地址为8的倍数的位置的前面填入一个字节内容为0xde的内容。如果当前地址正好是8的倍数,则没有东西被写入到内存。
关于.balignl 16,0xdeadbeef这句,功能说明没有错,就是想在某个位置插入0xdeadbeef这个特殊的内存值。错就错在我对这个16的理解上面,16就是16个字节,这是没有错的,但是这个16的由来,并不是我所理解的至少16个字节,才能在任何情况下保证插入这个特殊的内存值。我在博客留言中回答,举了个例子pc=0x0000007地址,偏移量为8个字节时,这个时候就不够用4个字节的内容了,以此推导出的,至少有16个字节才能保证这个特殊的内存值的插入也是完全错误的。
举个反例,如果给那位网友的解释,那就算有16个字节的偏移量,那如果PC地址为0x000000F时,也只需要一个字符的空间,那这个0xdeadbeef的值是不够的,以此类推,就算这个值为任意一值,按我之前的解释的错误逻辑,也都有不满足的情况,呵呵,所以我之前的推论有误,我现在把16这个值的由来进行说明。
ARM920T处理器核心,支持32与16位两种指令长度,16位的指令叫做thumb指令集,由于我使用的是32位指令集,所有一切都是以32位指令集进行说明。
既然是32位指令集,所以一条指令就占32位,即4个字节,所以在调试器中,地址显示也是4字节一跳的,所以PC的值,也是4字节一跳的,并不存在可能PC的值为0x00000007的情况。
这个地方填16个偏移量,是因为
.globl _start //不占内存
_start:
b reset //占四个字节的内存
ldr pc, _undefined_instruction //占四个字节的内存
ldr pc, _software_interrupt //占四个字节的内存
ldr pc, _prefetch_abort //占四个字节的内存
ldr pc, _data_abort//占四个字节的内存
ldr pc, _not_used //占四个字节的内存
ldr pc, _irq//占四个字节的内存
ldr pc, _fiq//占四个字节的内存
占了4x8 = 32 字节内存。
_undefined_instruction: .word undefined_instruction //占四个字节的内存
_software_interrupt:.word software_interrupt//占四个字节的内存
_prefetch_abort:.word prefetch_abort//占四个字节的内存
_data_abort:.word data_abort//占四个字节的内存
_not_used:.word not_used//占四个字节的内存
_irq:.word irq//占四个字节的内存
_fiq:.word fiq//占四个字节的内存
占了4X7=28个字节内存。
所以在这个.balignl 16,0xdeadbeef指令之前,一共占了4X15=60个字节的内存,所以本代码的作者当时就简单的在15这个数上,加了一个1,即16,把前面的指针移到地址为64的位置,然后在前面插上了0xdeadbeef这个特殊的值。
我不知道这个地方时作者的一个错误呢,歪打正着呢,还是怎么回事,其实这个偏移量的值有好多种情况,如果说最小值的话,那么也可以写成.balignl 8,0xdeadbeef,也同样可以达到相同的目的,因为60不是8的倍数,但是64是8的倍数(60到64之前都不是8的倍数,同样也不是16的倍数,所以写8和16都可行)如果写8,也正好插入到64前面,也即60这个内存起始地址,如果更大一点儿呢,那么填32也可以达到同样的效果,即.balignl 32,0xdeadbeef,道理同上。当然,不能为4,因为PC值在任何时候,都是4的倍数(64是4的倍数),只要不为0就为4的倍数,呵呵,这个值不行,如果用了这个值,0xdeadbeef永远也插不进去。
*/
/********************************************************************
*
* Startup Code (reset vector)
*
* do important init only if we don't start from memory!//做一些初始化,如果我们不是SDRAM启动的话
* setup Memory and board specific bits prior to relocation.
* relocate armboot to ram //重定位代码到SDRAM中
* setup stack//设置堆栈空间
* *******************************************************************/
/*保存变量的数据区*/
_TEXT_BASE:
.word TEXT_BASE //在TEXT_BASE定义在board\smdk2416、config.mk文件中这段话表示,用户告诉编译器编译地址的起始(或者说可以理解是为加载的地址,这样就能做到编译地址和运行地址的统一了)
/*
* Below variable is very important because we use MMU in U-Boot.
* Without it, we cannot run code correctly before MMU is ON.
* by scsuh.
*/
_TEXT_PHY_BASE:
.word CFG_PHY_UBOOT_BASE
.globl _armboot_start
_armboot_start:
.word _start
/*
* These are defined in the board-specific linker script.
*/
.globl _bss_start
_bss_start:
.word __bss_start
.globl _bss_end
_bss_end:
.word _end
#ifdef CONFIG_USE_IRQ
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
.word 0x0badc0de
/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
.word 0x0badc0de
#endif
/*上面这些代码,主要保存一些全局变量,用于BOOT程序从FLASH拷贝到RAM,或者其他的使用。还有一些变量的值是通过连接脚本得到的,比如TEXT_BASE位于/u-boot-1.1.6/board/xxx(开发板目录名称)/config.mk文件里,_bss_start、_end位于/u-boot-1.1.6/board/xxx(开发板目录名称)/u-boot.lds文件里,具体值是由编译器算出来的。
*/
/*
* the actual reset code //系统复位代码。系统一上电,就跳转到这里运行
*/
reset:
/*
* set the cpu to SVC32 mode //svc为操作系统保护模式
*/
mrs r0,cpsr //取得当前程序状态寄存器cpsr到r0
bic r0,r0,#0x1f //这里是位清除指令,把中断全部清除,只置位模式控制位。为中断提供服务的通常是0S,设备驱动程序的责任,因此在Bootloader的执行过程中可以不必响应任何中断。
orr r0,r0,#0xd3 //计算为超级保护模式
msr cpsr,r0//设置cpsr为超级保护模式
/*以上设置的作用:
设置CPU运行在SVC32模式,ARM共有7种模式:
用户模式(usr):arm处理器正常的程序执行状态
快速中断模式(fiq):用于高速数据传输或通道处理
超级保护模式(svc):操作系统使用的保护模式
数据访问终止模式(abt):当数据或指令预终止时进入该模式,可用于虚拟存储及存储保护
系统模式(sys):运行具有特殊的操作系统任务
未定义指令中止模式(uud):当未定义的指令执行时进入该模式。可用于支持硬件协处理器的软件仿真。
通过设置ARM的CPSR寄存器,让CPU运行在操作系统保护模式,为后面进行其他操作做好准备工作。
*/
#if defined(CONFIG_S3C2443) ||defined(CONFIG_S3C2450) || defined(CONFIG_S3C2416)
/*
* Retention IO power will be turen off whel sleep mode,
* but, when wakeup process starts, User should write '1'
* produce power on retention IO. PM check
*/
ldr r0, =0x4c00006c
ldr r1, =0x4c000064
ldr r2, [r0]
tst r2, #0x8
ldreq r2, [r1]
orreq r2, r2, #0x10000 /* (1<<16) */
streq r2, [r1]
#endif
/*
* we do sys-critical inits only at reboot,
* not when booting from ram!
*/
/*
1、关闭MMU和CPU内部指令/数据(I/D)cache
2、设置CPU的速度和时钟频率
3、RAM初始化
*/
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 */
/*
MCR指令用于将ARM处理器寄存器中的数据传输到协处理器寄存器中,格式为:
MCR 协处理器编码 ,协处理器操作码 1,源寄存器,目的寄存器1,目的寄存器2,协处理器操作码2。
其中协处理器操作码1和协处理器操作码2为协处理器将要执行的操作,源寄存器为ARM处理器的寄存器,目的寄存器1和目的寄存器2均为协处理器的寄存器。
*/
/*
* disable MMU stuff and caches禁止MMU和chache
*/
mrc p15, 0, r0, c1, c0, 0 //先把c1和c0寄存器的各位置0(r0=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
/*
* led GPH12 on
*/
#ifdef no_comiple
test1:
mov r1,#0x56000000
add r1,r1,#0x20 // set offset address
ldr r2,=(1<<6)
str r2,[r1,#0x0] // set GPC3 as output
ldr r2,=(0<<6)
str r2,[r1,#0x8] // disable pull-up/down
ldr r2,=(1<<3)
str r2,[r1,#0x4] // output low level
#endif
#ifdef CONFIG_ONENAND
/*
* With this check, we can change boot sequence as we want in run-time. * XXX: must modify to use "swp" not "ldr and str".
*/
check_onenand_boot:
mov r0, #0x1e400 /* check OneNAND via start buffer reg */
ldr r1, =0xfffffffe /* very tweaky and may occur bugs */
ldr r2, =0x00000f02
ldr r3, [r0]
str r1, [r0]
ldr r1, [r0]
str r3, [r0]
cmp r1, r2
bne 1024f
/* OneNAND is detected as boot device
* So, load <0x400 ~ 0xc00> to DataRam0
*/
fill_onenand_dr:
mov r2, #0
ldr r3, =0x0001e200
ldr r5, =0x0002 /* 0x400 ~ 0x800 */
ldr r6, =0x0001e400
ldr r7, =0x0802 /* fill 1KB in DR0 */
100: strh r2, [r3] /* block = 0, data buffer = 0 */
strh r2, [r3, #0x2] /* block = 0, data buffer = 0 */
strh r5, [r3, #0xe] /* set page, sector addr */
strh r7, [r6] /* set start buffer and count */
strh r2, [r6, #0x82] /* reset int status */
strh r2, [r6, #0x40] /* send LOAD command */
1: ldrh r8, [r6, #0x82] /* check int status */
tst r8, #(1<<15)
beq 1b
tst r5, #4;
ldr r5, =0x0004 /* 0x800 ~ 0xc00 */
add r7, r7, #0x0200 /* fill next 1KB 0x0a02 */
beq 100b
b 1024f
.ltorg /* without it, variables may go too far. */
1024:
#endif
/*
* Go setup Memory and board specific bits prior to relocation.
*/
bl lowlevel_init /* go setup pll,mux,memory */
/*进入lowlevel_init,这里主要是初始化存储寄存器,S3C2416的是Bank0-Bank6,比如位宽等,这个要根据自己的板子来进行响应的配置,比如网卡放在几个Bank,位宽多少,SDRAM放在那里,多大等。位于board/smdk2416/lowlevel_init.S:用于完成芯片存储器的初始化,执行完成后返回*/
#ifdef CONFIG_S3C2442
#ifdef CONFIG_PM
@ Check if this is a wake-up from sleep
ldr r1, PMST_ADDR
ldr r0, [r1]
tst r0, #0x8 @ PMST_SMR
bne WakeupStart
#endif
#endif
/* when we already run in ram, we don't need to relocate U-Boot.
* and actually, memory controller must be configured before U-Boot
* is running in ram.
*/
check_boot_device:
ldr r0, =0xff000fff
bic r1, pc, r0 /* r0 <- current base addr of code */
ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */
bic r2, r2, r0 /* r0 <- current base addr of code */
cmp r1, r2 /* compare r0, r1 */
beq after_copy /* r0 == r1 then skip flash copy */
#ifdef CONFIG_BOOT_MOVINAND
ldr sp, _TEXT_PHY_BASE
bl movi_bl2_copy
b after_copy
#endif
/* check boot device is nand or nor */
ldr r0, =0x00000000
ldr r3, [r0]
ldr r1, =0xfffffffe
str r1, [r0]
ldr r2, [r0]
str r3, [r0]
cmp r1, r2
#if defined(CONFIG_S3C2450) || defined(CONFIG_S3C2416)
/* Now iROM on 2450 is not support eFuse */
#if 1
b nand_copy
#else
beq nand_copy
#endif
#else
beq nand_copy
#endif
#ifdef CONFIG_ONENAND
ldr r3, [r0, #0x400]
ldr r1, =0xfffffffe
str r1, [r0, #0x400]
ldr r2, [r0, #0x400]
str r3, [r0, #0x400]
cmp r1, r2
beq jump_to_onenand
#endif
/* nor copy */
relocate: /* relocate U-Boot to RAM */
adr r0, _start /* r0 <- current position of code */
//取得_start的地址到r0。如果是在flash中运行,则_start的值就是0,如果是在RAM中运行,则_start=_TEXT_BASE=TEXT_BASE=0X33F80000。
(_TEXT_BASE在board/smdk2416/config.mk中定义)
@ ldr r1, _TEXT_BASE//把_TEXT_BASE地址处的值_TEXT_BASE,也就是BOOT在RAM中运行地址移到r1。测试时在flash还是在ARM
ldr r1, _TEXT_PHY_BASE /* r1 <- destination */
ldr r2, _armboot_start /*把_armboot_start 地址处的值也就是_start绝对地址(也就是在内存中的地址,这个绝对地址是在Link的时候确定的,如0x81008000)移到r2*/
ldr r3, _bss_start /*把_bss_start地址处的值也就是_bss_start绝对地址(也即在内存中的地址,这个绝对地址是在Link的时候确定的)移到r3*/
sub r2, r3, r2 /* r2 <- size of armboot*//*计算引导代码大小并存放到r2*/
add r2, r0, r2 /* r2 <- source end address*//*计算引导代码最后相对地址并存入R2*/
copy_loop: //重定位代码
ldmia r0!, {r3-r10} /* copy from source address [r0] */
/*从源地址[r0]读取32个字节到寄存器,并更新r0*/
stmia r1!, {r3-r10} /* copy to target address [r1] */
/*拷贝寄存器R3-R10的32个字节值保存到[r1]指明的地址,并更新R1的值*/
cmp r0, r2 /* until source end addreee [r2] */
/*循环拷贝,直到把所有的引导代码移植到内存*/
ble copy_loop
b after_copy
nand_copy:
mov r0, #0x1000
bl copy_from_nand
#ifdef CONFIG_ONENAND
b after_copy
jump_to_onenand:
bl temp_copy_onenand
onenand_copy:
mov r0, #0x400
bl copy_from_nand
#endif
after_copy:
#ifdef CONFIG_ENABLE_MMU
enable_mmu:
/* enable domain access */
ldr r5, =0x0000ffff
mcr p15, 0, r5, c3, c0, 0 @ load domain access register
/* Set the TTB register */
ldr r0, _mmu_table_base
ldr r1, =CFG_PHY_UBOOT_BASE
ldr r2, =0xfff00000
bic r0, r0, r2
orr r1, r0, r1
mcr p15, 0, r1, c2, c0, 0
/* Enable the MMU */
mmu_on:
mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #1 /* Set CR_M to enable MMU */
mcr p15, 0, r0, c1, c0, 0
nop
nop
nop
nop
#endif
/* Set up the stack*/
//初始化堆栈,为第二阶段的C语言做准备
stack_setup:
#ifdef CONFIG_MEMORY_UPPER_CODE
ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0xc)
#else
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 */
#endif
//数据段_bss_start ßà_bss_end的初始化
clear_bss:
ldr r0, _bss_start /* find start of bss segment*/
/*把_bss_start地址处存储的绝对地址移到r0*/
ldr r1, _bss_end /* stop here*/
/*把_bss_end 的地址处存储的绝对地址移到R1*/
mov r2, #0x00000000 /* clear*/
//初始化直接使用汉字,一次就是64个位
clbss_l:str r2, [r0] /* clear loop...*/
/*str指令用于从源寄存器中r2将一个32位的字数据传送到寄存器[r0]*/
add r0, r0, #4
cmp r0, r1
ble clbss_l /*小于或者等于跳转*/
ldr pc, _start_armboot //跳转到stage2
/*
Stage1 到此结束,然后开始stage2。也就是跳转到u-boot-1.1.6/board.c àstart_armboot中运行。把_start_armboot地址处的值也就是_start_armboot绝对地址移植到PC。
*/
_start_armboot:
.word start_armboot
#ifdef CONFIG_ENABLE_MMU
_mmu_table_base:
.word mmu_table
#endif
#ifdef CONFIG_ONENAND
temp_copy_onenand:
adr r0, _start /* r0 <- current position of code */
ldr r1, _TEXT_PHY_BASE /* test if we run from flash or RAM */
ldr r2, =0xbff
1: 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 1b
adr r0, onenand_copy
ldr r1, _TEXT_PHY_BASE
add r0, r0, r1
mov pc, r0
.ltorg
#endif
/*
* copy U-Boot to SDRAM and jump to ram (from NAND or OneNAND)
* r0: size to be compared
*/
.globl copy_from_nand
copy_from_nand:
mov r10, lr /* save return address */
mov r9, r0
/* get ready to call C functions */
ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer */
sub sp, sp, #12
mov fp, #0 /* no previous frame, so fp=0 */
#ifdef CONFIG_ONENAND
cmp r9, #0x1000
bne 2f
bl copy_uboot_to_ram
b 3f
2: bl onenand_cp
#else
mov r9, #0x1000
bl copy_uboot_to_ram
#endif
3: tst r0, #0x0
bne copy_failed
#if defined(CONFIG_S3C2450) || defined(CONFIG_S3C2416)
/* Confirm Booting Status NAND Booting or iROM NAND*/
ldr r6, =0x40008000
ldr r7, =0x24564236
swp r8, r7, [r6]
swp r5, r8, [r6]
cmp r7, r5
/* If compare value is same between r7 and r5, Booting Device is iROM */
beq 444f
mov r0, #0 /* NAND Booting */
b 555f
444:
mov r0, #0x40000000 /* iROM booting */
#else
mov r0, #0
#endif
555:
ldr r1, _TEXT_PHY_BASE
1: ldr r3, [r0], #4
ldr r4, [r1], #4
teq r3, r4
bne compare_failed /* not matched */
subs r9, r9, #4
bne 1b
4: mov lr, r10 /* all is OK */
mov pc, lr
copy_failed:
nop /* copy from nand failed */
b copy_failed
compare_failed:
nop /* compare failed */
b compare_failed
/*
* we assume that cache operation is done before. (eg. cleanup_before_linux())
* actually, we don't need to do anything about cache if not use d-cache in U-Boot
* So, in this function we clean only MMU. by scsuh
*
* void theLastJump(void *kernel, int arch_num, uint boot_params);
*/
#ifdef CONFIG_ENABLE_MMU
.globl theLastJump
theLastJump:
mov r9, r0
ldr r3, =0xfff00000
ldr r4, _TEXT_PHY_BASE
adr r5, phy_last_jump
bic r5, r5, r3
orr r5, r5, r4
mov pc, r5
phy_last_jump:
/*
* disable MMU stuff
*/
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
mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */
mov r0, #0
mov pc, r9
#endif
//以下是一些中断的定义和处理
/*******************************************************************
*
* Interrupt handling
*
********************************************************************/
@
@ IRQ stack frame.
@
#define S_FRAME_SIZE 72
#define S_OLD_R0 68
#define S_PSR 64
#define S_PC 60
#define S_LR 56
#define S_SP 52
#define S_IP 48
#define S_FP 44
#define S_R10 40
#define S_R9 36
#define S_R8 32
#define S_R7 28
#define S_R6 24
#define S_R5 20
#define S_R4 16
#define S_R3 12
#define S_R2 8
#define S_R1 4
#define S_R0 0
#define MODE_SVC 0x13
#define I_BIT 0x80
/*
* use bad_save_user_regs for abort/prefetch/undef/swi ...
* use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
*/
.macro bad_save_user_regs
@ carve out a frame on current user stack
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Save user registers (now in svc mode) r0-r12
ldr r2, _armboot_start
sub r2, r2, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
sub r2, r2, #(CFG_GBL_DATA_SIZE+8) @ set base 2 words into abort stack
@ get values for "aborted" pc and cpsr (into parm regs)
ldmia r2, {r2 - r3}
add r0, sp, #S_FRAME_SIZE @ grab pointer to old stack
add r5, sp, #S_SP
mov r1, lr
stmia r5, {r0 - r3} @ save sp_SVC, lr_SVC, pc, cpsr
mov r0, sp @ save current stack into r0 (param register)
.endm
.macro irq_save_user_regs
sub sp, sp, #S_FRAME_SIZE
stmia sp, {r0 - r12} @ Calling r0-r12
@ !!!! R8 NEEDS to be saved !!!! a reserved stack spot would be good.
add r8, sp, #S_PC
stmdb r8, {sp, lr}^ @ Calling SP, LR
str lr, [r8, #0] @ Save calling PC
mrs r6, spsr
str r6, [r8, #4] @ Save CPSR
str r0, [r8, #8] @ Save OLD_R0
mov r0, sp
.endm
.macro irq_restore_user_regs
ldmia sp, {r0 - lr}^ @ Calling r0 - lr
mov r0, r0
ldr lr, [sp, #S_PC] @ Get PC
add sp, sp, #S_FRAME_SIZE
subs pc, lr, #4 @ return & move spsr_svc into cpsr
.endm
.macro get_bad_stack
ldr r13, _armboot_start @ setup our mode stack
sub r13, r13, #(CONFIG_STACKSIZE+CFG_MALLOC_LEN)
sub r13, r13, #(CFG_GBL_DATA_SIZE+8) @ reserved a couple spots in abort stack
str lr, [r13] @ save caller lr in position 0 of saved stack
mrs lr, spsr @ get the spsr
str lr, [r13, #4] @ save spsr in position 1 of saved stack
mov r13, #MODE_SVC @ prepare SVC-Mode
@ msr spsr_c, r13
msr spsr, r13 @ switch modes, make sure moves will execute
mov lr, pc @ capture return pc
movs pc, lr @ jump to next instruction & switch modes.
.endm
.macro get_irq_stack @ setup IRQ stack
ldr sp, IRQ_STACK_START
.endm
.macro get_fiq_stack @ setup FIQ stack
ldr sp, FIQ_STACK_START
.endm
/*
* exception handlers
*/
.align 5
undefined_instruction:
@ get_bad_stack
@ bad_save_user_regs
bl do_undefined_instruction
.align 5
software_interrupt:
@ get_bad_stack
@ bad_save_user_regs
bl do_software_interrupt
.align 5
prefetch_abort:
@ get_bad_stack
@ bad_save_user_regs
bl do_prefetch_abort
.align 5
data_abort:
get_bad_stack
bad_save_user_regs
bl do_data_abort
.align 5
not_used:
@ get_bad_stack
@ bad_save_user_regs
bl do_not_used
#ifdef CONFIG_USE_IRQ
.align 5
irq:
get_irq_stack
irq_save_user_regs
bl do_irq
irq_restore_user_regs
.align 5
fiq:
get_fiq_stack
/* someone ought to write a more effiction fiq_save_user_regs */
irq_save_user_regs
bl do_fiq
irq_restore_user_regs
#else
.align 5
irq:
@ get_bad_stack
@ bad_save_user_regs
bl do_irq
.align 5
fiq:
@ get_bad_stack
@ bad_save_user_regs
bl do_fiq
#endif
#ifdef CONFIG_PM
.align 4
PMCTL1_ADDR: .long 0x56000080
PMST_ADDR: .long 0x4C000068
PMSR0_ADDR: .long 0x560000B8
GPBCON: .long 0x56000010
GPBDAT: .long 0x56000014
GPFCON_reg: .long 0x56000050
GPFDAT_reg: .long 0x56000054
.align 5
sleep_setting:
@ prepare the SDRAM self-refresh mode
ldr r0, =0x48000024 @ REFRESH Register
ldr r1, [r0]
orr r1, r1,#(1<<22) @ self-refresh bit set
@ prepare MISCCR[19:17]=111b to make SDRAM signals(SCLK0,SCLK1,SCKE) protected
ldr r2,=0x56000080 @ MISCCR Register
ldr r3,[r2]
orr r3,r3,#((1<<17)|(1<<18)|(1<<19))
@ prepare the Power_Off mode bit in CLKCON Register
ldr r4,=0x4c00000c @ CLKCON Register
ldr r5,=(1<<3)
b set_sdram_refresh
.align 5
set_sdram_refresh:
str r1,[r0] @ SDRAM self-refresh enable
@ wait until SDRAM into self-refresh
mov r1, #64
1: subs r1, r1, #1
bne 1b
@ set the MISCCR & CLKCON register for power off
str r3,[r2]
str r5,[r4]
nop @ waiting for power off
nop
nop
b .
.align 5
WakeupStart:
@ Clear sleep reset bit
ldr r0, PMST_ADDR
mov r1, #(1<<1) @ PMST_SMR
str r1, [r0]
@ Release the SDRAM signal protections
ldr r0, PMCTL1_ADDR
ldr r1, [r0]
bic r1, r1, #((1<<17)|(1<<18)|(1<<19)) @ (SCLKE | SCLK1 | SCLK0)
str r1, [r0]
@ Max1718_Set(); @for case 135 i.e 300MHz operation
@ GPBCON = (GPBCON & ~((3 << 20) | (3 << 16) | (3 << 14))) | (1 << 20) | (1 << 16) | (1 << 14);
ldr r1, GPBCON
ldr r0, [r1]
bic r0, r0, #( (3 << 20) | (3 << 16) | (3 << 14) )
orr r0, r0, #( (1 << 20) | (1 << 16) | (1 << 14) )
str r0, [r1]
// GPB7, 8, 10 : Output
@ GPFCON = (GPFCON & ~(0xff << 8)) | (0x55 << 8); // GPF4~7: Output , shared with LED4~7
ldr r1, GPFCON_reg
ldr r0, [r1]
bic r0, r0, #( (0xff << 8) )
orr r0, r0, #( (0x55 << 8) )
str r0, [r1]
@ GPBDAT = (GPBDAT & ~(1 << 7)) | (0 << 7); //D4
ldr r1, GPBDAT
ldr r0, [r1]
bic r0, r0, #( (1 << 7) )
orr r0, r0, #( (0 << 7) )
str r0, [r1]
@ GPFDAT = (GPFDAT & ~(0xf << 4)) | (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4); //D3~0
ldr r1, GPFDAT_reg
ldr r0, [r1]
bic r0, r0, #( (0xf << 4) )
orr r0, r0, #( (1 << 7) | (0 << 6) | (0 << 5) | (0 << 4) ) @D3~0
str r0, [r1]
@ Go...
ldr r0, PMSR0_ADDR @ read a return address
ldr r1, [r0]
mov pc, r1
nop
nop
1: b 1b @ infinite loop
#endif
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 stack_setup
最初看到这个代码的时候,发现功能还是很简单的,就是判断uboot是放在哪里的,flash? or SDRAM?如果放在SDRAM中,就不需要再把uboot代码从flash中搬移到SDRAM中,直接跳到stack_setup;如果是放在flash里的话,则要把代码从flash中搬移到指定的SDRAM地址(TEXT_BASE)中。细看之下,又发现了点问题:它是怎么知道uboot到底放在哪呢;又要把uboot放到SDRAM的什么地址去呢,也即TEXT_BASE是多少。
先说TEXT_BASE吧。TEXT_BASE在功能上是指示uboot将要SDRAM中存放的起始地址。(理解这个很重要)在uboot\cpu\s3c44b0\start.S中有如下声明和定义:_TEXT_BASE:。word TEXT_BASE 而在uboot\board\B2\config.mk文件中有如下赋值:TEXT_BASE = 0x0c100000。在基于dave\B2板子的uboot是把uboot放在SDRAM中的0x0c100000处的。(
个人暂时认为这个TEXT_BASE应该是可以修改的,比如TEXT_BASE=0x0c100004)再说这个_start:当uboot在flash中运行的时候,_start是程序的开始,也即地址0。而当uboot在SDRAM的时候,这个_start应该是多少呢??经过反复想,
反复想之后,才发现,这个_start就应该是TEXT_BASE。
由于_start是整个uboot的开头处,所以_start在uboot中的偏移地址_start_offset=0,这个无疑义。当uboot在flash中的时候,_start=0x00000000很好理解:flash映射起始地址为0x00000000。uboot放在flash当中的话,uboot起始地址就应该为0x00000000,而_start在uboot中的偏移地址为0,所以_start的绝对物理地址就应该是0x00000000。当uboot处于SDRAM中的时候,_start=??那么它为什么又会等于TEXT_BASE=0x0c1000000呢???原因就在于,我们要把(注意:是将要把,打算把)uboot搬到TEXT_BASE=0x0c100000(这个位置属于SDRAM的映射)处。那么uboot的绝对地址就应该是TEXT_BASE,而_start在uboot中的偏移地址是0,所以_start的绝对地址就是TEXT_BASE+0=TEXT_BASE。在ldr r1,_TEXT_BASE执行之后,r1=TEXT_BASE的;而adr r0,_start执行之后呢??adr指令是基于PC的相对寻址,执行之后r0=PC+_star_offset=PC。如果uboot放在SDRAM中的话,那么_start的绝对地址是TEXT_BASE,也即PC=TEXT_BASE。
至此,才明白了是如何判断是否要进行代码搬移的。
总结:开始一直以为,uboot在SDRAM中的话,其开始地址应该是SDRAM的映射开始地址,即0x0c000000,也即_start的绝对地址应该是0x0c000000。后来才发现,uboot放在SDRAM中的位置是由程序控制的,即放在TEXT_BASE处。这才明白上面那几行代码是怎么回事了~~~
话外音::突然间想到---TEXT_BASE是uboot在SDRAM的开始处,所以在SDRAM中的时候_start=TEXT_BASE。只有这样才能判断正确~~
在接下来就是第二阶段C语言实现部分。。。转帖分析。。
地址http://www.cevx.com/bbs/thread-28817-1-1.html
lib_arm/board.c:
start_armboot是U-Boot执行的第一个C语言函数,完成系统初始化工作,进入主循环,处理用户输入的命令。这里只简要列出了主要执行的函数流程:void start_armboot (void)
{
//全局数据变量指针gd占用r8。
DECLARE_GLOBAL_DATA_PTR;
/* 给全局数据变量gd安排空间*/
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
memset ((void*)gd, 0, sizeof (gd_t));
/* 给板子数据变量gd->bd安排空间*/
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset (gd->bd, 0, sizeof (bd_t));
monitor_flash_len = _bss_start - _armboot_start;//取u-boot的长度。
/* 顺序执行init_sequence数组中的初始化函数 */
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
if ((*init_fnc_ptr)() != 0) {
hang ();
}
}
/*配置可用的Flash */
size = flash_init ();
……
/* 初始化堆空间 */
mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
/* 重新定位环境变量, */
env_relocate ();
/* 从环境变量中获取IP地址 */
gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
/* 以太网接口MAC 地址 */
……
devices_init (); /* 设备初始化 */
jumptable_init (); //跳转表初始化
console_init_r (); /* 完整地初始化控制台设备 */
enable_interrupts (); /* 使能中断处理 */
/* 通过环境变量初始化 */
if ((s = getenv ("loadaddr")) != NULL) {
load_addr = simple_strtoul (s, NULL, 16);
}
/* main_loop()循环不断执行 */
for (;;) {
main_loop (); /* 主循环函数处理执行用户命令 -- common/main.c */
}
}初始化函数序列init_sequence[]
init_sequence[]数组保存着基本的初始化函数指针。这些函数名称和实现的程序文件在下列注释中。
init_fnc_t *init_sequence[] = {
cpu_init, /* 基本的处理器相关配置 -- cpu/arm920t/cpu.c */
board_init, /* 基本的板级相关配置 -- board/smdk2410/smdk2410.c */
interrupt_init, /* 初始化例外处理 -- cpu/arm920t/s3c24x0/interrupt.c */
env_init, /* 初始化环境变量 -- common/env_flash.c */
init_baudrate, /* 初始化波特率设置 -- lib_arm/board.c */
serial_init, /* 串口通讯设置 -- cpu/arm920t/s3c24x0/serial.c */
console_init_f, /* 控制台初始化阶段1 -- common/console.c */
display_banner, /* 打印u-boot信息 -- lib_arm/board.c */
dram_init, /* 配置可用的RAM -- board/smdk2410/smdk2410.c */
display_dram_config, /* 显示RAM的配置大小 -- lib_arm/board.c */
NULL,
};
整个u-boot的执行就进入等待用户输入命令,解析并执行命令的死循环中。
2、u-boot主要的数据结构
u-boot的主要功能是用于引导OS的,但是本身也提供许多强大的功能,可以通过输入命令行来完成许多操作。所以它本身也是一个很完备的系统。u-boot的大部分操作都是围绕它自身的数据结构,
这些数据结构是通用的,但是不同的板子初始化这些数据就不一样了。所以u-boot的通用代码是依赖于这些重要的数据结构的。这里说的数据结构其实就是一些全局变量。
1)gd 全局数据变量指针,它保存了u-boot运行需要的全局数据,类型定义:
typedef struct global_data {
bd_t *bd; //board data pointor板子数据指针
unsigned long flags; //指示标志,如设备已经初始化标志等。
unsigned long baudrate; //串口波特率
unsigned long have_console; /* 串口初始化标志*/
unsigned long reloc_off; /* 重定位偏移,就是实际定向的位置与编译连接时指定的位置之差,一般为0 */
unsigned long env_addr; /* 环境参数地址*/
unsigned long env_valid; /* 环境参数CRC检验有效标志 */
unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
unsigned char vfd_type; /* display type */
#endif
void **jt; /* 跳转表,1.1.6中用来函数调用地址登记 */
} gd_t;
2)bd 板子数据指针。板子很多重要的参数。 类型定义如下:
typedef struct bd_info {
int bi_baudrate; /* 串口波特率 */
unsigned long bi_ip_addr; /* IP 地址 */
unsigned char bi_enetaddr[6]; /* MAC地址*/
struct environment_s *bi_env;
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* 启动参数 */
struct /* RAM 配置 */
{
ulong start;
ulong size;
}bi_dram[CONFIG_NR_DRAM_BANKS];
} bd_t;
3)环境变量指针 env_t *env_ptr = (env_t *)(&environment[0]);(common/env_flash.c)
env_ptr指向环境参数区,系统启动时默认的环境参数environment[],定义在common/environment.c中。
参数解释:
bootdelay 定义执行自动启动的等候秒数
baudrate 定义串口控制台的波特率
netmask 定义以太网接口的掩码
ethaddr 定义以太网接口的MAC地址
bootfile 定义缺省的下载文件
bootargs 定义传递给Linux内核的命令行参数
bootcmd 定义自动启动时执行的几条命令
serverip 定义tftp服务器端的IP地址
ipaddr 定义本地的IP地址
stdin 定义标准输入设备,一般是串口
stdout 定义标准输出设备,一般是串口
stderr 定义标准出错信息输出设备,一般是串口
4)设备相关:
标准IO设备数组?evice_t *stdio_devices[] = { NULL, NULL, NULL };
设备列表 list_t devlist = 0;
device_t的定义:include\devices.h中:
typedef struct {
int flags; /* Device flags: input/output/system */
int ext; /* Supported extensions */
char name[16]; /* Device name */
/* GENERAL functions */
int (*start) (void); /* To start the device */
int (*stop) (void); /* To stop the device */
/* 输出函数 */
void (*putc) (const char c); /* To put a char */
void (*puts) (const char *s); /* To put a string (accelerator) */
/* 输入函数 */
int (*tstc) (void); /* To test if a char is ready... */
int (*getc) (void); /* To get that char */
/* Other functions */
void *priv; /* Private extensions */
} device_t;
u-boot把可以用为控制台输入输出的设备添加到设备列表devlist,并把当前用作标准IO的设备指针加入stdio_devices数组中。
在调用标准IO函数如printf()时将调用stdio_devices数组对应设备的IO函数如putc()。
5)命令相关的数据结构,后面介绍。
6)与具体设备有关的数据结构,
如flash_info_t flash_info[CFG_MAX_FLASH_BANKS];记录nor flash的信息。
nand_info_t nand_info[CFG_MAX_NAND_DEVICE]; nand flash块设备信息
3、u-boot重定位后的内存分布:
对于fs2410,RAM范围从0x30000000~0x34000000. u-boot占用高端内存区。从高地址到低地址内存分配如下:
显示缓冲区 (.bss_end~34000000)
u-boot(bss,data,text) (33f00000~.bss_end)
heap(for malloc)
gd(global data)
bd(board data)
stack
....
nor flash (0~2M)
三、u-boot的重要细节。
主要分析流程中各函数的功能。按启动顺序罗列一下启动函数执行细节。按照函数start_armboot流程进行分析:
1)DECLARE_GLOBAL_DATA_PTR;
这个宏定义在include/global_data.h中:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r8")
声明一个寄存器变量 gd 占用r8。这个宏在所有需要引用全局数据指针gd_t *gd的源码中都有申明。
这个申明也避免编译器把r8分配给其它的变量. 所以gd就是r8,这个指针变量不占用内存。
2)gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
对全局数据区进行地址分配,_armboot_start为0x3f000000,CFG_MALLOC_LEN是堆大小+环境数据区大小,config/smdk2410.h中CFG_MALLOC_LEN大小定义为192KB.
3)gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
分配板子数据区bd首地址。
这样结合start.s中栈的分配,
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 /* bdinfoCFG_GBL_DATA_SIZE =128B */
#ifdef CONFIG_USE_IRQ
sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)
#endif
sub sp, r0, #12 /* leave 3 words for abort-stack */
不难得出上文所述的内存分配结构。
下面几个函数是初始化序列表init_sequence[]中的函数:
4)cpu_init();定义于cpu/arm920t/cpu.c
分配IRQ,FIQ栈底地址,由于没有定义CONFIG_USE_IRQ,所以相当于空实现。
5)board_init;极级初始化,定义于board/smdk2410/smdk2410.c
设置PLL时钟,GPIO,使能I/D cache.
设置bd信息:gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;//板子的ID,没啥意义。
gd->bd->bi_boot_params = 0x30000100;//内核启动参数存放地址
6)interrupt_init;定义于cpu/arm920t/s3c24x0/interrupt.c
初始化2410的PWM timer 4,使其能自动装载计数值,恒定的产生时间中断信号,但是中断被屏蔽了用不上。
7)env_init;定义于common/env_flash.c(搜索的时候发现别的文件也定义了这个函数,而且没有宏定义保证只有一个被编译,这是个问题,有高手知道指点一下!)
功能:指定环境区的地址。default_environment是默认的环境参数设置。
gd->env_addr = (ulong)&default_environment[0];
gd->env_valid = 0;
8)init_baudrate;初始化全局数据区中波特率的值
gd->bd->bi_baudrate = gd->baudrate =(i > 0)
? (int) simple_strtoul (tmp, NULL, 10)
: CONFIG_BAUDRATE;
9)serial_init; 串口通讯设置 定义于cpu/arm920t/s3c24x0/serial.c
根据bd中波特率值和pclk,设置串口寄存器。
10)console_init_f;控制台前期初始化common/console.c
由于标准设备还没有初始化(gd->flags & GD_FLG_DEVINIT=0),这时控制台使用串口作为控制台
函数只有一句:gd->have_console = 1;
10)dram_init,初始化内存RAM信息。board/smdk2410/smdk2410.c
其实就是给gd->bd中内存信息表赋值而已。
gd->bd->bi_dram[0].start = PHYS_SDRAM_1;
gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;
初始化序列表init_sequence[]主要函数分析结束。
11)flash_init;定义在board/fs2410/flash.c
这个文件与具体平台关系密切,smdk2410使用的flash与FS2410不一样,所以移植时这个程序就得重写。
flash_init()是必须重写的函数,它做哪些操作呢?
首先是有一个变量flash_info_t flash_info[CFG_MAX_FLASH_BANKS]来记录flash的信息。flash_info_t定义:
typedef struct {
ulong size; /* 总大小BYTE */
ushort sector_count; /* 总的sector数*/
ulong flash_id; /* combined device & manufacturer code */
ulong start[CFG_MAX_FLASH_SECT]; /* 每个sector的起始物理地址。 */
uchar protect[CFG_MAX_FLASH_SECT]; /* 每个sector的保护状态,如果置1,在执行erase操作的时候将跳过对应sector*/
#ifdef CFG_FLASH_CFI //我不管CFI接口。
.....
#endif
} flash_info_t;
flash_init()的操作就是读取ID号,ID号指明了生产商和设备号,根据这些信息设置size,sector_count,flash_id.以及start[]、protect[]。
12)把视频帧缓冲区设置在bss_end后面。
addr = (_bss_end + (PAGE_SIZE - 1)) & ~(PAGE_SIZE - 1);
size = vfd_setmem (addr);
gd->fb_base = addr;
13)mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);
设置heap区,供malloc使用。下面的变量和函数定义在lib_arm/board.c
malloc可用内存由mem_malloc_start,mem_malloc_end指定。而当前分配的位置则是mem_malloc_brk。
mem_malloc_init负责初始化这三个变量。malloc则通过sbrk函数来使用和管理这片内存。
static ulong mem_malloc_start = 0;
static ulong mem_malloc_end = 0;
static ulong mem_malloc_brk = 0;
static
void mem_malloc_init (ulong dest_addr)
{
mem_malloc_start = dest_addr;
mem_malloc_end = dest_addr + CFG_MALLOC_LEN;
mem_malloc_brk = mem_malloc_start;
memset ((void *) mem_malloc_start, 0,
mem_malloc_end - mem_malloc_start);
}
void *sbrk (ptrdiff_t increment)
{
ulong old = mem_malloc_brk;
ulong new = old + increment;
if ((new < mem_malloc_start) || (new > mem_malloc_end)) {
return (NULL);
}
mem_malloc_brk = new;
return ((void *) old);
}
14)env_relocate() 环境参数区重定位
由于初始化了heap区,所以可以通过malloc()重新分配一块环境参数区,
但是没有必要,因为默认的环境参数已经重定位到RAM中了。
/**这里发现个问题,ENV_IS_EMBEDDED是否有定义还没搞清楚,而且CFG_MALLOC_LEN也没有定义,也就是说如果ENV_IS_EMBEDDED没有定义则执行malloc,是不是应该有问题?**/
15)IP,MAC地址的初始化。主要是从环境中读,然后赋给gd->bd对应域就OK。
16)devices_init ();定义于common/devices.c
int devices_init (void)//我去掉了编译选项,注释掉的是因为对应的编译选项没有定义。
{
devlist = ListCreate (sizeof (device_t));//创建设备列表
i2c_init (CFG_I2C_SPEED, CFG_I2C_SLAVE);//初始化i2c接口,i2c没有注册到devlist中去。
//drv_lcd_init ();
//drv_video_init ();
//drv_keyboard_init ();
//drv_logbuff_init ();
drv_system_init (); //这里其实是定义了一个串口设备,并且注册到devlist中。
//serial_devices_init ();
//drv_usbtty_init ();
//drv_nc_init ();
}
经过devices_init(),创建了devlist,但是只有一个串口设备注册在内。显然,devlist中的设备都是可以做为console的。
16) jumptable_init ();初始化gd->jt。1.1.6版本的jumptable只起登记函数地址的作用。并没有其他作用。
17)console_init_r ();后期控制台初始化
主要过程:查看环境参数stdin,stdout,stderr中对标准IO的指定的设备名称,再按照环境指定的名称搜索devlist,将搜到的设备指针赋给标准IO数组stdio_devices[]。置gd->flag标志
GD_FLG_DEVINIT。这个标志影响putc,getc函数的实现,未定义此标志时直接由串口serial_getc和serial_putc实现,定义以后通过标准设备数组stdio_devices[]中的putc和getc来实现IO。
下面是相关代码:
void putc (const char c)
{
#ifdef CONFIG_SILENT_CONSOLE
if (gd->flags & GD_FLG_SILENT)//GD_FLG_SILENT无输出标志
return;
#endif
if (gd->flags & GD_FLG_DEVINIT) {//设备list已经初始化
/* Send to the standard output */
fputc (stdout, c);
} else {
/* Send directly to the handler */
serial_putc (c);//未初始化时直接从串口输出。
}
}
void fputc (int file, const char c)
{
if (file < MAX_FILES)
stdio_devices[file]->putc (c);
}
为什么要使用devlist,std_device[]?
为了更灵活地实现标准IO重定向,任何可以作为标准IO的设备,如USB键盘,LCD屏,串口等都可以对应一个device_t的结构体变量,只需要实现getc和putc等函数,就能加入到devlist列表中去,也就可
以被assign为标准IO设备std_device中去。如函数
int console_assign (int file, char *devname); /* Assign the console 重定向标准输入输出*/
这个函数功能就是把名为devname的设备重定向为标准IO文件file(stdin,stdout,stderr)。其执行过程是在devlist中查找devname的设备,返回这个设备的device_t指针,并把指针值赋给std_device
[file]。
18)enable_interrupts(),使能中断。由于CONFIG_USE_IRQ没有定义,空实现。
#ifdef CONFIG_USE_IRQ
/* enable IRQ interrupts */
void enable_interrupts (void)
{
unsigned long temp;
__asm__ __volatile__("mrs %0, cpsr\n"
"bic %0, %0, #0x80\n"
"msr cpsr_c, %0"
: "=r" (temp)
:
: "memory");
}
#else
void enable_interrupts (void)
{
}
19)设置CS8900的MAC地址。
cs8900_get_enetaddr (gd->bd->bi_enetaddr);
20)初始化以太网。
eth_initialize(gd->bd);//bd中已经IP,MAC已经初始化
21)main_loop ();定义于common/main.c
至此所有初始化工作已经完毕。main_loop在标准转入设备中接受命令行,然后分析,查找,执行。
关于U-boot中命令相关的编程:
1、命令相关的函数和定义
@main_loop:这个函数里有太多编译选项,对于fs2410,去掉所有选项后等效下面的程序
void main_loop()
{
static char lastcommand[CFG_CBSIZE] = { 0, };
int len;
int rc = 1;
int flag;
char *s;
int bootdelay;
s = getenv ("bootdelay"); //自动启动内核等待延时
bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
s = getenv ("bootcmd"); //取得环境中设置的启动命令行
debug ("### main_loop: bootcmd=\"%s\"\n", s ? s : "");
if (bootdelay >= 0 && s && !abortboot (bootdelay))
{
run_command (s, 0);//执行启动命令行,smdk2410.h中没有定义CONFIG_BOOTCOMMAND,所以没有命令执行。
}
for (;;) {
len = readline(CFG_PROMPT);//读取键入的命令行到console_buffer
flag = 0; /* assume no special flags for now */
if (len > 0)
strcpy (lastcommand, console_buffer);//拷贝命令行到lastcommand.
else if (len == 0)
flag |= CMD_FLAG_REPEAT;
if (len == -1)
puts ("\n");
else
rc = run_command (lastcommand, flag); //执行这个命令行。
if (rc <= 0) {
/* invalid command or not repeatable, forget it */
lastcommand[0] = 0;
}
}
@run_comman();在命令table中查找匹配的命令名称,得到对应命令结构体变量指针,以解析得到的参数调用其处理函数执行命令。
@命令结构构体类型定义:command.h中,
struct cmd_tbl_s {
char *name; /* 命令名 */
int maxargs; /* 最大参数个数maximum number of arguments */
int repeatable; /* autorepeat allowed? */
/* Implementation function 命令执行函数*/
int (*cmd)(struct cmd_tbl_s *, int, int, char *[]);
char *usage; /* Usage message (short) */
#ifdef CFG_LONGHELP
char *help; /* Help message (long) */
#endif
#ifdef CONFIG_AUTO_COMPLETE
/* do auto completion on the arguments */
int (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
#endif
};
typedef struct cmd_tbl_s cmd_tbl_t;
//定义section属性的结构体。编译的时候会单独生成一个名为.u_boot_cmd的section段。
#define Struct_Section __attribute__ ((unused,section (".u_boot_cmd")))
//这个宏定义一个命令结构体变量。并用name,maxargs,rep,cmd,usage,help初始化各个域。
#define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}
2、在u-boot中,如何添加一个命令:
1)CFG_CMD_* 命令选项位标志。在include/cmd_confdefs.h 中定义。
每个板子的配置文件(如include/config/fs2410.h)中都可以定义u-boot
需要的命令,如果要添加一个命令,必须添加相应的命令选项。如下:
#define CONFIG_COMMANDS \
(CONFIG_CMD_DFL | \
CFG_CMD_CACHE | \
/*CFG_CMD_NAND |*/ \
/*CFG_CMD_EEPROM |*/ \
/*CFG_CMD_I2C |*/ \
/*CFG_CMD_USB |*/ \
CFG_CMD_REGINFO | \
CFG_CMD_DATE | \
CFG_CMD_ELF)
定义这个选项主要是为了编译命令需要的源文件,大部分命令都在common文件夹下对应一个源文件
cmd_*.c ,如cmd_cache.c实现cache命令。 文件开头就有一行编译条件:
#if(CONFIG_COMMANDS&CFG_CMD_CACHE)
也就是说,如果配置头文件中CONFIG_COMMANDS不或上相应命令的选项,这里就不会被编译。
2)定义命令结构体变量,如:
U_BOOT_CMD(
dcache, 2, 1, do_dcache,
"dcache - enable or disable data cache\n",
"[on, off]\n"
" - enable or disable data (writethrough) cache\n"
);
其实就是定义了一个cmd_tbl_t类型的结构体变量,这个结构体变量名为__u_boot_cmd_dcache。
其中变量的五个域初始化为括号的内容。分别指明了命令名,参数个数,重复数,执行命令的函数,命令提示。
每个命令都对应这样一个变量,同时这个结构体变量的section属性为.u_boot_cmd.也就是说每个变量编译结束
在目标文件中都会有一个.u_boot_cmd的section.一个section是连接时的一个输入段,如.text,.bss,.data等都是section名。
最后由链接程序把所有的.u_boot_cmd段连接在一起,这样就组成了一个命令结构体数组。
u-boot.lds中相应脚本如下:
. = .;
__u_boot_cmd_start = .;
.u_boot_cmd : { *(.u_boot_cmd) }
__u_boot_cmd_end = .;
可以看到所有的命令结构体变量集中在__u_boot_cmd_start开始到__u_boot_cmd_end结束的连续地址范围内,
这样形成一个cmd_tbl_t类型的数组,run_command函数就是在这个数组中查找命令的。
3)实现命令处理函数。命令处理函数的格式:
void function (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
总体来说,如果要实现自己的命令,应该在include/com_confdefs.h中定义一个命令选项标志位。
在板子的配置文件中添加命令自己的选项。按照u-boot的风格,可以在common/下面添加自己的cmd_*.c,并且定义自己的命令结构体变量,如U_BOOT_CMD(
mycommand, 2, 1, do_mycommand,
"my command!\n",
"...\n"
" ..\n"
);
然后实现自己的命令处理函数do_mycommand(cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])。
四、U-boot在ST2410的移植,基于NOR FLASH和NAND FLASH启动。
1、从smdk2410到ST2410:
ST2410板子的核心板与FS2410是一样的。我没有整到smdk2410的原理图,从网上得知的结论总结如下,
fs2410与smdk2410 RAM地址空间大小一致(0x30000000~0x34000000=64MB);
NOR FLASH型号不一样,FS2410用SST39VF1601系列的,smdk2410用AMD产LV系列的;
网络芯片型号和在内存中映射的地址完全一致(CS8900,IO方式基地址0x19000300)
2、移植过程:
移植u-boot的基本步骤如下
(1) 在顶层Makefile中为开发板添加新的配置选项,使用已有的配置项目为例。
smdk2410_config : unconfig
@./mkconfig $(@:_config=) arm arm920t smdk2410 NULL s3c24×0
参考上面2行,添加下面2行。
fs2410_config : unconfig
@./mkconfig $(@:_config=) arm arm920t fs2410 NULL s3c24×0
(2) 创建一个新目录存放开发板相关的代码,并且添加文件。
board/fs2410/config.mk
board/fs2410/flash.c
board/fs2410/fs2410.c
board/fs2410/Makefile
board/fs2410/memsetup.S
board/fs2410/u-boot.lds
注意将board/fs2410/Makefile中smdk2410.o全部改为fs2410.o
(3) 为开发板添加新的配置文件
可以先复制参考开发板的配置文件,再修改。例如:
$cp include/configs/smdk2410.h include/configs/fs2410.h
如果是为一颗新的CPU移植,还要创建一个新的目录存放CPU相关的代码。
(4) 配置开发板
$ make fs2410_config
3、移植要考虑的问题:
从smdk2410到ST2410移植要考虑的主要问题就是NOR flash。从上述分析知道,u-boot启动时要执行flash_init() 检测flash的ID号,大小,secotor起始地址表和保护状态表,这些信息全部保存在
flash_info_t flash_info[CFG_MAX_FLASH_BANKS]中。
另外,u-boot中有一些命令如saveenvt需要要擦写flash,间接调用两个函数:flash_erase和write_buff。在board/smdk2410/flash.c
实现了与smdk2410板子相关的nor flash函数操作。由于write_buffer中调用了write_hword去具体写入一个字到flash中,这个函数本身是与硬件无关的,
所以与硬件密切相关的三个需要重写的函数是flash_init, flash_erase,write_hword;
4、SST39VF1601:
FS2410板nor flash型号是SST39VF1601,根据data sheet,其主要特性如下:
16bit字为访问单位。2MBTYE大小。
sector大小2kword=4KB,block大小32Kword=64KB;这里我按block为单位管理flash,即flash_info结构体变量中的sector_count是block数,起始地址表保存也是所有block的起始地址。
SST Manufacturer ID = 00BFH ;
SST39VF1601 Device ID = 234BH;
软件命令序列如下图。
5、我实现的flash.c主要部分:
//相关定义:
# define CFG_FLASH_WORD_SIZE unsigned short //访问单位为16b字
#define MEM_FLASH_ADDR1 (*(volatile CFG_FLASH_WORD_SIZE *)(CFG_FLASH_BASE + 0x000005555<<1 ))
//命令序列地址1,由于2410地址线A1与SST39VF1601地址线A0连接实现按字访问,因此这个地址要左移1位。
#define MEM_FLASH_ADDR2 (*(volatile CFG_FLASH_WORD_SIZE *)(CFG_FLASH_BASE + 0x000002AAA<<1 )) //命令序列地址2
#define READ_ADDR0 (*(volatile CFG_FLASH_WORD_SIZE *)(CFG_FLASH_BASE + 0x0000))
//flash信息读取地址1,A0=0,其余全为0
#define READ_ADDR1 (*(volatile CFG_FLASH_WORD_SIZE *)(CFG_FLASH_BASE + 0x0001<<1)) //flash信息读取地址2,A0=1,其余全为0
flash_info_t flash_info[CFG_MAX_FLASH_BANKS]; /* 定义全局变量flash_info[1]*/
//flash_init(),我实现的比较简单,因为是与板子严重依赖的,只要检测到的信息与板子提供的已知信息符合就OK。
ulong flash_init (void)
{
int i;
CFG_FLASH_WORD_SIZE value;
flash_info_t *info;
for (i = 0; i < CFG_MAX_FLASH_BANKS; i++)
{
flash_info .flash_id=FLASH_UNKNOWN;
}
info=(flash_info_t *)(&flash_info[0]);
//进入读ID状态,读MAN ID和device id
MEM_FLASH_ADDR1=(CFG_FLASH_WORD_SIZE)(0x00AA);
MEM_FLASH_ADDR2=(CFG_FLASH_WORD_SIZE)(0x0055);
MEM_FLASH_ADDR1=(CFG_FLASH_WORD_SIZE)(0x0090);
value=READ_ADDR0; //read Manufacturer ID
if(value==(CFG_FLASH_WORD_SIZE)SST_MANUFACT)
info->flash_id = FLASH_MAN_SST;
else
{
panic("NOT expected FLASH FOUND!\n");return 0;
}
value=READ_ADDR1; //read device ID
if(value==(CFG_FLASH_WORD_SIZE)SST_ID_xF1601)
{
info->flash_id += FLASH_SST1601;
info->sector_count = 32; //32 block
info->size = 0x00200000; // 2M=32*64K
}
else
{
panic("NOT expected FLASH FOUND!\n");return 0;
}
//建立sector起始地址表。
if ((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_SST )
{
for (i = 0; i < info->sector_count; i++)
info->start = CFG_FLASH_BASE + (i * 0x00010000);
}
//设置sector保护信息,对于SST生产的FLASH,全部设为0。
for (i = 0; i < info->sector_count; i++)
{
if((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_SST)
info->protect = 0;
}
//结束读ID状态:
*((CFG_FLASH_WORD_SIZE *)&info->start[0])= (CFG_FLASH_WORD_SIZE)0x00F0;
//设置保护,将u-boot镜像和环境参数所在的block的proctect标志置1
flash_protect (FLAG_PROTECT_SET,
CFG_FLASH_BASE,
CFG_FLASH_BASE + monitor_flash_len - 1,
&flash_info[0]);
flash_protect (FLAG_PROTECT_SET,
CFG_ENV_ADDR,
CFG_ENV_ADDR + CFG_ENV_SIZE - 1, &flash_info[0]);
return info->size;
}
//flash_erase实现
这里给出修改的部分,s_first,s_last是要擦除的block的起始和终止block号.对于protect[]置位的block不进行擦除。
擦除一个block命令时序按照上面图示的Block-Erase进行。
for (sect = s_first; sect<=s_last; sect++)
{
if (info->protect[sect] == 0)
{ /* not protected */
addr = (CFG_FLASH_WORD_SIZE *)(info->start[sect]);
if ((info->flash_id & FLASH_VENDMASK) == FLASH_MAN_SST)
{
MEM_FLASH_ADDR1 = (CFG_FLASH_WORD_SIZE)0x00AA;
MEM_FLASH_ADDR2 = (CFG_FLASH_WORD_SIZE)0x0055;
MEM_FLASH_ADDR1 = (CFG_FLASH_WORD_SIZE)0x0080;
MEM_FLASH_ADDR1 = (CFG_FLASH_WORD_SIZE)0x00AA;
MEM_FLASH_ADDR2 = (CFG_FLASH_WORD_SIZE)0x0055;
addr[0] = (CFG_FLASH_WORD_SIZE)0x0050; /* block erase */
for (i=0; i<50; i++)
udelay(1000); /* wait 1 ms */
}
else
{
break;
}
}
}
.........
start = get_timer (0); //在指定时间内不能完成为超时。
last = start;
addr = (CFG_FLASH_WORD_SIZE *)(info->start[l_sect]);//查询DQ7是否为1,DQ7=1表明擦除完毕
while ((addr[0] & (CFG_FLASH_WORD_SIZE)0x0080) != (CFG_FLASH_WORD_SIZE)0x0080) {
if ((now = get_timer(start)) > CFG_FLASH_ERASE_TOUT) {
printf ("Timeout\n");
return 1;
}
................
//write_word操作,这个函数由write_buff一调用,完成写入一个word的操作,其操作命令序列由上图中Word-Program指定。
static int write_word (flash_info_t *info, ulong dest, ulong data)
{
volatile CFG_FLASH_WORD_SIZE *dest2 = (CFG_FLASH_WORD_SIZE *)dest;
volatile CFG_FLASH_WORD_SIZE *data2 = (CFG_FLASH_WORD_SIZE *)&data;
ulong start;
int flag;
int i;
/* Check if Flash is (sufficiently) erased */
if ((*((volatile ulong *)dest) & data) != data) {
return (2);
}
/* Disable interrupts which might cause a timeout here */
flag = disable_interrupts();
for (i=0; i<4/sizeof(CFG_FLASH_WORD_SIZE); i++)
{
MEM_FLASH_ADDR1 = (CFG_FLASH_WORD_SIZE)0x00AA;
MEM_FLASH_ADDR2 = (CFG_FLASH_WORD_SIZE)0x0055;
MEM_FLASH_ADDR1 = (CFG_FLASH_WORD_SIZE)0x00A0;
dest2 = data2;
/* re-enable interrupts if necessary */
if (flag)
enable_interrupts();
/* data polling for D7 */
start = get_timer (0);
while ((dest2 & (CFG_FLASH_WORD_SIZE)0x0080) !=
(data2 & (CFG_FLASH_WORD_SIZE)0x0080)) {
if (get_timer(start) > CFG_FLASH_WRITE_TOUT) {
return (1);
}
}
}
return (0);
}
这些代码在与nor flash相关的命令中都会间接被调用。所以u-boot可移植性的另一个方面就是规定一些函数调用接口和全局变量,这些函数的实现是硬件相关的,移植时只需要实现这些函数。
而全局变量是具体硬件无关的。u-boot在通用目录中实现其余与硬件无关的函数,这些函数就只与全局变量和函数接口打交道了。 通过编译选项设置来灵活控制是否需要编译通用部分。
6、增加从Nand 启动的代码:
FS2410板有跳线,跳线短路时从NAND启动,否则从NOR启动。根据FS2410 BIOS源码,我修改了start.s加入了可以从两种FLASH中启动u-boot的
代码。原理在于:在重定位之前先读BWSCON寄存器,判断OM0位是0(有跳线,NAND启动)还是1(无跳线,NOR启动),采取不同的重定位代码
分别从nand或nor中拷贝u-boot镜像到RAM中。这里面也有问题,比如从Nand启动后,nor flash的初始化代码和与它相关的命令都是不能使用的。
这里我采用比较简单的方法,定义一个全局变量标志_boot_flash保存当前启动FLASH标志,_boot_flash=0则表明是NOR启动,否则是从NAND。
在每个与nor flash 相关的命令执行函数一开始就判断这个变量,如果为1立即返回。flash_init()也必须放在这个if(!_boot_flash)条件中。
这里方法比较笨,主要是为了能在跳线处于任意状态时都能启动u-boot。
修改后的start.s如下。
.......
//修改1
.globl _boot_flash
_boot_flash: //定义全局标志变量,0:NOR FLASH启动,1:NAND FLASH启动。
.word 0x00000000
.........
///修改2:
ldr r0,=BWSCON
ldr r0,[r0]
ands r0,r0,#6
beq nand_boot //OM0=0,有跳线,从Nand启动。nand_boot在后面定义。
............
//修改4,这里在全局变量_boot_flash中设置当前启动flash设备是NOR还是NAND
//这里已经完成搬运到RAM的工作,即将跳转到RAM中_start_armboot函数中执行。
adr r1,_boot_flash //取_boot_flash的当前地址,这时还在NOR FLASH或者NAND 4KB缓冲中。
ldr r2,_TEXT_BASE
add r1,r1,r2 //得到_boot_flash重定位后的地址,这个地址在RAM中。
ldr r0,=BWSCON
ldr r0,[r0]
ands r0,r0,#6 //
mov r2,#0x00000001
streq r2,[r1] //如果当前是从NAND启动,置_boot_flash为1
ldr pc, _start_armboot
_start_armboot: .word start_armboot
........
//////// 修改4,从NAND拷贝U-boot镜像(最大128KB),这段代码由fs2410 BIOS修改得来。
nand_boot:
mov r5, #NFCONF
ldr r0, =(1<<15)|(1<<12)|(1<<11)|(7<<8)|(7<<4)|(7)
str r0, [r5]
bl ReadNandID
mov r6, #0
ldr r0, =0xec73
cmp r5, r0
beq x1
ldr r0, =0xec75
cmp r5, r0
beq x1
mov r6, #1
x1:
bl ReadNandStatus
mov r8, #0 //r8是PAGE数变量
ldr r9, _TEXT_BASE //r9指向u-boot在RAM中的起始地址。
x2:
ands r0, r8, #0x1f
bne x3 //此处意思在于页数是32的整数倍的时候才进行一次坏块检查 1 block=32 pages,否则直接读取页面。
mov r0, r8
bl CheckBadBlk //检查坏块返回值非0表明当前块不是坏块。
cmp r0, #0
addne r8, r8, #32 //如果当前块坏了,跳过读取操作。 1 block=32 pages
bne x4
x3:
mov r0, r8
mov r1, r9
bl ReadNandPage //读取一页(512B)
add r9, r9, #512
add r8, r8, #1
x4:
cmp r8, #256 //一共读取256*512=128KB。
bcc x2
mov r5, #NFCONF //DsNandFlash
ldr r0, [r5]
and r0, r0, #~0x8000
str r0, [r5]
adr lr,stack_setup //注意这里直接跳转到stack_setup中执行
mov pc,lr