Uboot运行分析(一) . .

http://blog.csdn.net/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 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

对于UBOOT的relocate代码的深入理解。可以看看http://www.51hei.com/mcu/1131.html的解析。。我也把他记下来,以便以后自己复习。

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

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值