2009-12-10

对照Startup.s学习ARM汇编指令(quote)

(1)GBLL 伪指令用于定义一个全局的逻辑变量,并初始化为{False}。
GBLL    BOOTLOADER
BOOTLOADER   SETL    {TRUE}
(2)GET(或 INCLUDE)
GET 伪指令用于将一个源文件包含到当前的源文件中,并将被包含的源文件在当前位置进行汇编处理。可以使用 INCLUDE 代替 GET。
INCLUDE ..\\..\\kernel\\oal\\startup.s
(3)IMPORT 伪指令用于通知编译器要使用的标号在其他的源文件中定义,但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。
    IMPORT      BootloaderMain
IMPORT      MMUSetup
(4)BL  带返回的跳转指令
(5)BEQ表示“相等则跳转”,即当CPSR中的Z标志置位时发生跳转
B   Label    ;程序无条件跳转到标号Label处执行
CMP R1,#0   ;当CPSR寄存器中的Z条件码置位时,程序跳转到标号Label处执行
BEQ Label   
(6)LDR 指令的格式为:
LDR{条件} 目的寄存器,<存储器地址>
LDR 指令用于从存储器中将一个 32 位的字数据传送到目的寄存器中。该指令通常用于从存储器中读取 32 位的字数据到通用寄存器,然后对数据进行处理。当程序计数器 PC 作为目的寄存器时,指令从存储器中读取的字数据被当作目的地址,从而可以实现程序流程的跳转。
指令示例:
LDR  R0,[R1]           ;将存储器地址为R1的字数据读入寄存器R0。
LDR  R0,[R1,R2]       ;将存储器地址为R1+R2的字数据读入寄存器R0。
LDR  R0,[R1,#8]      ;将存储器地址为R1+8的字数据读入寄存器R0。
LDR  R0,[R1,R2] ! ;将存储器地址为R1+R2的字数据读入寄存器R0,并将新地址R1+R2写入R1
LDR  R0,[R1,#8] ! ;将存储器地址为R1+8的字数据读入寄存器R0,并将新地址R1+8写入R1。
LDR  R0,[R1],R2   ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2写入R1。
LDR  R0,[R1,R2,LSL#2]!  ;将存储器地址为R1+R2×4的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
LDR   R0,[R1],R2,LSL#2   ;将存储器地址为R1的字数据读入寄存器R0,并将新地址R1+R2×4写入R1。
(7)STR 指令的格式为:
STR{条件} 源寄存器,<存储器地址>
STR 指令用于从源寄存器中将一个 32 位的字数据传送到存储器中。与LDR对应
(8)采用多寄存器寻址方式,一条指令可以完成多个寄存器值的传送。这种寻址方式可以用一条指令完成传送最多 16 个通用寄存器的值。以下指令:
LDMIA   R0,{R1,R2,R3,R4}   ;R1←[R0]
                                   ;R2←[R0+4]
                                  ;R3←[R0+8]
                                  ;R4←[R0+12]
该指令的后缀 IA表示在每次执行完加载/存储操作后,R0 按字长度增加,因此,指令可将连续存储单元的值传送到 R1~R4。
(9)SBC 指令的格式为:
SBC{条件}{S} 目的寄存器,操作数 1,操作数 2
SBC指令用于把操作数1减去操作数 2,再减去 CPSR 中的C 条件标志位的反码,并将结果存放到目的寄存器中。操作数1应是一个寄存器,操作数2可以是一个寄存器,被移位的寄存器,或一个立即数。该指令使用进位标志 来表示借位,这样就可以做大于 32 位的减法。注意不要忘记设置 S后缀来更改进位标志。该指令可用于有符号数或无符号数的减法运算。
指令示例:
SUBS  R0,R1,R2     ; R0 = R1 - R2 - !C,并根据结果设置CPSR的进位标志位
(10)BX  带状态切换的跳转指令
(11)MCR 指令的格式为:
MCR{条件} 协处理器编码,协处理器操作码 1,源寄存器,目的寄存器 1,目的寄存器 2,协处理器操作码 2
MCR 指令用于将 ARM 处理器寄存器中的数据传送到协处理器寄存器中,若协处理器不能成功完成操作,则产生未定义指令异常。其中协处理器操作码1和协处理器操作码2为协处理器将 要执行的操作,源寄存器为 ARM 处理器的寄存器,目的寄存器1和目的寄存器2均为协处理器的寄存器。
指令示例:
MCR   P3,3,R0,C4,C5,6    ;该指令将ARM处理器寄存器R0中的数据传送到协处理器P3的寄存器C4和C5中。 
(12)CMP 指令的格式为:
CMP{条件} 操作数 1,操作数 2
CMP 指令用于把一个寄存器的内容和另一个寄存器的内容或立即数进行比较,同时更新 CPSR 中条件标志位的值。该指令进行一次减法运算,但不存储结果,只更改条件标志位。标志位表示的是操作数 1 与操作数 2 的关系(大、小、相等),例如,当操作数 1 大于操作操作数 2,则此后的有 GT 后缀的指令将可以执行。
指令示例:
CMP R1,R0  ;将寄存器R1的值与寄存器R0的值相减,并根据结果设置CPSR的标志位
CMP R1,#100 ;将寄存器R1的值与立即数100相减,并根据结果设置CPSR的标志位
(13)批量数据加载/存储指令LDM(或 STM)指令的格式为:
LDM(或 STM){条件}{类型} 基址寄存器{!},寄存器列表{∧}
LDM(或 STM)指令用于从由基址寄存器所指示的一片连续存储器到寄存器列表所指示的多个寄存器之间传送数据,该指令的常见用途是将多个寄存器的内容入栈(SDM)或出栈(LDM)。其中,{类型}为以下几种情况:
IA  每次传送后地址加 1;
IB  每次传送前地址加 1;
DA  每次传送后地址减 1;
DB  每次传送前地址减 1;
FD  满递减堆栈;
ED  空递减堆栈;
FA  满递增堆栈;
EA  空递增堆栈;
{!}为可选后缀,若选用该后缀,则当数据传送完毕之后,将最后的地址写入基址寄存器,否则基址寄存器的内容不改变。
STMFD  R13!,{R0,R4-R12,LR}  ;将寄存器列表中的寄存器(R0,R4 到R12,LR)存入堆栈
LDMFD  R13!,{R0,R4-R12,PC} ;将堆栈内容恢复到寄存器(R0,R4到R12,LR)
(14)ORR 指令的格式为:
ORR{条件}{S} 目的寄存器,操作数 1,操作数 2
ORR 指令用于在两个操作数上进行逻辑或运算,并把结果放置到目的寄存器中。操作数 1
应是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。该指令常用于设置操作数 1 的某些位。
指令示例:
ORR   R0,R0,#3             ; 该指令设置R0的0、1位,其余位保持不变。
(15)BIC 指令的格式为:
BIC{条件}{S} 目的寄存器,操作数 1,操作数 2
BIC指令用于清除操作数1 的某些位,并把结果放置到目的寄存器中。操作数 1 应是一个寄存器,操作数 2 可以是一个寄存器,被移位的寄存器,或一个立即数。操作数 2 为 32 位的掩码,如果在掩码中设置了某一位,则清除这一位。未设置的掩码位保持不变。
指令示例:
BIC   R0,R0,#%1011     ; 该指令清除 R0 中的位 0、1、和 3,其余的位保持不变。
(16)
ADR(小范围的地址读取伪指令)
ADRL(中等范围的地址读取伪指令)
LDR(大范围的地址读取伪指令)
ldr     r0, =0xFFFFC000
用于将基于PC的地址或基于寄存器的地址读取到寄存器中。
///伪指令通过汇编编译器替换成对应的ARM/Thumb 指令。
以下指令的疑问:
ldr        r2, =(EbootImageSize/16)    // 加小括号代表什么?-----地址空间里存的数据

常用ARM汇编指令(quote)

ARM指令集可以分为六大类,分别为数据处理指令、Load/Store指令、跳转指令、程序状态寄存器处理指令、协处理器指令和异常产生指令。
ARM指令使用的基本格式如下:
〈opcode〉{〈cond〉}{S}     〈Rd〉,〈Rn〉{,〈operand2〉}
opcode 操作码;指令助记符,如LDR、STR等。
cond 可选的条件码;执行条件,如EQ、NE等。
S 可选后缀;若指定“S”,则根据指令执行结果更新CPSR中的条件码。
Rd 目标寄存器。
Rn 存放第1操作数的寄存器。
operand2 第2个操作数
 
arm的寻址方式如下:
立即寻址
寄存器寻址
寄存器间接寻址
基址加偏址寻址
堆栈寻址
块拷贝寻址
相对寻址
这里不作详细描述,可以查阅相关文档。
 
数据处理指令
Load/Store指令
程序状态寄存器与通用寄存器之间的传送指令
转移指令
异常中断指令
协处理器指令

 
 
在S3C2410、S3C2440的数据手册中对各种汇编指令有详细的描述;这里只对较常见的作写介绍。
1、相对跳转指令:b、bl
这两条指令的不同之处在于bl指令除了跳转之外,还将返回地址(bl的下一条指令的地址)保存在lr寄存器中。
这两条指令的可跳转范围是当前指令前后32M。
 

b funa
....
funa:
    b funb
....
funb:
....

 
2、数据传送指令mov,地址读取伪指令ldr
mov指令可以把一个寄存器的值赋给另外一个寄存器,或者把一个常数赋给寄存器。

mov r1, r2
/*上面是r1 = r2*/
mov r1,#1024
/*r1 = 1024*/

mov传送的常数必须能用立即数来表示。当不能用立即数表示时,可以用ldr命令来赋值。
ldr是伪命令,不是真实存在的指令,编译器会把它扩展成真正的指令;如果该常数能用“立即数”来表示,则使用mov指令,否则编译时将该常数保存在某个位置,使用内存读取指令把它读出来。

ldr r1, = 1024
/*r1 = 1024*/

3、内存访问指令 ldr、str、ldm、stm
ldr既可以指低至读取伪指令,也可以是内存访问指令。当他的第二个参数前面有'='时标伪指令,否则表内存访问指令。
ldr指令从内存中读取数据到寄存器,str指令把寄存器的指存储到内存中,他们的操作数都是32位的。
 
 
ldr r1, [r2, #4] /*将地址为r2+4的内存单元数据读取到r1中*/
ldr r1,[r2] /*将地址为r2的内存单元数据读取到r1中*/
ldr r1,[r2], #4/*将地址为r2的内存单元数据读取到r1中,然后r2=r2+4*/
str r1 ,[r2, #4]/*将r1的数据保存到地址为r2+4的内存单元中*/
str r1, [r2]/*。。。。*/
str r1, [r2],#4/*将r1的数据保存到地址为r2的内存单元,然后r2= r2+4*/

多寄存器传送指令可以用一条指令将16个可见寄存器(R0~R15)的任意子集合(或全部)存储到存储器或从存储器中读取数据到该寄存器集合中。与单寄存器存取指令相比,多寄存器数据存取可用的寻址模式更加有限。多寄存器存取指令的汇编格式如下:
LDM/STM{<cond>}<add mode>  Rn{!},  <registers>
4、加减指令 add、sub
add r1, r2, #1 /*r1=r2+1*/
sub r1, r2, #1 /*r1=r2-1*/

5、程序状态寄存器的访问指令msr,mrs
ARM指令中有两条指令,用于在状态寄存器和通用寄存器之间传送数据。修改状态寄存器一般是通过“读取-修改-写回”三个步骤的操作来实现的。 这两条指令分别是:
状态寄存器到通用寄存器的传送指令(MRS)
通用寄存器到状态寄存器的传送指令(MSR)
其汇编格式如下:
MRS{<cond>} Rd,CPSR|SPSR
其汇编格式如下:
MSR{<cond>} CPSR_f | SPSR_f,#<32-bit immediate>
MSR{<cond>} CPSR_<field> | SPSR_<field>,Rm
msr cpsr, r0 /*复制r0到cpsr中*/
mrs r0, cpsr /*复制cpsr到r0中*/

6、异常中断指令
异常中断指令可以分为一下两种:
软件中断指令(SWI)
断点指令(BKPT—仅用于v5T体系)
软件中断指令SWI用于产生SWI异常中断,用来实现在用户模式下对操作系统中特权模式的程序的调用;断点中断指令BKPT主要用于产生软件断点,供调试程序用。
7、其他伪指令
.extern main
.text
.global _start
_start:

'.extern' 定义一个外部符号(可以是变量也可以是函数),上面的代码表示表文本文件中引用的main是一个外部函数。
'.text'表示下面的语句都属于代码段
'.global'将本文件中的某个程序标号定义为全局的,如‘_start’就是个全局函数

ARM汇编学习的总结(quote)

ARM汇编学习的总结
ARM汇编指令的一些总结
ARM汇编指令很多,但是真正常用的不是很多,而且需要认真琢磨的又更少了。
比较有用的是MOV B BL LDR STR
还是通过具体汇编代码来学习吧。
      @ disable watch dog timer     
   mov   r1, #0x53000000   //立即数寻址方式
   mov   r2, #0x0
   str   r2, [r1]       
MOV没有什么好说的,只要掌握几个寻址方式就可以了,而且ARM的寻址方式比386的简单很多。立即数寻址方式,立即数要求以“#作前缀,对于十六进制的数,还要求在#后面加上0x或者&0x大家很好理解。有一次我碰到了&ff这个数,现在才明白跟0xff是一样的。
STR是比较重要的指令了,跟它对应的是LDRARM指令集是加载/存储型的,也就是说它只处理在寄存器中的数据。那么对于系统存储器的访问就经常用到STRLDR了。STR是把寄存器上的数据传输到指定地址的存储器上。它的格式我个人认为很特殊:
    STR(条件源寄存器,<存储器地址>
比如 STR R0, [R1] ,意思是R0-> [R1],它把源寄存器写在前面,跟MOVLDR都相反。
LDR应该是非常常见了。LDR就是把数据从存储器传输到寄存器上。而且有个伪指令也是LDR,因此我有个百思不得其解的问题。看这段代码:
mov r1, #GPIO_CTL_BASE
   add   r1, r1, #oGPIO_F
   ldr   r2,=0x55aa   // 0x55aa是个立即数啊,前面加个=干什么?
   str   r2, [r1, #oGPIO_CON]
   mov   r2, #0xff
   str   r2, [r1, #oGPIO_UP]
   mov   r2, #0x00
   str   r2, [r1, #oGPIO_DAT]
对于当中的ldr 那句,我就不明白了,如果你把=去掉,是不能通过编译的。我查了一些资料,个人感觉知道了原因:这个=应该表示LDR不是ARM指令,而是伪指令。作为伪指令的时候,LDR的格式如下:
    LDR 寄存器, =数字常量/Label
它的作用是把一个32位的地址或者常量调入寄存器。嗬嗬,那大家可能会问,
MOV r2,#0x55aa也可以啊。应该是这样的。不过,LDR是伪指令啊,也就是说编译时编译器会处理它的。怎么处理的呢?——规则如下:如果该数字常量在MOV指令范围内,汇编器会把这个指令作为MOV。如果不在MOV范围中,汇编器把该常量放在程序后面,用LDR来读取,PC和该常量的偏移量不能超过4KB
这么一说,虽然似懂非懂,但是能够解释这个语句了。
 
 
然后说一下跳转指令。ARM有两种跳转方式。
1 mov pc <跳转地址〉
 这种向程序计数器PC直接写跳转地址,能在4GB连续空间内任意跳转。
2)通过 B BL BLX BX 可以完成在当前指令向前或者向后32MB的地址空间的跳转(为什么是32MB呢?寄存器是32位的,此时的值是24位有符号数,所以32MB)。
B是最简单的跳转指令。要注意的是,跳转指令的实际值不是绝对地址,而是相对地址——是相对当前PC值的一个偏移量,它的值由汇编器计算得出。
BL非常常用。它在跳转之前会在寄存器LR(R14)中保存PC的当前内容。BL的经典用法如下:
       bl NEXT   ; 跳转到NEXT
       ……
    NEXT
       ……
       mov pc, lr    ; 从子程序返回。
 
最后提一下Thumb指令。ARM体系结构还支持16位的Thumb指令集。Thumb指令集是ARM指令集的子集,它保留了32位代码优势的同时还大大节省了存储空间。由于Thumb指令集的长度只有16位,所以它的指令比较多。它和ARM各有自己的应用场合。对于系统性能有较高要求,应使用32位存储系统和ARM指令集;对于系统成本和功耗有较高要求,应使用16位存储系统和ARM指令集。
对ARM异常(Exceptions)的理解
毕设笔记
1.对ARM异常(Exceptions)的理解
所有的系统引导程序前面中会有一段类似的代码,如下:
.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                       ;快速中断异常
从中我们可以看出,ARM支持7种异常。问题时发生了异常后ARM是如何响应的呢?第一个复位异常很好理解,它放在0x0的位置,一上电就执行它,而且我们的程序总是从复位异常处理程序开始执行的,因此复位异常处理程序不需要返回。那么怎么会执行到后面几个异常处理函数呢?
看看书后,明白了ARM对异常的响应过程,于是就能够回答以前的这个疑问。
当一个异常出现以后,ARM会自动执行以下几个步骤:
1)把下一条指令的地址放到连接寄存器LR(通常是R14),这样就能够在处理异常返回时从正确的位置继续执行。
2)将相应的CPSR(当前程序状态寄存器)复制到SPSR(备份的程序状态寄存器)中。从异常退出的时候,就可以由SPSR来恢复CPSR
(3) 根据异常类型,强制设置CPSR的运行模式位。
4)强制PC(程序计数器)从相关异常向量地址取出下一条指令执行,从而跳转到相应的异常处理程序中。
至于这些异常类型各代表什么,我也没有深究。因为平常就关心reset了,也没有必要弄清楚。
ARM规定了异常向量的地址:
   b       reset            ; 复位 0x0
ldr pc, _undefined_instruction ;未定义的指令异常 0x4
       ldr     pc, _software_interrupt     ;软件中断异常    0x8
       ldr     pc, _prefetch_abort          ;预取指令    0xc
       ldr     pc, _data_abort               ;数据        0x10
       ldr     pc, _not_used                  ;未使用      0x14
       ldr     pc, _irq                       ;慢速中断异常   0x18
        ldr   pc, _fiq                       ;快速中断异常    0x1c
这样理解这段代码就非常简单了。碰到异常时,PC会被强制设置为对应的异常向量,从而跳转到相应的处理程序,然后再返回到主程序继续执行。
这些引导程序的中断向量,是仅供引导程序自己使用的,一旦引导程序引导Linux内核完毕后,会使用自己的中断向量。
嗬嗬,这又有问题了。比如,ARM发生中断(irq)的时候,总是会跑到0x18上执行啊。那Linux内核又怎么能使用自己的中断向量呢?原因在于Linux内核采用页式存储管理。开通MMU的页面映射以后,CPU所发出的地址就是虚拟地址而不是物理地址。就Linux内核而言,虚拟地址0x18经过映射以后的物理地址就是0xc000 0018。所以Linux把中断向量放到0xc000 0018就可以了。
另外,说一下MMU。说句实话,还不是很明白这个MMU机理。参加Intel培训的时候,李眈说了MMU的两个主要作用:
(1)安全性:规定访问权限
 (2) 提供地址空间:把不连续的空间转换成连续的。
第2点是不是实现页式存储的意思?
2005年6月9日
补充一下:  05/06/14

.globl _start ;系统复位位置
_start: b reset ;各个异常向量对应的跳转代码
ldr pc, _undefined_instruction ;未定义的指令异常

……

_undefined_instruction :
.word undefined_instruction

也许有人会有疑问,同样是跳转指令,为什么第一句用的是 b reset;
而后面的几个都是用ldr?

为了理解这个问题,我们以未定义的指令异常为例。

当发生了这个异常后,CPU总是跳转到0x4,这个地址是虚拟地址,它映射到哪个物理地址
取决于具体的映射。
ldr pc, _undefined_instruction
相对寻址,跳转到标号_undefined_instruction,然而真正的跳转地址其实是_undefined_instruction的内容——undefined_instruction。那句.word的相当于:
_undefined_instruction dw undefined_instruction (详见毕设笔记3)。
这 个地址undefined_instruction到底有多远就难说了,也许和标号_undefined_instruction在同一个页面,也许在很 远的地方。不过除了reset,其他的异常是MMU开始工作之后才可能发生的,因此undefined_instruction 的地址也经过了MMU的映射。
在刚加电的时候,CPU从0x0开始执行,MMU还没有开始工作,此时的虚拟地址和物理地址相同;另一方面,重启在MMU开始工作后也有可能发生,如果reset也用ldr就有问题了,因为这时候虚拟地址和物理地址完全不同。

因此,之所以reset用b,就是因为reset在MMU建立前后都有可能发生,而其他的异常只有在MMU建立之后才会发生。用b reset,reset子程序与reset向量在同一页面,这样就不会有问题(b是相对跳转的)。如果二者相距太远,那么编译器会报错的。
 

2009-12-09

UBI以及UBIFS(quote)

ubi以及ubifs


1、很多人在linux上面build的时候经常会遇到缺少liblzo.so之类的库文件,我记得的有在编译mplayer会出现这个错误,还有这次使用ubifs的时候也是。
直接使用sudo apt-get install liblzo2-dev安装库文件。

2、ubifs
2.1、什么是ubifs?
由IBM、nokia工程师Thomas Gleixner,Artem Bityutskiy等人于2006年发起,致力于开发性能卓越、扩展性高的FLASH专用文件系统,以解决当前嵌入式环境下以FLASH作为MTD设备使用时的技术瓶颈。
关键字:
UBI:一种类似于LVM的逻辑卷管理层。主要实现损益均衡,逻辑擦除块、卷管理,坏块管理等。
UBIFS:基于UBI的FLASH日志文件系统。
有关ubifs的详细介绍,请参考:
http://www.linux-mtd.infradead.org/doc/ubi.html
http://www.linux-mtd.infradead.org/doc/ubifs.html

2.2、如何得到ubifs?
2.6.22以后,ubifs活跃于git管理工程中:
git://git.infradead.org/ubi-2.6.git
2.6.27以后,ubifs被整合进内核树中,用户只需下载最新内核即可获取ubifs支持。

2.3、如何使用ubifs?

软件环境:
linux-2.6.28
arm-linux-gcc 3.4.5
硬件环境:
s3c2410
k9f1208

2.4、准备

1、内核
配置的时候选上
1)Device Drivers  --->Memory Technology Device (MTD) support  --->UBI - Unsorted block images  --->Enable UBI
2)File systems  --->Miscellaneous filesystems  --->UBIFS file system support

2、mtd-utils工具(flash_eraseall、ubiattach、ubimkvol)准备
1)下载(mtd-utils、zlib、lzo)源码
wget http://debian.mirror.inra.fr/debian/pool/main/m/mtd-utils/mtd-utils_20080508.orig.tar.gz
wget http://www.zlib.net/zlib-1.2.3.tar.gz
wget http://www.oberhumer.com/opensource/lzo/download/lzo-2.03.tar.gz

2)编译安装zlib
tar xzvf zlib-1.2.3.tar.gz
cd zlib-1.2.3
CC=arm-linux-gcc ./configure --shared --prefix=/usr/local/arm/3.4.5/arm-linux
make
make install
cd ..

3)编译安装lzo
tar xzvf lzo-2.03.tar.gz
cd lzo-2.03
CC=arm-linux-gcc ./configure --host=arm-linux --prefix=/usr/local/arm/3.4.5/arm-linux
make
make install
cd ..

