uboot 解析

admin 2021年6月12日11:30:50评论123 views字数 11942阅读39分48秒阅读模式

flash介绍

(1)IIC EEPROM

IIC EEPROM,采用的是IIC通信协议。 
IIC通信协议具有的特点: 
【1】简单的两条总线线路,一条串行数据线(SDA),一条串行时钟线(SCL); 
【2】串行半双工通信模式的8位双向数据传输,位速率标准模式下可达100Kbit/s; 
【3】一种电可擦除可编程只读存储器,掉电后数据不丢失,由于芯片能够支持单字节擦写,且支持擦除的次数非常之多,一个地址位可重复擦写的理论值为100万次,常用芯片型号有 AT24C02、FM24C02、CAT24C02等,其常见的封装多为DIP8,SOP8,TSSOP8等; 

(2)SPI NorFlash

SPINorFlash,采用的是SPI 通信协议。 
【1】有4线(时钟,两个数据线,片选线)或者3线(时钟,两个数据线)通信接口,由于它有两个数据线能实现全双工通信,因此比IIC通信协议的 IIC EEPROM的读写速度上要快很多。 
【2】SPI NorFlash具有NOR技术Flash Memory的特点,即程序和数据可存放在同一芯片上,拥有独立的数据总线和地址总线,能快速随机读取,允许系统直接从Flash中读取代码执行;可以单字节或单字编程,但不能单字节擦除,必须以Sector为单位或对整片执行擦除操作,在对存储器进行重新编程之前需要对Sector或整片进行预编程和擦除操作。 
【3】NorFlash在擦写次数上远远达不到IIC EEPROM,并且由于NOR技术Flash Memory的擦除和编程速度较慢,块尺寸又较大,因此擦除和编程操作所花费的时间会很长;但SPI NorFlash接口简单,使用的引脚少,易于连接,操作方便,并且可以在芯片上直接运行代码,其稳定性出色,传输速率高,在小容量时具有很高的性价比,这使其很适合应于嵌入式系统中作为 FLASH ROM,所以在市场的占用率非常高。 
【4】常见到的S25FL128、MX25L1605、W25Q64等型号都是SPI NorFlash,其常见的封装多为SOP8,SOP16,WSON8,US0N8,QFN8、BGA24等。 

(3)Parallel NorFalsh(CFIFlash)

  Parallel NorFalsh,也叫做并行NorFlash,采用的Parallel接口通信协议。拥有独立的数据线和地址总线,它同样继承了NOR技术FlashMemory的所有特点;由于采用了Parallel接口,ParallelNorFalsh相对于SPINorFlash,支持的容量更大,读写的速度更快,但是由于占用的地址线和数据线太多,在电路电子设计上会占用很多资源。ParallelNorFalsh读写时序类似于SRAM,只是写的次数较少,速度也慢,由于其读时序类似于SRAM,读地址也是线性结构,所以多用于不需要经常更改程序代码的数据存储。 
  常见到的S29GL128、MX29GL512、SST39VF020等型号都是ParallelNorFlash,其常见的封装多为TSSOP32、TSOP48、BGA64,PLCC32等 
   

(4)Parallel NandFlash

Parallel NandFlash同样采用了Parallel接口通信协议,NandFlash在工艺制程方面分有三种类型:SLC、MLC、TLC。 
NandFlash技术Flash Memory具有以下特点: 
【1】以页为单位进行读和编程操作,以块为单位进行擦除操作; 
【2】具有快编程和快擦除的功能,其块擦除时间是2ms,而NOR技术的块擦除时间达到几百ms; 
【3】芯片尺寸小,引脚少,是位成本(bit cost)最低的固态存储器;芯片包含有坏块,其数目取决于存储器密度。坏块不会影响有效块的性能,但设计者需要有一套的坏块管理策略!

