u-boot_smdkv210 分析

来源:互联网 发布:大麦户源码php 编辑:程序博客网 时间:2024/06/05 09:30

u-boot_smdkv210分析一:源码目录结构

1.board
本目录存放与已有开发板相关的文件。每种开发板有一个子目录,子目录仅存放与开发板相关的c文件和配置文件,不包含开发板CPU架构通用的实现文件。
每个目录下有如下文件(以samsung\smdkc110为例):
Makefile
config.mk
smdkc110.c      和板子相关的代码
flash.c         Flash操作代码
u-boot.lds      全局链接文件

2.common
实现u-boot命令行下支持的命令,每一条命令对应一个文件。例如bootm命令对应的是cmd_bootm.c。

3.cpu
与CPU架构相关目录,每一款支持的CPU或架构均在一个子目录下。
每个目录下有如下文件(以s5pc11x为例):
Makefile
config.mk
cpu.c           和处理器相关的代码
interrupts.c    中断处理代码
serial.c        串口初始化代码
start.s         全局开始启动代码。下一篇将分析该文件

4.disk
对磁盘的支持。

5.doc
文档目录。

6.drivers
设备驱动程序目录。比如串口、USB、mmc等。

7.fs
支持的文件系统。u-boot支持cramfs、ext2、fat、fdos、jffs2、reiserfs、ubifs、yaffs2文件系统。

8.include
使用的头文件均在改目录下,还有对各种硬件平台支持的汇编文件、系统配置文件和文件系统支持的文件。
该目录下configs目录有与开发板相关的配置文件。例如smdkv210single.h。
该目录下asm目录有与cpu体系结构相关的头文件,例如asm-arm目录下有arch-s5pc11x目录。

9.lib_xxx
与体系结构相关的库文件。如ARM相关的库放在lib_arm目录下。例如u-boot启动后首先打印的版本信息version_string[]就在board.c中定义。

10.net
与网络协议栈相关的代码,bootp协议、tftp协议、rarp协议和nfs文件系统等实现。

11.tools
生成u-boot工具,例如mkimage。

12.其他
examples等。
sd_fusing:烧写到sd卡的源文件和脚本文件。

 

 

 

u-boot_smdkv210分析二:启动代码start.s分析

1.链接文件
. = 0x00000000;