4)编译mtd-utils
tar xzvf mtd-utils_20080508.orig.tar.gz
cd mtd-utils-20080508

修改Makefile文件:
#CROSS=arm-linux-
修改为  CROSS=arm-linux-
BUILDDIR := $(CROSS:-=)
修改为  BUILDDIR := .

修改ubi-utils/Makefile文件:
添加    CROSS=arm-linux-

修改    ubi-utils/new-utils/Makefile文件:
添加    CROSS=arm-linux-

make WITHOUT_XATTR=1

ubi-utils子目录下生成我们需要的ubiattach、ubimkvol等文件(请确保是交叉编译所得)

3、mtd-utils工具(mkfs.ubifs、ubinize)准备
git-clone git://git.infradead.org/mtd-utils.git
cd mtd-utils/
make

mkfs.ubifs子目录下生成我们需要的mkfs.ubifs工具
ubi-utils/new-utils子目录下生成我们需要的ubinize工具


2.5、使用

1、使用ramfs或nfs启动系统,执行以下命令挂载ubifs:
1)flash_eraseall /dev/mtd4
2)ubiattach /dev/ubi_ctrl -m 4
3)ubimkvol /dev/ubi0 -N rootfs -s 50MiB
4)mount -t ubifs ubi0_0 /mnt或mount -t ubifs ubi0:rootfs /mnt