对比Parallel NorFalsh,NandFlash在擦除、读写方面,速度快,使用擦写次数更多,并且它强调更高的性能,更低的成本,更小的体积,更大的容量,更长的使用寿命。这使NandFlash很擅于存储纯资料或数据等,在嵌入式系统中用来支持文件系统。其主要用来数据存储,大部分的U盘都是使用 NandFlash,当前NandFlash在嵌入式产品中应用仍然极为广泛,因此坏块管理、掉电保护等措施就需要依赖NandFlash使用厂家通过软件进行完善。

(5)SPINandFlash

  SPINandFlash,采用了SPINorFlash一样的SPI的通信协议,在读写的速度上没什么区别,但在存储结构上却采用了与ParallelNandFlash相同的结构,所以SPInand相对于SPInorFlash具有擦写的次数多,擦写速度快的优势,但是在使用以及使用过程中会同样跟ParallelNandFlash一样会出现坏块,因此,也需要做特殊坏块处理才能使用; 
  SPINandFlash相对比ParallelNandFlash还有一个重要的特点,那就是芯片自己有内部ECC纠错模块,用户无需再使用ECC算法计算纠错,用户可以在系统应用当中可以简化代码,简单操作; 
常见到的W25N01GVZEIG、GD5F4GQ4UBYIG、F50L1G41A等型号都是SPINandFlash,其常见的封装多为QFN8、BGA24等。 

(6)eMMCFlash

  eMMC采用统一的MMC标准接口,自身集成MMCController,存储单元与NandFlash相同。针对Flash的特性,eMMC产品内部已经包含了Flash管理技术,包括错误探测和纠正,Flash平均擦写,坏块管理,掉电保护等技术。MMC接口速度高达每秒52MBytes,eMMC具有快速、可升级的性能,同时其接口电压可以是1.8v或者是3.3v。 
  eMMC相当于NandFlash+主控IC,对外的接口协议与SD、TF卡一样,主要是针对手机或平板电脑等产品的内嵌式存储器标准规格。eMMC的一个明显优势是在封装中集成了一个控制器,它提供标准接口并管理闪存,使得手机厂商就能专注于产品开发的其它部分,并缩短向市场推出产品的时间。这些特点对于希望通过缩小光刻尺寸和降低成本的NAND供应商来说,同样的重要。 
  eMMC由一个嵌入式存储解决方案组成,带有MMC(多媒体卡)接口、快闪存储器设备(NandFlash)及主控制器,所有都在一个小型的BGA封装,最常见的有BGA153封装;我们通常见到的KLMAG8DEDD、THGBMAG8B4JBAIM、EMMC04G-S100等型号都是eMMCFlash。eMMCFlash存储容量大,市场上32GByte容量都常见了,其常见的封装多为BGA153、BGA169、BGA100等。 
   

(7)USF2.0

  JEDEC在2013年9月发布了新一代的通用闪存存储器标准USF2.0,该标准下得闪存读写速度可以高达每秒1400MB,这相当于在两秒钟内读写两个CD光盘的数据,不仅比eMMC有更巨大的优势,而且它甚至能够让电脑上使用的闪存存储介质固态硬盘也相形见绌。UFS闪存规格采用了新的标准2.0接口,它使用的是串行界面,很像PATA、SATA的转换,并且它支持全双工运行,可同时读写操作,还支持指令队列。相对之下,eMMC是半双工,读写必须分开执行,指令也是打包,在速度上就已经是略逊一筹了,而且UFS芯片不仅传输速度快,功耗也要比eMMC5.0低一半,可以说是日后旗舰手机闪存的理想搭配。目前仅有少数的半导体厂商有提供封装成品,如三星、东芝电子等。

uboot 执行流程

uboot 属于bootloader(引导程序)的一种,是用来引导启动内核的,它的最终目的就是,从flash中读出内核,放到内存中,启动内核。UBOOT需要具有读写flash的能力。

uboot刚开始被放到flash中,板子上电后,会自动把其中的一部分代码拷到内存中执行,这部分代码负责把剩余的uboot代码拷到内存中,然后uboot代码再把kernel部分代码也拷到内存中,并且启动,内核启动后,挂着根文件系统,执行应用程序。

