vivi boot loader的实现
发布日期:2021-07-22 22:18:25 浏览次数:4 分类:技术文章

本文共 56309 字,大约阅读时间需要 187 分钟。

 
vivi boot loader的实现
 
参考资料:
  1. 嵌入式系统 Boot Loader 技术内幕, 詹荣开 (zhanrk@sohu.com)
  2. Getting started with VIVI, Janhoon Lyu, nandy@mizi.com
  3. 嵌入式设备上的Linux 系统开发,A. Santhanam etc.
  4. Linux system development on an embedded device, A. Santhanam
  5. vivi 有关资料http://www.mizi.com/developer/s3c2410x/index.html
  6. smdk2410的硬件和软件/linux相关资料 search 2410
 
说明:本文文字结构照抄” 嵌入式系统 Boot Loader 技术内幕, 詹荣开 ( )” 一文,以vivi中head.S作为stage1, main()作为stage2,解释了VIVI for SMDK2410 (based on S3C2410) 开发系统的bootloader的实现。将原文放在这里是为了方便读者。注意,VIVI的实现并非完全跟原文一致。多谢原文作者詹大侠的详细解释。
附录有一节 __SETUP kernel 的作用来自 jeppeter (member) from http:// linuxforum.net
 
文中对MTD subsystem linux没作解释。Google “MTD linux subsystem 文件系统 JFSS2 ”可以获得足够的解释。
 
如有错误,烦请email 告知。多些
 
Ver.0.95
Jones S Z Xu
2004-09-29
Chapter 1 Boot loader基本结构
由于 Boot Loader 的实现依赖于 CPU 的体系结构,因此大多数 Boot Loader 都分为 stage1 stage2 两大部分。依赖于 CPU 体系结构的代码,比如设备初始化代码等,通常都放在 stage1 中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而 stage2 则通常用 C 语言来实现,这样可以实现给复杂的功能,而且代码会具有更好的可读性和可移植性。
 
Boot Loader stage1 通常包括以下步骤 ( 以执行的先后顺序 )
 
硬件设备初始化。
为加载 Boot Loader stage2 准备 RAM 空间。
拷贝 Boot Loader stage2 RAM 空间中。
设置好堆栈。
跳转到 stage2 C 入口点。
 
Boot Loader stage2 通常包括以下步骤 ( 以执行的先后顺序 )
 
初始化本阶段要使用到的硬件设备。
检测系统内存映射 (memory map)
kernel 映像和根文件系统映像从 flash 上读到 RAM 空间中。
为内核设置启动参数。
调用内核。
 
1.1 Boot Loader 的 stage1
 
1.1.1 基本的硬件初始化
 
这是 Boot Loader 一开始就执行的操作,其目的是为 stage2 的执行以及随后的 kernel 的执行准备好一些基本的硬件环境。它通常包括以下步骤(以执行的先后顺序):
 
1 屏蔽所有的中断。为中断提供服务通常是 OS 设备驱动程序的责任,因此在 Boot Loader 的执行全过程中可以不必响应任何中断。中断屏蔽可以通过写 CPU 的中断屏蔽寄存器或状态寄存器(比如 ARM CPSR 寄存器)来完成。
 
2 设置 CPU 的速度和时钟频率。
 
3 RAM 初始化。包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等。
 
4 初始化 LED 。典型地,通过 GPIO 来驱动 LED ,其目的是表明系统的状态是 OK 还是 Error 。如果板子上没有 LED ,那么也可以通过初始化 UART 向串口打印 Boot Loader Logo 字符信息来完成这一点。
 
5 关闭 CPU 内部指令/数据 cache
 
 
VIVI 在第一阶段完成以下任务
 
 
Disable watch dog timer ;                                      disable all interrupts ; 
 
 initialise system clocks; initialise the static memory                      All LED on   
 
 set GPIO for UART                                                 Initialize UART 0 ; 
 
copy_myself to ram;                                                jump to ram
 get read to call C functions                                           setup stack pointer
call main
1.1.2 为加载 stage2 准备 RAM 空间
 
为了获得更快的执行速度,通常把 stage2 加载到 RAM 空间中来执行,因此必须为加载 Boot Loader stage2 准备好一段可用的 RAM 空间范围。
 
由于 stage2 通常是 C 语言执行代码,因此在考虑空间大小时,除了 stage2 可执行映象的大小外,还必须把堆栈空间也考虑进来。此外,空间大小最好是 memory page 大小 ( 通常是 4KB) 的倍数。一般而言, 1M RAM 空间已经足够了。具体的地址范围可以任意安排,比如 blob 就将它的 stage2 可执行映像安排到从系统 RAM 起始地址 0xc0200000 开始的 1M 空间内执行。但是,将 stage2 安排到整个 RAM 空间的最顶 1MB( 也即 (RamEnd-1MB) - RamEnd) 是一种值得推荐的方法。
 
为了后面的叙述方便,这里把所安排的 RAM 空间范围的大小记为: stage2_size( 字节 ) ,把起始地址和终止地址分别记为: stage2_start stage2_end( 这两个地址均以 4 字节边界对齐 ) 。因此:
 
stage2_end stage2_start stage2_size
 
 
另外,还必须确保所安排的地址范围的的确确是可读写的 RAM 空间,因此,必须对你所安排的地址范围进行测试。具体的测试方法可以采用类似于 blob 的方法,也即:以 memory page 为被测试单位,测试每个 memory page 开始的两个字是否是可读写的。为了后面叙述的方便,我们记这个检测算法为: test_mempage ,其具体步骤如下:
 
1 先保存 memory page 一开始两个字的内容。
 
2 向这两个字中写入任意的数字。比如:向第一个字写入 0x55 ,第 2 个字写入 0xaa
 
3 然后,立即将这两个字的内容读回。显然,我们读到的内容应该分别是 0x55 0xaa 。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。
 
4 再向这两个字中写入任意的数字。比如:向第一个字写入 0xaa ,第 2 个字中写入 0x55
 
5 然后,立即将这两个字的内容立即读回。显然,我们读到的内容应该分别是 0xaa 0x55 。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。
 
6 恢复这两个字的原始内容。测试完毕。
 
为了得到一段干净的 RAM 空间范围,我们也可以将所安排的 RAM 空间范围进行清零操作。
 
1.1.3 拷贝 stage2 RAM
 
拷贝时要确定两点: (1) stage2 的可执行映象在固态存储设备的存放起始地址和终止地址; (2) RAM 空间的起始地址。
 
1.1.4 设置堆栈指针 sp
 
堆栈指针的设置是为了执行 C 语言代码作好准备。通常我们可以把 sp 的值设置为 (stage2_end-4) ,也即在 1.1.2 节所安排的那个 1MB RAM 空间的最顶端 ( 堆栈向下生长 )
 
此外,在设置堆栈指针 sp 之前,也可以关闭 led 灯,以提示用户我们准备跳转到 stage2
 
经过上述这些执行步骤后,系统的物理内存布局应该如下图 2 所示。
 
1.1.5 跳转到 stage2 C 入口点
 
在上述一切都就绪后,就可以跳转到 Boot Loader stage2 去执行了。比如,在 ARM 系统中,这可以通过修改 PC 寄存器为合适的地址来实现。
 
head.S 负责完成硬件初始化操作,具体分析见源码注释 ,汇编差不多忘光了,下面注释中有关汇编的东西多些。
其中 "linkage.h"                
#define SYMBOL_NAME_STR(X) #X
#define SYMBOL_NAME(X) X
#ifdef __STDC__
#define SYMBOL_NAME_LABEL(X) X##:
#else
#define SYMBOL_NAME_LABEL(X) X/**/:
#endif
 
#define __ALIGN .align 0
#define __ALIGN_STR ".align 0"
 
#ifdef __ASSEMBLY__
 
#define ALIGN __ALIGN
#define ALIGN_STR __ALIGN_STR
 
#define ENTRY(name) /
 .globl SYMBOL_NAME(name); /
 ALIGN; /
 SYMBOL_NAME_LABEL(name)
 
#endif
 
其中 "machine.h" 包括了
smdk2410.h ( 有关开发板的配置 ) ,
包括 memory map, Porocessor memory map ,FLASH, ROM, DRAM 的物理地址和在 VIVI 中用的虚拟地址 (?) Architecture magic and machine type, UART CPU DRAM 的初始化参数等
 
smdk2410.h 进一步包括 s3c2410.h 有关 CPU 的设置, Definition of constants related to the S3C2410 microprocessor(based on ARM 920T).
 
 
 
/*
 * vivi/arch/s3c2410/head.S:
 *   Initialise hardware
 *
 * Copyright (C) 2001 MIZI Research, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 *
 * Author: Janghoon Lyu <nandy@mizi.com>
 * Date : $Date: 2003/02/26 10:38:11 $
 *
 * $Revision: 1.18 $
 *
 *
 * History:
 *
 * 2002-05-14: Janghoon Lyu <nandy@mizi.com>
 *   - Initial code
 *
 */
 
#include "config.h"                   // à autoconf.h 空的
#include "linkage.h"                 // 定义
#include "machine.h"
 
@ Start of executable code
 
ENTRY(_start)                     // 入口点
ENTRY(ResetEntryPoint)
 
@
@ Exception vector table (physical address = 0x00000000)
@    // 异常向量 物理地址 0x0000000
 
@ 0x00: Reset             // 基本操作 :复位   B 是最简单的分支。一旦遇到一个 B 指令, ARM 处理器将立即跳转到给定的地址,从那里继续执行。注意存储在分支指令中的实际的值是相对当前的 R15
       b     Reset
 
@ 0x04: Undefined instruction exception      // 处理未定义的指令
UndefEntryPoint:
       b     HandleUndef
 
@ 0x08: Software interrupt exception           // 软中断
SWIEntryPoint:
       b     HandleSWI
 
@ 0x0c: Prefetch Abort (Instruction Fetch Memory Abort) // 中文名不知道
PrefetchAbortEnteryPoint:
       b     HandlePrefetchAbort
 
@ 0x10: Data Access Memory Abort       //
DataAbortEntryPoint:
       b     HandleDataAbort
 
@ 0x14: Not used        //
NotUsedEntryPoint:
       b     HandleNotUsed
 
@ 0x18: IRQ(Interrupt Request) exception     // 中断 ( 普通 )
IRQEntryPoint:
       b     HandleIRQ
 
@ 0x1c: FIQ(Fast Interrupt Request) exception     // fast 中断处理
FIQEntryPoint:
       b     HandleFIQ
 
@
@ VIVI magics
@
 
@ 0x20: magic number so we can verify that we only put
       .long   0
@ 0x24:
       .long   0
@ 0x28: where this vivi was linked, so we can put it in memory in the right place
       .long   _start
@ 0x2C: this contains the platform, cpu and machine id
       .long   ARCHITECTURE_MAGIC
@ 0x30: vivi capabilities
       .long   0
#ifdef CONFIG_PM             // power management //vivi 未用
@ 0x34:
       b     SleepRamProc
#endif
#ifdef CONFIG_TEST          //test mode    vivi 未用
@ 0x38:
       b     hmi
#endif
 
 
@
@ Start VIVI head
@
Reset:                                       // 第一步 RESET
       @ disable watch dog timer              //disable watch dog 定时器
       mov       r1, #0x53000000
       mov       r2, #0x0
       str   r2, [r1]                  //add 0x5300_0000 0 bit 5=0 disable this timer
 