2、如果你想使用ubifs为rootfs,把文件系统内容解压到ubifs挂载目录下,并修改内核启动参数为:
console=ttySAC0 ubi.mtd=4 root=ubi0:rootfs rootfstype=ubifs

3、如果你想直接在bootloader下烧写ubifs映像,使用以下命令制作ubi烧写映像:
mkfs.ubifs -r rootfs -m 512 -e 15872 -c 3303 -o ubifs.img
ubinize -o ubi.img -m 512 -p 16KiB -s 256 ubinize.cfg

其中:
1)以上命令的参数可从ubifs挂载信息中提取:
UBI: attaching mtd4 to ubi0                                                    
UBI: physical eraseblock size:   16384 bytes (16 KiB)                          
UBI: logical eraseblock size:    15872 bytes                                   
UBI: smallest flash I/O unit:    512                                           
UBI: sub-page size:              256                                           
UBI: VID header offset:          256 (aligned 256)                             
UBI: data offset:                512                                           
UBI: attached mtd4 to ubi0             

2)配置文件ubinize.cfg的内容为:
[ubifs]
mode=ubi
image=ubifs.img
vol_id=0
vol_size=50MiB
vol_type=dynamic
vol_name=rootfs
vol_flags=autoresize

ubi and ubifs应用手记(quote)