uboot启动主要分为两个阶段,主要在start.s文件中, 
一:第一阶段主要做的是硬件的初始化,包括以下几个方面: 
(1)设置处理器模式为SVC模式,关闭看门狗,屏蔽中断,初始化sdram,设置栈,设置时钟; 
(2)从flash拷贝代码到内存,清除bss段等,bss段是用来存储静态变量,全局变量的,然后程序跳转到start_arm_boot函数,宣告第一阶段的结束。 
二:第二阶段比较复杂,做的工作主要是: 
(1)从flash中读出内核。 
(2)启动内核。 
start_arm_boot的主要流程为,设置机器id,初始化flash,然后进入main_loop,等待uboot命令; 
uboot要启动内核,主要经过两个函数,第一个是s=getenv(“bootcmd”),第二个是run_command(s…),所以要启动内核,需要根据bootcmd环境变量的内容启动,bootcmd环境变量一般指示了从某个flash地址读取内核到启动的内存地址,然后启动bootm。 
uboot启动的内核为uImage,这种格式的内核是由两部分组成:真正的内核和内核头部组成,头部中包括内核中的一些信息,比如内核的加载地址,入口地址。 
uboot在接受到启动命令后,要做的主要是: 
【1】读取内核头部, 
【2】移动内核到合适的加载地址, 
【3】启动内核,执行do_bootm_linux 
do_bootm_linux主要做的为: 
(1)设置启动参数,在特定的地址,保存启动参数,函数分别为setup_start_tag,setup_memory_tag,setup_commandline_tag,setup_end_tag,根据名字我们就知道具体的段内存储的信息,memory中为板子的内存大小信息,commandline为命令行信息, 
(2)跳到入口地址,启动内核 
启动的函数为the_kernel(0,bd->bi_arch_number,bd->bi_boot_param) 
bd->bi_arch_number为板子的机器码,bd->bi_boot_param为启动参数的地址

分析uboot代码

路径: arch/arm/cpu/arm920t/start.S

start.S开始分析uboot启动流程

.globl _start                                //声明_start全局符号,这个符号会被lds链接脚本用到
_start:
b start_code //跳转到start_code符号处,0x00
ldr pc, _undefined_instruction //0x04
ldr pc, _software_interrupt //0x08
ldr pc, _prefetch_abort //0x0c
ldr pc, _data_abort //0x10
ldr pc, _not_used //0x14
ldr pc, _irq //0x18
ldr pc, _fiq //0x20

_undefined_instruction: .word undefined_instruction
//定义_undefined_instruction指向undefined_instruction(32位地址)

_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

.balignl 16,0xdeadbeef

代码会跳转到start_code

start_code:

/*设置CPSR寄存器,让CPU进入管理模式*/
mrs r0, cpsr //读出cpsr的值
bic r0, r0, #0x1f //清位
orr r0, r0, #0xd3 //位或
msr cpsr, r0 //写入cpsr

#if defined(CONFIG_AT91RM9200DK) || defined(CONFIG_AT91RM9200EK)
/*
* relocate exception table
*/

ldr r0, =_start
ldr r1, =0x0 //r1等于异常向量基地址
mov r2, #16
copyex:
subs r2, r2, #1 //减16次,s表示每次减都要更新条件标志位
ldr r3, [r0], #4
str r3, [r1], #4 //将_start标号后的16个符号存到异常向量基地址0x0~0x3c处
bne copyex //直到r2减为0
#endif

#ifdef CONFIG_S3C24X0

/* 关看门狗*/
# define pWTCON 0x53000000
# define INTMSK 0x4A000008 /* Interrupt-Controller base addresses */
# define INTSUBMSK 0x4A00001C
# define CLKDIVN 0x4C000014 /* clock divisor register */

ldr r0, =pWTCON
mov r1, #0x0
str r1, [r0] //关看门狗,使WTCON寄存器=0

/*关中断*/
mov r1, #0xffffffff
ldr r0, =INTMSK
str r1, [r0] //关闭所有中断
# if defined(CONFIG_S3C2410)
ldr r1, =0x3ff
ldr r0, =INTSUBMSK
str r1, [r0] //关闭次级所有中断
# endif