. = ALIGN(4);
.text      :
{
  cpu/s5pc11x/start.o    (.text)
  cpu/s5pc11x/s5pc110/cpu_init.o  (.text)

又链接文件可知,首先启动的是start.o,现在从start.s开始分析。

2.启动阶段
u-boot的启动分为两个阶段:
stage1: 系统上电后执行的汇编代码,完成系统初始化、代码搬移等操作。
stage2:搭建c环境,进入c语言执行。


3.start.s

#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED)    阶段启动相关配置
 .word 0x2000
 .word 0x0
 .word 0x0
 .word 0x0
#endif


.globl _start
_start: b reset                                 复位入口,此处使用b指令为相对调整,不依赖运行地址
 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

_undefined_instruction:
 .word undefined_instruction                   定义异常处理函数地址
_software_interrupt:
 .word software_interrupt
_prefetch_abort:
 .word prefetch_abort
_data_abort:
 .word data_abort
_not_used:
 .word not_used
_irq:
 .word irq
_fiq:
 .word fiq
_pad:
 .word 0x12345678 /* now 16*4=64 */           保证16字节对齐
.global _end_vect
_end_vect:

 .balignl 16,0xdeadbeef                       同样是保证16字节对齐,详见.align实验文章
/*
 *************************************************************************
 *
 * Startup Code (reset vector)                       启动代码(复位向量)此处仅进行重要的初始化操作,搬移代码和建立堆栈
 *
 * do important init only if we don't start from memory!
 * setup Memory and board specific bits prior to relocation.
 * relocate armboot to ram
 * setup stack
 *
 *************************************************************************
 */

_TEXT_BASE:
 .word TEXT_BASE                            TEST_BASE为根目录下Makefile传递进来的参数,具体为0xc3e00000

/*
 * Below variable is very important because we use MMU in U-Boot.
 * Without it, we cannot run code correctly before MMU is ON.
 * by scsuh.                                        下面的代码非常重要,因为我们使用了MMU,没有这段代码,在MMC开启前我们将不能正确的运行代码
 */
_TEXT_PHY_BASE:
 .word CFG_PHY_UBOOT_BASE                  由dram的物理地址0x20000000加上0x3e00000而得,即0x23e00000.这个地址为MMU开启前的物理地址

.globl _armboot_start
_armboot_start:
 .word _start                                复位地址,具体为0xc3e00010

/*
 * These are defined in the board-specific linker script.
 */
.globl _bss_start
_bss_start:
 .word __bss_start                           __bss_start在链接脚本文件中的bss段开始,_end在bss段结尾,用于清零bss端,这两个值在链接时才确定

.globl _bss_end
_bss_end:
 .word _end

#if defined(CONFIG_USE_IRQ)                         如果使用中断,定义中断栈地址
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
 .word 0x0badc0de

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
 .word 0x0badc0de
#endif

/*
 * the actual reset code
 */

reset:
 /*
  * set the cpu to SVC32 mode and IRQ & FIQ disable
  */
 @;mrs r0,cpsr
 @;bic r0,r0,#0x1f
 @;orr r0,r0,#0xd3
 @;msr cpsr,r0
 msr cpsr_c, #0xd3  @ I & F disable, Mode: 0x13 - SVC  1.进入svc模式,中断禁止


/*
 *************************************************************************
 *
 * CPU_init_critical registers
 *
 * setup important registers
 * setup memory timing
 *
 *************************************************************************
 */
         /*
         * we do sys-critical inits only at reboot, 仅在关键初始化时执行,而不是在从ram复位时执行
         * not when booting from ram!
         */
cpu_init_crit:

#ifndef CONFIG_EVT1
#if 0 
 bl v7_flush_dcache_all
#else
 bl disable_l2cache                  2.禁止l2cache

 mov r0, #0x0 @
 mov r1, #0x0 @ i 
 mov r3, #0x0
 mov r4, #0x0
lp1:
 mov r2, #0x0 @ j
lp2: 
 mov r3, r1, LSL #29  @ r3 = r1(i) <<29
 mov r4, r2, LSL #6  @ r4 = r2(j) <<6
 orr r4, r4, #0x2  @ r3 = (i<<29)|(j<<6)|(1<<1)
 orr r3, r3, r4
 mov r0, r3   @ r0 = r3
 bl CoInvalidateDCacheIndex                        3.清除数据缓存 8 * 1024
 add r2, #0x1  @ r2(j)++
 cmp r2, #1024  @ r2 < 1024
 bne lp2   @ jump to lp2
 add r1, #0x1  @ r1(i)++
 cmp r1, #8   @ r1(i) < 8
 bne lp1   @ jump to lp1

 bl set_l2cache_auxctrl                            4.锁定l2cache
 
 bl enable_l2cache                                 5.使能l2cache地址对齐
#endif
#endif
 
 bl disable_l2cache                                6.禁止l2cache

 bl set_l2cache_auxctrl_cycle                      7.锁定l2cache

 bl enable_l2cache                                 8.使能l2cache
 
       /*
        * Invalidate L1 I/D
        */
        mov r0, #0                  @ set up for MCR
        mcr p15, 0, r0, c8, c7, 0   @ invalidate TLBs      9.禁止TLB
        mcr p15, 0, r0, c7, c5, 0   @ invalidate icache    10.禁止指令缓存

       /*
        * disable MMU stuff and caches
        */
        mrc p15, 0, r0, c1, c0, 0
        bic r0, r0, #0x00002000     @ clear bits 13 (--V-)
        bic r0, r0, #0x00000007     @ clear bits 2:0 (-CAM)
        orr r0, r0, #0x00000002     @ set bit 1 (--A-) Align
        orr r0, r0, #0x00000800     @ set bit 12 (Z---) BTB
        mcr  p15, 0, r0, c1, c0, 0                         11.禁止MMC和cache


        /* Read booting information */
        ldr r0, =PRO_ID_BASE
        ldr r1, [r0,#OMR_OFFSET]
        bic r2, r1, #0xffffffc1                           12.读取启动信息

#ifdef CONFIG_VOGUES
 /* PS_HOLD(GPH0_0) set to output high */
 ldr r0, =ELFIN_GPIO_BASE
 ldr r1, =0x00000001
 str r1, [r0, #GPH0CON_OFFSET]

 ldr r1, =0x5500
 str r1, [r0, #GPH0PUD_OFFSET]

 ldr r1, =0x01
 str r1, [r0, #GPH0DAT_OFFSET]
#endif

 /* NAND BOOT */
 cmp r2, #0x0  @ 512B 4-cycle      13.识别各种启动方式,并将识别到的启动识别码写入R3中
 moveq r3, #BOOT_NAND

 cmp r2, #0x2  @ 2KB 5-cycle
 moveq r3, #BOOT_NAND

 cmp r2, #0x4  @ 4KB 5-cycle 8-bit ECC
 moveq r3, #BOOT_NAND

 cmp r2, #0x6  @ 4KB 5-cycle 16-bit ECC
 moveq r3, #BOOT_NAND

 cmp r2, #0x8  @ OneNAND Mux
 moveq r3, #BOOT_ONENAND

 /* SD/MMC BOOT */
 cmp     r2, #0xc
 moveq   r3, #BOOT_MMCSD 

 /* NOR BOOT */
 cmp     r2, #0x14
 moveq   r3, #BOOT_NOR 

#if 0 /* Android C110 BSP uses OneNAND booting! */
 /* For second device booting */
 /* OneNAND BOOTONG failed */
 cmp     r2, #0x8
 moveq   r3, #BOOT_SEC_DEV
#endif

 /* Uart BOOTONG failed */
 cmp     r2, #(0x1<<4)
 moveq   r3, #BOOT_SEC_DEV
 
 ldr r0, =INF_REG_BASE
 str r3, [r0, #INF_REG3_OFFSET]                                 14.将启动标识码写入INF_REG3中

 /*
  * Go setup Memory and board specific bits prior to relocation.    15.重定位前初始化存储器和板特殊位
  */

 ldr sp, =0xd0036000 /* end of sram dedicated to u-boot */      16.分配给u-boot的sram的结尾 sram为0xd0020000-d003ffff 分配大小为90k
 sub sp, sp, #12 /* set stack */
 mov fp, #0
 
 bl lowlevel_init /* go setup pll,mux,memory */              17.调用lowlevel_init函数初始化pll memory等与板子相关的内容 函数位于board目录下
 /* To hold max8698 output before releasing power on switch,
  * set PS_HOLD signal to high
  */
 ldr r0, =0xE010E81C  /* PS_HOLD_CONTROL register */            18.PS_HOLD输出高电平,PS_HOLD使能。PMIC相关
 ldr r1, =0x00005301  /* PS_HOLD output high */
 str r1, [r0]

 /* get ready to call C functions */
 ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer */     19.建立临时栈指针,内容为0x23e00000
 sub sp, sp, #12
 mov fp, #0   /* no previous frame, so fp=0 */

 /* when we already run in ram, we don't need to relocate U-Boot.
  * and actually, memory controller must be configured before U-Boot   20.如果程序已经在ram中运行,我们不需要重新定位u-boot。
  * is running in ram.                                                    实际上存储器一定在u-boot在ram中运行前被初始化了
  */
 ldr r0, =0xff000fff
 bic r1, pc, r0  /* r0 <- current base addr of code */  21.r1=当前PC
 ldr r2, _TEXT_BASE  /* r1 <- original base addr in ram */
 bic r2, r2, r0  /* r0 <- current base addr of code */  22.r2=定位后运行地址
 cmp     r1, r2                  /* compare r0, r1                  */
 beq     after_copy  /* r0 == r1 then skip flash copy   */  23.如果r1=r2,跳过复制部分

#if defined(CONFIG_EVT1)
 /* If BL1 was copied from SD/MMC CH2 */
 ldr r0, =0xD0037488
 ldr r1, [r0]                          24.取0xd0037488地址的值
 ldr r2, =0xEB200000
 cmp r1, r2
 beq     mmcsd_boot                        25.如果等于0xEB200000,跳转到mmcsd_boot
#endif

 ldr r0, =INF_REG_BASE                 26.读取存储的INF_REG3中的启动类型
 ldr r1, [r0, #INF_REG3_OFFSET]
 cmp r1, #BOOT_NAND  /* 0x0 => boot device is nand */
 beq nand_boot
 cmp r1, #BOOT_ONENAND /* 0x1 => boot device is onenand */
 beq onenand_boot
 cmp     r1, #BOOT_MMCSD
 beq     mmcsd_boot
 cmp     r1, #BOOT_NOR
 beq     nor_boot
 cmp     r1, #BOOT_SEC_DEV
 beq     mmcsd_boot

nand_boot:
 mov r0, #0x1000                       27.以下函数实现代码的搬移
 bl copy_from_nand
 b after_copy

onenand_boot:
 bl onenand_bl2_copy
 b after_copy

mmcsd_boot:
#if DELETE
 ldr     sp, _TEXT_PHY_BASE     
 sub     sp, sp, #12
 mov     fp, #0
#endif
 bl      movi_bl2_copy
 b       after_copy

nor_boot:
 bl      read_hword
 b       after_copy


after_copy:

#if defined(CONFIG_ENABLE_MMU)
enable_mmu:
 /* enable domain access */
 ldr r5, =0x0000ffff                 28.定义使能域的访问权限
 mcr p15, 0, r5, c3, c0, 0  @load domain access register

 /* Set the TTB register */
 ldr r0, _mmu_table_base
 ldr r1, =CFG_PHY_UBOOT_BASE
 ldr r2, =0xfff00000
 bic r0, r0, r2
 orr r1, r0, r1
 mcr p15, 0, r1, c2, c0, 0           29.将MMU启用前的的mmu_table_base转成sdram中的地址,并写入cp15的c2中

 /* Enable the MMU */
mmu_on:
 mrc p15, 0, r0, c1, c0, 0           30.启用mmu
 orr r0, r0, #1
 mcr p15, 0, r0, c1, c0, 0
 nop
 nop
 nop
 nop
#endif

skip_hw_init:
 /* Set up the stack          */
stack_setup:
#if defined(CONFIG_MEMORY_UPPER_CODE)
 ldr sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
#else
 ldr r0, _TEXT_BASE  /* upper 128 KiB: relocated uboot   */      0xc3e00000
 sub r0, r0, #CFG_MALLOC_LEN /* malloc area                      */      0x4000
 sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */      128
#if defined(CONFIG_USE_IRQ)
 sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)                2*4*1024
#endif
 sub sp, r0, #12  /* leave 3 words for abort-stack    */      31.为取址终止异常预留3个字空间

#endif

clear_bss:
 ldr r0, _bss_start  /* find start of bss segment        */
 ldr r1, _bss_end  /* stop here                        */
 mov  r2, #0x00000000  /* clear                            */

clbss_l:
 str r2, [r0]  /* clear loop...                    */      32.清除bss端内存
 add r0, r0, #4
 cmp r0, r1
 ble clbss_l
 
 ldr pc, _start_armboot                                                  33.第一阶段结束,进入c程序阶段

_start_armboot:
 .word start_armboot

#if defined(CONFIG_ENABLE_MMU)
_mmu_table_base:
 .word mmu_table
#endif

/*
 * copy U-Boot to SDRAM and jump to ram (from NAND or OneNAND)
 * r0: size to be compared
 * Load 1'st 2blocks to RAM because U-boot's size is larger than 1block(128k) size
 */
 .globl copy_from_nand
copy_from_nand:
 push {lr}  /* save return address */

 mov r9, r0
 
 mov r9, #0x100  /* Compare about 8KB */
 bl copy_uboot_to_ram                                 35.从nandflash中读取512k到0x23e00000中
 tst  r0, #0x0
 bne copy_failed

#if defined(CONFIG_EVT1)
 ldr r0, =0xd0020000                                   36.iram的起始地址
#else 
 ldr r0, =0xd0030000                                   37.iram的中间地址
#endif
 ldr r1, _TEXT_PHY_BASE /* 0x23e00000 */

#if !defined(CONFIG_SECURE_BOOT)
1: ldr r3, [r0], #4                                      38.取r0+4地址的值到r3中
 ldr r4, [r1], #4                                      39.取r1+4地址的值到r4中
 teq r3, r4
 bne compare_failed /* not matched */                 40.如果r3和r4不相等,比较失败
 subs r9, r9, #4
 bne 1b
#endif
 pop {pc}  /* all is OK */                   41.复制成功,返回

copy_failed:
 nop   /* copy from nand failed */
 b copy_failed

compare_failed:
 nop   /* compare failed */
 b compare_failed

/*
 * we assume that cache operation is done before. (eg. cleanup_before_linux())
 * actually, we don't need to do anything about cache if not use d-cache in U-Boot
 * So, in this function we clean only MMU. by scsuh
 *
 * void theLastJump(void *kernel, int arch_num, uint boot_params);
 */
#if defined(CONFIG_ENABLE_MMU)
 .globl theLastJump
theLastJump:
 mov r9, r0                    保存内核地址
 ldr r3, =0xfff00000
 ldr r4, _TEXT_PHY_BASE
 adr r5, phy_last_jump
 bic r5, r5, r3
 orr r5, r5, r4
 mov pc, r5
phy_last_jump:
 /*
  * disable MMU stuff             关闭MMU
  */
 mrc p15, 0, r0, c1, c0, 0
 bic r0, r0, #0x00002300 /* clear bits 13, 9:8 (--V- --RS) */
 bic r0, r0, #0x00000087 /* clear bits 7, 2:0 (B--- -CAM) */
 orr r0, r0, #0x00000002 /* set bit 2 (A) Align */
 orr r0, r0, #0x00001000 /* set bit 12 (I) I-Cache */
 mcr p15, 0, r0, c1, c0, 0

 mcr p15, 0, r0, c8, c7, 0 /* flush v4 TLB */

 mov r0, #0
 mov pc, r9                      跳转到内核地址
#endif
/*
 *************************************************************************
 *
 * Interrupt handling
 *
 *************************************************************************
 */
@
@ IRQ stack frame.
@
#define S_FRAME_SIZE 72

#define S_OLD_R0 68
#define S_PSR  64
#define S_PC  60
#define S_LR  56
#define S_SP  52

#define S_IP  48
#define S_FP  44
#define S_R10  40
#define S_R9  36
#define S_R8  32
#define S_R7  28
#define S_R6  24
#define S_R5  20
#define S_R4  16
#define S_R3  12
#define S_R2  8
#define S_R1  4
#define S_R0  0

#define MODE_SVC 0x13
#define I_BIT  0x80

/*                                                     定义异常时保存寄存器的宏
 * use bad_save_user_regs for abort/prefetch/undef/swi ...
 * use irq_save_user_regs / irq_restore_user_regs for IRQ/FIQ handling
 */

 .macro bad_save_user_regs
 sub sp, sp, #S_FRAME_SIZE  @ carve out a frame on current user stack
 stmia sp, {r0 - r12}   @ Save user registers (now in svc mode) r0-r12

 ldr r2, _armboot_start
 sub r2, r2, #(CFG_MALLOC_LEN)
 sub r2, r2, #(CFG_GBL_DATA_SIZE+8) @ set base 2 words into abort stack
 ldmia r2, {r2 - r3}   @ get values for "aborted" pc and cpsr (into parm regs)
 add r0, sp, #S_FRAME_SIZE  @ grab pointer to old stack

 add r5, sp, #S_SP
 mov r1, lr
 stmia r5, {r0 - r3}   @ save sp_SVC, lr_SVC, pc, cpsr
 mov r0, sp    @ save current stack into r0 (param register)
 .endm

 .macro irq_save_user_regs
 sub sp, sp, #S_FRAME_SIZE
 stmia sp, {r0 - r12}   @ Calling r0-r12
 add r8, sp, #S_PC   @ !!!! R8 NEEDS to be saved !!!! a reserved stack spot would be good.
 stmdb r8, {sp, lr}^   @ Calling SP, LR
 str lr, [r8, #0]   @ Save calling PC
 mrs r6, spsr
 str r6, [r8, #4]   @ Save CPSR
 str r0, [r8, #8]   @ Save OLD_R0
 mov r0, sp
 .endm

 .macro irq_restore_user_regs
 ldmia sp, {r0 - lr}^   @ Calling r0 - lr
 mov r0, r0
 ldr lr, [sp, #S_PC]   @ Get PC
 add sp, sp, #S_FRAME_SIZE
 subs pc, lr, #4   @ return & move spsr_svc into cpsr
 .endm

 .macro get_bad_stack
 ldr r13, _armboot_start  @ setup our mode stack (enter in banked mode)
 sub r13, r13, #(CFG_MALLOC_LEN) @ move past malloc pool
 sub r13, r13, #(CFG_GBL_DATA_SIZE+8) @ move to reserved a couple spots for abort stack

 str lr, [r13]   @ save caller lr in position 0 of saved stack
 mrs lr, spsr   @ get the spsr
 str lr, [r13, #4]   @ save spsr in position 1 of saved stack

 mov r13, #MODE_SVC   @ prepare SVC-Mode
 @ msr spsr_c, r13
 msr spsr, r13   @ switch modes, make sure moves will execute
 mov lr, pc    @ capture return pc
 movs pc, lr    @ jump to next instruction & switch modes.
 .endm

 .macro get_bad_stack_swi
 sub r13, r13, #4   @ space on current stack for scratch reg.
 str r0, [r13]   @ save R0's value.
 ldr r0, _armboot_start  @ get data regions start
 sub r0, r0, #(CFG_MALLOC_LEN) @ move past malloc pool
 sub r0, r0, #(CFG_GBL_DATA_SIZE+8) @ move past gbl and a couple spots for abort stack
 str lr, [r0]   @ save caller lr in position 0 of saved stack
 mrs r0, spsr   @ get the spsr
 str lr, [r0, #4]   @ save spsr in position 1 of saved stack
 ldr r0, [r13]   @ restore r0
 add r13, r13, #4   @ pop stack entry
 .endm

 .macro get_irq_stack   @ setup IRQ stack
 ldr sp, IRQ_STACK_START
 .endm

 .macro get_fiq_stack   @ setup FIQ stack
 ldr sp, FIQ_STACK_START
 .endm

/*
 * exception handlers                         异常处理句柄
 */
 .align 5
undefined_instruction:
 get_bad_stack
 bad_save_user_regs
 bl do_undefined_instruction

 .align 5
software_interrupt:
 get_bad_stack_swi
 bad_save_user_regs
 bl do_software_interrupt

 .align 5
prefetch_abort:
 get_bad_stack
 bad_save_user_regs
 bl do_prefetch_abort

 .align 5
data_abort:
 get_bad_stack
 bad_save_user_regs
 bl do_data_abort

 .align 5
not_used:
 get_bad_stack
 bad_save_user_regs
 bl do_not_used

#if defined(CONFIG_USE_IRQ)

 .align 5
irq:
 get_irq_stack
 irq_save_user_regs
 bl do_irq
 irq_restore_user_regs

 .align 5
fiq:
 get_fiq_stack
 /* someone ought to write a more effiction fiq_save_user_regs */
 irq_save_user_regs
 bl do_fiq
 irq_restore_user_regs

#else

 .align 5
irq:
 get_bad_stack
 bad_save_user_regs
 bl do_irq

 .align 5
fiq:
 get_bad_stack
 bad_save_user_regs
 bl do_fiq

#endif
 .align 5
.global arm_cache_flush
arm_cache_flush:
       mcr     p15, 0, r1, c7, c5, 0           @ invalidate I cache
       mov     pc, lr                          @ back to caller

/*
 *     v7_flush_dcache_all()
 *
 *     Flush the whole D-cache.
 *
 *     Corrupted registers: r0-r5, r7, r9-r11
 *
 *     - mm    - mm_struct describing address space
 */
       .align 5
.global v7_flush_dcache_all
v7_flush_dcache_all:

 ldr r0, =0xffffffff
 mrc p15, 1, r0, c0, c0, 1   @ Read CLIDR
 ands r3, r0, #0x7000000
 mov r3, r3, LSR #23         @ Cache level value (naturally aligned)
 beq  Finished
 mov r10, #0
Loop1:        
 add r2, r10, r10, LSR #1    @ Work out 3xcachelevel
 mov r1, r0, LSR r2          @ bottom 3 bits are the Ctype for this level
 and r1, r1, #7              @ get those 3 bits alone
 cmp r1, #2
 blt Skip                     @ no cache or only instruction cache at this level
 mcr p15, 2, r10, c0, c0, 0   @ write the Cache Size selection register
 mov r1, #0
 mcr p15, 0, r1, c7, c5, 4   @ PrefetchFlush to sync the change to the CacheSizeID reg
 mrc p15, 1, r1, c0, c0, 0   @ reads current Cache Size ID register
 and r2, r1, #0x7             @ extract the line length field
 add r2, r2, #4              @ add 4 for the line length offset (log2 16 bytes)
 ldr r4, =0x3FF
 ands r4, r4, r1, LSR #3     @ R4 is the max number on the way size (right aligned)
 clz r5, r4                  @ R5 is the bit position of the way size increment
 ldr r7, =0x00007FFF
 ands r7, r7, r1, LSR #13    @ R7 is the max number of the index size (right aligned)
Loop2:        
 mov r9, r4                       @ R9 working copy of the max way size (right aligned)
Loop3:        
 orr r11, r10, r9, LSL r5         @ factor in the way number and cache number into R11
 orr r11, r11, r7, LSL r2         @ factor in the index number
 mcr p15, 0, r11, c7, c6, 2   @ invalidate by set/way
 subs r9, r9, #1                  @ decrement the way number
 bge Loop3
 subs r7, r7, #1                  @ decrement the index
 bge Loop2
Skip:         
 add r10, r10, #2                 @ increment the cache number
 cmp r3, r10
 bgt Loop1
Finished:
 mov pc, lr
 
       .align  5
.global disable_l2cache
disable_l2cache:
 mrc     p15, 0, r0, c1, c0, 1
 bic     r0, r0, #(1<<1)
 mcr     p15, 0, r0, c1, c0, 1
 mov pc, lr


       .align  5
.global enable_l2cache
enable_l2cache:
 mrc     p15, 0, r0, c1, c0, 1
 orr     r0, r0, #(1<<1)
 mcr     p15, 0, r0, c1, c0, 1
 mov     pc, lr

       .align  5
.global set_l2cache_auxctrl
set_l2cache_auxctrl:
 mov r0, #0x0
 mcr     p15, 1, r0, c9, c0, 2
 mov     pc, lr

       .align  5
.global set_l2cache_auxctrl_cycle
set_l2cache_auxctrl_cycle:
 mrc  p15, 1, r0, c9, c0, 2                   值0010_0000_0010_0000_0001_1100_0111?
 bic  r0, r0, #(0x1<<29)
 bic  r0, r0, #(0x1<<21)
 bic  r0, r0, #(0x7<<6)
 bic  r0, r0, #(0x7<<0)
 mcr  p15, 1, r0, c9, c0, 2
 mov     pc,lr

 .align 5
CoInvalidateDCacheIndex:
 ;/* r0 = index */
 mcr     p15, 0, r0, c7, c6, 2
 mov     pc,lr


#if defined(CONFIG_INTEGRATOR) && defined(CONFIG_ARCH_CINTEGRATOR)
/* Use the IntegratorCP function from board/integratorcp/platform.S */
#elif defined(CONFIG_S5PC11X)
/* For future usage of S3C64XX*/
#else
 .align 5
.globl reset_cpu
reset_cpu:
 ldr r1, rstctl /* get addr for global reset reg */
 mov r3, #0x2 /* full reset pll+mpu */
 str r3, [r1] /* force reset */              复位CPU
 mov r0, r0
_loop_forever:
 b _loop_forever
rstctl:
 .word PM_RSTCTRL_WKUP

#endif

 

 

 

 

 

u-boot_smdkv210分析三:启动代码lowlevel.s分析

_TEXT_BASE:
 .word TEXT_BASE

 .globl lowlevel_init
lowlevel_init:
 push {lr}                                        1.lr入栈

 /* check reset status  */
 
 ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)   2.读取复位标志,如果是睡眠唤醒,跳过接下来的初始化
 ldr r1, [r0]
 bic r1, r1, #0xfff6ffff
 cmp r1, #0x10000
 beq wakeup_reset_pre
 cmp r1, #0x80000
 beq wakeup_reset_from_didle

 /* IO Retention release */
 ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)   3.复位引脚到默认功能?
 ldr r1, [r0]
 ldr r2, =IO_RET_REL
 orr r1, r1, r2
 str r1, [r0]

 /* Disable Watchdog */
 ldr r0, =ELFIN_WATCHDOG_BASE /* 0xE2700000 */    4.关闭看门狗定时器
 mov r1, #0
 str r1, [r0]

 /* SRAM(2MB) init for SMDKC110 */                   5.配置sram引脚,16位数据宽度,22位地址宽度
 /* GPJ1 SROM_ADDR_16to21 */
 ldr r0, =ELFIN_GPIO_BASE
 
 ldr r1, [r0, #GPJ1CON_OFFSET]
 bic r1, r1, #0xFFFFFF
 ldr r2, =0x444444
 orr r1, r1, r2
 str r1, [r0, #GPJ1CON_OFFSET]

 ldr r1, [r0, #GPJ1PUD_OFFSET]
 ldr r2, =0x3ff
 bic r1, r1, r2
 str r1, [r0, #GPJ1PUD_OFFSET]

 /* GPJ4 SROM_ADDR_16to21 */
 ldr r1, [r0, #GPJ4CON_OFFSET]
 bic r1, r1, #(0xf<<16)
 ldr r2, =(0x4<<16)
 orr r1, r1, r2
 str r1, [r0, #GPJ4CON_OFFSET]

 ldr r1, [r0, #GPJ4PUD_OFFSET]
 ldr r2, =(0x3<<8)
 bic r1, r1, r2
 str r1, [r0, #GPJ4PUD_OFFSET]


 /* CS0 - 16bit sram, enable nBE, Byte base address */
 ldr r0, =ELFIN_SROM_BASE /* 0xE8000000 */
 mov r1, #0x1
 str r1, [r0]

 /* PS_HOLD pin(GPH0_0) set to high */
 ldr r0, =(ELFIN_CLOCK_POWER_BASE + PS_HOLD_CONTROL_OFFSET)
 ldr r1, [r0]
 orr r1, r1, #0x300                                 6.设置PMIC控制引脚
 orr r1, r1, #0x1 
 str r1, [r0]

 /* when we already run in ram, we don't need to relocate U-Boot.
  * and actually, memory controller must be configured before U-Boot
  * is running in ram.
  */
 ldr r0, =0xff000fff                                 7.读取当前PC与链接地址进行比较,如果已经在ram中运行,跳过sdram初始化
 bic r1, pc, r0  /* r0 <- current base addr of code */
 ldr r2, _TEXT_BASE  /* r1 <- original base addr in ram */
 bic r2, r2, r0  /* r0 <- current base addr of code */
 cmp     r1, r2      /* compare r0, r1                  */
 beq     1f   /* r0 == r1 then skip sdram init   */

 /* init PMIC chip */
 bl PMIC_InitIp                                      8.PMIC初始化

 /* init system clock */
 bl system_clock_init                                9.系统时钟初始化

 /* Memory initialize */
 bl mem_ctrl_asm_init                                10.存储器控制器初始化
 
1:
 /* for UART */
 bl uart_asm_init                                    11.串口初始化

 bl tzpc_init                                        12.取消存储保护区域

#if defined(CONFIG_ONENAND)
 bl onenandcon_init                                  13.onenand初始化
#endif

#if defined(CONFIG_NAND)
 /* simple init for NAND */
 bl nand_asm_init                                    14.nand简单的初始化
#endif

 /* check reset status  */
 
 ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)
 ldr r1, [r0]
 bic r1, r1, #0xfffeffff
 cmp r1, #0x10000
 beq wakeup_reset_pre                                15.再次检查复位状态?

 /* ABB disable */
 ldr r0, =0xE010C300                                 16.关闭ABB?
 orr r1, r1, #(0x1<<23)
 str r1, [r0]

 /* Print 'K' */
 ldr r0, =ELFIN_UART_CONSOLE_BASE                    17.打印底层初始化完毕字符K
 ldr r1, =0x4b4b4b4b
 str r1, [r0, #UTXH_OFFSET]

 pop {pc}                                            18.返回到start.s

wakeup_reset_from_didle:
 /* Wait when APLL is locked */
 ldr r0, =ELFIN_CLOCK_POWER_BASE                     *深度睡眠需要等待APLL锁定
lockloop:
 ldr r1, [r0, #APLL_CON0_OFFSET]
 and r1, r1, #(1<<29)
 cmp r1, #(1<<29)
 bne  lockloop
 beq exit_wakeup

wakeup_reset_pre:
 mrc p15, 0, r1, c1, c0, 1 @Read CP15 Auxiliary control register
 and r1, r1, #0x80000000 @Check L2RD is disable or not
 cmp r1, #0x80000000  
 bne wakeup_reset  @if L2RD is not disable jump to wakeup_reset *如果L2RD没被禁用,跳转到唤醒复位
 
 bl disable_l2cache                         *禁用l2cache
 bl v7_flush_dcache_all                     *清空dcache
 /* L2 cache enable at sleep.S of kernel
  * bl enable_l2cache
  */

wakeup_reset:
 /* init system clock */
 bl system_clock_init                        *系统时钟初始化
 bl mem_ctrl_asm_init                        *存储器控制初始化
 bl tzpc_init                                *存储器保护禁用
#if defined(CONFIG_ONENAND)
 bl onenandcon_init                          *onenand初始化
#endif
#if defined(CONFIG_NAND)
 bl nand_asm_init                            *nand初始化
#endif

exit_wakeup:
 /*Load return address and jump to kernel*/
 ldr r0, =(INF_REG_BASE+INF_REG0_OFFSET)
 ldr r1, [r0] /* r1 = physical address of s5pc110_cpu_resume function*/

 mov pc, r1  /*Jump to kernel */         *r1存放唤醒的物理地址,返回到内核中
 nop
 nop

/*
 * system_clock_init: Initialize core clock and bus clock.
 * void system_clock_init(void)
 */
system_clock_init:

 ldr r0, =ELFIN_CLOCK_POWER_BASE @0xe0100000

 /* Set Mux to FIN */
 ldr r1, =0x0
 str r1, [r0, #CLK_SRC0_OFFSET]

 ldr r1, =APLL_LOCKTIME_VAL
 str r1, [r0, #APLL_LOCK_OFFSET]

 /* Disable PLL */
#if defined(CONFIG_CHECK_MPLL_LOCK)
retryloop:
#endif
 ldr r1, =0x0
 str r1, [r0, #APLL_CON0_OFFSET]
 ldr r1, =0x0
 str r1, [r0, #MPLL_CON_OFFSET]          *将APLL和MPLL禁用

 ldr r1, =0x0
 str r1, [r0, #MPLL_CON_OFFSET]          *将MPLL禁用

 ldr    r1, [r0, #CLK_DIV0_OFFSET]      *取DIV0值
 ldr r2, =CLK_DIV0_MASK
 bic r1, r1, r2                          *清零

 ldr r2, =CLK_DIV0_VAL
 orr r1, r1, r2
 str r1, [r0, #CLK_DIV0_OFFSET]          *写入DIV0值

 ldr r1, =APLL_VAL
 str r1, [r0, #APLL_CON0_OFFSET]         *分别设置APLL MPLL VPLL

 ldr r1, =MPLL_VAL
 str r1, [r0, #MPLL_CON_OFFSET]

 ldr r1, =VPLL_VAL
 str r1, [r0, #VPLL_CON_OFFSET]
#if defined(CONFIG_EVT1)
 ldr r1, =AFC_ON
 str r1, [r0, #APLL_CON1_OFFSET]         *是否开启AFC
#endif
 mov r1, #0x10000
1: subs r1, r1, #1                      *耗时等待稳定
 bne 1b

#if defined(CONFIG_CHECK_MPLL_LOCK)
 /* MPLL software workaround */
 ldr r1, [r0, #MPLL_CON_OFFSET]
 orr     r1, r1, #(1<<28)                *该位在文档中是保留位?
 str r1, [r0, #MPLL_CON_OFFSET]

 mov r1, #0x100
1: subs r1, r1, #1                      *耗时等待
 bne 1b

 ldr r1, [r0, #MPLL_CON_OFFSET]
 and r1, r1, #(1<<29)
 cmp r1, #(1<<29)
 bne  retryloop                       *未锁定继续重试

 /* H/W lock detect disable */
 ldr r1, [r0, #MPLL_CON_OFFSET]
 bic     r1, r1, #(1<<28)
 str r1, [r0, #MPLL_CON_OFFSET]
#endif

 ldr r1, [r0, #CLK_SRC0_OFFSET]
 ldr r2, =0x10001111
 orr r1, r1, r2
 str r1, [r0, #CLK_SRC0_OFFSET]          *复位时未接入PLL,现在将ACLK等接入PLL

#if defined(CONFIG_MCP_AC)

 /* CLK_SRC6[25:24] -> OneDRAM clock sel = MPLL */
 ldr r1, [r0, #CLK_SRC6_OFFSET]
 bic r1, r1, #(0x3<<24)
 orr r1, r1, #0x01000000
 str r1, [r0, #CLK_SRC6_OFFSET]          *DRAM选择SCLKMPLL

 /* CLK_DIV6[31:28] -> 4=1/5, 3=1/4(166MHZ@667MHz), 2=1/3 */
 ldr r1, [r0, #CLK_DIV6_OFFSET]
 bic r1, r1, #(0xF<<28)
 bic r1, r1, #(0x7<<12) @; ONENAND_RATIO: 0 *SCLK_ONENAND = MOUTFLASH / (ONENAND_RATIO + 1)
 orr r1, r1, #0x30000000                     *SCLK_ONEDRAM = MOUTONEDR / (ONEDRAM_RATIO + 1)
 str r1, [r0, #CLK_DIV6_OFFSET]

#elif defined (CONFIG_MCP_N)
 /* CLK_SRC6[25:24] -> OneDRAM clock sel = 00:SCLKA2M, 01:SCLKMPLL */
 ldr r1, [r0, #CLK_SRC6_OFFSET]
 mov r1, #0x00000000
 str r1, [r0, #CLK_SRC6_OFFSET]

 /* CLK_DIV6[31:28] -> 0=1/1 */
 ldr r1, [r0, #CLK_DIV6_OFFSET]
 mov r1, #0x00000000
 str r1, [r0, #CLK_DIV6_OFFSET]


#elif defined (CONFIG_MCP_H)

 /* CLK_SRC6[25:24] -> OneDRAM clock sel = 00:SCLKA2M, 01:SCLKMPLL */
 ldr r1, [r0, #CLK_SRC6_OFFSET]
 bic r1, r1, #(0x3<<24)
 orr r1, r1, #0x00000000
 str r1, [r0, #CLK_SRC6_OFFSET]

 /* CLK_DIV6[31:28] -> 4=1/5, 3=1/4(166MHZ@667MHz), 2=1/3 */
 ldr r1, [r0, #CLK_DIV6_OFFSET]
 bic r1, r1, #(0xF<<28)
 bic r1, r1, #(0x7<<12) @; ONENAND_RATIO: 0
 orr r1, r1, #0x00000000
 str r1, [r0, #CLK_DIV6_OFFSET] 

#elif defined (CONFIG_MCP_B) || defined (CONFIG_MCP_D)

 /* CLK_SRC6[25:24] -> OneDRAM clock sel = 00:SCLKA2M, 01:SCLKMPLL */
 ldr r1, [r0, #CLK_SRC6_OFFSET]
 bic r1, r1, #(0x3<<24)
 orr r1, r1, #0x01000000
 str r1, [r0, #CLK_SRC6_OFFSET]

 /* CLK_DIV6[31:28] -> 4=1/5, 3=1/4(166MHZ@667MHz), 2=1/3 */
 ldr r1, [r0, #CLK_DIV6_OFFSET]
 bic r1, r1, #(0xF<<28)
 bic r1, r1, #(0x7<<12) @; ONENAND_RATIO: 0
 orr r1, r1, #0x30000000
 str r1, [r0, #CLK_DIV6_OFFSET]

#elif defined (CONFIG_MCP_SINGLE)

 /* CLK_DIV6 */
 ldr r1, [r0, #CLK_DIV6_OFFSET]
 bic r1, r1, #(0x7<<12) @; ONENAND_RATIO: 0     *SCLK_ONENAND = MOUTFLASH / (ONENAND_RATIO + 1)
 str r1, [r0, #CLK_DIV6_OFFSET]                  *SCLK_ONEDRAM = MOUTONEDR / (ONEDRAM_RATIO + 1)

#endif 

 mov pc, lr


/*
 * uart_asm_init: Initialize UART in asm mode, 115200bps fixed.
 * void uart_asm_init(void)
 */
uart_asm_init:

 /* set GPIO(GPA) to enable UART */
 @ GPIO setting for UART
 ldr r0, =ELFIN_GPIO_BASE
 ldr r1, =0x22222222
 str    r1, [r0, #GPA0CON_OFFSET]

 ldr     r1, =0x2222
 str     r1, [r0, #GPA1CON_OFFSET]           *设置GPIO为UART

 // HP V210 use. SMDK not use.
#if defined(CONFIG_VOGUES)
 ldr    r1, =0x100
 str    r1, [r0, #GPC0CON_OFFSET]

 ldr    r1, =0x4
 str    r1, [r0, #GPC0DAT_OFFSET]
#endif

 ldr r0, =ELFIN_UART_CONSOLE_BASE  @0xEC000000
 mov r1, #0x0
 str r1, [r0, #UFCON_OFFSET]
 str r1, [r0, #UMCON_OFFSET]

 mov r1, #0x3
 str r1, [r0, #ULCON_OFFSET]

 ldr r1, =0x3c5
 str r1, [r0, #UCON_OFFSET]              *设置位数等

 ldr r1, =UART_UBRDIV_VAL
 str r1, [r0, #UBRDIV_OFFSET]            *设置波特率

 ldr r1, =UART_UDIVSLOT_VAL
 str r1, [r0, #UDIVSLOT_OFFSET]          *波特率小数校正

 ldr r1, =0x4f4f4f4f
 str r1, [r0, #UTXH_OFFSET]  @'O'    *输出u-boot第一个字符‘O’

 mov pc, lr

/*
 * Nand Interface Init for SMDKC110
 */
nand_asm_init:

 /* Setting GPIO for NAND */
 /* This setting is NAND initialze code at booting time in iROM. */

 ldr r0, =ELFIN_GPIO_BASE
 
 ldr r1, [r0, #MP01CON_OFFSET]
 bic r1, r1, #(0xf<<8)
 orr r1, r1, #(0x3<<8)                   *0011 = NFCSn[0]  设置nand flash的选通引脚
 str r1, [r0, #MP01CON_OFFSET]
 
*00 = Pull-up/ down disabled
*01 = Pull-down enabled
*10 = Pull-up enabled
*11 = Reserved

 ldr r1, [r0, #MP01PUD_OFFSET]
 bic r1, r1, #(0x3<<4)                   *取消上拉下拉
 str r1, [r0, #MP01PUD_OFFSET]

 ldr r1, [r0, #MP03CON_OFFSET]
 bic r1, r1, #0xFFFFFF                   *全部设置为nf的功能,CLE ALE WE RE RnB0~3
 ldr r2, =0x22222222
 orr r1, r1, r2
 str r1, [r0, #MP03CON_OFFSET]

 ldr r1, [r0, #MP03PUD_OFFSET]
 ldr r2, =0x3fff
 bic r1, r1, r2
 str r1, [r0, #MP03PUD_OFFSET]           *取消上拉下拉

 ldr r0, =ELFIN_NAND_BASE

 ldr r1, [r0, #NFCONF_OFFSET]
 ldr r2, =0x777F
 bic r1, r1, r2
 ldr r2, =NFCONF_VAL                     *Duration =  HCLK x TACLS  Duration = HCLK x ( TWRPH0 + 1 ) Duration =  HCLK x ( TWRPH1 + 1 )
 orr r1, r1, r2                                               7                          7                                   7
 str r1, [r0, #NFCONF_OFFSET]            *0 = SLC NAND Flash    1 = 512 Bytes/page     1 = 4 address cycle

 ldr r1, [r0, #NFCONT_OFFSET]
 ldr r2, =0x707C7
 bic r1, r1, r2
 ldr r2, =NFCONT_VAL                     *ECC  LOCK   INTERRUPT 等设置
 orr r1, r1, r2
 str r1, [r0, #NFCONT_OFFSET]

 ldr r1, [r0, #NFCONF_OFFSET]
 orr r1, r1, #0x70
 orr r1, r1, #0x7700
 str     r1, [r0, #NFCONF_OFFSET]        *前面已经设置了7770了啊?

 ldr r1, [r0, #NFCONT_OFFSET]
 orr r1, r1, #0x03
 str     r1, [r0, #NFCONT_OFFSET]        *使能NAND控制器

 mov pc, lr

/*
 * Setting TZPC[TrustZone Protection Controller]
 */
tzpc_init:

 ldr r0, =ELFIN_TZPC0_BASE                   无保护区域
  mov r1, #0x0
  str r1, [r0]
  mov r1, #0xff
  str r1, [r0, #TZPC_DECPROT0SET_OFFSET]      取消保护区域译码
  str r1, [r0, #TZPC_DECPROT1SET_OFFSET]
 str r1, [r0, #TZPC_DECPROT2SET_OFFSET]  

  ldr  r0, =ELFIN_TZPC1_BASE
  str r1, [r0, #TZPC_DECPROT0SET_OFFSET]
  str r1, [r0, #TZPC_DECPROT1SET_OFFSET]
 str r1, [r0, #TZPC_DECPROT2SET_OFFSET]  

  ldr r0, =ELFIN_TZPC2_BASE
  str r1, [r0, #TZPC_DECPROT0SET_OFFSET]
  str r1, [r0, #TZPC_DECPROT1SET_OFFSET]
 str r1, [r0, #TZPC_DECPROT2SET_OFFSET]
 str r1, [r0, #TZPC_DECPROT3SET_OFFSET]

  ldr r0, =ELFIN_TZPC3_BASE
  str r1, [r0, #TZPC_DECPROT0SET_OFFSET]
  str r1, [r0, #TZPC_DECPROT1SET_OFFSET]
 str r1, [r0, #TZPC_DECPROT2SET_OFFSET]  

  mov pc, lr

/*
 * OneNAND Interface Init                       *未使用,不分析
 */
onenandcon_init:

 @; GPIO setting for OneNAND
 ldr r0, =ELFIN_GPIO_BASE @0xE0200000
 ldr r1, [r0, #MP01CON_OFFSET]
 orr r1, r1, #0x00550000
 str r1, [r0, #MP01CON_OFFSET]

 ldr r1, [r0, #MP03CON_OFFSET]
 orr r1, r1, #0x0550
 orr r1, r1, #0x00550000
 str r1, [r0, #MP03CON_OFFSET]

 ldr r1, =0xFFFF
 str r1, [r0, #MP01DRV_SR_OFFSET]
 str r1, [r0, #MP03DRV_SR_OFFSET]
 str r1, [r0, #MP06DRV_SR_OFFSET]
 str r1, [r0, #MP07DRV_SR_OFFSET]

wait_orwb:
 @; Read ONENAND_IF_STATUS
 ldr r0, =ELFIN_ONENANDCON_BASE @; 0xB0600000
 ldr r1, [r0, #ONENAND_IF_STATUS_OFFSET]
 bic r1, r1, #0xFFFFFFFE
 cmp r1, #0x0

 @; ORWB != 0x0
 bne wait_orwb

 @; write new configuration to onenand system configuration1 register
 ldr r1, =0xF006   @; Sync.
 ldr r2, =(ELFIN_ONENAND_BASE+0x1E442) @; 0x1E442(REG_SYS_CONF1)
 strh r1, [r2]

 @; read one dummy halfword
 ldrh r1, [r2]
 ldrh r1, [r2]

 @; write new configuration to ONENAND_IF_CTRL
 ldr r0, =ELFIN_ONENANDCON_BASE @; 0xB0600000
 @;ldr r1, =0x2F006   @; ONENAND_IF_CTRL_REG_VAL (GCE off)
 ldr r1, =0x402F006   @; ONENAND_IF_CTRL_REG_VAL (GCE on)
 str r1, [r0, #ONENAND_IF_CTRL_OFFSET]

 mov pc, lr


#ifdef CONFIG_ENABLE_MMU

 #ifdef CONFIG_MCP_SINGLE
/*
 * MMU Table for SMDKC110
 * 0x0000_0000 -- 0xBFFF_FFFF => Not Allowed
 * 0xB000_0000 -- 0xB7FF_FFFF => A:0xB000_0000 -- 0xB7FF_FFFF
 * 0xC000_0000 -- 0xC7FF_FFFF => A:0x3000_0000 -- 0x37FF_FFFF
 * 0xC800_0000 -- 0xDFFF_FFFF => Not Allowed
 * 0xE000_0000 -- 0xFFFF_FFFF => A:0xE000_0000 -- 0XFFFF_FFFF
 */

 /* form a first-level section entry */
.macro FL_SECTION_ENTRY base,ap,d,c,b   // 定义生成描述符的宏,分别控制基地址/访问权限/域/cache/buffer。使用的是1M的section
 .word (\base << 20) | (\ap << 10) | \
       (\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
.endm
.section .mmudata, "a"                       // 定义MMU数据段,在lds文件里面用到了,“a”表示这是一个需要鉴权的段
 .align 14                                           // 一级页表必须14位对齐,因为C2的[31:14]为TLB基地址,虚地址的[31:20]为索引表的[13:2]。
 // the following alignment creates the mmu table at address 0x4000.
 .globl mmu_table
mmu_table:
 .set __base,0
 // Access for iRAM
 .rept 0x100
 FL_SECTION_ENTRY __base,3,0,0,0
 .set __base,__base+1
 .endr

 // Not Allowed
 .rept 0x200 - 0x100
 .word 0x00000000
 .endr

 .set __base,0x200                              // __base的值可视为物理地址的高12位
 // should be accessed
 .rept 0x600 - 0x200                           // rept后的值可视为虚拟地址的高12位
 FL_SECTION_ENTRY __base,3,0,1,1
 .set __base,__base+1
 .endr

 .rept 0x800 - 0x600
 .word 0x00000000
 .endr

 .set __base,0x800
 // should be accessed
 .rept 0xb00 - 0x800
 FL_SECTION_ENTRY __base,3,0,0,0
 .set __base,__base+1
 .endr

/* .rept 0xc00 - 0xb00
 .word 0x00000000
 .endr */

 .set __base,0xB00
 .rept 0xc00 - 0xb00
 FL_SECTION_ENTRY __base,3,0,0,0
 .set __base,__base+1
 .endr

 .set __base,0x200
 // 256MB for SDRAM with cacheable
 .rept 0xD00 - 0xC00
 FL_SECTION_ENTRY __base,3,0,1,1         *0xC000_0000映射到0x2000_0000
 .set __base,__base+1
 .endr

 // access is not allowed.
 @.rept 0xD00 - 0xC80
 @.word 0x00000000
 @.endr

 .set __base,0xD00
 // 1:1 mapping for debugging with non-cacheable
 .rept 0x1000 - 0xD00
 FL_SECTION_ENTRY __base,3,0,0,0
 .set __base,__base+1
 .endr 
 
 #else // CONFIG_MCP_AC, CONFIG_MCP_H, CONFIG_MCP_B

/*
 * MMU Table for SMDKC110
 * 0x0000_0000 -- 0xBFFF_FFFF => Not Allowed
 * 0xB000_0000 -- 0xB7FF_FFFF => A:0xB000_0000 -- 0xB7FF_FFFF
 * 0xC000_0000 -- 0xC7FF_FFFF => A:0x3000_0000 -- 0x37FF_FFFF
 * 0xC800_0000 -- 0xDFFF_FFFF => Not Allowed
 * 0xE000_0000 -- 0xFFFF_FFFF => A:0xE000_0000 -- 0XFFFF_FFFF
 */

 /* form a first-level section entry */
.macro FL_SECTION_ENTRY base,ap,d,c,b
 .word (\base << 20) | (\ap << 10) | \
       (\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
.endm
.section .mmudata, "a"
 .align 14
 // the following alignment creates the mmu table at address 0x4000.
 .globl mmu_table
mmu_table:
 .set __base,0
 // Access for iRAM
 .rept 0x100
 FL_SECTION_ENTRY __base,3,0,0,0
 .set __base,__base+1
 .endr

 // Not Allowed
 .rept 0x300 - 0x100
 .word 0x00000000
 .endr

#if defined(CONFIG_MCP_N)
 .set __base,0x300
 // should be accessed
 .rept 0x400 - 0x300
 FL_SECTION_ENTRY __base,3,0,1,1
 .set __base,__base+1
 .endr
#else
 .set __base,0x300
 // should be accessed
 .rept 0x350 - 0x300
 FL_SECTION_ENTRY __base,3,0,1,1
 .set __base,__base+1
 .endr

 // Not Allowed
 .rept 0x400 - 0x350
 .word 0x00000000
 .endr
#endif

 .set __base,0x400
 // should be accessed
 .rept 0x500 - 0x400
 FL_SECTION_ENTRY __base,3,0,1,1
 .set __base,__base+1
 .endr

 .rept 0x800 - 0x500
 .word 0x00000000
 .endr

 .set __base,0x800
 // should be accessed
 .rept 0xb00 - 0x800
 FL_SECTION_ENTRY __base,3,0,0,0
 .set __base,__base+1
 .endr

 .set __base,0xB00
 .rept 0xc00 - 0xb00
 FL_SECTION_ENTRY __base,3,0,0,0
 .set __base,__base+1
 .endr

#if defined(CONFIG_MCP_N)
 .set __base,0x300
 // 256MB for SDRAM with cacheable
 .rept 0xD00 - 0xC00
 FL_SECTION_ENTRY __base,3,0,1,1
 .set __base,__base+1
 .endr
#else
 .set __base,0x300
 // 80MB for SDRAM with cacheable
 .rept 0xC50 - 0xC00
 FL_SECTION_ENTRY __base,3,0,1,1
 .set __base,__base+1
 .endr

 // Not Allowed
 .rept 0xD00 - 0xC50
 .word 0x00000000
 .endr
#endif

 .set __base,0xD00
 // 1:1 mapping for debugging with non-cacheable
 .rept 0x1000 - 0xD00
 FL_SECTION_ENTRY __base,3,0,0,0
 .set __base,__base+1
 .endr
 #endif
#endif

 

 

 

 

u-boot_smdkv210分析四:启动代码汇编部分总结

1.进入管理模式
2.禁用cache
3.清空cache
4.使能cache
5.禁止TLB
6.禁止指令cache
7.禁止MMU和cache
8.读取启动信息
9.将启动信息分析后写入reg中
10.进入lowlevel.s
11.读取复位标志,如果是唤醒跳过硬件初始化
12.关闭看门狗
13.配置sram引脚
14.配置PMIC引脚
15.判断是否在ram中运行,是的话跳过16-18
16.PMIC初始化
17.系统时钟初始化
18.dram控制器初始化cpu_init.s
19.串口汇编初始化,打印'O'
20.取消存储保护区域
21.onenand初始化
22.nand初始化
23.关闭ABB
24.打印'K'后退出lowlevel
25.设置PS_HOLD输出高电平
26.读取reg中保存的启动类型
27.按启动类型将代码复制到ram中
28.定义MMU中的域访问权限
29.将mmu_table转成dram的物理地址
30.启用mmu
31.设置用户堆栈指针
32.清空bss段
33.通过直接跳转方式,进入dram运行bl2段代码

 

 

 

 

 

u-boot_smdkv210分析五:硬件启动过程

1.上电启动

位于地址空间0xD0000000的是irom和iram。
由于地址0为Mirrored region depending on the boot mode. 所以启动后直接进入0xD0000000运行。
irom中存储的是三星固化的一段64k的启动代码,这段代码又称bl0,完成基本的初始化后读取启动选取引脚设置,
将相应存储器的8k的bl1段代码复制到iram中并运行。

 

2.运行bl1

bl1为用户编写的启动代码。此处使用u-boot,即u-boot的最开始运行的启动代码。
这段代码位于u-boot的开始,完成clk、dram、nand、MMU、串口等基本的初始化,并将完整的u-boot复制到dram中。
最后跳转到dram中运行u-boot,完成各种功能。

 

3.运行bl2

bl2为完整的u-boot,完成bootloader的各种功能。

完整的启动流程如图所示:

 

 

 

u-boot_smdkv210分析六:内存分配

1.内存分配图(引用网络图片)

2.u-boot映像的地址0并非指物理地址0,由不同的启动方式映射到不同的地址。例如v210是映射到0xD0000000处的irom。

3.TEXT_BASE等指向SDRAM的地址均为虚拟地址。

4.TEXT_BASE为顶层Makefile中定义的,例如三星官方BSP中定义的是0xC3E00000,它是程序实际的链接首地址。

5.SDRAM_BASE被MMU映射在0xC0000000。

6._end和__bss_start为链接脚本文件中最后定义的bss段,在链接时确定,并与u-boot映像编译在一起。

7.在bl1段运行时,u-boot映像被复制到TEXT_BASE开始的地址处。

8. u-boot分配用户栈顶的代码为:
 ldr r0, _TEXT_BASE  /* upper 128 KiB: relocated uboot   */      将0xc3e00000加载到r0
 sub r0, r0, #CFG_MALLOC_LEN /* malloc area                      */      r0减去0x4000的malloc域
 sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo                        */      r0减去128字节的全局结构体
#if defined(CONFIG_USE_IRQ)
 sub r0, r0, #(CONFIG_STACKSIZE_IRQ+CONFIG_STACKSIZE_FIQ)   如果用户有使用IRQ,再减去2*4*1024的中断栈空间
#endif
 sub sp, r0, #12  /* leave 3 words for abort-stack    */      为取址终止异常预留3个字空间后设置好用户sp

 

 

 

 

 

u-boot_smdkv210分析七:Makefile详解

 

2.1       U-Boot Makefile分析

2.1.1             U-Boot编译命令

       对于mini2440开发板,编译U-Boot需要执行如下的命令:

$  make  mini2440_config

$  make  all

       使用上面的命令编译U-Boot,编译生成的所有文件都保存在源代码目录中。为了保持源代码目录的干净,可以使用如下命令将编译生成的文件输出到一个外部目录,而不是在源代码目录中,下面的2种方法都将编译生成的文件输出到 /tmp/build目录:

$  export  BUILD_DIR=/tmp/build

$  make  mini2440_config

$  make  all

$  make  O=/tmp/build  mini2440_config  (注意是字母O,而不是数字0)

$  make  all

 

       为了简化分析过程,方便读者理解,这里主要针对第一种编译方式(目标输出到源代码所在目录)进行分析。

2.1.2             U-Boot配置、编译、连接过程

       U-Boot开头有一些跟主机软硬件环境相关的代码,在每次执行make命令时这些代码都被执行一次。

 

1.                                                 1.      U-Boot 配置过程

(1)定义主机系统架构

HOSTARCH := $(shell uname -m | \

       sed -e s/i.86/i386/ \

           -e s/sun4u/sparc64/ \

           -e s/arm.*/arm/ \

           -e s/sa110/arm/ \

           -e s/powerpc/ppc/ \

           -e s/ppc64/ppc/ \

           -e s/macppc/ppc/)

       “sed –e”表示后面跟的是一串命令脚本,而表达式“s/abc/def/”表示要从标准输入中,查找到内容为“abc”的,然后替换成“def”。其中“abc”表达式用可以使用“.”作为通配符。

       命令“uname –m”将输出主机CPU的体系架构类型。作者的电脑使用Intel Core2系列的CPU,因此“uname –m”输出“i686”。 “i686”可以匹配命令“sed -e s/i.86/i386/”中的“i.86”,因此在作者的机器上执行Makefile,HOSTARCH将被设置成“i386” 。

(2)定义主机操作系统类型

HOSTOS := $(shell uname -s | tr '[:upper:]' '[:lower:]' | \

           sed -e 's/\(cygwin\).*/cygwin/')

       “uname –s”输出主机内核名字,作者使用Linux发行版Ubuntu9.10,因此“uname –s”结果是“Linux”。“tr '[:upper:]' '[:lower:]'”作用是将标准输入中的所有大写字母转换为响应的小写字母。因此执行结果是将HOSTOS 设置为“linux”。

(3)定义执行shell脚本的shell

# Set shell to bash if possible, otherwise fall back to sh

SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \

       else if [ -x /bin/bash ]; then echo /bin/bash; \

       else echo sh; fi; fi)

       "$$BASH"的作用实质上是生成了字符串“$BASH”(前一个$号的作用是指明第二个$是普通的字符)。若执行当前Makefile的shell中定义了“$BASH”环境变量,且文件“$BASH”是可执行文件,则SHELL的值为“$BASH”。否则,若“/bin/bash”是可执行文件,则SHELL值为“/bin/bash”。若以上两条都不成立,则将“sh”赋值给SHELL变量。

       由于作者的机器安装了bash shell,且shell默认环境变量中定义了“$BASH”,因此SHELL 被设置为$BASH 。

(4)设定编译输出目录

ifdef O

ifeq ("$(origin O)", "command line")

BUILD_DIR := $(O)

endif

endif

       函数$( origin, variable) 输出的结果是一个字符串,输出结果由变量variable定义的方式决定,若variable在命令行中定义过,则origin函数返回值为"command line"。假若在命令行中执行了“export BUILD_DIR=/tmp/build”的命令,则“$(origin O)”值为“command line”,而BUILD_DIR被设置为“/tmp/build”。

ifneq ($(BUILD_DIR),)

saved-output := $(BUILD_DIR)

 

# Attempt to create a output directory.

$(shell [ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR})

       若${BUILD_DIR}表示的目录没有定义,则创建该目录。

# Verify if it was successful.

BUILD_DIR := $(shell cd $(BUILD_DIR) && /bin/pwd)

$(if $(BUILD_DIR),,$(error output directory "$(saved-output)" does not exist))

endif # ifneq ($(BUILD_DIR),)

       若$(BUILD_DIR)为空,则将其赋值为当前目录路径(源代码目录)。并检查$(BUILD_DIR)目录是否存在。

OBJTREE           := $(if $(BUILD_DIR),$(BUILD_DIR),$(CURDIR))

SRCTREE          := $(CURDIR)

TOPDIR             := $(SRCTREE)

LNDIR        := $(OBJTREE)

… …

MKCONFIG      := $(SRCTREE)/mkconfig

… …

ifneq ($(OBJTREE),$(SRCTREE))

obj := $(OBJTREE)/

src := $(SRCTREE)/

else

obj :=

src :=

endif

       CURDIR变量指示Make当前的工作目录,由于当前Make在U-Boot顶层目录执行Makefile,因此CURDIR此时就是U-Boot顶层目录。

       执行完上面的代码后, SRCTREE,src变量就是U-Boot代码顶层目录,而OBJTREE,obj变量就是输出目录,若没有定义BUILD_DIR环境变量,则SRCTREE,src变量与OBJTREE,obj变量都是U-Boot源代码目录。而MKCONFIG则表示U-Boot根目录下的mkconfig脚本。

1.                                                 2.      make mini2440_config命令执行过程

       下面分析命令“make mini2440_config”执行过程,为了简化分析过程这里主要分析将编译目标输出到源代码目录的情况。

mini2440_config :      unconfig

       @$(MKCONFIG) $(@:_config=) arm arm920t mini2440 samsung s3c24x0

       其中的依赖“unconfig”定义如下:

unconfig:

       @rm -f $(obj)include/config.h $(obj)include/config.mk \

              $(obj)board/*/config.tmp $(obj)board/*/*/config.tmp \

              $(obj)include/autoconf.mk $(obj)include/autoconf.mk.dep

        其中“@”的作用是执行该命令时不在shell显示。“obj”变量就是编译输出的目录,因此“unconfig”的作用就是清除上次执行make *_config命令生成的配置文件(如include/config.h,include/config.mk等)。

       $(MKCONFIG)在上面指定为“$(SRCTREE)/mkconfig”。$(@:_config=)为将传进来的所有参数中的_config替换为空(其中“@”指规则的目标文件名,在这里就是“mini2440_config ”。$(text:patternA=patternB),这样的语法表示把text变量每一个元素中结尾的patternA的文本替换为patternB,然后输出) 。因此$(@:_config=)的作用就是将mini2440_config中的_config去掉,得到mini2440。

       因此“@$(MKCONFIG) $(@:_config=) arm arm920t mini2440 samsung s3c24x0”实际上就是执行了如下命令:

./mkconfig mini2440 arm arm920t mini2440 samsung s3c24x0

       即将“mini2440 arm arm920t mini2440 samsung s3c24x0”作为参数传递给当前目录下的mkconfig脚本执行。

       在mkconfig脚本中给出了mkconfig的用法:

# Parameters:  Target  Architecture  CPU  Board [VENDOR] [SOC]

       因此传递给mkconfig的参数的意义分别是:

mini2440:Target(目标板型号)

arm:Architecture (目标板的CPU架构)

arm920t:CPU (具体使用的CPU型号)

mini2440:Board

samsung:VENDOR(生产厂家名)

s3c24x0:SOC

       下面再来看看mkconfig脚本到底做了什么。

(1)确定开发板名称BOARD_NAME

       在mkconfig脚本中有如下代码:

APPEND=no      # no表示创建新的配置文件,yes表示追加到配置文件中

BOARD_NAME="" # Name to print in make output

TARGETS=""

 

while [ $# -gt 0 ] ; do

    case "$1" in

    --) shift ; break ;;

    -a) shift ; APPEND=yes ;;

    -n) shift ; BOARD_NAME="${1%%_config}" ; shift ;;

    -t) shift ; TARGETS="`echo $1 | sed 's:_: :g'` ${TARGETS}" ; shift ;;

    *)  break ;;

    esac

done

 

[ "${BOARD_NAME}" ] || BOARD_NAME="$1" 

       环境变量$#表示传递给脚本的参数个数,这里的命令有6个参数,因此$#是6 。shift的作用是使$1=$2,$2=$3,$3=$4….,而原来的$1将丢失。因此while循环的作用是,依次处理传递给mkconfig脚本的选项。由于我们并没有传递给mkconfig任何的选项,因此while循环中的代码不起作用。

       最后将BOARD_NAME的值设置为$1的值,在这里就是“mini2440”。

(2)检查参数合法性

[ $# -lt 4 ] && exit 1

[ $# -gt 6 ] && exit 1

 

if [ "${ARCH}" -a "${ARCH}" != "$2" ]; then

       echo "Failed: \$ARCH=${ARCH}, should be '$2' for ${BOARD_NAME}" 1>&2

       exit 1

fi

       上面代码的作用是检查参数个数和参数是否正确,参数个数少于4个或多于6个都被认为是错误的。

(3)创建到目标板相关的目录的链接

#

# Create link to architecture specific headers

#

if [ "$SRCTREE" != "$OBJTREE" ] ; then         #若编译目标输出到外部目录,则下面的代码有效

       mkdir -p ${OBJTREE}/include

       mkdir -p ${OBJTREE}/include2

       cd ${OBJTREE}/include2

       rm -f asm

       ln -s ${SRCTREE}/include/asm-$2 asm

       LNPREFIX="http://www.cnblogs.com/include2/asm/"

       cd ../include

       rm -rf asm-$2

       rm -f asm

       mkdir asm-$2

       ln -s asm-$2 asm

else              

       cd ./include

       rm -f asm

       ln -s asm-$2 asm

fi

       若将目标文件设定为输出到源文件所在目录,则以上代码在include目录下建立了到asm-arm目录的符号链接asm。其中的ln -s asm-$2 asm即ln -s asm-arm asm 。

rm -f asm-$2/arch

 

if [ -z "$6" -o "$6" = "NULL" ] ; then

       ln -s ${LNPREFIX}arch-$3 asm-$2/arch

else

       ln -s ${LNPREFIX}arch-$6 asm-$2/arch

fi

       建立符号链接include/asm-arm/arch ,若$6(SOC)为空,则使其链接到include/asm-arm/arch-arm920t目录,否则就使其链接到include/asm-arm/arch-s3c24x0目录。(事实上include/asm-arm/arch-arm920t并不存在,因此$6是不能为空的,否则会编译失败)

if [ "$2" = "arm" ] ; then

       rm -f asm-$2/proc

       ln -s ${LNPREFIX}proc-armv asm-$2/proc

fi

       若目标板是arm架构,则上面的代码将建立符号连接include/asm-arm/proc,使其链接到目录proc-armv目录。

       建立以上的链接的好处:编译U-Boot时直接进入链接文件指向的目录进行编译,而不必根据不同开发板来选择不同目录。

(4)构建include/config.mk文件

#

# Create include file for Make

#

echo "ARCH   = $2" >  config.mk

echo "CPU    = $3" >> config.mk

echo "BOARD  = $4" >> config.mk

 

[ "$5" ] && [ "$5" != "NULL" ] && echo "VENDOR = $5" >> config.mk

 

[ "$6" ] && [ "$6" != "NULL" ] && echo "SOC    = $6" >> config.mk

       上面代码将会把如下内容写入文件inlcude/config.mk文件:

ARCH   = arm

CPU    = arm920t

BOARD  = mini2440

VENDOR = samsung

SOC    = s3c24x0

(5)指定开发板代码所在目录

# Assign board directory to BOARDIR variable

if [ -z "$5" -o "$5" = "NULL" ] ; then

    BOARDDIR=$4

else

    BOARDDIR=$5/$4

fi

       以上代码指定board目录下的一个目录为当前开发板专有代码的目录。若$5(VENDOR)为空则BOARDDIR设置为$4(BOARD),否则设置为$5/$4(VENDOR/BOARD)。在这里由于$5不为空,因此BOARDDIR被设置为samsung/mini2440 。

(6)构建include/config.h文件

#

# Create board specific header file

#

if [ "$APPEND" = "yes" ] # Append to existing config file

then

       echo >> config.h

else

       > config.h            # Create new config file

fi

echo "/* Automatically generated - do not edit */" >>config.h

 

for i in ${TARGETS} ; do

       echo "#define CONFIG_MK_${i} 1" >>config.h ;

done

 

cat << EOF >> config.h

#define CONFIG_BOARDDIR board/$BOARDDIR

#include <config_defaults.h>

#include <configs/$1.h>

#include <asm/config.h>

EOF

exit 0

       这里的“cat << EOF >> config.h”表示将输入的内容追加到config.h中,直到出现“EOF”这样的标识为止。

       若APPEND为no,则创建新的include/config.h文件。若APPEND为yes,则将新的配置内容追加到include/config.h文件后面。由于APPEND的值保持“no”,因此config.h被创建了,并添加了如下的内容:

       /* Automatically generated - do not edit */

       #define CONFIG_BOARDDIR board/samsung/mini2440

       #include <config_defaults.h>

       #include <configs/mini2440.h>

       #include <asm/config.h>

       下面总结命令make mini2440_config执行的结果(仅针对编译目标输出到源代码目录的情况):

(1)    创建到目标板相关的文件的链接

       ln -s asm-arm asm

       ln -s arch-s3c24x0 asm-arm/arch

       ln -s proc-armv asm-arm/proc

(2)    创建include/config.mk文件,内容如下所示:

       ARCH   = arm

       CPU    = arm920t

       BOARD  = mini2440

       VENDOR = samsung

       SOC    = s3c24x0

(3)    创建与目标板相关的文件include/config.h,如下所示:

       /* Automatically generated - do not edit */

       #define CONFIG_BOARDDIR board/samsung/mini2440

       #include <config_defaults.h>

       #include <configs/mini2440.h>

       #include <asm/config.h>

1.                                                 3.      make all命令执行过程

       若没有执行过“make <board_name>_config”命令就直接执行“make all”命令则会出现如下的才错误信息,然后停止编译:

       System not configured - see README

       U-Boot是如何知道用户没有执行过“make <board_name>_config”命令的呢?阅读U-Boot源代码就可以发现了,Makefile中有如下代码:

ifeq ($(obj)include/config.mk,$(wildcard $(obj)include/config.mk)) # config.mk存在

all: 

sinclude $(obj)include/autoconf.mk.dep

sinclude $(obj)include/autoconf.mk

… …

else        # config.mk不存在

… …

       @echo "System not configured - see README" >&2

       @ exit 1

… …

endif      # config.mk

       若include/config.mk 文件存在,则$(wildcard $(obj)include/config.mk) 命令执行的结果是“$(obj)include/config.mk”展开的字符串,否则结果为空。由于include/config.mk是“make <board_name>_config”命令执行过程生成的,若从没有执行过“make <board_name>_config”命令则include/config.mk必然不存在。因此Make就执行else分支的代码,在输出“System not configured - see README”的信息后就返回了。

       下面再来分析“make all”命令正常执行的过程,在Makefile中有如下代码:

(1)include/autoconf.mk生成过程

all:

sinclude $(obj)include/autoconf.mk.dep

sinclude $(obj)include/autoconf.mk

       include/autoconf.mk文件中是与开发板相关的一些宏定义,在Makefile执行过程中需要根据某些宏来确定执行哪些操作。下面简要分析include/autoconf.mk生成的过程,include/autoconf.mk生成的规则如下:

$(obj)include/autoconf.mk: $(obj)include/config.h

       @$(XECHO) Generating $@ ; \

       set -e ; \

       : Extract the config macros ; \

       $(CPP) $(CFLAGS) -DDO_DEPS_ONLY -dM include/common.h | \

              sed -n -f tools/scripts/define2mk.sed > $@.tmp && \

       mv $@.tmp $@

       include/autoconf.mk依赖于make <board_name>_config 命令生成的include/config.h。因此执行make <board_name>_config命令后再执行make all将更新include/autoconf.mk。

       编译选项“-dM”的作用是输出include/common.h中定义的所有宏。根据上面的规则,编译器提取include/common.h中定义的宏,然后输出给tools/scripts/define2mk.sed脚本处理,处理的结果就是include/autoconf.mk文件。其中tools/scripts/define2mk.sed脚本的主要完成了在include/common.h中查找和处理以“CONFIG_”开头的宏定义的功能。

       include/common.h文件包含了include/config.h文件,而include/config.h文件又包含了config_defaults.h,configs/mini2440.h,asm/config.h文件。因此include/autoconf.mk实质上就是config_defaults.h,configs/mini2440.h,asm/config.h三个文件中“CONFIG_”开头的有效的宏定义的集合。

       下面接着分析Makefile的执行。

# load ARCH, BOARD, and CPU configuration

include $(obj)include/config.mk

export    ARCH CPU BOARD VENDOR SOC

       将make mini2440_config命令生成的include/config.mk包含进来。

# 若主机架构与开发板结构相同,就使用主机的编译器,而不是交叉编译器

ifeq ($(HOSTARCH),$(ARCH))

CROSS_COMPILE ?=

endif

       若主机与目标机器体系架构相同,则使用gcc编译器而不是交叉编译器。

# load other configuration

include $(TOPDIR)/config.mk

       最后将U-Boot顶层目录下的config.mk文件包含进来,该文件包含了对编译的一些设置。下面对U-Boot顶层目录下的config.mk文件进行分析:

(2)config.mk文件执行过程

1设置obj与src

       在U-Boot顶层目录下的config.mk文件中有如下代码:

ifneq ($(OBJTREE),$(SRCTREE))

ifeq ($(CURDIR),$(SRCTREE))

dir :=

else

dir := $(subst $(SRCTREE)/,,$(CURDIR))

endif

 

obj := $(if $(dir),$(OBJTREE)/$(dir)/,$(OBJTREE)/)

src := $(if $(dir),$(SRCTREE)/$(dir)/,$(SRCTREE)/)

 

$(shell mkdir -p $(obj))

else

obj :=

src :=

endif

       由于目标输出到源代码目录下,因此执行完上面的代码后,src和obj都是空。

2设置编译选项

PLATFORM_RELFLAGS =

PLATFORM_CPPFLAGS =          #编译选项

PLATFORM_LDFLAGS =           #连接选项

       用这3个变量表示交叉编译器的编译选项,在后面Make会检查交叉编译器支持的编译选项,然后将适当的选项添加到这3个变量中。

#

# Option checker (courtesy linux kernel) to ensure

# only supported compiler options are used

#

cc-option = $(shell if $(CC) $(CFLAGS) $(1) -S -o /dev/null -xc /dev/null \

              > /dev/null 2>&1; then echo "$(1)"; else echo "$(2)"; fi ;)

       变量CC和CFLAGS在后面的代码定义为延时变量,其中的CC即arm-linux-gcc。函数cc-option用于检查编译器CC是否支持某选项。将2个选项作为参数传递给cc-option函数,该函数调用CC编译器检查参数1是否支持,若支持则函数返回参数1,否则返回参数2 (因此CC编译器必须支持参数1或参数2,若两个都不支持则会编译出错)。可以像下面这样调用cc-option函数,并将支持的选项添加到FLAGS中:

FLAGS +=$(call cc-option,option1,option2)

3指定交叉编译工具

#

# Include the make variables (CC, etc...)

#

AS  = $(CROSS_COMPILE)as

LD  = $(CROSS_COMPILE)ld

CC  = $(CROSS_COMPILE)gcc

CPP       = $(CC) -E

AR = $(CROSS_COMPILE)ar

NM = $(CROSS_COMPILE)nm

LDR      = $(CROSS_COMPILE)ldr

STRIP   = $(CROSS_COMPILE)strip

OBJCOPY = $(CROSS_COMPILE)objcopy

OBJDUMP = $(CROSS_COMPILE)objdump

RANLIB      = $(CROSS_COMPILE)RANLIB

       对于arm开发板,其中的CROSS_COMPILE在lib_arm/config.mk文件中定义:

CROSS_COMPILE ?= arm-linux-

       因此以上代码指定了使用前缀为“arm-linux-”的编译工具,即arm-linux-gcc,arm-linux-ld等等。

4包含与开发板相关的配置文件

# Load generated board configuration

sinclude $(OBJTREE)/include/autoconf.mk

 

ifdef      ARCH

sinclude $(TOPDIR)/lib_$(ARCH)/config.mk   # include architecture dependend rules

endif

       $(ARCH)的值是“arm”,因此将“lib_arm/config.mk”包含进来。lib_arm/config.mk脚本指定了交叉编译器,添加了一些跟CPU架构相关的编译选项,最后还指定了cpu/arm920t/u-boot.lds为U-Boot的连接脚本。

ifdef      CPU

sinclude $(TOPDIR)/cpu/$(CPU)/config.mk             # include  CPU specific rules

endif

       $(CPU)的值是“arm920t”,因此将“cpu/arm920t/config.mk”包含进来。这个脚本主要设定了跟arm920t处理器相关的编译选项。

ifdef      SOC

sinclude $(TOPDIR)/cpu/$(CPU)/$(SOC)/config.mk       # include  SoC  specific rules

endif

       $(SOC)的值是s3c24x0,因此Make程序尝试将cpu/arm920t/s3c24x0/config.mk包含进来,而这个文件并不存在,但是由于用的是“sinclude”命令,所以并不会报错。

ifdef      VENDOR

BOARDDIR = $(VENDOR)/$(BOARD)

else

BOARDDIR = $(BOARD)

endif

       $(BOARD)的值是mini2440,VENDOR的值是samsung,因此BOARDDIR的值是samsung/mini2440。BOARDDIR变量表示开发板特有的代码所在的目录。

ifdef      BOARD

sinclude $(TOPDIR)/board/$(BOARDDIR)/config.mk   # include board specific rules

endif

       Make将“board/samsung/mini2440/config.mk”包含进来。该脚本只有如下的一行代码:

TEXT_BASE = 0x33F80000

       U-Boot编译时将使用TEXT_BASE作为代码段连接的起始地址。

LDFLAGS += -Bstatic -T $(obj)u-boot.lds $(PLATFORM_LDFLAGS)

ifneq ($(TEXT_BASE),)

LDFLAGS += -Ttext $(TEXT_BASE)

endif

       执行完以上代码后,LDFLAGS中包含了“-Bstatic -T u-boot.lds ”和“-Ttext 0x33F80000”的字样。

5指定隐含的编译规则

# Allow boards to use custom optimize flags on a per dir/file basis

BCURDIR := $(notdir $(CURDIR))

$(obj)%.s:     %.S

       $(CPP) $(AFLAGS) $(AFLAGS_$(@F)) $(AFLAGS_$(BCURDIR)) -o $@ $<

$(obj)%.o:    %.S

       $(CC)  $(AFLAGS) $(AFLAGS_$(@F)) $(AFLAGS_$(BCURDIR)) -o $@ $< -c

$(obj)%.o:    %.c

       $(CC)  $(CFLAGS) $(CFLAGS_$(@F)) $(CFLAGS_$(BCURDIR)) -o $@ $< -c

$(obj)%.i:     %.c

       $(CPP) $(CFLAGS) $(CFLAGS_$(@F)) $(CFLAGS_$(BCURDIR)) -o $@ $< -c

$(obj)%.s:     %.c

       $(CC)  $(CFLAGS) $(CFLAGS_$(@F)) $(CFLAGS_$(BCURDIR)) -o $@ $< -c -S

       例如:根据以上的定义,以“.s”结尾的目标文件将根据第一条规则由同名但后缀为“.S”的源文件来生成,若不存在“.S”结尾的同名文件则根据最后一条规则由同名的“.c”文件生成。

下面回来接着分析Makefile的内容:

# U-Boot objects....order is important (i.e. start must be first)

 

OBJS  = cpu/$(CPU)/start.o

LIBS += cpu/$(CPU)/lib$(CPU).a

ifdef SOC

LIBS += cpu/$(CPU)/$(SOC)/lib$(SOC).a

endif

ifeq ($(CPU),ixp)

LIBS += cpu/ixp/npe/libnpe.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 fs/yaffs2/libyaffs2.a \

       fs/ubifs/libubifs.a

… …

LIBS += common/libcommon.a

LIBS += libfdt/libfdt.a

LIBS += api/libapi.a

LIBS += post/libpost.a

 

LIBS := $(addprefix $(obj),$(LIBS))

       LIBS变量指明了U-Boot需要的库文件,包括平台/开发板相关的目录、通用目录下相应的库,都通过相应的子目录编译得到的。

       对于mini2440开发板,以上跟平台相关的有以下几个:

cpu/$(CPU)/start.o

board/$(VENDOR)/common/lib$(VENDOR).a

cpu/$(CPU)/lib$(CPU).a

cpu/$(CPU)/$(SOC)/lib$(SOC).a

lib_$(ARCH)/lib$(ARCH).a

       其余都是与平台无关的。

ifeq ($(CONFIG_NAND_U_BOOT),y)

NAND_SPL = nand_spl

U_BOOT_NAND = $(obj)u-boot-nand.bin

endif

 

ifeq ($(CONFIG_ONENAND_U_BOOT),y)

ONENAND_IPL = onenand_ipl

U_BOOT_ONENAND = $(obj)u-boot-onenand.bin

ONENAND_BIN ?= $(obj)onenand_ipl/onenand-ipl-2k.bin

endif

       对于有的开发板,U-Boot支持在NAND Flash启动,这些开发板的配置文件定义了CONFIG_NAND_U_BOOT,CONFIG_ONENAND_U_BOOT。对于s3c2440,U-Boot原始代码并不支持NAND Flash启动,因此也没有定义这两个宏。

ALL += $(obj)u-boot.srec $(obj)u-boot.bin $(obj)System.map $(U_BOOT_NAND) $(U_BOOT_ONENAND)

all:         $(ALL)

       其中U_BOOT_NAND与U_BOOT_ONENAND 为空,而u-boot.srec,u-boot.bin,System.map都依赖与u-boot。因此执行“make all”命令将生成u-boot,u-boot.srec,u-boot.bin,System.map 。其中u-boot是ELF文件,u-boot.srec是Motorola S-Record format文件,System.map 是U-Boot的符号表,u-boot.bin是最终烧写到开发板的二进制可执行的文件。

       下面再来分析u-boot.bin文件生成的过程。ELF格式“u-boot”文件生成规则如下:

$(obj)u-boot:       depend $(SUBDIRS) $(OBJS) $(LIBBOARD) $(LIBS) $(LDSCRIPT) $(obj)u-boot.lds

              $(GEN_UBOOT)

ifeq ($(CONFIG_KALLSYMS),y)

              smap=`$(call SYSTEM_MAP,u-boot) | \

                     awk '$$2 ~ /[tTwW]/ {printf $$1 $$3 "\\\\000"}'` ; \

              $(CC) $(CFLAGS) -DSYSTEM_MAP="\"$${smap}\"" \

                     -c common/system_map.c -o $(obj)common/system_map.o

              $(GEN_UBOOT) $(obj)common/system_map.o

endif

       这里生成的$(obj)u-boot目标就是ELF格式的U-Boot文件了。由于CONFIG_KALLSYMS未定义,因此ifeq ($(CONFIG_KALLSYMS),y)与endif间的代码不起作用。

       其中depend,$(SUBDIRS),$(OBJS),$(LIBBOARD),$(LIBS),$(LDSCRIPT), $(obj)u-boot.lds是$(obj)u-boot的依赖,而$(GEN_UBOOT)编译命令。

下面分析$(obj)u-boot的各个依赖:

1依赖目标depend

# Explicitly make _depend in subdirs containing multiple targets to prevent

# parallel sub-makes creating .depend files simultaneously.

 

depend dep: $(TIMESTAMP_FILE) $(VERSION_FILE) $(obj)include/autoconf.mk

              for dir in $(SUBDIRS) cpu/$(CPU) $(dir $(LDSCRIPT)) ; do \

                     $(MAKE) -C $$dir _depend ; done

       对于$(SUBDIRS),cpu/$(CPU),$(dir $(LDSCRIPT))中的每个元素都进入该目录执行“make _depend”,生成各个子目录的.depend文件,.depend列出每个目标文件的依赖文件。

       2依赖SUBDIRS

       SUBDIRS    = tools \

         examples/standalone \

         examples/api

 

       $(SUBDIRS):     depend

                     $(MAKE) -C $@ all

       执行tools ,examples/standalone ,examples/api目录下的Makefile。

       3OBJS

       OBJS的值是“cpu/arm920t/start.o”。它使用如下代码编译得到:

$(OBJS):      depend

       $(MAKE) -C cpu/$(CPU) $(if $(REMOTE_BUILD),$@,$(notdir $@))

       以上规则表明,对于OBJS包含的每个成员,都进入cpu/$(CPU)目录(即cpu/arm920t)编译它们。

4LIBBOARD

LIBBOARD = board/$(BOARDDIR)/lib$(BOARD).a

LIBBOARD := $(addprefix $(obj),$(LIBBOARD))

… …

$(LIBBOARD): depend $(LIBS)

              $(MAKE) -C $(dir $(subst $(obj),,$@))

       这里LIBBOARD的值是 $(obj)board/samsung/mini2440/libmini2440.a。make执行board/samsung/mini2440/目录下的Makefile,生成libmini2440.a 。

       5LIBS

       LIBS变量中的每个元素使用如下的规则编译得到:

$(LIBS):       depend $(SUBDIRS)

              $(MAKE) -C $(dir $(subst $(obj),,$@))

       上面的规则表明,对于LIBS中的每个成员,都进入相应的子目录执行“make”命令编译它们。例如对于LIBS中的“common/libcommon.a”成员,程序将进入common目录执行Makefile,生成libcommon.a 。

6LDSCRIPT

LDSCRIPT := $(SRCTREE)/cpu/$(CPU)/u-boot.lds

… …

$(LDSCRIPT):   depend

              $(MAKE) -C $(dir $@) $(notdir $@)

       “$(MAKE) -C $(dir $@) $(notdir $@)”命令经过变量替换后就是“make -C cpu/arm920t/  u-boot.lds”。也就是转到cpu/arm920t/目录下,执行“make u-boot.lds”命令。

7$(obj)u-boot.lds

$(obj)u-boot.lds: $(LDSCRIPT)

              $(CPP) $(CPPFLAGS) $(LDPPFLAGS) -ansi -D__ASSEMBLY__ -P - <$^ >$@

       以上执行结果实质上是将cpu/arm920t/u-boot.lds经编译器简单预处理后输出到U-Boot顶层目录下的u-boot.lds文件。其中的cpu/arm920t/u-boot.lds文件内容如下:

/* 输出为ELF文件,小端方式, */

OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")

OUTPUT_ARCH(arm)   

ENTRY(_start)

SECTIONS

{

       . = 0x00000000;

        . = ALIGN(4);

       .text :

       {

/* cpu/arm920t/start.o放在最前面,保证最先执行的是start.o */

                     cpu/arm920t/start.o    (.text)

/*以下2个文件必须放在前4K,因此也放在前面,其中board/samsung/mini2440/lowlevel_init.o 包含内存初始化所需代码,而 board/samsung/mini2440/nand_read.o 包含U-Boot从NAND Flash搬运自身的代码 */

                board/samsung/mini2440/lowlevel_init.o (.text)

                 board/samsung/mini2440/nand_read.o (.text)

/* 其他文件的代码段 */

              *(.text)

       }

 

/* 只读数据段 */

       . = ALIGN(4);

       .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

 

/* 代码段 */

       . = ALIGN(4);

       .data : { *(.data) }

 

/* u-boot自定义的got段 */

       . = ALIGN(4);

       .got : { *(.got) }

        . = .;

       __u_boot_cmd_start = .;          /*将 __u_boot_cmd_start指定为当前地址 */

       .u_boot_cmd : { *(.u_boot_cmd) }             /* 存放所有U-Boot命令对应的cmd_tbl_t结构体 */

       __u_boot_cmd_end = .;           /*  将__u_boot_cmd_end指定为当前地址  */

 

/* bss段 */

       . = ALIGN(4);

       __bss_start = .;

       .bss (NOLOAD) : { *(.bss) . = ALIGN(4); }

       _end = .;              /*  将_end指定为当前地址  */

}

       u-boot.lds实质上是U-Boot连接脚本。对于生成的U-Boot编译生成的“u-boot”文件,可以使用objdump命令可以查看它的分段信息:

$  objdump -x u-boot | more

       部分输出信息如下:

u-boot:     file format elf32-little

u-boot

architecture: UNKNOWN!, flags 0x00000112:

EXEC_P, HAS_SYMS, D_PAGED

start address 0x33f80000

 

Program Header:

    LOAD off    0x00008000 vaddr 0x33f80000 paddr 0x33f80000 align 2**15

         filesz 0x0002f99c memsz 0x00072c94 flags rwx

   STACK off    0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**2

         filesz 0x00000000 memsz 0x00000000 flags rwx

 

Sections:

Idx Name          Size      VMA       LMA       File off  Algn

  0 .text         00024f50  33f80000  33f80000  00008000  2**5

                  CONTENTS, ALLOC, LOAD, READONLY, CODE

  1 .rodata       00008b78  33fa4f50  33fa4f50  0002cf50  2**3

                  CONTENTS, ALLOC, LOAD, READONLY, DATA

  2 .data         00001964  33fadac8  33fadac8  00035ac8  2**2

                  CONTENTS, ALLOC, LOAD, DATA

  3 .u_boot_cmd   00000570  33faf42c  33faf42c  0003742c  2**2

                  CONTENTS, ALLOC, LOAD, DATA

  4 .bss          00043294  33fafa00  33fafa00  0003799c  2**8

                  ALLOC

… …

       u-boot.lds还跟U-Boot启动阶段复制代码到RAM空间的过程以及U-Boot命令执行过程密切相关,具体请结合U-Boot源代码理解。

       编译命令GEN_UBOOT

GEN_UBOOT = \

              UNDEF_SYM=`$(OBJDUMP) -x $(LIBBOARD) $(LIBS) | \

              sed  -n -e 's/.*\($(SYM_PREFIX)__u_boot_cmd_.*\)/-u\1/p'|sort|uniq`;\

              cd $(LNDIR) && $(LD) $(LDFLAGS) $$UNDEF_SYM $(__OBJS) \

                     --start-group $(__LIBS) --end-group $(PLATFORM_LIBS) \

                     -Map u-boot.map -o u-boot

       以上命令使用$(LDFLAGS)作为连接脚本,最终生成“u-boot”文件。

u-boot.bin文件生成过程

       生成u-boot.bin文件的规则如下:

$(obj)u-boot.bin: $(obj)u-boot

              $(OBJCOPY) ${OBJCFLAGS} -O binary $< $@

       从U-Boot编译输出信息中可以知道上面的命令实质上展开为:

       arm-linux-objcopy --gap-fill=0xff -O binary u-boot u-boot.bin

       编译命令中的“-O binary”选项指定了输出的文件为二进制文件。而“--gap-fill=0xff”选项指定使用“0xff”填充段与段间的空闲区域。这条编译命令实现了ELF格式的U-Boot文件到BIN格式的转换。

System.map文件的生成

       System.map是U-Boot的符号表,它包含了U-Boot的全局变量和函数的地址信息。将System.map生成的规则如下:

SYSTEM_MAP = \

              $(NM) $1 | \

              grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \

              LC_ALL=C sort

$(obj)System.map:     $(obj)u-boot

              @$(call SYSTEM_MAP,$<) > $(obj)System.map

arm-linux-nm u-boot | grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | LC_ALL=C sort  > System.map

       也就是将arm-linux-nm命令查看u-boot的输出信息经过过滤和排序后输出到System.map。为了了解System.map文件的作用,打开System.map:

33f80000 T _start

33f80020 t _undefined_instruction

33f80024 t _software_interrupt

33f80028 t _prefetch_abort

33f8002c t _data_abort

33f80030 t _not_used

33f80034 t _irq

33f80038 t _fiq

33f80040 t _TEXT_BASE

33f80044 T _armboot_start

33f80048 T _bss_start

33f8004c T _bss_end

… …

       System.map表示的是地址标号到该标号表示的地址的一个映射关系。System.map每一行的格式都是“addr type name”,addr是标号对应的地址值,name是标号名,type表示标号的类型。

     U-Boot的编译和运行并不一定要生成System.map,这个文件主要是提供给用户或外部程序调试时使用的。

 

 

ARM的MMU由CP15协处理器管理。

一. 与MMU管理有关的寄存器有:

C1:某些位 用于配置MMU中的一些操作
C2:页表基地址,有效的为[31:14],所以页表地址必须16KB对齐。
C3:域(domain)的访问控制属性
C4:保留
C5:内存访问失效状态指示
C6:内存访问失效时失效的地址
C8:控制和清除TLB内容相关的操作
C10:控制和锁定TLB内容相关的操作

二. 禁止/使能MMU

C1的0位控制禁止/使能MMU:
MRC P15,0,R0,C1,0,0
ORR R0,#01
MCR P15,0,R0,C1,0,0

三. 段描述符

31                      20  19                        12  11  10  9  8        5  4  3  2  1  0

段基地址                    应为0                          AP   0   域                  C  B  1  0

上述是一个以Section方式定义的段描述符:
一个Section为1M,虚拟地址到物理地址的映射实际是高12位地址的置换。
C2的18位基地址作为索引表的高18位,虚拟地址的高12位作为索引表的低12位,共同构成低两位为0的字对齐索引表地址。
查到的表内容就是这个段描述符,这个描述符里面的段基地址就是要被替换的虚拟地址高12位。
这个描述符还控制了这1M空间的读写、域、cache和buffer属性。

四. 汇编建表准备

.macro FL_SECTION_ENTRY base,ap,d,c,b
    .word (\base << 20) | (\ap << 10) | (\d << 5) | (1<<4) | (\c << 3) | (\b << 2) | (1<<1)
.endm
.section .mmudata, "a"
    .align 14
    .globl mmu_table

宏FL_SECTION_ENTRY根据控制参数建立一个字大小的描述符。
.align 为保证16KB地址对齐。

五. 汇编建表

    .set __base,0x200
    // 256MB for SDRAM with cacheable
    .rept 0xD00 - 0xC00
    FL_SECTION_ENTRY __base,3,0,1,1
    .set __base,__base+1
    .endr

__base为这部分代码建表物理地址的基地址。
rept控制建描述符数量,用0xD00-0xC00为了从虚拟地址的0开始连续的建表,增强可读性。
调用宏创建描述符。
__base每循环一次加一。

六. 实例

下面查看内存发现0xC0000000的确映射到0x20000000了。
SMDKV210 # md 20000000 20                                                       
20000000: 12345678 bdbcd51e bd6ff697 bf71f6ff    xV4.......o...q.               
20000010: fadcdf3f 7b4fe79f 9eb4cfff 8badefd7    ?.....O{........               
20000020: 8fa7ebdf d7afdf7d 6bacf5bf dbb5dbd6    ....}......k....               
20000030: ca39fdbf cba7cfd3 fff5f6bf 2b9553fd    ..9..........S.+               
20000040: df2dce57 ffa1601f cb478f55 8fa7cfff    W.-..`..U.G.....               
20000050: bb81c74f 9bb0f4df b7abd2d3 eba5fe37    O...........7...               
20000060: 9fafffdf df67f7be eea5b6bf bae5cd5b    ......g.....[...               
20000070: fb55ffd6 9be5d8ff c9b5fdfe 83afc09f    ..U.............               
SMDKV210 # md c0000000 20                                                       
c0000000: 12345678 bdbcd51e bd6ff697 bf71f6ff    xV4.......o...q.               
c0000010: fadcdf3f 7b4fe79f 9eb4cfff 8badefd7    ?.....O{........               
c0000020: 8fa7ebdf d7afdf7d 6bacf5bf dbb5dbd6    ....}......k....               
c0000030: ca39fdbf cba7cfd3 fff5f6bf 2b9553fd    ..9..........S.+               
c0000040: df2dce57 ffa1601f cb478f55 8fa7cfff    W.-..`..U.G.....               
c0000050: bb81c74f 9bb0f4df b7abd2d3 eba5fe37    O...........7...               
c0000060: 9fafffdf df67f7be eea5b6bf bae5cd5b    ......g.....[...               
c0000070: fb55ffd6 9be5d8ff c9b5fdfe 83afc09f    ..U............. 

 

 

 

 

 

 

原创粉丝点击