ubi and ubifs应用手记

1.配置ubi and ubifs
in .config
  CONFIG_MTD_UBI=y
    CONFIG_UBIFS_FS=y
    CONFIG_CRYPTO_ALGAPI=y  CONFIG_CRYPTO_DEFLATE=y CONFIG_CRYPTO_LZO=y CONFIG_CRC16=y  CONFIG_LZO_COMPRESS=y CONFIG_LZO_DECOMPRESS=y
注意:如果配置成模块(=m),则可以手动加载   

2.manual attach/detach ubi to mtd
  ./ubiattach /dev/ubi_ctrl -m mtdnumber
  ./ubidetaach /dev/ubi_ctrl -m mtdnumber


3.manual create ubi volume
  ./ubimkvol /dev/ubi_device_number -s size -N name
  like:
  ./ubimkvol /dev/ubi0 -s 300MiB -N ubifs1
4.mount ubifs volume
  mount -t ubifs ubi0:ubifs1 /tmp/ubifs1


5.ubi node and ubi_ctrl node
#cat /sys/class/misc/ubi_ctrl/dev
10:63
加入/dev下没有ubi_ctrl,则我们可以sudo mknod ubi_ctrl c 10 63创建一个端点
#./ubiattach /dev/ubi_ctrl -m 6
#cat /sys/class/ubi/ubi0/dev
252:0
当我们attach ubi0 to mtd6后,如果/dev下没有ubi0,则创建一个,sudo mknod ubi0 c 252 0

6.我们可以手动create volume,然后手动mount ubifs,也可以在PC上创建ubi.img(创建好volume,volume写有数据)烧录进mtd device
How to generate ubi image and write to mtd device
  ./mkfs.ubifs -r a205_rootdisk -m 4096 -e 516096 -c 40 -o ubifs.img  
  ./ubinize -o ubi.img -m 4096 -p 512KiB  ubinize.cfg     
  ./ubiformat -q /dev/mtd5 -f ubi.img  


  -m minimum I/O unit size
  -e  maximum logical erase block count
  -c maximum logical erase block count
  -x compression type - "lzo", "favor_lzo", "zlib" or "none" (default: "lzo")
  -p size of the physical eraseblock of the flash this UBI image is created for in bytes,

注意:在PC上ubuntu使用mkfs.ubifs and ubinize,则我们要用普通的gcc来编译它们,同时在ubuntu上装上lzo库:sudo apt-get install liblzo2-dev
附录:
1).ubinize.cfg
[ubifs]
mode=ubi
image=ubifs.img
vol_id=0
vol_size=500MiB
vol_type=dynamic                     //if vol_type=static, then ubi volume is read only
vol_name=ubifs0
vol_flags=autoresize
这样这样当./ubiattach /dev/ubi_ctrl -m n后,就可以mount -t ubifs ubi0:ubifs0 /tmp