/* 设置时钟频率, FCLK:HCLK:PCLK = 1:2:4 ,而FCLK默认为120Mhz*/
ldr r0, =CLKDIVN
mov r1, #3
str r1, [r0]

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_crit //关闭mmu,并初始化各个bank

#endif

call_board_init_f:
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) //CONFIG_SYS_INIT_SP_ADDR=0x30000f80
bic sp, sp, #7 //sp=0x30000f80
ldr r0,=0x00000000
bl board_init_f

进入第一个C函数:board_init_f()

该函数主要工作是:

清空gd指向的结构体、通过init_sequence函数数组,来初始化各个函数以及逐步填充gd结构体,最后划分内存区域,将数据保存在gd里,然后调用relocate_code()对uboot重定位

(gd是用来传递给内核的参数)

void board_init_f(ulong bootflag) // bootflag=0x00000000
{
bd_t *bd;
init_fnc_t **init_fnc_ptr;
gd_t *id;
ulong addr, addr_sp;
#ifdef CONFIG_PRAM
ulong reg;
#endif

bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_F, "board_init_f");
/* Pointer is writable since we allocated a register for it */
gd = (gd_t *) ((CONFIG_SYS_INIT_SP_ADDR) & ~0x07);

*gd是一个全局变量 *gd指向r8寄存器,所以r8专门提供给gd使用

__asm__ __volatile__("": : :"memory");           //memory:让cpu重新读取内存的数据

memset((void *)gd, 0, sizeof(gd_t)); //将0x30000f80地址上的gd_t结构体清0

gd->mon_len = _bss_end_ofs;
// _bss_end_ofs =__bss_end__ - _start,在反汇编找到等于0xae4e0,所以mon_len等于uboot的数据长度
gd->fdt_blob = (void *)getenv_ulong("fdtcontroladdr", 16, (uintptr_t)gd->fdt_blob);

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr)
//调用init_sequence[]数组里的各个函数
{
if ((*init_fnc_ptr)() != 0) //执行函数,若函数执行出错,则进入hang()
{            hang (); //打印错误信息,然后一直while
}

}

上面的init_sequence[]数组里存了各个函数,比如有:

board_early_init_f():设置系统时钟,设置各个GPIO引脚

timer_init():初始化定时器

env_init():设置gd的成员变量

init_baudrate():设置波特率

dram_init():设置gd->ram_size= 0x04000000(64MB)

addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size;  // addr=0x34000000 
// CONFIG_SYS_SDRAM_BASE: SDRAM基地址,为0X30000000
// gd->ram_size: 等于0x04000000


#if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF))
/* reserve TLB table */
addr -= (4096 * 4); //addr=33FFC000

addr &= ~(0x10000 - 1); // addr=33FF0000,

gd->tlb_addr = addr; //将64kB分配给TLB,所以TLB地址为33FF0000~33FFFFFF
#endif

/* round down to next 4 kB limit */
addr &= ~(4096 - 1); //4kb对齐, addr=33FF0000
debug("Top of RAM usable for U-Boot at: %08lxn", addr);
/*
* reserve memory for U-Boot code, data & bss
* round down to next 4 kB limit
*/

addr -= gd->mon_len; // 在前面分析过gd->mon_len=0xae4e0,
//所以addr=33FF0000 -0xae4e0=33F41B20,

addr &= ~(4096 - 1); //4095=0xfff,4kb对齐, addr=33F41000
//所以分配给uboot各个段的重定位地址为33F41000~33FFFFFF
debug("Reserving %ldk for U-Boot at: %08lxn", gd->mon_len >> 10, addr);

#ifndef CONFIG_SPL_BUILD
addr_sp = addr - TOTAL_MALLOC_LEN; //分配一段malloc空间给addr_sp
//TOTAL_MALLOC_LEN=1024*1024*4,所以malloc空间为33BF1000~33F40FFF