#ifdef CONFIG_S3C2410_MPORT3    // 另一种 Platform SMDK
       mov       r1, #0x56000000
       mov       r2, #0x00000005
       str   r2, [r1, #0x70]
       mov r2, #0x00000001
       str   r2, [r1, #0x78]
       mov       r2, #0x00000001
       str r2, [r1, #0x74]
#endif
 
       @ disable all interrupts            // 禁止 所有中断
       mov       r1, #INT_CTL_BASE            //0x4A00_0000 source pending register
       mov       r2, #0xffffffff
       str   r2, [r1, #oINTMSK]             //0x4A00_0008
//0x4A00_0008 INTERRUPT MASK register=0xFFFFFFFF, disable all int
       ldr   r2, =0x7ff
       str   r2, [r1, #oINTSUBMSK]              //0x4A00_001C
              //Interrupt sub mask register , bit[10:0] = 1 ->0x7FF ->disable all
 
       @ initialise system clocks         // 初始化系统时钟
       mov       r1, #CLK_CTL_BASE          // LOCK TIME COUNT REGISTER(LOCKTIME)
                                          //0x4c000000
       mvn       r2, #0xff000000                  
       str   r2, [r1, #oLOCKTIME]          //0x4C000000 ->0xFF00_0000;
 
       @ldr       r2, mpll_50mhz           //CPU 定成 50Mhz
       @str       r2, [r1, #oMPLLCON]          //
#ifndef CONFIG_S3C2410_MPORT1         // 如果未定义成 MPORT1 ( 一种 plat form)
       @ 1:2:4
       mov       r1, #CLK_CTL_BASE
       mov       r2, #0x3               
       str   r2, [r1, #oCLKDIVN]            //
// vCLKDIVN          0x3         /* FCLK:HCLK:PCLK = 1:2:4 */
       mrc p15, 0, r1, c1, c0, 0             @ read ctrl register
       orr   r1, r1, #0xc0000000             @ Asynchronous 
       mcr p15, 0, r1, c1, c0, 0             @ write ctrl register
 
       @ now, CPU clock is 200 Mhz          //CPU 定成 200Mhz
       mov       r1, #CLK_CTL_BASE
       ldr   r2, mpll_200mhz
       str   r2, [r1, #oMPLLCON]
#else                                         //platform= MPORT1 , 以下不理
       @ 1:2:2
    mov r1, #CLK_CTL_BASE
    ldr r2, clock_clkdivn
    str r2, [r1, #oCLKDIVN]
 
    mrc p15, 0, r1, c1, c0, 0       @ read ctrl register
    orr r1, r1, #0xc0000000     @ Asynchronous
    mcr p15, 0, r1, c1, c0, 0       @ write ctrl register
 
    @ now, CPU clock is 100 Mhz
    mov r1, #CLK_CTL_BASE
    ldr r2, mpll_100mhz
    str r2, [r1, #oMPLLCON]
#endif
       bl    memsetup                          // 2 memsetup
 
#ifdef CONFIG_PM                                  // 如果有 Power management: 不用
       @ Check if this is a wake-up from sleep
       ldr   r1, PMST_ADDR
       ldr   r0, [r1]
       tst    r0, #(PMST_SMR)
       bne WakeupStart
#endif
 
 
#ifdef CONFIG_S3C2410_SMDK               //SMDK platform
       @ All LED on                                   // 点灯,好歹通知一下外面的同志 3
       mov       r1, #GPIO_CTL_BASE
       add r1, r1, #oGPIO_F
       ldr   r2,=0x55aa
       str   r2, [r1, #oGPIO_CON]
       mov       r2, #0xff
       str   r2, [r1, #oGPIO_UP]
       mov       r2, #0x00
       str   r2, [r1, #oGPIO_DAT]
#endif
 
#if 0
       @ SVC
       mrs r0, cpsr
       bic r0, r0, #0xdf
       orr   r1, r0, #0xd3
       msr cpsr_all, r1
#endif
                                          // 设置串口 ,内外联络的通道
       @ set GPIO for UART
       mov       r1, #GPIO_CTL_BASE         // 0x5600_0000
       add r1, r1, #oGPIO_H        
// oGPIO_H 0x70 PORT H CONTROL REGISTERS
 
       ldr   r2, gpio_con_uart        // vGPHCON= 0x0016faaa
       str   r2, [r1, #oGPIO_CON]         // 01 01 10 11 11 10 10 10 10 10 10 B
                     //oGPIO_CON = 0x0 
// GPH0 bit[1:0] = 10 nCTS0
// GPH1 bit[3:2] = 10  nRTS0
// GPH2 bit[5:4] 10  TXD0
// GPH3 bit[7:6] = 10  RXD0
// GPH4 bit[9:8] = 10 TXD1
// GPH5 bit[11:10] 10 RXD1
// GPH6 bit[13:12] = 11  nRTS1
// GPH7 bit[15:14] = 11 nCTS1
// GPH8 bit[17:16] 10 UEXTCLK
// GPH9 bit[19:18] = 01 Output
// GPH10 bit[21:20] 01 Output
 
       ldr   r2, gpio_up_uart         // vGPHUP 0x000007ff = 0111 1111 1111 B
       str   r2, [r1, #oGPIO_UP]    
// oGPIO_UP 0x8 /* R/W, Pull-up disable register */
              //  0x7FF ->  1: The pull-up function is disabled. For all GPHx
// reg GPHUP 0x56000078
 
       bl    InitUART                     //initialize UART
 
#ifdef CONFIG_DEBUG_LL               //low level debugging info
       @ Print current Program Counter    //vivi def 没用
       ldr   r1, SerBase                 // 往串口上输出 info
       mov       r0, #'/r'
       bl    PrintChar
       mov       r0, #'/n'
       bl    PrintChar
       mov       r0, #'@'
       bl    PrintChar
       mov       r0, pc
       bl    PrintHexWord
#endif
 
 
#ifdef CONFIG_BOOTUP_MEMTEST        // comment 'Low Level Hardware Debugging'
 bool ' Enable simple memory test' CONFIG_BOOTUP_MEMTEST //vivi def 没用
       @ simple memory test to find some DRAM flaults.
       bl    memtest //check the first 1MB in increments of 4k// 改大点 3
#endif
 
#ifdef CONFIG_S3C2410_NAND_BOOT
       bl    copy_myself
 
       @ jump to ram
       ldr   r1, =on_the_ram         // on_the_ram 的地址装入 r1
       add pc, r1, #0                     //pc = r1+0
       nop
       nop
1:    b     1b          @ infinite loop        // 硬是看不懂这个 B
 
on_the_ram:
#endif
 
#ifdef CONFIG_DEBUG_LL
       ldr   r1, SerBase
       ldr   r0, STR_STACK
       bl    PrintWord
       ldr   r0, DW_STACK_START
       bl    PrintHexWord
#endif
 
       @ get read to call C functions
       ldr   sp, DW_STACK_START      @ setup stack pointer
                     // STACK_BASE+STACK_SIZE-4
// STACK_BASE = (VIVI_PRIV_RAM_BASE - STACK_SIZE)
//STACK 从上往下用。   所以 STACK_START = STACK_BASE+STACK_SIZE-4
       mov       fp, #0                    @ no previous frame, so fp=0
       mov       a2, #0                   @ set argv to NULL
 
       bl    main                    @ call main // 如果正常,一去不复返的了
 
mov     pc, #FLASH_BASE   @ otherwise, reboot, //FLASH_BASE=ROM_BASE0 = 0x0
 
@
@ End VIVI head
@
 
/*
 * subroutines
 */
 
@
@ Wake-up codes
@
#ifdef CONFIG_PM
WakeupStart:                     // power management
       @ Clear sleep reset bit
       ldr   r0, PMST_ADDR
       mov       r1, #PMST_SMR
       str   r1, [r0]
 
       @ Release the SDRAM signal protections
       ldr   r0, PMCTL1_ADDR
       ldr   r1, [r0]
       bic r1, r1, #(SCLKE | SCLK1 | SCLK0)
       str   r1, [r0]
 
       @ Go...
       ldr   r0, PMSR0_ADDR @ read a return address
       ldr   r1, [r0]
       mov       pc, r1
       nop
       nop
1:    b     1b          @ infinite loop
 
SleepRamProc:                        //power management
       @ SDRAM is in the self-refresh mode */
       ldr   r0, REFR_ADDR
       ldr   r1, [r0]
       orr   r1, r1, #SELF_REFRESH
       str   r1, [r0]
 
       @ wait until SDRAM into self-refresh
       mov       r1, #16
1:    subs       r1, r1, #1
       bne 1b
 
       @ Set the SDRAM singal protections
       ldr   r0, PMCTL1_ADDR
       ldr   r1, [r0]
       orr   r1, r1, #(SCLKE | SCLK1 | SCLK0)
       str   r1, [r0]
 
       /* Sleep... Now */
       ldr   r0, PMCTL0_ADDR
       ldr   r1, [r0]
       orr   r1, r1, #SLEEP_ON
       str   r1, [r0]   
1:    b     1b
 
#ifdef CONFIG_TEST
hmi:                                  
       ldr   r0, PMCTL0_ADDR      // PMCTL0_ADDR: .long 0x4c00000c, Clock Gen Ctrl
       ldr   r1, =0x7fff0           // reset clock gen ctrl
       str   r1, [r0]
      
       @ All LED on                            // 点灯?
       mov       r1, #GPIO_CTL_BASE
       add r1, r1, #oGPIO_F
       ldr   r2,=0x55aa
       str   r2, [r1, #oGPIO_CON]
       mov       r2, #0xff
       str   r2, [r1, #oGPIO_UP]
       mov       r2, #0xe0
       str   r2, [r1, #oGPIO_DAT]
1:    b     1b
#endif
 
#endif
 
ENTRY(memsetup)                           //memsetup 子程序
       @ initialise the static memory
 
       @ set memory control registers
       mov       r1, #MEM_CTL_BASE         //memory controller
       adrl r2, mem_cfg_val
       add r3, r1, #52
1:    ldr   r4, [r2], #4
       str   r4, [r1], #4
       cmp       r1, r3
       bne 1b
 
       mov       pc, lr                           // 这里返回了么?
              注意注意:这里 memsetup 已经返回了,下面是独立的子程序了呢
 
#ifdef CONFIG_S3C2410_NAND_BOOT // NAND 如此, NOR 应该如何处理呢?
@                                       // 不需要 copy vivi to ram?????
@ copy_myself: copy vivi to ram
@
copy_myself:
       mov       r10, lr
 
       @ reset NAND
       mov       r1, #NAND_CTL_BASE
       ldr   r2, =0xf830           @ initial value
       str   r2, [r1, #oNFCONF]
       ldr   r2, [r1, #oNFCONF]
       bic r2, r2, #0x800        @ enable chip
       str   r2, [r1, #oNFCONF]
       mov       r2, #0xff         @ RESET command
       strb r2, [r1, #oNFCMD]
       mov       r3, #0                    @ wait
1:    add r3, r3, #0x1
       cmp       r3, #0xa
       blt   1b
2:    ldr   r2, [r1, #oNFSTAT]      @ wait ready
       tst    r2, #0x1
       beq 2b
       ldr   r2, [r1, #oNFCONF]
       orr   r2, r2, #0x800        @ disable chip
       str   r2, [r1, #oNFCONF]
 
       @ get read to call C functions (for nand_read())
       ldr   sp, DW_STACK_START     @ setup stack pointer
       mov       fp, #0                    @ no previous frame, so fp=0
 
       @ copy vivi to RAM
       ldr   r0, =VIVI_RAM_BASE //(DRAM_BASE + DRAM_SIZE - VIVI_RAM_SIZE)
                     //0x33f00000
       mov     r1, #0x0               //start address, now vivi is in steppingstone
       mov       r2, #0x20000                       //128k ?
       bl    nand_read_ll
// nand_read_ll(unsigned char *buf, unsigned long start_addr, int size)
// ro = buf , r1 =start , size = r2=128k ??yeah 要这么多干吗?
       tst    r0, #0x0                // 返回值在 r0
       beq ok_nand_read                    //nand_read_ll() 顺利返回
#ifdef CONFIG_DEBUG_LL
bad_nand_read:
       ldr   r0, STR_FAIL
       ldr   r1, SerBase
       bl    PrintWord
1:    b     1b          @ infinite loop
#endif
      
ok_nand_read:
#ifdef CONFIG_DEBUG_LL
       ldr   r0, STR_OK
       ldr   r1, SerBase
       bl    PrintWord
#endif
 
       @ verify
       mov       r0, #0                    //flash start add? no. 是用 NAND 启动时,从 NAND copy phy add=0 4Kbytes SRAM(stepping stone) 中的 vivi
       ldr   r1, =0x33f00000    //VIVI_RAM_BASE
// VIVI_RAM_BASE = (DRAM_BASE + DRAM_SIZE(64M) - VIVI_RAM_SIZE)
//= 0x3000_0000+0x0400_0000-0x0010_0000 = 0x33f0_0000
       mov       r2, #0x400      @ 4 bytes * 1024 = 4K-bytes // 4K VIVI_RAM_BASE NO   比较 4Kbytes YES
go_next:
       ldr   r3, [r0], #4             // [r0] 指向的数据 (32bit?) 放进 r3 ,然后 r0+4->r0
       ldr   r4, [r1], #4             // [r1] 指向的数据 (32bit?) 放进 r4 ,然后 r1+4->r1
       teq r3, r4                    // 比较 r3/r4 的大小
       bne notmatch              // 出现不匹配的情况
       subs       r2, r2, #4        //r2-4 ->r2, if subs 结果为 0 flagZ==1
       beq done_nand_read // r2==0 条件 (flagZ==1) 成立,跳到 done_nand_read
       bne go_next         //flagZ==0; 说明 r2!=0
notmatch:
#ifdef CONFIG_DEBUG_LL
       sub r0, r0, #4
       ldr   r1, SerBase
       bl    PrintHexWord
       ldr   r0, STR_FAIL
       ldr   r1, SerBase
       bl    PrintWord
#endif
1:    b     1b                 // 匹配与否,不匹配时去哪里了?
done_nand_read:
 
#ifdef CONFIG_DEBUG_LL
       ldr   r0, STR_OK
       ldr   r1, SerBase
       bl    PrintWord
#endif
 
       mov       pc, r10                  // 返回罗,在函数入口处 mov     r10, lr
 
 
@ clear memory
@ r0: start address
@ r1: length
mem_clear:
       mov       r2, #0
       mov       r3, r2
       mov       r4, r2
       mov       r5, r2
       mov       r6, r2
       mov       r7, r2
       mov       r8, r2
       mov       r9, r2
 
clear_loop:
       stmia      r0!, {r2-r9}
       subs       r1, r1, #(8 * 4)
       bne clear_loop
 
       mov       pc, lr
 
#endif @ CONFIG_S3C2410_NAND_BOOT
 
 
#ifdef CONFIG_BOOTUP_MEMTEST // Low Level Hardware Debugging à Enable simple memory test //vivi 未用
@
@ Simple memory test function
@
memtest:
       mov       r10, lr
 
#ifdef CONFIG_DEBUG_LL        //low level debugging, serport 上面捣腾信息
       mov       r0, #'M'
       ldr   r1, SerBase
       bl    PrintChar
       mov       r0, #'T'
       ldr   r1, SerBase
       bl    PrintChar
       mov       r0, #'S'
       ldr   r1, SerBase
       bl    PrintChar
       mov       r0, #'T'
       ldr   r1, SerBase
       bl    PrintChar
       mov       r0, #' '
       ldr   r1, SerBase
       bl    PrintChar
#endif
 
        /* check the first 1MB in increments of 4k */       // 循环测试 1M SDRAM// 我们应该改大点
        mov     r7, #0x1000
        mov     r6, r7, lsl #8 /* 4k << 2^8 = 1MB */
        mov     r5, #DRAM_BASE             
//DRAM_BASE =DRAM_BASE0= 0x30000000      /* base address of dram bank 0 */
 
mem_test_loop:
        mov     r0, r5 
        bl      testram_nostack
        teq     r0, #1 
        beq     badram 
 
        add     r5, r5, r7
        subs    r6, r6, r7
        bne     mem_test_loop
 
 
       @ the first megabyte is OK. so let us clear it.
        mov     r0, #((1024 * 1024) / (8 * 4)) @ 1MB in steps of 32 bytes
        mov     r1, #DRAM_BASE
        mov     r2, #0 
        mov     r3, #0 
        mov     r4, #0 
        mov     r5, #0 
        mov     r6, #0 
        mov     r7, #0 
        mov     r8, #0 
        mov     r9, #0 
 
clear_loop_memtest:
        stmia   r1!, {r2-r9}
        subs    r0, r0, #(8 * 4)
        bne     clear_loop_memtest
 
#ifdef CONFIG_DEBUG_LL
       ldr   r0, STR_OK
       ldr   r1, SerBase
       bl    PrintWord
#endif
 
       mov       pc, r10           @ return            memtest return
 
badram:
#ifdef CONFIG_DEBUG_LL
       ldr   r0, STR_FAIL
       ldr   r1, SerBase
       bl    PrintWord
#endif
1:    b     1b          @ loop         有坏 // 死循环?
 
 
@ testmem.S: memory tester, test if there is RAM available at given location
@    //called by memtest
@ Copyright (C) 2001 Russell King (rmk@arm.linux.org.uk)
@
@ This version clobbers registers r1-r4, so be sure to store their contents
@ in a safe position. This function is not APCS compliant, so only use it
@ from assembly code.
@
@ r0 = address to test
@ returns r0 = 0 - ram present, r0 = 1 - no ram
@ clobbers r1 - r4
ENTRY(testram_nostack)
        ldmia   r0, {r1, r2}    @ store current value in r1 and r2
        mov     r3, #0x55       @ write 0x55 to first word
        mov     r4, #0xaa       @ 0xaa to second
        stmia   r0, {r3, r4}
        ldmia   r0, {r3, r4}    @ read it back
        teq     r3, #0x55       @ do the values match
        teqeq   r4, #0xaa
        bne     bad             @ oops, no
        mov     r3, #0xaa       @ write 0xaa to first word
        mov     r4, #0x55       @ 0x55 to second
        stmia   r0, {r3, r4}
        ldmia   r0, {r3, r4}    @ read it back
       teq     r3, #0xaa       @ do the values match
        teqeq   r4, #0x55
bad:    stmia   r0, {r1, r2}    @ in any case, restore old data
        moveq   r0, #0          @ ok - all values matched
        movne   r0, #1          @ no ram at this location
        mov     pc, lr
 
#endif @ CONFIG_BOOTUP_MEMTEST
 
 
 
@ Initialize UART
@
@ r0 = number of UART port
 
//SerBase = UART0_CTL_BASE = UART_CTL_BASE =0x50000000
// 0x5000_0000 UART channel 0 line control register
 
 
InitUART:
       ldr   r1, SerBase                 //0x5000_0000
       mov       r2, #0x0
       str   r2, [r1, #oUFCON]        // 清零 oUFCON
//#define oUFCON                0x08       /* R/W, UART FIFO control register */
// UFCON0 0x50000008 R/W UART channel 0 FIFO control register
Tx FIFO Trigger Level [7:6] : 00 : Empty
Rx FIFO Trigger Level [5:4]: 00 : 00 = 4-byte
Tx FIFO Reset [2] : 0 = Normal
Rx FIFO Reset [1] : 0 = Normal
FIFO Enable [0] 0 = Disable
       str   r2, [r1, #oUMCON]       // * (0x5000_0000 + oUMCON   (0x0C) ) = 0;
// UART modem control register
//  Auto Flow Control (AFC) [4] 0 = Disable
// Request to Send [0] = 0; // If AFC bit is enabled, this value will be ignored. In this case
the S3C2410A will control nRTS automatically. If AFC bit is disabled, nRTS must be controlled by software.   0 = 'H' level (Inactivate nRTS)    1 = 'L' level (Activate nRTS)
 
       mov       r2, #0x3         //
       str   r2, [r1, #oULCON] //* (0x5000_0000 + oULCON(0x00) ) = 0x3 =0011B
// UART line control register      8N1
// Infra-Red Mode [6] : 0 : 0 = Normal mode operation
// Parity Mode [5:3] : 000 = 0xx = No parity
// Number of Stop Bit [2] : 0 : 0 = One stop bit per frame
// Word Length [1:0] : 11 = 8-bit
 
       ldr   r2, =0x245            //
       str   r2, [r1, #oUCON]  
/// * (0x5000_0000 + oUCON(0x04) ) = 0x245 = 10 0100 0101 B
// UART control register
// Receive Mode [1:0] : 01 = Interrupt requ est or polling mode
//  Transmit Mode [3:2] 01 = Interrupt request or polling mode
// [4] reserved
// Loopback Mode [5]  0 = normal
// Rx Error Status Interrupt Enable [6]   1  = Generate receive error status interrupt.
// Rx Time Out Enable Interrupt [ 7] 0 =  Disable
// Rx Interrupt Type [8] = 0 Pulse
// Tx Interrupt Type [9] = 1 Level
// Clock Selection [10] = 0 =PCLK UBRDIVn = (int)(PCLK / (bps x 16) ) -1
// PCLK = ¼ FCLK =50 000 000 Hz
#define UART_BRD ((50000000 / (UART_BAUD_RATE * 16)) - 1)
// 115200 
       mov       r2, #UART_BRD
       str   r2, [r1, #oUBRDIV] // //* (0x5000_0000 + oUBRDIV(0x28)) = UART_BRD
// /* R/W, Baud rate divisor register */
 
       mov       r3, #100
       mov       r2, #0x0
1:    sub r3, r3, #0x1    //r3 = r3 –1 // 目的想延迟,结果延迟不料
       tst    r2, r3             // r2 r3 “bit AND”, if result =0 ,flagZ=1, else flagZ=0
       bne 1b          // if flagZ = 0 ,goto 1 // if r2 r3 ‘bit AND’ 结果 1 才会 goto1
 
#if 0
       mov       r2, #'U'
       str   r2, [r1, #oUTXHL]
 
1:    ldr   r3, [r1, #oUTRSTAT]
       and r3, r3, #UTRSTAT_TX_EMPTY
       tst    r3, #UTRSTAT_TX_EMPTY
       bne 1b  
 
       mov       r2, #'0'
       str   r2, [r1, #oUTXHL]
 
1:    ldr   r3, [r1, #oUTRSTAT]
       and r3, r3, #UTRSTAT_TX_EMPTY
       tst    r3, #UTRSTAT_TX_EMPTY
       bne 1b  
#endif
 
       mov       pc, lr                           //return
 
 
@
@ Exception handling functions
@    // 基本都是 infinite loop
HandleUndef:                            //handle undefined instructions
#ifdef CONFIG_DEBUG_LL
       mov       r12, r14
       ldr   r0, STR_UNDEF
       ldr   r1, SerBase
       bl    PrintWord
       bl    PrintFaultAddr
#endif
1:    b     1b          @ infinite loop
 
HandleSWI:                       //handle software interrupt exception
#ifdef CONFIG_DEBUG_LL
       mov       r12, r14
       ldr   r0, STR_SWI
       ldr   r1, SerBase
       bl    PrintWord
       bl    PrintFaultAddr
#endif
1:    b     1b          @ infinite loop
 
HandlePrefetchAbort:               //handle prefetch abort exception
#ifdef CONFIG_DEBUG_LL
       mov       r12, r14
       ldr   r0, STR_PREFETCH_ABORT
       ldr   r1, SerBase
       bl    PrintWord
       bl    PrintFaultAddr
#endif
1:    b     1b          @ infinite loop
 
HandleDataAbort:                     //handle data access memory abort
#ifdef CONFIG_DEBUG_LL
       mov       r12, r14
       ldr   r0, STR_DATA_ABORT
       ldr   r1, SerBase
       bl    PrintWord
       bl    PrintFaultAddr
#endif
1:    b     1b          @ infinite loop
 
HandleIRQ:                        //handle IRQ exception
#ifdef CONFIG_DEBUG_LL
       mov       r12, r14
       ldr   r0, STR_IRQ
       ldr   r1, SerBase
       bl    PrintWord
       bl    PrintFaultAddr
#endif
1:    b     1b          @ infinite loop
 
HandleFIQ:                        //handle FIRQ exception
#ifdef CONFIG_DEBUG_LL
       mov       r12, r14
       ldr   r0, STR_FIQ
       ldr   r1, SerBase
       bl    PrintWord
       bl    PrintFaultAddr
#endif
1:    b     1b          @ infinite loop
 
HandleNotUsed:
#ifdef CONFIG_DEBUG_LL
       mov       r12, r14
       ldr   r0, STR_NOT_USED
       ldr   r1, SerBase
       bl    PrintWord
       bl    PrintFaultAddr
#endif
1:    b     1b          @ infinite loop
 
 
@
@ Low Level Debug
@
#ifdef CONFIG_DEBUG_LL
 
@
@ PrintFaultAddr: Print falut address
@
@ r12: contains address of instruction + 4
@
PrintFaultAddr:
       mov       r0, r12                  @ Print address of instruction + 4
       ldr   r1, SerBase
       bl    PrintHexWord
       mrc p15, 0, r0, c6, c0, 0      @ Read fault virtual address
       ldr   r1, SerBase
       bl    PrintHexWord
       mov       pc, lr
 
@ PrintHexNibble : prints the least-significant nibble in R0 as a
@ hex digit
@   r0 contains nibble to write as Hex
@   r1 contains base of serial port
@   writes ro with XXX, modifies r0,r1,r2
@   TODO : write ro with XXX reg to error handling
@   Falls through to PrintChar
PrintHexNibble:
       adr r2, HEX_TO_ASCII_TABLE
       and r0, r0, #0xF
       ldr   r0, [r2, r0]      @ convert to ascii
       b     PrintChar
 
@ PrintChar : prints the character in R0
@   r0 contains the character
@   r1 contains base of serial port
@   writes ro with XXX, modifies r0,r1,r2
@   TODO : write ro with XXX reg to error handling
PrintChar:
TXBusy:
       ldr   r2, [r1, #oUTRSTAT]
       and r2, r2, #UTRSTAT_TX_EMPTY
       tst    r2, #UTRSTAT_TX_EMPTY
       beq TXBusy 
       str   r0, [r1, #oUTXHL]
       mov       pc, lr
 
@ PrintWord : prints the 4 characters in R0
@   r0 contains the binary word
@   r1 contains the base of the serial port
@   writes ro with XXX, modifies r0,r1,r2
@   TODO : write ro with XXX reg to error handling
PrintWord:
       mov       r3, r0
       mov       r4, lr
       bl    PrintChar
 
       mov       r0, r3, LSR #8              /* shift word right 8 bits */
       bl    PrintChar
 
       mov       r0, r3, LSR #16            /* shift word right 16 bits */
       bl    PrintChar
      
       mov       r0, r3, LSR #24            /* shift word right 24 bits */
       bl    PrintChar
 
       mov       r0, #'/r'
       bl    PrintChar
 
       mov       r0, #'/n'
       bl    PrintChar
 
       mov       pc, r4
 
@ PrintHexWord : prints the 4 bytes in R0 as 8 hex ascii characters
@   followed by a newline
@   r0 contains the binary word
@   r1 contains the base of the serial port
@   writes ro with XXX, modifies r0,r1,r2
@   TODO : write ro with XXX reg to error handling
PrintHexWord:
       mov       r4, lr
       mov       r3, r0
       mov       r0, r3, LSR #28
       bl    PrintHexNibble
       mov       r0, r3, LSR #24
       bl    PrintHexNibble
       mov       r0, r3, LSR #20
       bl    PrintHexNibble
       mov       r0, r3, LSR #16
       bl    PrintHexNibble
       mov       r0, r3, LSR #12
       bl    PrintHexNibble
       mov       r0, r3, LSR #8
       bl    PrintHexNibble
       mov       r0, r3, LSR #4
       bl    PrintHexNibble
       mov       r0, r3
       bl    PrintHexNibble
 
       mov       r0, #'/r'
       bl    PrintChar
 
       mov       r0, #'/n'
       bl    PrintChar
 
       mov       pc, r4
#endif
      
@
@ Data Area
@
@ Memory configuration values
.align 4
mem_cfg_val:
       .long      vBWSCON
       .long      vBANKCON0
       .long      vBANKCON1
       .long      vBANKCON2
       .long      vBANKCON3
       .long      vBANKCON4
       .long      vBANKCON5
       .long      vBANKCON6
       .long      vBANKCON7
       .long      vREFRESH
       .long      vBANKSIZE
       .long      vMRSRB6
       .long      vMRSRB7
 
@ Processor clock values
.align 4
clock_locktime:
       .long      vLOCKTIME
mpll_50mhz:
       .long      vMPLLCON_50
#ifdef CONFIG_S3C2410_MPORT1
mpll_100mhz:
       .long   vMPLLCON_100
#endif
mpll_200mhz:
       .long      vMPLLCON_200
clock_clkcon:
       .long      vCLKCON
clock_clkdivn:
       .long      vCLKDIVN
@ initial values for serial
uart_ulcon:
       .long      vULCON
uart_ucon:
       .long      vUCON
uart_ufcon:
       .long      vUFCON
uart_umcon:
       .long      vUMCON
@ inital values for GPIO
gpio_con_uart:
       .long      vGPHCON
gpio_up_uart:
       .long      vGPHUP
 
       .align     2
DW_STACK_START:
       .word     STACK_BASE+STACK_SIZE-4
 
#ifdef CONFIG_DEBUG_LL
       .align     2
HEX_TO_ASCII_TABLE:
       .ascii      "0123456789ABCDEF"
STR_STACK:
       .ascii      "STKP"
STR_UNDEF:
       .ascii      "UNDF"
STR_SWI:
       .ascii      "SWI "
STR_PREFETCH_ABORT:
       .ascii      "PABT"
STR_DATA_ABORT:
       .ascii      "DABT"
STR_IRQ:
       .ascii      "IRQ "
STR_FIQ:
       .ascii      "FIQ"
STR_NOT_USED:
       .ascii      "NUSD"
       .align 2
STR_OK:
       .ascii      "OK "
STR_FAIL:
       .ascii      "FAIL"
STR_CR:
       .ascii "/r/n"
#endif
 
.align 4
SerBase:
#if defined(CONFIG_SERIAL_UART0)
       .long UART0_CTL_BASE
#elif defined(CONFIG_SERIAL_UART1)
       .long UART1_CTL_BASE
#elif defined(CONFIG_SERIAL_UART2)
       .long UART2_CTL_BASE
#else
#error not defined base address of serial
#endif
 
#ifdef CONFIG_PM
.align 4
PMCTL0_ADDR:
       .long 0x4c00000c
PMCTL1_ADDR:
       .long 0x56000080
PMST_ADDR:
       .long 0x560000B4
PMSR0_ADDR:
       .long 0x560000B8
REFR_ADDR:
       .long 0x48000024
#endif
 
 
1.2 Boot Loader 的 stage2
 
正如前面所说, stage2 的代码通常用 C 语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性。但是与普通 C 语言应用程序不同的是,在编译和链接 boot loader 这样的程序时,我们不能使用 glibc 库中的任何支持函数。其原因是显而易见的。这就给我们带来一个问题,那就是从那里跳转进 main() 函数呢?直接把 main() 函数的起始地址作为整个 stage2 执行映像的入口点或许是最直接的想法。但是这样做有两个缺点: 1) 无法通过 main() 函数传递函数参数; 2) 无法处理 main() 函数返回的情况。一种更为巧妙的方法是利用 trampoline( 弹簧床 ) 的概念。也即,用汇编语言写一段 trampoline 小程序,并将这段 trampoline 小程序来作为 stage2 可执行映象的执行入口点。然后我们可以在 trampoline 汇编小程序中用 CPU 跳转指令跳入 main() 函数中去执行;而当 main() 函数返回时, CPU 执行路径显然再次回到我们的 trampoline 程序。简而言之,这种方法的思想就是:用这段 trampoline 小程序来作为 main() 函数的外部包裹 (external wrapper)
 
下面给出一个简单的 trampoline 程序示例 ( 来自 blob)
 
 
.text
 
.globl _trampoline
_trampoline:
       bl    main
       /* if main ever returns we just call it again */
       b     _trampoline
 
 
 
 
可以看出,当 main() 函数返回后,我们又用一条跳转指令重新执行 trampoline 程序――当然也就重新执行 main() 函数,这也就是 trampoline( 弹簧床 ) 一词的意思所在。
 
vivi 中采用如下的代码 实现 循环调用: main 返回之后 reset
@ get read to call C functions
       ldr   sp, DW_STACK_START      @ setup stack pointer
                     // STACK_BASE+STACK_SIZE-4
// STACK_BASE = (VIVI_PRIV_RAM_BASE - STACK_SIZE)
//STACK 从上往下用。   所以 STACK_START = STACK_BASE+STACK_SIZE-4
       mov       fp, #0                    @ no previous frame, so fp=0
       mov       a2, #0                   @ set argv to NULL
 
       bl    main                    @ call main // 如果正常,一去不复返的了
 
mov     pc, #FLASH_BASE   @ otherwise, reboot, //FLASH_BASE=ROM_BASE0 = 0x0
 
1.2.1 初始化本阶段要使用到的硬件设备
 
这通常包括:( 1 )初始化至少一个串口,以便和终端用户进行 I/O 输出信息;( 2 )初始化计时器等。   在初始化这些设备之前,也可以重新把 LED 灯点亮,以表明我们已经进入 main() 函数执行。   设备初始化完成后,可以输出一些打印信息,程序名字字符串、版本号等。
 
VIVI 而言,这些已经在 stage 1 中完成了。
 
1.2.2 检测系统的内存映射( memory map
 
所谓内存映射就是指在整个 4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。比如,在 SA-1100 CPU 中,从 0xC000,0000 开始的 512M 地址空间被用作系统的 RAM 地址空间,而在 Samsung S3C44B0X CPU 中,从 0x0c00,0000 0x1000,0000 之间的 64M 地址空间被用作系统的 RAM 地址空间。虽然 CPU 通常预留出一大段足够的地址空间给系统 RAM ,但是在搭建具体的嵌入式系统时却不一定会实现 CPU 预留的全部 RAM 地址空间。也就是说,具体的嵌入式系统往往只把 CPU 预留的全部 RAM 地址空间中的一部分映射到 RAM 单元上,而让剩下的那部分预留 RAM 地址空间处于未使用状态。由于上述这个事实,因此 Boot Loader stage2 必须在它想干点什么 ( 比如,将存储在 flash 上的内核映像读到 RAM 空间中 ) 之前检测整个系统的内存映射情况,也即它必须知道 CPU 预留的全部 RAM 地址空间中的哪些被真正映射到 RAM 地址单元,哪些是处于 "unused" 状态的。
 
VIVI CONFIG_BOOTUP_MEMTEST 中的 memtest 完成基本的 RAM 检测
 
1.2.3 加载内核映像和根文件系统映像
 
(1) 规划内存占用的布局
 
这里包括两个方面: (1) 内核映像所占用的内存范围;( 2 )根文件系统所占用的内存范围。在规划内存占用的布局时,主要考虑基地址和映像的大小两个方面。
 
对于内核映像,一般将其拷贝到从 (MEM_START 0x8000) 这个基地址开始的大约 1MB 大小的内存范围内 ( 嵌入式 Linux 的内核一般都不操过 1MB) 。为什么要把从 MEM_START MEM_START 0x8000 这段 32KB 大小的内存空出来呢?这是因为 Linux 内核要在这段内存中放置一些全局数据结构,如:启动参数和内核页表等信息。
 
而对于根文件系统映像,则一般将其拷贝到 MEM_START+0x0010,0000 开始的地方。如果用 Ramdisk 作为根文件系统映像,则其解压后的大小一般是 1MB
 
2 )从 Flash 上拷贝
 
由于像 ARM 这样的嵌入式 CPU 通常都是在统一的内存地址空间中寻址 Flash 等固态存储设备的,因此从 Flash 上读取数据与从 RAM 单元中读取数据并没有什么不同。用一个简单的循环就可以完成从 Flash 设备上拷贝映像的工作:
 
 
while(count) {
       *dest++ = *src++; /* they are all aligned with word boundary */
       count -= 4; /* byte number */
};
 
 
 
 
1.2.4 设置内核的启动参数
 
应该说,在将内核映像和根文件系统映像拷贝到 RAM 空间中后,就可以准备启动 Linux 内核了。但是在调用内核之前,应该作一步准备工作,即:设置 Linux 内核的启动参数。
 
Linux 2.4.x 以后的内核都期望以标记列表 (tagged list) 的形式来传递启动参数。启动参数标记列表以标记 ATAG_CORE 开始,以标记 ATAG_NONE 结束。每个标记由标识被传递参数的 tag_header 结构以及随后的参数值数据结构来组成。数据结构 tag tag_header 定义在 Linux 内核源码的 include/asm/setup.h 头文件中:
 
 
/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE   0x00000000
 
struct tag_header {
       u32 size; /* 注意,这里 size 是字数为单位的 */
       u32 tag;
};
……
struct tag {
       struct tag_header hdr;
       union {
              struct tag_core            core;
              struct tag_mem32       mem;
              struct tag_videotext    videotext;
              struct tag_ramdisk      ramdisk;
              struct tag_initrd    initrd;
              struct tag_serialnr       serialnr;
              struct tag_revision      revision;
              struct tag_videolfb      videolfb;
              struct tag_cmdline      cmdline;
 
              /*
               * Acorn specific
               */
              struct tag_acorn   acorn;
 
              /*
               * DC21285 specific
               */
              struct tag_memclk      memclk;
       } u;
};
 
 
 
 
在嵌入式 Linux 系统中,通常需要由 Boot Loader 设置的常见启动参数有: ATAG_CORE ATAG_MEM ATAG_CMDLINE ATAG_RAMDISK ATAG_INITRD 等。
 
比如,设置 ATAG_CORE 的代码如下:
 
 
params = (struct tag *)BOOT_PARAMS;
 
       params->hdr.tag = ATAG_CORE;
       params->hdr.size = tag_size(tag_core);
 
       params->u.core.flags = 0;
       params->u.core.pagesize = 0;
       params->u.core.rootdev = 0;
 
       params = tag_next(params);
 
 
 
 
其中, BOOT_PARAMS 表示内核启动参数在内存中的起始基地址,指针 params 是一个 struct tag 类型的指针。宏 tag_next() 将以指向当前标记的指针为参数,计算紧临当前标记的下一个标记的起始地址。注意,内核的根文件系统所在的设备 ID 就是在这里设置的。
 
下面是设置内存映射情况的示例代码:
 
 
for(i = 0; i < NUM_MEM_AREAS; i++) {
              if(memory_map[i].used) {
                     params->hdr.tag = ATAG_MEM;
                     params->hdr.size = tag_size(tag_mem32);
 
                     params->u.mem.start = memory_map[i].start;
                     params->u.mem.size = memory_map[i].size;
                    
                     params = tag_next(params);
              }
}
 
 
 
 
可以看出,在 memory_map []数组中,每一个有效的内存段都对应一个 ATAG_MEM 参数标记。
 
Linux 内核在启动时可以以命令行参数的形式来接收信息,利用这一点我们可以向内核提供那些内核不能自己检测的硬件参数信息,或者重载 (override) 内核自己检测到的信息。比如,我们用这样一个命令行参数字符串 "console=ttyS0,115200n8" 来通知内核以 ttyS0 作为控制台,且串口采用 "115200bps 、无奇偶校验、 8 位数据位 " 这样的设置。下面是一段设置调用内核命令行参数字符串的示例代码:
 
 
char *p;
 
       /* eat leading white space */
       for(p = commandline; *p == ' '; p++)
              ;
 
       /* skip non-existent command lines so the kernel will still
    * use its default command line.
        */
       if(*p == '/0')
              return;
 
       params->hdr.tag = ATAG_CMDLINE;
       params->hdr.size = (sizeof(struct tag_header) + strlen(p) + 1 + 4) >> 2;
 
       strcpy(params->u.cmdline.cmdline, p);
 
       params = tag_next(params);
 
 
 
 
请注意在上述代码中,设置 tag_header 的大小时,必须包括字符串的终止符 '/0' ,此外还要将字节数向上圆整 4 个字节,因为 tag_header 结构中的 size 成员表示的是字数。
 
下面是设置 ATAG_INITRD 的示例代码,它告诉内核在 RAM 中的什么地方可以找到 initrd 映象 ( 压缩格式 ) 以及它的大小:
 
 
       params->hdr.tag = ATAG_INITRD2;
       params->hdr.size = tag_size(tag_initrd);
      
       params->u.initrd.start = RAMDISK_RAM_BASE;
       params->u.initrd.size = INITRD_LEN;
      
       params = tag_next(params);
 
 
 
 
下面是设置 ATAG_RAMDISK 的示例代码,它告诉内核解压后的 Ramdisk 有多大(单位是 KB ):
 
 
params->hdr.tag = ATAG_RAMDISK;
params->hdr.size = tag_size(tag_ramdisk);
      
params->u.ramdisk.start = 0;
params->u.ramdisk.size = RAMDISK_SIZE; /* 请注意,单位是 KB */
params->u.ramdisk.flags = 1; /* automatically load ramdisk */
      
params = tag_next(params);
 
 
 
 
最后,设置 ATAG_NONE 标记,结束整个启动参数列表:
 
 
static void setup_end_tag(void)
{
       params->hdr.tag = ATAG_NONE;
       params->hdr.size = 0;
}
 
 
 
 
1.2.5 调用内核
 
Boot Loader 调用 Linux 内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到 MEM_START 0x8000 地址处。在跳转时,下列条件要满足:
 
1 CPU 寄存器的设置:
 
R0 0
 
 
R1 =机器类型 ID ;关于 Machine Type Number ,可以参见 linux/arch/arm/tools/mach-types
 
 
R2 =启动参数标记列表在 RAM 中起始基地址;
 
 
2 CPU 模式:
 
必须禁止中断( IRQs FIQs );
 
 
CPU 必须 SVC 模式;
 
 
3 Cache MMU 的设置:
 
MMU 必须关闭;
 
 
指令 Cache 可以打开也可以关闭;
 
 
数据 Cache 必须关闭;
如果用 C 语言,可以像下列示例代码这样来调用内核:
 
 
void (*theKernel)(int zero, int arch, u32 params_addr) = (void (*)(int, int, u32))KERNEL_RAM_BASE;
……
theKernel(0, ARCH_NUMBER, (u32) kernel_params_start);
 
 
 
 
注意, theKernel() 函数调用应该永远不返回的。如果这个调用返回,则说明出错。
 
 
Stage2在VIVI中的具体体现
Vivi/init/main.c
 
int main(int argc, char *argv[])
{
/* * Step 1:   NB: MMU. */
/* 硬件上按下某个 key 之后, clear_mem(USER_RAM_BASE(),USER_RAM_SIZE);
// clear_mem() 空的呢            */
       reset_handler();
 
/*  Step 2: Board 檬扁拳 钦聪促 . */
// init_time(), set_gpios(setting GPIO registers)
       ret = board_init();
 
/* Step 3:*   4G 府聪绢 (linear) 窍霸 *   MMU 难技夸 .    */
//    
       mem_map_init();
 
void mem_map_init(void)
{
#ifdef CONFIG_S3C2410_NAND_BOOT
         mem_map_nand_boot();
//mem_mapping_linear-> 4G 空间线性映射成 4K 1M 空间,并将其中的有效的 DRAM 做成 cacheable
#else
         mem_map_nor();
//       copy_vivi_to_ram();     // à memcpy(VIVI_RAM_BASE, VIVI_ROM_BASE, VIVI_RAM_SIZE); NOR 在这里将 vivi nor flash copy ram VIVI_RAM_BASE= (DRAM_BASE + DRAM_SIZE - VIVI_RAM_SIZE), VIVI_ROM_BASE=0x0, VIVI_RAM_SIZE=SZ_1M //oho, NAND stage 1 copy 128K 还多呢 ,1M 可是把 kernel 也给 copy DRAM 中了,可惜位置太高,在 VIVI_RAM 中,不在 boot_mem_base+KERNEL_OFFSET ,所以后面还会再 copy 一次的
//       mem_mapping_linear();-> 4G 空间线性映射成 4K 1M 空间,并将其中的 DRAM 做成 cacheable
//       nor_flash_mapping(); à FLASH_BASE FLASH_UNCACHED_BASE 都映射到 FLASH_BASE
         //0x0 à 0x0 ,cacheable , 0x1000_0000 -> 0x0 , uncacheable   SIZE FLASH_SIZE
//       nor_flash_remapping();->"Map flash virtual section to DRAM at VIVI_RAM_BASE;       
//                *(mmu_tlb_base + (VIVI_ROM_BASE >> 20)) =         (VIVI_RAM_BASE | MMU_SECDESC | MMU_CACHEABLE);         
         //0x0 à vivi_ram_base , cacheable SIZE 1M = MMU_SECTION_SIZE = VIVI RAM 的大小
#endif
         cache_clean_invalidate();
//clean and invalidate all cache lines
         tlb_invalidate();
//Invalidate all TLB entries
}
 
       mmu_init();
 
//       /* Invalidate caches */   /* Load page table pointer */ /* Write domain id (cp15_r3) */
         /* Set control register v4 */    /* Clear out 'unwanted' bits (then put them in if we need them) */
         /* Turn on what we want */   /* Fault checking enabled */
#ifdef CONFIG_CPU_D_CACHE_ON enable Data cache
#ifdef CONFIG_CPU_I_CACHE_ON  enable Instruction cache
         /* MMU enabled */
 
       / * Now, vivi is running on the ram. MMU is enabled.
        */
 
       /* Step 4: initialize the heap area*/
       ret = heap_init();
 
//malloc_init(): initialize heap area at (VIVI_RAM_BASE - HEAP_SIZE), size =         HEAP_SIZE (SZ_1M) 这一段为 virtual-phy linear // 除了 NOR 中的 0x0->VIVI_RAM_BASE, FLASH_UNCACED_BASE(0x1000_0000) à 0x0
 
       /* Step 5:   MTD 颇萍记 partition 沥焊啊       *. */
       ret = mtd_dev_init();
 
//       mtd_init(); 初始化不同的 MTD 根据 CONFIG_MTD_xxx, and CONFIG_S3C2410_AMD_BOOT, INTEL_BOOT
// 注意,这里的初始化 MTD partition 上面的文件系统没有上面关系
//intel_init() 其实就是 jedec_init
#ifdef CONFIG_MTD_CFI
         add_command(&flash_cmd);
#endif
 
       /* Step 6:.      */
       init_priv_data();
 
#ifdef CONFIG_PARSE_PRIV_DATA:
 vivi will be able to get MTD partition information from MTD.
 #else, vivi will use default parameters in the vivi's code.
init_priv_data(void)
{
         ret_def = get_default_priv_data();
//get_default_param_tlb()     
->cp smdk.c default_vivi_parameter to VIVI_PRIV_RAM_BASE+PARAMETER_TLB_OFFSET virt=phy in this area
//such as mach_type, media_type,boot_mem_base,baudrate, Xmodem, boot_delay
//get_default_linux_cmd())
->cp smdk.c char linux_cmd[] = "noinitrd root=/dev/bon/2 init=/linuxrc console=ttyS0";
to (VIVI_PRIV_RAM_BASE + LINUX_CMD_OFFSET)
//get_default_mtd_partition()
->smdk 中的 default_mtd_partitions to VIVI_PRIV_RAM_BASE + MTD_PART_OFFSET
         ret_saved = load_saved_priv_data();
// 将以前 saved param partition DRAM_BASE+xxx_OFFSET 复制到 VIVI_PRIV_RAM_BASE+xxx_OFFSET
         // 缺省用 saved,  上此 saved 参数
}
 
 
       /* Step 7: */
 
       misc();
//       add_command(&cpu_cmd);
/* add user command ; cpu_cmd 可以     Display cpu information and Change cpu clock and bus clock/n");
       init_builtin_cmds();
/* Register basic user commands */
 
       /* Step 8: */
       boot_or_vivi();
// timeout( 可以在 default_parameter boot_delay 中设定 , 可以用 boot_delay 命令设定,可以。。无关紧要 )
// key 按下就 vivi, 否则就 run_autoboot()
// à ”boot” à command_boot() à
//       media_type = get_param_value("media_type", &ret);//default = MT_S3C2410->NAND or NOR
// *vivi_params = (VIVI_PRIV_RAM_BASE + PARAMETER_TLB_OFFSET + 16); 取参数( def or saved
         kernel_part = get_mtd_partition("kernel"); //default=
// mtd_parts = (mtd_partition_t *)(VIVI_PRIV_RAM_BASE + MTD_PART_OFFSET + 16); 取参数 (def or saved)
         from = kernel_part->offset; // default smdk.c default_mtd_partitions offset=0x30000
         size = kernel_part->size; // default smdk.c default_mtd_partitions size=0xC0000
 
         boot_kernel(from, size, media_type); //def: boot_kernel(0x30000,0xC0000,NAND/NOR);
      
       return 0;
}
main() 到此结束
 
int boot_kernel(ulong from, size_t size, int media_type)
{
         int ret;
         ulong boot_mem_base;        /* base address of bootable memory ,vitual = phy add */
         ulong to;
         ulong mach_type;
 
         boot_mem_base = get_param_value("boot_mem_base", &ret);
//default: 0x3000_0000; vir = linear
         if (ret) {
                   printk("Can't get base address of bootable memory/n");
                   printk("Get default DRAM address. (0x%08lx/n", DRAM_BASE);
                   boot_mem_base = DRAM_BASE;
         }
 
         /* copy kerne image */
         to = boot_mem_base + LINUX_KERNEL_OFFSET ; //0x3000_8000 的说
//copy_vivi_to_ram 中已经把 kernel copy 到了 VIVI_RAM ,浪费的说
 
         ret = copy_kernel_img(to, (char *)from, size, media_type);
//                case MT_NOR_FLASH:
//                          memcpy((char *)to, (from + FLASH_UNCACHED_BASE), size);
// virtual add FLASH_UNCACHED_BASE 对应 phy add 0x0, 又从 flash 里面 copy 一次 kernel to
//                 case MT_SMC_S3C2410: // NAND 的规矩 copy
//                          ret = nand_read_ll((unsigned char *)dst, (unsigned long)src, (int)size);
 
         /* 检查是否是 compressed linux kernel image */
         if (*(ulong *)(to + 9*4) != LINUX_ZIMAGE_MAGIC) {
                   printk("Warning: this binary is not compressed linux kernel image/n");
                   printk("zImage magic = 0x%08lx/n", *(ulong *)(to + 9*4));
         } else {
                   printk("zImage magic = 0x%08lx/n", *(ulong *)(to + 9*4));
         }
 
         /* Setup linux parameters and linux command line */
         setup_linux_param(boot_mem_base + LINUX_PARAM_OFFSET); 0x3000_0100
// LINUX_PARAM_OFFSET     0x100
//       params->u1.s.page_size = LINUX_PAGE_SIZE ; = SZ_4K
//       params->u1.s.nr_pages = (DRAM_SIZE >> LINUX_PAGE_SHIFT); //SZ_4K =12bit =LINUX_PAGE_SHIFT
// 加点什么 for NOR?
//       /* set linux command line */
//       linux_cmd = get_linux_cmd_line();    -> char *linux_cmd_line = (char *)(VIVI_PRIV_RAM_BASE + LINUX_CMD_OFFSET + 8);
//       if (linux_cmd == NULL) {                 //default = smdk.c char linux_cmd[] = "noinitrd root=/dev/bon/2 init=/linuxrc console=ttyS0";
//                 printk("Wrong magic: could not found linux command line/n");
//       } else {
//                 memcpy(params->commandline, linux_cmd, strlen(linux_cmd) + 1);
//                 printk("linux command line is: /"%s/"/n", linux_cmd);
//       }
 
         /* Get machine type */
         mach_type = get_param_value("mach_type", &ret); //default :smdk2410.h  MACH_TYPE 193 
         printk("MACH_TYPE = %d/n", mach_type);
 
         /* Go Go Go */
         printk("NOW, Booting Linux....../n");
         call_linux(0, mach_type, to);
// void call_linux(long a0, long a1, long a2)
/* r0 = must contain a zero or else the kernel loops
 * r1 = architecture type
 * r2 = address to be executed */
//{
//       cache_clean_invalidate();
//       tlb_invalidate();
 
__asm__(
         "mov r0, %0/n"                      // a0
         "mov r1, %1/n"                      // a1
         "mov r2, %2/n"                      // a2
         "mov ip, #0/n"
         "mcr p15, 0, ip, c13, c0, 0/n"          /* zero PID */
         "mcr p15, 0, ip, c7, c7, 0/n" /* invalidate I,D caches */
         "mcr p15, 0, ip, c7, c10, 4/n"          /* drain write buffer */
         "mcr p15, 0, ip, c8, c7, 0/n" /* invalidate I,D TLBs */
         "mrc p15, 0, ip, c1, c0, 0/n" /* get control register */
         "bic   ip, ip, #0x0001/n"                   /* disable MMU */
         "mcr p15, 0, ip, c1, c0, 0/n" /* write control register */
         "mov pc, r2/n"
         "nop/n"
         "nop/n"
         : /* no outpus */
         : "r" (a0), "r" (a1), "r" (a2)
         );
}
         return 0;    
}
 
实现自己的 s3c2410 系统需要对 bootloader 修改的部分:
1. INTEL_CMD for MTD device DONE
2. 常量 FLASH_SIZE, ROM_SIZE, RAM_SIZE
3. linux_cmd[] != "noinitrd root=/dev/bon/2 init=/linuxrc console=ttyS0";
mem=16M root=/dev/mtdblock2 init=/bin/init
来自 89712 kernel CONFIG_CMDLINE="mem=16M root=/dev/mtdblock2 init=/bin/init" Arch/arm/setup.c
 
root = MAJOR MINOR MTD 参数等等有关
4. Linux parameter 的问题,如何传入,现有 vivi struct param_struct 传入,我们应该改点什么才对;在 CS89712 hermit-P2 中,用的是 struct tag 传入,可是没打算 boot; EP7211 hermit 中,打算 boot, 但是用的是 param_struct;
Hermit-P1 中用的也是 tag
我们的系统在 param 或者 tag 里面传出自己 flash/ram 的特性 ft
       params->u1.s.rootdev = MKDEV(MTD_MAJOR, MTD_MINOR); //(31,3)
 
附录 一 CDB89712 linux中的有关参数
其中 rootdev = (MTD_MAJOR,MTD_MINOR) = (31,3) /* i boot on /dev/mtd3 */ 
//    /dev/mtdblock3? (31,3)
The default location for rootfs image is mtd3, put your image at the mtd3 start address (or modify the 'MTD_MINOR' variable in tags.h).
mtd3: 00400000 00020000 "cdb89712 flash free partition" ( starting at 0x400000, put your rootfs image here)
 
但是,干吗 CONFIG_CMDLINE="mem=16M root=/dev/mtdblock2 init=/bin/init"
?? /dev/mtdblock2 (31,2)
 
呵呵,没法,只好自己试。
Struct mtd_partition {
       {      bootloader           // /dev/mtdblock0 31 0
       },{    kernel                  // /dev/mtdblock1 31 1
       },{    jffs2                     // /dev/mtdblock2 31 2
       }
}
 
/dev/mtdblock 1 2 都试试
2410 arch/arm/mach-s3c2410/smdk.c
 
附录二 需要对kernel(zImage)和jffs2解压么?
vivi boot_kernel 可没有 uncompress/decompress kernel 的说
 
附录三 关于 mtd partition bootloader kernel 中的差别
 
bootloader/ vivi 中,为了方便 用户从命令行输入 parameter 参数,所以增加了一个 param partition, ;在 bootloader 后期,会将有关参数 cp bootmem + LINUX_PARAM_OFFSET 处。
而在 kernel 中,通常就用 3 个标准的 partition bootloader/kernel/ rootfs(e.g.jffs2)
 
linux kernel 启动的入口处
 
MACHINE_START(SMDK2410, "Samsung-SMDK2410")
       // 大清早刚进来, MMU 还没启动呢
       BOOT_MEM(0x30000000, 0x48000000, 0xe8000000)
       //pram:0x30000000: physical start address of RAM
       //pio: 0x4800_0000 phy adrr of 8MB region containing IO for use with the debugging macros in arch/arm/kernel/debug-armv.S
       //vio: 0xe8000_0000 virtual address of the 8MB debugging region
 
       BOOT_PARAMS(0x30000100)
       //Physical address of struct param_struct or tag list, giving the kernel various parameters about its execution enviroment
       FIXUP(fixup_smdk)
       MAPIO(smdk_map_io)
       INITIRQ(s3c2410_init_irq)
MACHINE_END
 
附录 关于 do_map_probe
 
vivi 中,没有支持 jedec_probe //command_set001, 所以我们自己增加了 intel_flash.c etc
 
kernel 中,对 jedec_probe 的支持没有问题, command_set0001 也存在,所以我们可以完全不理会下层的实现,专心将 drivers/mtd/maps/cdb89712.c 改造成 smdk2410.c 就可以了 .
cdb89712.c 中有关将片内 SRAM BOOT_ROM 映射为 MTD 的部分不理会。
 
注意在 config.in 中增加相应的: CONFIG_MTD_SMDK2410
 
 
附录 param_struct tag_list 的对比
 
以下代码来自 testasecca boot-patch for 89712
cdb89712/kernel/arch/boot/compressed/misc.c
 
int hermit_linux_cmdfunc(int argc, char *argv[])        
{
        struct tag *tag = (struct tag *) LINUX_PARAM_ADDRESS;
 
        /* zero param block */
        memzero (tag, LINUX_PARAM_SIZE);
 
        /* set up core tag */
        tag->hdr.tag = ATAG_CORE;
        tag->hdr.size = tag_size(tag_core);
        tag->u.core.flags = 0;
        tag->u.core.pagesize = 0x1000;
        tag->u.core.rootdev = MKDEV(MTD_MAJOR, MTD_MINOR);
 
        /* 16 MB of SDRAM at 0xc0000000 */
        tag = tag_next(tag);
        tag->hdr.tag = ATAG_MEM;
        tag->hdr.size = tag_size(tag_mem32);
        tag->u.mem.size = DRAM1_SIZE >> 12;
        tag->u.mem.start = DRAM1_START;
 
        /* an initial ramdisk image in flash at 0x00700000 */
/*      tag = tag_next(tag);
        tag->hdr.tag = ATAG_INITRD;
        tag->hdr.size = tag_size(tag_initrd);
        tag->u.initrd.start = INITRD_LOAD_ADDRESS;
        tag->u.initrd.size = INITRD_SIZE; */
        /* the command line arguments */
 
/*      if (argc > 1) {
                tag = tag_next(tag);
                tag->hdr.tag = ATAG_CMDLINE;
                tag->hdr.size = (COMMAND_LINE_SIZE + 3 +
                         sizeof(struct tag_header)) >> 2;
 
                {
                        const unsigned char *src;
                        unsigned char *dst;
                        dst = tag->u.cmdline.cmdline;
                        memzero (dst, COMMAND_LINE_SIZE);
                        while (--argc > 0) {
                                src = *++argv;
                                hprintf ("Doing %s/n", src);
                                while (*src)
                                        *dst++ = *src++;
                                *dst++ = ' ';
                        }
                        *--dst = '/0';
                }
        }
*/
 
 
        tag = tag_next(tag);
        tag->hdr.tag = 0;
        tag->hdr.size = 0;
 
        /* branch to kernel image */
        __asm__ volatile (
        "       mov     r4, #0x00000000/n"      /* start of flash */
        "       add     r4, r4, #0x00020000/n" /* kernel offset in flash*/
        "       mov     r0, #0/n"               /* kernel sanity check */
        "       mov     r1, #107/n"             /* CDB89712 arch. number */
        "       mov     r2, #0/n"
        "       mov     r3, #0/n"
//        "       mov     pc, r4"                 /* go there! */
        );
 
        /* never get here */
        return 0;
}
 
以下来自VIVI/lib/boot_kernel.c
static void setup_linux_param(ulong param_base)
{
               struct param_struct *params = (struct param_struct *)param_base;
               char *linux_cmd;
 
               printk("Setup linux parameters at 0x%08lx/n", param_base);
               memset(params, 0, sizeof(struct param_struct));
 
               /* ²¿¿À¿Á ÇØÁà¾ß µÉ °Íµé.. ³­µð°¡ °æÇèÀûÀ¸·Î ´ëÃæ ÂïÀº °Í.. */
               params->u1.s.page_size = LINUX_PAGE_SIZE;
               params->u1.s.nr_pages = (DRAM_SIZE >> LINUX_PAGE_SHIFT);
#if 0
                    params->u1.s.page_size = LINUX_PAGE_SIZE;
                    params->u1.s.nr_pages = (dram_size >> LINUX_PAGE_SHIFT);
                    params->u1.s.ramdisk_size = 0;
                    params->u1.s.rootdev = rootdev;
                    params->u1.s.flags = 0;
 
                    /* TODO */
                    /* If use ramdisk */
                    /*
                    params->u1.s.initrd_start = ?;
                    params->u1.s.initrd_size = ?;
                    params->u1.s.rd_start = ?;
                    */
 
#endif
 
               /* set linux command line */
               linux_cmd = get_linux_cmd_line();
               if (linux_cmd == NULL) {
                               printk("Wrong magic: could not found linux command line/n");
               } else {
                               memcpy(params->commandline, linux_cmd, strlen(linux_cmd) + 1);
                               printk("linux command line is: /"%s/"/n", linux_cmd);
               }
}
 
附录 struct param_struct解释
asm-arm/setup.h
 
/* This is the old deprecated way to pass parameters to the kernel */
struct param_struct {
    union {
        struct {
            unsigned long page_size;            /* 0 */
            unsigned long nr_pages;             /* 4 */
            unsigned long ramdisk_size;         /* 8 */
            unsigned long flags;                /* 12 */
#define FLAG_READONLY   1
#define FLAG_RDLOAD     4
#define FLAG_RDPROMPT   8
            unsigned long rootdev;              /* 16 */
            unsigned long video_num_cols;       /* 20 */
            unsigned long video_num_rows;       /* 24 */
            unsigned long video_x;              /* 28 */
            unsigned long video_y;              /* 32 */
            unsigned long memc_control_reg;     /* 36 */
            unsigned char sounddefault;         /* 40 */
            unsigned char adfsdrives;           /* 41 */
            unsigned char bytes_per_char_h;     /* 42 */
            unsigned char bytes_per_char_v;     /* 43 */
            unsigned long pages_in_bank[4];     /* 44 */
            unsigned long pages_in_vram;        /* 60 */
            unsigned long initrd_start;         /* 64 */
            unsigned long initrd_size;          /* 68 */
            unsigned long rd_start;             /* 72 */
            unsigned long system_rev;           /* 76 */
            unsigned long system_serial_low;    /* 80 */
            unsigned long system_serial_high;   /* 84 */
            unsigned long mem_fclk_21285;       /* 88 */
        } s;
        char unused[256];
    } u1;
    union {
        char paths[8][128];
        struct {
            unsigned long magic;
            char n[1024 - sizeof(unsigned long)];
        } s;
    } u2;
    char commandline[COMMAND_LINE_SIZE];
};
 
Kernel initialisation parameters on ARM Linux
---------------------------------------------
 
The following document describes the kernel initialisation parameter
structure, otherwise known as 'struct param_struct' which is used
for most ARM Linux architectures.
 
This structure is used to pass initialisation parameters from the
kernel loader to the Linux kernel proper, and may be short lived
through the kernel initialisation process. As a general rule, it
should not be referenced outside of arch/arm/kernel/setup.c:setup_arch().
 
There are a lot of parameters listed in there, and they are described
below:
 
 page_size
 
   This parameter must be set to the page size of the machine, and
   will be checked by the kernel.
 
 nr_pages
 
   This is the total number of pages of memory in the system. If
   the memory is banked, then this should contain the total number
   of pages in the system.
 
   If the system contains separate VRAM, this value should not
   include this information.
 
 ramdisk_size
 
   This is now obsolete, and should not be used.
 
 flags
 
   Various kernel flags, including:
    bit 0 - 1 = mount root read only
    bit 1 - unused
    bit 2 - 0 = load ramdisk
    bit 3 - 0 = prompt for ramdisk
 
 rootdev
 
   major/minor number pair of device to mount as the root filesystem.
 
 video_num_cols
 video_num_rows
 
   These two together describe the character size of the dummy console,
   or VGA console character size. They should not be used for any other
   purpose.
 
   It's generally a good idea to set these to be either standard VGA, or
   the equivalent character size of your fbcon display. This then allows
   all the bootup messages to be displayed correctly.
 
 video_x
 video_y
 
   This describes the character position of cursor on VGA console, and
   is otherwise unused. (should not used for other console types, and
   should not be used for other purposes).
 
 memc_control_reg
 
   MEMC chip control register for Acorn Archimedes and Acorn A5000
   based machines. May be used differently by different architectures.
 
 sounddefault
 
   Default sound setting on Acorn machines. May be used differently by
   different architectures.
 
 adfsdrives
 
   Number of ADFS/MFM disks. May be used differently by different
   architectures.
 
 bytes_per_char_h
 bytes_per_char_v
 
   These are now obsolete, and should not be used.
 
 pages_in_bank[4]
 
   Number of pages in each bank of the systems memory (used for RiscPC).
   This is intended to be used on systems where the physical memory
   is non-contiguous from the processors point of view.
 
 pages_in_vram
 
   Number of pages in VRAM (used on Acorn RiscPC). This value may also
   be used by loaders if the size of the video RAM can't be obtained
   from the hardware.
 
 initrd_start
 initrd_size
 
   This describes the kernel virtual start address and size of the
   initial ramdisk.
 
 rd_start
 
   Start address in sectors of the ramdisk image on a floppy disk.
 
 system_rev
 
   system revision number.
 
 system_serial_low
 system_serial_high
 
   system 64-bit serial number
 
 mem_fclk_21285
 
   The speed of the external oscillator to the 21285 (footbridge),
   which control's the speed of the memory bus, timer & serial port.
   Depending upon the speed of the cpu its value can be between
   0-66 MHz. If no params are passed or a value of zero is passed,
   then a value of 50 Mhz is the default on 21285 architectures.
 
 paths[8][128]
 
   These are now obsolete, and should not be used.
 
 commandline
 
   Kernel command line parameters. Details can be found elsewhere.
 
 
附录四 MTD_PARTITION 从vivi bootloader和kernel两个角度
vivi bootlader NAND 下的 partition ,
vivi/arch/smdk.c
 
以下为缺省
#ifdef CONFIG_S3C2410_NAND_BOOT
mtd_partition_t default_mtd_partitions[] = {
         {
                   name:                   "vivi",
                   offset:                  0,
                   size:           0x00020000,       // 128k
                   flag:            0
         }, {
                   name:                   "param",
                   offset:                  0x00020000,       //128k
                   size:           0x00010000,       //64k
                   flag:            0
         }, {
                   name:                   "kernel",
                   offset:                  0x00030000,       //192k
                   size:           0x000C0000,       //768k
                   flag:            0
         }, {
                   name:                   "root",
                   offset:                  0x00100000,      
                   size:           0x00140000,
                   flag:            MF_BONFS       //simple block filesystem over NAND
                   // #define MF_BONFS                   0x00000004, priv_data.h
         }
};
 
s3c2410/kernel/drivers/mtd/nand/smc_smdk2410.c
 
#undef CONFIG_MTD_SMC_S3C2410_SMDK_PARTITION
 
/*
 * MTD structure for S3C2410 Development Board
 */
static struct mtd_info *s3c2410_mtd = NULL;
 
#ifdef CONFIG_MTD_SMC_S3C2410_SMDK_PARTITION
#include <linux/mtd/partitions.h>
 
static struct mtd_partition smc_partitions[] = {
         {
                   name:                   "kernel",
                   size:           0x000c0000,
//                 size:           0x00200000,
                   offset:                  0x0,
                   mask_flags:         MTD_WRITEABLE, /*force read-only */
         }, {
                   name:                   "root",
                   size:           0x00a00000,
                   offset:                  MTDPART_OFS_APPEND,
                   mask_flags:         MTD_WRITEABLE, /* force read-only */
         }
};
#endif
 
所以,在 vivi bootlader 中需要对 SMC 进行划分区 .
 
问题
除了通过 linux-parameter kernel 所在区域传入 boot-kernel 了之外, root.cramfs partition 分区信息怎么传入的呢?
答案
smdk.c cmd line? linux_cmd[] = "noinitrd root=/dev/bon/2 init=/linuxrc console=ttyS0"; // 呵呵,考,躲这里的说
这里用的虽然是 root=/dev/bon/2, 但是
悖论“ linux 的设备都是在 /dev/ 下,访问这些设备文件需要设备驱动程序支持,而访问设备文件才能取得设备号,才能加载驱动程序,那么第一设备驱动程序是怎么加载呢”
答案 ROOT_DEV, 不需要访问设备文件,直接制定设备号。
就是是直接用设备号 (bon,2), 它的 partition 信息在哪里?在 bootloader 中虽然用 vivi> bon part 0 192k 2M 做了分区, part2 就是 for root 的,但这些信息并不会传入 kernel, 除非。。??
 
cramfsck 打开 root.cramfs, 看看里面的 bon/2 的定义,是否有 init script 完成分区
Let's write root filesystem in SMC. The steps of this work are the same as those of above. But there is one caution.
vivi can't write the root image, which size is bigger than 1.2MB, on SMC. Because vivi is coded to use decided partition size of bon filesystem that is a kinds of layer for nand flash, although it controls all area of SMC. The decided size is approximately 1.2~1.3MB.
So, please use small size of root image when writing root filesystem on SMC. If you have finished this work well and rebooted target system, you can use the console of target system.
这就是为啥 vivi 首先用自己做 SMC partition, writing (1) vivi (2) kernel (3)root.cramfs<1.3M
然后用 Ztelent/minicom 下载 root_qtopia.cramfs, imagewrite image 写入 NAND ,大约有 40M
drivers/mtd/nand/bon.c
int __init part_setup(char *options)
{
    if (!options || !*options) return 0;
   PARTITION_OFFSET = simple_strtoul(options, &options, 0);
    if (*options == 'k' || *options == 'K') {
         PARTITION_OFFSET *= 1024;
    } else if (*options == 'm' || *options == 'M') {
         PARTITION_OFFSET *= 1024;
    }
    return 0;
}
__setup("nand_part_offset=", part_setup);
 
虽然 vivi/drivers/mtd/nand/bon.c 中有 comand_part 命令能够将在 vivi 命令行中输入的 bon 命令划分 bon, 但是这些 partition 参数可没看见被传入 kernel such as param_struct/a_tag
 
终于搞清楚了,
VIVI 中,在 bon 划分 partition 时,在 offset = mtd->size - mtd->erasesize 最后一块 eraseblock 中,写入了 BON_MAGIC (8BYTE), PARTITION INFO
vivi/drivers/mtd/bon.c
write_partition()
         memcpy(buf, bon_part_magic, 8);
         s = (unsigned int *)(buf+8);
         *s++ = num_part;
         for (i = 0; i < num_part; i++) {
                   *s++ = parts[i].offset;
                   *s++ = parts[i].size;
                   *s++ = parts[i].flag;
         }
kernel linux/drivers/mtd/nand/bon.c
read_partition_info()
         if (MTD_READ(mtd, offset, 512, &retlen, buf) < 0) {
             goto next_block;
         }
         if (strncmp(buf, BON_MAGIC, 8) == 0) break;
然后是:    s = (unsigned int *)(buf + 8);
   for(i=0;i < bon.num_part; i++) {
         char name[8];
//       int num_block;
         bon.parts[i].offset = *s++;
         bon.parts[i].size = *s++;
         bon.parts[i].flag = *s++;
}
附录五 关于__setup 在内核中的作用 ,参考linux/init/main.c
以下内容来自
jeppeter (member) from http:// linuxforum.net
 
 
你的这个问题 , 我从 google 上查找到了一些资料 , 再结合内核源代码 , 就在这里把这个问题说的清楚一点 .
首先 , 这里有一个简短的回答 ,
http://mail.nl.linux.org/kernelnewbies/2003-03/msg00043.html
 
从这上面的意思是这里会从 main.c 中的 checksetup 函数中运行 , 这个函数是这样的
 
static int __init checksetup(char *line)
{
struct kernel_param *p;
 
p = &__setup_start;
do {
int n = strlen(p->str);
if (!strncmp(line,p->str,n)) {
if (p->setup_func(line+n))
return 1;
}
p++;
} while (p < &__setup_end);
return 0;
}
 
 
 
这里的意思是从 __setup_start 开始处到 __setup_end 处中查找一个数据结构 , 这个数据结构中有 str setup_func 这两个数据成员变量 .
只要与这里面的 str 与输入的参数字符串相匹配 , 就会调用个这个字符串后面所指的内容 ,
对于你这里所说的 __setup("console=",console_setup); 就是你在启动 linux 内核的时候如果有这么一个参数输入 console=ttyS1, 那内核就会
把默认的 tty 定位为 ttyS1, 这个在 consol_setup 函数的字符串处理中完成 , 因为它最后是确定 prefered_console 的参数 .
 
 
那把这在这里实现这个的内容是这样的 ,
 
__setup() 是一个宏定义 , include/linux/init.h 这个文件中 .
struct kernel_param {
const char *str;
int (*setup_func)(char *);
};
 
extern struct kernel_param __setup_start, __setup_end;
 
#define __setup(str, fn) /
static char __setup_str_##fn[] __initdata = str; /
static struct kernel_param __setup_##fn __attribute__((unused)) __initsetup = { __setup_str_##fn, fn }
 
在这个情景中作了替换是这样的
 
static char __setup_str_console_setup[] = "console=";
static struct kernel_param __setup_console_setup = { __setup_str_console_setup, console_setup}
 
 
 
这样你还可能不是很清楚 , 那你就要参考 arch/i386/vmlinuz.lds 这个关于 ld 链接器的脚本文件有这样的一段
 
__setup_start = .;
.setup.init : { *(.setup.init) }
__setup_end = .;
 
 
这里的意思就是 __setup_start 是一个节的开始 , __setup_end 是一个节的结束 , 这个节的名称是 .setup,init,
这个你可以用 readelf -a 这个来看一下你的 vmlinux-2.4.20-8( 后面的数字与你的内核版本有关 ) 这个文件 ,
可以看到有一个叫 .setup.init 的节 ,__setup_start 就是指这个节的开始 , 那这个节中有什么内容呢 , 其实就是一个
数据结构 , 一个就是 str, 一个就是 setup_func, 与我前面的说法相一致 , 那具体是什么呢 , 就是一个在 .init.data 节中存储的
字符串 -----__initdata 是一个宏 , 就是 (__attribute__ ((__section__ (".data.init")))), 所以你可以 .data.init vmlinux-2.4.20-8 中的
在文件中的偏移量与加载的的虚拟地址偏移量相减就可以得到 ,
举个例子 , 所有的这些都是用 readelf od 命令得到的
我现在用的内核版本 , 它的 .setup.init 的节在 0x26dd60 的文件偏移处 .
[10] .data.init PROGBITS c0368040 268040 005d18 00 WA 0 0 32
[11] .setup.init PROGBITS c036dd60 26dd60 0001b0 00 WA 0 0 4
 
再查找 console_setup vmlinux-2.4.20-8 所被映射为内存地址 ,
840: c0355d40 343 FUNC LOCAL DEFAULT 9 console_setup
 
这就可以知道了它所在的位置 , 就是 0xc0355d40, 这就是它的虚拟映射地址
 
再用下面一条命令
od --address-radix=x -t x4 vmlinux-2.4.20-8 |grep -A 20 26dd60 |head -20 | grep c0355d40
可以得到
26de40 c036943b c0355d10 c0369447 c0355d40
 
很明显 , 这个函数的处理字符串在内存中的地址是 0xc0369447, 与前面得到的 .data.init 节在内存映射中的位置
0xc0368040 相减就是 0x1407, .data.init 在文件中的偏移量 0x268040 相加就得到 0x269447
这样用
od --address-radix=x -a vmlinux-2.4.20-8 |grep -A 2 269440
 
就可以得到下面的内容 ,
269440 b l i n k = nul c o n s o l e = nul
269450 r e s e r v e = nul nul nul nul nul nul nul nul
269460 ` dc4 6 @ ` dc4 6 @ c p u f r e q =
 
"console=" 这个值果真就在这里 .
 
( : 前面 od 的选项 --address-radix= 表示的是显示文件偏移量的格式 , 默认下是 o 就是八进制 , -t 表示显示文件二进制的形式
默认是 o6 就是八进制的 6 位长 , -a 表示显示的是字符串格式 .)
这是一点感受 , 与大家分享 , 希望大家提出宝贵意见 .
 
 

转载地址:https://blog.csdn.net/menuconfig/article/details/1754711 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:第三十六章 数词、日期和度量衡
下一篇:Perl、PHP、ASP、JSP技术比较

发表评论

最新留言

很好
[***.249.68.14]2022年05月23日 01时41分24秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

最新文章

SpringBoot入门(五)请求参数处理(二) 2019-12-30 11:47:55
SpringBoot入门(七)视图解析/Thymeleaf/拦截器/文件上传 2019-12-30 11:47:55
SpringBoot入门(六)数据响应与内容协商 2019-12-30 11:47:55
力扣题59螺旋矩阵II 2019-12-30 11:47:53
Redis入门(四)主从复制 2019-12-30 11:47:53
Redis入门(五)集群 2019-12-30 11:47:53
Redis入门(六)应用问题 2019-12-30 11:47:54
SpringBoot入门(一) 2019-12-30 11:47:54
Shell编程入门 2019-12-30 11:47:52
力扣题739每日温度 2019-12-30 11:47:52
Redis入门(二) 2019-12-30 11:47:52
力扣题844比较含退格的字符串 2019-12-30 11:47:52
力扣题977有序数组的平方 2019-12-30 11:47:52
力扣题904水果成篮 2019-12-30 11:47:52
Redis入门(三) 2019-12-30 11:47:53
Git入门 2019-12-30 11:47:50
Git和GitHub 2019-12-30 11:47:51
力扣题621任务调度器 2019-12-30 11:47:51
力扣题617合并二叉树 2019-12-30 11:47:51
力扣题647回文子串 2019-12-30 11:47:51