2)如果是想mount crafms image,只要
./ubinize -o ubi.img -m 4096 -p 512KiB  ubinize.cfg     
./ubiformat -q /dev/mtd5 -f ubi.img
ubinize.cfg
[ubifs]
mode=ubi
image=cramfs.img
vol_id=0
vol_size=500MiB
vol_type=dynamic                    
vol_name=cramfs
vol_flags=autoresize
这样当./ubiattach /dev/ubi_ctrl -m n后就可以从cat /proc/mtd中看到一个ubi volume仿真的mtd device,我们只要mount这个mtd设备对应的mtdblock就可以了(如mount -t cramfs /dev/mtdblock10 /tmp),注意,既然是烧录了cramfs到ubi volume,则我们只能以cramfs方式mount这个volume,不能再以ubifs方式(mount -t ubifs ubi0:cramfs /tmp)mount这个volume.但如果我们用./ubiupdate /dev/ubi0_0 -t wipe out擦干净这个volume后,我们是可以用ubifs方式mount这个volume,但mount起来这个volume,进入mount的目录,是什么内容也没有的。
3)三个volume的ubinize.cfg(注意[]中名字不能一样,vol_id不能一样,vol_name不能一样,另vol_flags=auto_resize只能使用在一个volume上)
[ubifs1]
mode=ubi
image=ubifs.img
vol_id=0
vol_size=20MiB
vol_type=dynamic
vol_name=ubifs0
vol_alignment=1

[cramfs1]
mode=ubi
image=smallroot.cramfs
vol_id=1
vol_size=20MiB
vol_type=dynamic
vol_name=cramfs
vol_alignment=1
[cramfs2]
mode=ubi
image=qtroot.cramfs
vol_id=2
vol_size=50MiB
vol_type=dynamic
vol_name=cramfs2
vol_alignment=1
vol_flags=autoresize
这样当使用./ubiformat写入ubi.img后,则./ubiattach后,我们可以知道多了三个假的mtd device.
第一个可以用mount -t ubifs ubi0:ubifs0 /tmp/ubifs1
第二个可以用mount -t cramfs /dev/mtdblockn /tmp/cramfs1
第二个可以用mount -t cramfs /dev/mtdblockm /tmp/cramfs2

7. How to disable compression?
UBIFS compression may be disabled for whole file system during the image creation time using the "-x none" mkfs.ubifs option. However, if UBIFS compression is enabled, it may be disabled for individual files by cleaning the inode compression flag:
$ chattr -c /mnt/ubifs/file
in shell, or

ioctl(fd, FS_IOC_GETFLAGS, &flags);

flags &= ~FS_COMPR_FL;

ioctl(fd, FS_IOC_SETFLAGS, &flags);
in C programs. Similarly, if compression is disabled by default, you may enable if for individual inodes by setting the compression flag. Note, the code which uses the compression flag works fine on other Linux file-systems, because the flag is just ignored in this case.
It might be a good idea to disable compression for say, mp3 or jpeg files which would anyway not compress and UBIFS would just waste CPU time trying to compress them. The compression may also be disabled if one wants faster file I/O, because UBIFS would not need to compress or decompress the data on reads and write. However, I/O speed may actually become slower if compression is disabled. Indeed, in case of a very fast CPU and very slow flash compressed writes are faster, but this is usually not true for embedded systems.

8.mount cramfs on ubi volume
  ubi volume is fake mtd device.
# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 180000 00080000 "Bootloader"
mtd1: 400000 00080000 "Kernel 0"
mtd2: 400000 00080000 "Kernel 1"
mtd3: 80000 00080000 "Boot up screen"
mtd4: a00000 00080000 "Rescue file system"
mtd5: 1400000 00080000 "Root file system"
mtd6: 3e800000 00080000 "Data area1"
mtd7: 3e800000 00080000 "Data area2"
mtd8: 6a400000 00080000 "Data area3"
mtd9: 16380000 00080000 "reserve"


#./ubiattach /dev/ubi_ctrl -m 6
#./ubimkvol /dev/ubi0 -s 300MiB -N ubifs1
# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 180000 00080000 "Bootloader"
mtd1: 400000 00080000 "Kernel 0"
mtd2: 400000 00080000 "Kernel 1"
mtd3: 80000 00080000 "Boot up screen"
mtd4: a00000 00080000 "Rescue file system"
mtd5: 1400000 00080000 "Root file system"
mtd6: 3e800000 00080000 "Data area1"
mtd7: 3e800000 00080000 "Data area2"
mtd8: 6a400000 00080000 "Data area3"
mtd9: 16380000 00080000 "reserve"
mtd10: 12c3c000 0007e000 "ubifs1"
# cp cramfs.img /dev/mtdblock10
# mount -t cramfs /dev/mtdblock6 /tmp

After create fake mtd device(ubi volume), mount jffs2
#mount -t jffs2 /dev/mtdblock10 /mnt
9.ubiupdatevol /dev/ubi0_0 -t   //wipe out volume
  ubiupdatevol /dev/ubi0_0 fs.img  //write image to volume

  ./ubiupdatevol /dev/ubi0_0 ubifs.img          //之后we can mount ubifs: mount -t ubifs ubi0:ubifs0 /tmp来挂载这个ubifs
  ./ubiupdatevol /dev/ubi0_1 smallroot.cramfs  //之后我们就可以mount -t cramfs /dev/mtdblockn /tmp来挂载这个cramfs

10.挂载vfat
1)制作vfat.img(在PC上制作)
$ dd if=/dev/zero of=vfat.img bs=1M count=20
#losetup /dev/loop0 vfat.img
#mkfs.vfat /dev/loop0
注意:这有一个warnning,但不用理会:Loop device does not match a floppy size, using default hd params
#mount -t vfat /dev/loop0 vfat_mount_point
往vfat_mount_point目录写东西,或copy东西到这目录
#umount vfat_mount_point
#losetup -d /dev/loop0
2)用ubinize打包成ubi.img,然后用ubiformat写入mtd devie。方法二是用ubiupdatevol先wipe out volume,然后用ubiupdatevol将vfat.img
写入volume.
但注意:因为emulate mtd device是不支持写操作的,所以我mount -t /dev/mtdblockn,这个mtdblockn是一个ubi volume emuluate的mtd device,
所以mount的vfat只可以读,写是无法保存的。(测试中写是能完成,ls也能看到,但sync后重启unit,重新mount可以看到写的数据是没有保存如vfat的)


11.ubifs(read/write/attach/mount)speed,
Wrtie speed -------------------speed=1.66M/s
# time dd if=/dev/zero of=/tmp/ubifs1/zero100M  bs=1M count=100;time sync
100+0 records in
100+0 records out
real    0m 59.13s
user    0m 0.00s
sys     0m 4.62s
real    0m 1.11s
user    0m 0.00s
sys     0m 0.74s
Read speed-----------------------speed=2.27M/s
# time cp ubifs1/zero100M /dev/null;time sync
s3c-nand: 1 bit(s) error detected, corrected successfully
s3c-nand: 1 bit(s) error detected, corrected successfully
s3c-nand: 1 bit(s) error detected, corrected successfully
real    0m 44.06s
user    0m 0.14s
sys     0m 42.67s
real    0m 0.06s
user    0m 0.00s
sys     0m 0.01s