addr_sp -= sizeof (bd_t); //分配一段bd_t结构体大的空间
    bd = (bd_t *) addr_sp; //bd指向刚刚分配出来的bd_t结构体
    gd->bd = bd; // 0x30000f80处的gd变量的成员bd等于bd_t基地址

    addr_sp -= sizeof (gd_t); //分配一个gd_t结构体大的空间
    id = (gd_t *) addr_sp; //id指向刚刚分配的gd_t结构体
    gd->irq_sp = addr_sp; //0x30000f80处的gd变量的成员irq_sp等于gd_t基地址
    addr_sp -= 12;
    addr_sp &= ~0x07;
    ... ...

    relocate_code(addr_sp, id, addr); //进入relocate_code()函数,重定位代码,以及各个符号
    // addr_sp: 栈顶,该栈顶向上的位置用来存放gd->irq_sp、id 、gd->bd、malloc、uboot、TLB(64kb),
    //id: 存放 gd_t结构体的首地址
    // addr: 等于存放uboot重定位地址33F41000
}

34000000 尾地址
TLB空间(64KB)


33FF0000
uboot重定位空间


33F41000
malloc空间4MB


33BF1000
bd_结构体




gd_结构体







addr_sp-=12




addr_sp&=~0x07

gd 变量 gd 结构体


30000f80




3000000 基地址

此时uboot还在flash中运行,然后会进入start.S的relocate_code()里进行uboot重定位 

重定位

relocate_code:
mov r4, r0 /* save addr_sp */ // addr_sp栈顶值
mov r5, r1 /* save addr of gd */ // id值
mov r6, r2 /* save addr of destination */ // addr值:uboot重定位地址

/* Set up the stack */
stack_setup:
mov sp, r4 //设置栈addr_sp
adr r0, _start //在顶层目录下system.map符号文件中找到_start =0,所以r0=0
cmp r0, r6 //判断_start(uboot重定位之前的地址)和addr(重定位地址)是否一样
beq clear_bss /* skip relocation */
mov r1, r6 /* r1 <- scratch for copy_loop */ //r1= addr(重定位地址)
ldr r3, _bss_start_ofs //_bss_start_ofs=__bss_start - _start(uboot代码大小)
add r2, r0, r3 /* r2 <- source end address*/ //r2= uboot重定位之前的结束地址

copy_loop:
ldmia r0!, {r9-r10} /* copy from source address [r0] */
//将r0处的两个32位数据拷到r9-r10中,然后r0+=8

stmia r1!, {r9-r10} /* copy to target address [r1]*/
//将拷出来的两个数据放入r1(重定位地址)处,然后r1+=8

cmp r0, r2 /* until source end address [r2]*/ //判断拷贝的数据是否到结束地址
blo copy_loop

uboot在SDRAM运行,由于代码没修改,**PC**也会跳到0x1e0**(flash地址)**上

新的uboot采用了动态链接地址的方法,在链接脚本uboot.lds中,可以看到这两个段(.rel.dyn、.dynsym):