12.配置ubifs as rootfs
in .config:
  CONFIG_CMDLINE="console=ttySAC0 ubi.mtd=5 root=ubi0:rootfs rootfstype=ubifs"

then if we had wrote root fs ubi image to mtd5, then we can boot up with ubi root fs.

13.遇到的rw filesystem change to read only filesystem
# ./ubiattach /dev/ubi_ctrl -m 6
UBI: attaching mtd6 to ubi0
UBI: physical eraseblock size:   524288 bytes (512 KiB)
UBI: logical eraseblock size:    516096 bytes
UBI: smallest flash I/O unit:    4096
UBI: VID header offset:          4096 (aligned 4096)
UBI: data offset:                8192
PEB 0 is bad
PEB 32 is bad
UBI: attached mtd6 to ubi0
UBI: MTD device name:            "Data area1"
UBI: MTD device size:            1000 MiB
UBI: number of good PEBs:        1998
UBI: number of bad PEBs:         2
UBI: max. allowed volumes:       128
UBI: wear-leveling threshold:    4096
UBI: number of internal volumes: 1
UBI: number of user volumes:     3
UBI: available PEBs:             0
UBI: total number of reserved PEBs: 1998
UBI: number of PEBs reserved for bad PEB handling: 19
UBI: max/mean erase counter: 7/0
UBI: background thread "ubi_bgt0d" started, PID 940
./ubiupdatevol /dev/ubi0_0 -t
UBI error: ubi_io_write: error -5 while writing 4096 bytes to PEB 21:4096, written 0 bytes
UBI warning: ubi_eba_write_leb: failed to write VID header to LEB 2147479551:0, PEB 21
UBI: try another PEB
UBI error: ubi_io_write: error -5 while writing 4096 bytes to PEB 12:4096, written 0 bytes
UBI warning: ubi_eba_write_leb: failed to write VID header to LEB 2147479551:0, PEB 12
UBI: try another PEB
UBI error: ubi_io_write: error -5 while writing 4096 bytes to PEB 1999:4096, written 0 bytes
UBI warning: ubi_eba_write_leb: failed to write VID header to LEB 2147479551:0, PEB 1999
UBI: try another PEB
UBI error: ubi_io_write: error -5 while writing 24576 bytes to PEB 1998:8192, written 0 bytes
UBI warning: ubi_eba_write_leb: failed to write 24576 bytes at offset 0 of LEB 2147479551:0, PEB 1998
UBI warning: ubi_ro_mode: switch to read-only mode
UBI error: ubi_io_write: read-only mode
UBI error: erase_worker: failed to erase PEB 7, error -30
UBI error: do_work: work failed with error code -30
UBI error: ubi_thread: ubi_bgt0d: work failed with error code -30
ubiupdatevol: error!: cannot truncate volume "/dev/ubi0_0"
              error 30 (Read-only file system)
UBI warning: vol_cdev_release: update of volume 0 not finished, volume is damaged
遇到此问题后,再flash_eraseall /dev/mt6或/dev/mtd7后,再写image to /dev/mtd7 or /dev/mtd6都出错
./ubiformat -q /dev/mtd6 -f ubi.img.20M_none
libmtd: error!: cannot write 32768 bytes to mtd6 (eraseblock 1, offset 0)
        error 5 (Input/output error)
ubiformat: error!: cannot write eraseblock 1
           error 5 (Input/output error)
          
遇到此问题后,我在uboot下执行
NAND erase: device 0 offset 0x600000, size 0x400000
ret:0 erase.addr:600000
Erasing at 0x600000 --  12% complete.ret:0 erase.addr:680000
Erasing at 0x680000 --  25% complete.ret:0 erase.addr:700000
Erasing at 0x700000 --  37% complete.ret:0 erase.addr:780000
Erasing at 0x780000 --  50% complete.ret:0 erase.addr:800000
Erasing at 0x800000 --  62% complete.ret:0 erase.addr:880000
Erasing at 0x880000 --  75% complete.ret:0 erase.addr:900000
Erasing at 0x900000 --  87% complete.ret:0 erase.addr:980000
Erasing at 0x980000 -- 100% complete.
OK
SMDK2450 # nand write c0000000 600000 300000
NAND write: device 0 offset 0x600000, size 0x300000
 0 bytes written: ERROR


 最后,我重新用笔尖fine tine nand flash的引脚(特别是/WE),终于救回了这片flash


 结论:好像是此flash已经损坏,或者是引脚接触不好----因为erase是ok的,而且nand read读也是ok的,导致无法写入PEB,会使得
 UBIFS变为只读。



14.mkfs.ubifs -c issus  (注意 fat directory is empty)
dannylo@fs1:~/cram2fs_tools$ ./mkfs.ubifs -r fat  -m 4096 -e 516096 -c 10 -o test.img
Error: too low max. count of LEBs, minimum is 17
dannylo@fs1:~/cram2fs_tools$ ./mkfs.ubifs -r fat  -m 4096 -e 516096 -c 19 -o test.img
Error: too many log LEBs, maximum is 2
dannylo@fs1:~/cram2fs_tools$ ./mkfs.ubifs -r fat  -m 4096 -e 516096 -c 21 -o test.img
Error: too many log LEBs, maximum is 4
dannylo@fs1:~/cram2fs_tools$ ./mkfs.ubifs -r fat  -m 4096 -e 516096 -c 22 -o test.img
结论:即最小的ubifs.img为11M.注意对要烧录进volume的cramfs or cram2fs format的image的size好像没有要求。

15.ubi face bad block
我们在uboot中用nand markbad 试过mark bad block(一块在mtd device的首块,一块在中间,一块在最后),测试表面ubi在attach时的scan能认出
bad block nand skip bad block.同时用ubiformat烧写ubi.img时也会自动跳过bad block.

U-BOOT源码分析及移植(quote)

本文从以下几个方面粗浅地分析u-boot并移植到FS2410板上:
1、u-boot工程的总体结构
2、u-boot的流程、主要的数据结构、内存分配。
3、u-boot的重要细节,主要分析流程中各函数的功能。
4、基于FS2410板子的u-boot移植。实现了NOR Flash和NAND Flash启动,网络功能。 
这些认识源于自己移植u-boot过程中查找的资料和对源码的简单阅读。下面主要以smdk2410为分析对象。


一、u-boot工程的总体结构:
1、源代码组织
 对于ARM而言,主要的目录如下:
board                  平台依赖          存放电路板相关的目录文件,每一套板子对 应一个目录。如smdk2410(arm920t)
                                                                                                                    
cpu                    平台依赖           存放CPU相关的目录文件,每一款CPU对应一个目录,例如:arm920t、 xscale、i386等目录
lib_arm                平台依赖            存放对ARM体系结构通用的文件,主要用于实现ARM平台通用的函数,如软件浮点。

common              通用          通用的多功能函数实现,如环境,命令,控制台相关的函数实现。
include                通用               头文件和开发板配置文件,所有开发板的配置文件都在configs目录下                                       
lib_generic         通用             通用库函数的实现
net                    通用                存放网络协议的程序
drivers              通用               通用的设备驱动程序,主要有以太网接口的驱动,nand驱动。
.......
2.makefile简要分析
所有这些目录的编译连接都是由顶层目录的makefile来确定的。
在执行make之前,先要执行make $(board)_config 对工程进行配置,以确定特定于目标板的各个子目录和头文件。
$(board)_config:是makefile 中的一个伪目标,它传入指定的CPU,ARCH,BOARD,SOC参数去执行mkconfig脚本。
这个脚本的主要功能在于连接目标板平台相关的头文件夹,生成config.h文件包含板子的配置头文件。
使得makefile能根据目标板的这些参数去编译正确的平台相关的子目录。
以smdk2410板为例,执行 make smdk2410_config,
主要完成三个功能:
@在include文件夹下建立相应的文件(夹)软连接,

#如果是ARM体系将执行以下操作:
#ln -s     asm-arm        asm  
#ln -s  arch-s3c24x0    asm-arm/arch
#ln -s   proc-armv       asm-arm/proc

@生成Makefile包含文件include/config.mk,内容很简单,定义了四个变量:
ARCH   = arm
CPU    = arm920t
BOARD  = smdk2410
SOC    = s3c24x0

@生成include/config.h头文件,只有一行:
/* Automatically generated - do not edit */
#include "config/smdk2410.h"

顶层makefile先调用各子目录的makefile,生成目标文件或者目标文件库。
然后再连接所有目标文件(库)生成最终的u-boot.bin。
连接的主要目标(库)如下:
OBJS  = cpu/$(CPU)/start.o
LIBS  = lib_generic/libgeneric.a
LIBS += board/$(BOARDDIR)/lib$(BOARD).a
LIBS += cpu/$(CPU)/lib$(CPU).a
ifdef SOC
LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a
endif
LIBS += lib_$(ARCH)/lib$(ARCH).a
LIBS += fs/cramfs/libcramfs.a fs/fat/libfat.a fs/fdos/libfdos.a fs/jffs2/libjffs2.a \
 fs/reiserfs/libreiserfs.a fs/ext2/libext2fs.a
LIBS += net/libnet.a
LIBS += disk/libdisk.a
LIBS += rtc/librtc.a
LIBS += dtt/libdtt.a
LIBS += drivers/libdrivers.a
LIBS += drivers/nand/libnand.a
LIBS += drivers/nand_legacy/libnand_legacy.a
LIBS += drivers/sk98lin/libsk98lin.a
LIBS += post/libpost.a post/cpu/libcpu.a
LIBS += common/libcommon.a
LIBS += $(BOARDLIBS)
显然跟平台相关的主要是:
cpu/$(CPU)/start.o
board/$(BOARDDIR)/lib$(BOARD).a 
cpu/$(CPU)/lib$(CPU).a
cpu/$(CPU)/$(SOC)/lib$(SOC).a 
lib_$(ARCH)/lib$(ARCH).a
这里面的四个变量定义在include/config.mk(见上述)。
其余的均与平台无关。
所以考虑移植的时候也主要考虑这几个目标文件(库)对应的目录。

关于u-boot 的makefile更详细的分析可以参照http://blog.mcuol.com/User/lvembededsys/Article/4355_1.htm
3、u-boot的通用目录是怎么做到与平台无关的?
include/config/smdk2410.h  
 这个头文件中主要定义了两类变量。
一类是选项,前缀是CONFIG_,用来选择处理器、设备接口、命令、属性等,主要用来 决定是否编译某些文件或者函数。

另一类是参数,前缀是CFG_,用来定义总线频率、串口波特率、Flash地址等参数。这些常数参量主要用来支持通用目录中的代码,定义板子资源参数。
这两类宏定义对u-boot的移植性非常关键,比如drive/CS8900.c,对cs8900而言,很多操作都是通用的,但不是所有的板子上面都有这个芯片,即使有它在内存中映射的基地址也是平台相关的。所以对于smdk2410板,在smdk2410.h中定义了
 #define CONFIG_DRIVER_CS8900 1              /* we have a CS8900 on-board */
 #define CS8900_BASE 0x19000300              /*IO mode base address*/
CONFIG_DRIVER_CS8900 的定义使得cs8900.c可以被编译(当然还得定义CFG_CMD_NET才行),因为cs8900.c中在函数定义的前面就有编译条件判断: #ifdef CONFIG_DRIVER_CS8900 如果这个选项没有定义,整个cs8900.c就不会被编译了。
而常数参量CS8900_BASE则用在cs8900.h头文件中定义各个功能寄存器的地址。u-boot的CS8900工作在IO模式下,只要给定IO寄存器在内存中映射的基地址,其余代码就与平台无关了。
 
  u-boot的命令也是通过目标板的配置头文件来配置的,比如要添加ping命令,就必须添加CFG_CMD_NET和CFG_CMD_PING才行。不然common/cmd_net.c就不会被编译了。
 从这里我可以这么认为,u-boot工程可配置性和移植性可以分为两层:
 一是由makefile来实现,配置工程要包含的文件和文件夹上,用什么编译器。
 二是由目标板的配置头文件来实现源码级的可配置性,通用性。主要使用的是#ifdef #else #endif 之类来实现的。
4、smkd2410其余重要的文件
include/s3c24x0.h        定义了s3x24x0芯片的各个特殊功能寄存器(SFR)的地址。
cpu/arm920t/start.s         在flash中执行的引导代码,也就是bootloader中的stage1,负责初始化硬件环境,把u-boot从flash加载到RAM中去,然后跳 到lib_arm/board.c中的start_armboot中去执行。
lib_arm/board.c          u-boot的初始化流程,尤其是u-boot用到的全局数据结构gd,bd的初始化,以及设备和控制台的初始化。
board/smdk2410/flash.c       在board目录下代码的都是严重依赖目标板,对于不同的CPU,SOC,ARCH,u-boot都有相对通用的代码,但是板子构成却是多样的,主要是内 存地址,flash型号,外围芯片如网络。对fs2410来说,主要考虑从smdk2410板来移植,差别主要在nor flash上面。

二、u-boot的流程、主要的数据结构、内存分配
1、u-boot的启动流程:
从文件层面上看主要流程是在两个文件中:cpu/arm920t/start.s,lib_arm/board.c, 
1)start.s 
   在flash中执行的引导代码,也就是bootloader中的stage1,负责初始化硬件环境,把u-boot从flash加载到RAM中去,然后跳到lib_arm/board.c中的start_armboot中去执行。