```c
.rel.dym:{
_rel_dyn_start= .;
*(.rel*)
_rel_dyn_end .;
}
.dynsym:{
_dynsym_start =.;
*(.dynsym)
}

该两个段里,便是保存了各个文件的相对动态信息(.rel.dyn)、动态链接地址的符号(.dynsym)

重定位的剩余代码

#ifndef CONFIG_SPL_BUILD
/*
* fix .rel.dyn relocations
*/

ldr r0, _TEXT_BASE /* r0 <- Text base */ //r0=text段基地址=0
sub r9, r6, r0 /* r9 <- relocation offset */ //r9= 重定位后的偏移值=33F41000
ldr r10, _dynsym_start_ofs /* r10 <- sym table ofs */
//_dynsym_start_ofs =__dynsym_start - _start=0x73608
//所以r10=动态符号表的起始偏移值=0x73608

add r10, r10, r0 /* r10 <- sym table in FLASH */
//r10=flash上的动态符号表基地址=0x73608

ldr r2, _rel_dyn_start_ofs /* r2 <- rel dyn start ofs */
//r2=__rel_dyn_start - _start=0x6b568
//所以r2=相对动态信息的起始偏移值=0x6b568

add r2, r2, r0 /* r2 <- rel dyn start in FLASH */
//r2=flash上的相对动态信息基地址=0x6b568

ldr r3, _rel_dyn_end_ofs /* r3 <- rel dyn end ofs */
// _rel_dyn_end_ofs=__rel_dyn_end - _start=00073608
//所以r3=相对动态信息的结束偏移值=00073608
add r3, r3, r0 /* r3 <- rel dyn end in FLASH */
//r3=flash上的相对动态信息结束地址=0x6b568
fixloop:
ldr r0, [r2] /* r0 <- location to fix up, IN FLASH! */
//以0x20为例,r0=0x6b568地址处的内容= 0x20

add r0, r0, r9 /* r0 <- location to fix up in RAM */
//r0=33F41000+0x20=33F41020

ldr r1, [r2, #4] //r1= 33F41024地址处的内容=0x17
and r7, r1, #0xff
cmp r7, #23 /* relative fixup? */ //0x17=23,所以相等
beq fixrel //跳到:fixerl

cmp r7, #2 /* absolute fixup? */
beq fixabs
/* ignore unknown type of fixup */
b fixnext
fixabs:
/* absolute fix: set location to (offset) symbol value */
mov r1, r1, LSR #4 /* r1 <- symbol index in .dynsym */
add r1, r10, r1 /* r1 <- address of symbol in table */
ldr r1, [r1, #4] /* r1 <- symbol value */
add r1, r1, r9 /* r1 <- relocated sym addr */
b fixnext

fixrel:
/* relative fix: increase location by offset */
ldr r1, [r0] //r1=33F41020地址处的内容=0x1e0
add r1, r1, r9 //r1=0x1e0+33F41000= 33F411e0

fixnext:
str r1, [r0] //改变链接地址里的内容, 33F41020=33F411e0 (之前为0x1e0)
add r2, r2, #8 //r2等于下一个相对动态信息(0x24)的地址
cmp r2, r3 //若没到尾部__rel_dyn_end,便继续执行: fixloop
blo fixloop
#endif

#### **清除bss段**

```c
/*重定位完成后,清除bss段*/
clear_bss:
#ifndef CONFIG_SPL_BUILD
ldr r0, _bss_start_ofs //获取flash上的bss段起始位置
ldr r1, _bss_end_ofs //获取flash上的bss段结束位置
mov r4, r6 /* reloc addr */ //获取r6(SDRAM上的uboot基地址)
add r0, r0, r4 //加上重定位偏移值,得到SDRAM上的bss段起始位置
add r1, r1, r4 //得到SDRAM上的bss段结束位置
mov r2, #0x00000000 /* clear*/

clbss_l:
str r2, [r0] /* clear loop... */ //开始清除SDRAM上的bss段
add r0, r0, #4
cmp r0, r1
bne clbss_l
bl coloured_LED_init
bl red_led_on
#endif
#ifdef CONFIG_NAND_SPL                   //未定义,所以不执行
... ...
#else //执行else

ldr r0, _board_init_r_ofs //r0=flash上的board_init_r()函数地址偏移值
adr r1, _start //0
add lr, r0, r1 //返回地址lr=flash上的board_init_r()函数
add lr, lr, r9 //加上重定位偏移值(r9)后,lr=SDRAM上的board_init_r()函数

/* setup parameters for board_init_r */
mov r0, r5 /* gd_t */ //r0=id值
mov r1, r6 /* dest_addr */ //r1=uboot重定位地址
/* jump to it ... */
mov pc, lr //跳转: board_init_r()函数

_board_init_r_ofs:
.word board_init_r - _start //获取在flash上的board_init_r()函数地址偏移值

#endif

接下来便会进入uboot的board_init_r()函数,该函数会对各个外设初始化、环境变量初始化等

本文始发于微信公众号(墨鱼实验室):uboot 解析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年6月12日11:30:50
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   uboot 解析https://cn-sec.com/archives/396414.html

发表评论

匿名网友 填写信息