1.1.6版本的start.s流程:
 硬件环境初始化
     进入svc模式;关闭watch dog;屏蔽所有IRQ掩码;设置时钟频率FCLK、HCLK、PCLK;清I/D cache;禁止MMU和CACHE;配置memory control;
 重定位
     如果当前代码不在连接指定的地址上(对smdk2410是0x3f000000)则需要把u-boot从当前位置拷贝到RAM指定位置中;
 建立堆栈,堆栈是进入C函数前必须初始化的。
 清.bss区
 跳到start_armboot函数中执行。(lib_arm/board.c)
  2)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重定位后的内存分布:
   对于smdk2410,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/smdk2410/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:这个函数里有太多编译选项,对于smdk2410,去掉所有选项后等效下面的程序
  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/smdk2410.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[i].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[i] = 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[i] = 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[i] = data2[i];

       /* re-enable interrupts if necessary */
       if (flag)
         enable_interrupts();

       /* data polling for D7 */
       start = get_timer (0);
       while ((dest2[i] & (CFG_FLASH_WORD_SIZE)0x0080) !=
       (data2[i] & (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
  ///
  /*************************************************
  *
  *Nand basic functions:
  *************************************************
  */

  //读取Nand的ID号,返回值在r5中
  ReadNandID:
   mov     r7,#NFCONF
   ldr      r0,[r7,#0]  //NFChipEn();
   bic      r0,r0,#0x800
   str      r0,[r7,#0]
   mov      r0,#0x90  //WrNFCmd(RdIDCMD);
   strb     r0,[r7,#4]
   mov      r4,#0   //WrNFAddr(0);
   strb     r4,[r7,#8]
  y1:           //while(NFIsBusy());
   ldr      r0,[r7,#0x10]
   tst      r0,#1
   beq      y1
   ldrb     r0,[r7,#0xc] //id  = RdNFDat()<<8;
   mov      r0,r0,lsl #8
   ldrb     r1,[r7,#0xc] //id |= RdNFDat();
   orr      r5,r1,r0
   ldr      r0,[r7,#0]  //NFChipDs();
   orr      r0,r0,#0x800
   str      r0,[r7,#0]
   mov   pc,lr

  //读取Nand状态,返回值在r1,此处没有用到返回值。

  ReadNandStatus:
   mov   r7,#NFCONF
   ldr      r0,[r7,#0]   //NFChipEn();
   bic      r0,r0,#0x800
   str      r0,[r7,#0]
   mov      r0,#0x70     //WrNFCmd(QUERYCMD);
   strb     r0,[r7,#4]
   ldrb     r1,[r7,#0xc]  //r1 = RdNFDat();
   ldr      r0,[r7,#0]   //NFChipDs();
   orr      r0,r0,#0x800
   str      r0,[r7,#0]
   mov   pc,lr

  //等待Nand内部操作完毕
  WaitNandBusy:
   mov      r0,#0x70  //WrNFCmd(QUERYCMD);
   mov      r1,#NFCONF
   strb     r0,[r1,#4]
  z1:                //while(!(RdNFDat()&0x40));
   ldrb     r0,[r1,#0xc]
   tst      r0,#0x40
   beq     z1
   mov      r0,#0   //WrNFCmd(READCMD0);
   strb     r0,[r1,#4]
   mov      pc,lr

  //检查坏block:
  CheckBadBlk:
   mov     r7, lr
   mov     r5, #NFCONF
 
   bic     r0, r0, #0x1f   //addr &= ~0x1f;
   ldr      r1,[r5,#0]  //NFChipEn()
   bic      r1,r1,#0x800
   str      r1,[r5,#0]

   mov      r1,#0x50    //WrNFCmd(READCMD2)
   strb     r1,[r5,#4]
   mov    r1, #6
   strb     r1,[r5,#8]  //WrNFAddr(6)
   strb     r0,[r5,#8]  //WrNFAddr(addr)
   mov      r1,r0,lsr #8 //WrNFAddr(addr>>8)
   strb     r1,[r5,#8]
   cmp      r6,#0     //if(NandAddr)
   movne    r0,r0,lsr #16 //WrNFAddr(addr>>16)
   strneb   r0,[r5,#8]
 
   bl  WaitNandBusy //WaitNFBusy()

   ldrb r0, [r5,#0xc] //RdNFDat()
   sub  r0, r0, #0xff
 
   mov      r1,#0   //WrNFCmd(READCMD0)
   strb     r1,[r5,#4]
 
   ldr      r1,[r5,#0]  //NFChipDs()
   orr      r1,r1,#0x800
   str      r1,[r5,#0]
 
   mov  pc, r7

  ReadNandPage:
   mov     r7,lr
   mov      r4,r1
   mov      r5,#NFCONF

   ldr      r1,[r5,#0]  //NFChipEn()
   bic      r1,r1,#0x800
   str      r1,[r5,#0]

   mov      r1,#0   //WrNFCmd(READCMD0)
   strb     r1,[r5,#4]
   strb     r1,[r5,#8]  //WrNFAddr(0)
   strb     r0,[r5,#8]  //WrNFAddr(addr)
   mov      r1,r0,lsr #8 //WrNFAddr(addr>>8)
   strb     r1,[r5,#8]
   cmp      r6,#0   //if(NandAddr)
   movne    r0,r0,lsr #16 //WrNFAddr(addr>>16)
   strneb   r0,[r5,#8]
 
   ldr      r0,[r5,#0]  //InitEcc()
   orr      r0,r0,#0x1000
   str      r0,[r5,#0]
 
   bl       WaitNandBusy //WaitNFBusy()
 
   mov      r0,#0   //for(i=0; i<512; i++)
  r1:
   ldrb     r1,[r5,#0xc] //buf[i] = RdNFDat()
   strb     r1,[r4,r0]
   add      r0,r0,#1
   bic      r0,r0,#0x10000
   cmp      r0,#0x200
   bcc      r1
 
   ldr      r0,[r5,#0]  //NFChipDs()
   orr      r0,r0,#0x800
   str      r0,[r5,#0]
  
   mov   pc,r7

关于nand命令,我尝试打开CFG_CMD_NAND选项,并定义
    #define CFG_MAX_NAND_DEVICE 1
   #define MAX_NAND_CHIPS 1
   #define CFG_NAND_BASE 0x4e000000
   添加boar_nand_init()定义(空实现)。但是连接时出现问题,原因是u-boot使用的是软浮点,而我的交叉编译arm-linux-gcc是硬件浮点。
   看过一些解决方法,比较麻烦,还没有解决这个问题,希望好心的高手指点。不过我比较纳闷,u-boot在nand部分哪里会用到浮点运算呢?
  
  7、添加网络命令。
  我尝试使用ping命令,其余的命令暂时不考虑。
  在common/cmd_net中,首先有条件编译 #if (CONFIG_COMMANDS & CFG_CMD_NET),然后在命令函数do_ping(...)定义之前有条件编译判断
  #if (CONFIG_COMMANDS & CFG_CMD_PING) 。所以在include/cofig/fs2410.h中必须打开这两个命令选项。
   #define CONFIG_COMMANDS \
    (CONFIG_CMD_DFL  | \
    CFG_CMD_CACHE  | \
    CFG_CMD_REGINFO  | \
    CFG_CMD_DATE  | \
    CFG_CMD_NET | \  //
    CFG_CMD_PING |\ //
    CFG_CMD_ELF)
  并且设定IP:192.168.0.12。

   至此,整个移植过程已经完成。编译连接生成u-boot.bin,烧到nand 和nor上都能顺利启动u-boot,使用ping命令时出现问题,
   发现ping自己的主机竟然超时,还以为是程序出了问题,后来才发现是windows防火墙的问题。关闭防火墙就能PING通了。
  
   总体来说,u-boot是一个很特殊的程序,代码庞大,功能强大,自成体系。为了在不同的CPU,ARCH,BOARD上移植进行了很多灵活的设计。
在u-boot的移植过程中学到很多东西,尤其是程序设计方法方面真的是大开了眼界。u-boot在代码级可移植性和底层程序开发技术上给人很好的启发。
很多东西没有搞明白,尤其是u-boot最重要的功能--引导OS这部分还没有涉及。linux内核还没入门呢,路漫漫其修远兮,吾将上下而求索。
 没有IDE环境看u-boot这种makefile工程很费劲,我用UltraEdit干了这件事,后来才发现可以使用source insight 这个软件。。。。。。。。这些工作都是自己学习过程的总结,谬误之处在所难免,请高手不吝指正。。