uboot源码——汇编阶段的start.S文件

以下内容源于朱有鹏嵌入式课程的学习与整理,如有侵权请告知删除。

一、总结

1、关于阶段的定义

第一阶段,即在内部SRAM运行的阶段,简单地理解为汇编阶段。此阶段主要涉及start.S文件,在cpu/s5pc11x/目录下。第一阶段以ldr pc _start_armboot为结束。

第二阶段,即在DDR中运行的阶段,简单地理解为C语言阶段。此阶段主要涉及start_armboot函数,在uboot/lib_arm/board.c文件的444~908行。

2、第一阶段完成的任务

  • 异常向量表的实现;     
  • 设置进入特权模式,即SVC模式;
  • 检查恢复状态;             
  • IO状态恢复;
  • 关看门狗;                     
  • 一些与SRAM、SROM相关的GPIO设置;
  • 开发板的供电锁存;     
  • 时钟的初始化;
  • DDR的初始化;
  • 串口的初始化;         
  • 重新设置栈空间;
  • uboot的重定位;      
  • 转换表的建立;
  • 使能MMU。

可见uboot的第一阶段初始化了SoC内部的一些部件,初始化DDR并且重定位。

uboot被分割成两部分,即前8kb和整个ubooot,则前8k内容肯定包括第一阶段的操作任务,其中很重要的操作有重定位。

二、start.S文件的解析

uboot的链接脚本的分析见博文uboot的链接脚本u-boot.lds分析。

uboot中整个程序的入口取决于链接脚本中ENTRY声明的地方。uboot的链接脚本中有一句代码“ ENTRY(_start)”,因此_start符号所在的文件就是起始文件,所处的位置就是起始位置。通过使用SI工具查找符号“_start”,发现其所在文件是cpu\s5pc11x\start.S文件。

//省略其他代码
#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED).word 0x2000.word 0x0.word 0x0.word 0x0
#endif.globl _start //入口在这里
_start: b	resetldr	pc, _undefined_instructionldr	pc, _software_interruptldr	pc, _prefetch_abortldr	pc, _data_abortldr	pc, _not_usedldr	pc, _irqldr	pc, _fiq
//省略其他代码

1、start.S所包含的头文件

#include <config.h>
#include <version.h>
#if defined(CONFIG_ENABLE_MMU)
#include <asm/proc/domain.h>
#endif
#include <regs.h>

(1)config.h文件在mkconfig脚本中生成,此文件内容为“#include <configs/x210.h>”。

(2)version.h文件内容是“ #include "version_autogenerated.h" ”。

version_autogenerated.h文件内容为“ #define U_BOOT_VERSION "U-Boot 1.3.4" ”。

version_autogenerated.h这个文件是在主Makefile中自动生成的,相关的生成代码如下。

$(VERSION_FILE):@( printf '#define U_BOOT_VERSION "U-Boot %s%s"\n' "$(U_BOOT_VERSION)" \'$(shell $(CONFIG_SHELL) $(TOPDIR)/tools/setlocalversion $(TOPDIR))' \) > $@.tmp@cmp -s $@ $@.tmp && rm -f $@.tmp || mv -f $@.tmp $@

(3)由于定义了宏CONFIG_ENABLE_MMU,因此包含asm/proc/domain.h,实际包含include\asm-arm\proc-arm\domain.h文件。

2、启动代码的16字节头部

#if defined(CONFIG_EVT1) && !defined(CONFIG_FUSED).word 0x2000.word 0x0.word 0x0.word 0x0
#endif

(1)arm7和arm9的arm指令集,一个字类型是32bit,即4个字节。

(2)此段代码用16个字节填充占位(这些数字貌似可以任意?字节内容后续计算重新填充?这里填充的原因是SD卡需要16字节的校验?)

(3).word是arm汇编的伪指令,表示“当前地址(某个内存地址)的值(这个内存地址所对应的存储单元所存储的数值)为XX”。比如,.word 0x2000表示当前地址的值为0x2000(注意,不是说当前地址编号是0x2000,而是说当前地址所对应的存储单元存储的数值是0x2000),.word _start 表示当前地址的值为_start。

3、构建异常向量表

.globl _start
_start: b	resetldr	pc, _undefined_instructionldr	pc, _software_interruptldr	pc, _prefetch_abortldr	pc, _data_abortldr	pc, _not_usedldr	pc, _irqldr	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 */.global _end_vect
_end_vect:.balignl 16,0xdeadbeef

(1)异常向量表由硬件决定,软件只是参照硬件设计来实现。

(2)但此向量表只是虚有其表,并未做非常细致的异常处理。

(3)复位异常处理代码是b reset,reset是个函数。

reset:/** set the cpu to SVC32 mode and IRQ & FIQ disable*/@;mrs	r0,cpsr@;bic	r0,r0,#0x1f@;orr	r0,r0,#0xd3@;msr	cpsr,r0msr	cpsr_c, #0xd3		@ I & F disable, Mode: 0x13 - SVC   0b1101 0011

注意里面对cpsr这个寄存器的赋值为0b1101_0011。关于cpsr寄存器的位含义,见博客:ARM通识——ARM的7种基本工作模式、37个通用寄存器

(4)上图中的最后一句是让内存16字节对齐,如果不对齐,用0xdeadbeef这个数字填充。

4、一些地址值

_TEXT_BASE:.word	TEXT_BASE/** Below variable is very important because we use MMU in U-Boot.* Without it, we cannot run code correctly before MMU is ON.* by scsuh.*/
_TEXT_PHY_BASE:.word	CFG_PHY_UBOOT_BASE.globl _armboot_start
_armboot_start:.word _start/** These are defined in the board-specific linker script.*/
.globl _bss_start
_bss_start:.word __bss_start.globl _bss_end
_bss_end:.word _end

(1)_TEXT_BASE(4字节)这个内存地址存放的内容为TEXT_BASE(即0xc3e00000)。由uboot源码——链接脚本u-boot.lds分析可知,这个TEXT_BASE就是链接地址。

(2)_TEXT_PHY_BASE(4字节)这个内存地址存放的内容为CFG_PHY_UBOOT_BASE。CFG_PHY_UBOOT_BASE定义在x210_sd.h中,它的值为 MEMORY_BASE_ADDRESS + 0x3e00000,而MEMORY_BASE_ADDRESS的值为0x30000000,因此CFG_PHY_UBOOT_BASE为0x33e00000。其实它就是uboot在DDR中的物理地址。

(3)结合后续的第12点配置MMU,可以明白为什么配置时将uboot的链接地址设置为0xc3e00000,因为它被映射成0x33e00000这个物理地址,而这个物理地址就是uboot在内存中开始存放的地方。参考uboot中的虚拟地址映射_天糊土的博客。

(4)总结,即标签表示地址,.word后面的表示此内存地址中存储的值。

比如_bss_end: .word _end表示地址_bss_end上存放的值是_end。

5、reset中断处理函数

reset:/** set the cpu to SVC32 mode and IRQ & FIQ disable*/@;mrs	r0,cpsr@;bic	r0,r0,#0x1f@;orr	r0,r0,#0xd3@;msr	cpsr,r0msr	cpsr_c, #0xd3		@ I & F disable, Mode: 0x13 - SVC   0b1101 0011

(1)因为0xd3=1101 0011,则cpu设置为SVC模式、arm状态,禁止FIQ、IRQ中断。

(2)整个uboot工作时,cpu一直处于SVC模式。

6、CPU的初始化(设置l1,l2cache和MMU等内容)

cpu_init_crit://此处为条件编译语句,删除了部分代码bl	disable_l2cachebl	set_l2cache_auxctrl_cyclebl	enable_l2cache/** Invalidate L1 I/D*/mov	r0, #0                  @ set up for MCRmcr	p15, 0, r0, c8, c7, 0   @ invalidate TLBsmcr	p15, 0, r0, c7, c5, 0   @ invalidate icache/** disable MMU stuff and caches*/mrc	p15, 0, r0, c1, c0, 0bic	r0, r0, #0x00002000     @ clear bits 13 (--V-) 0010 0000 0000 0000bic	r0, r0, #0x00000007     @ clear bits 2:0 (-CAM) 0000 0000 0000 0111orr	r0, r0, #0x00000002     @ set bit 1 (--A-) Align 0000 0000 0000 0010orr	r0, r0, #0x00000800     @ set bit 12 (Z---) BTB 0000 1000 0000 0000@ 这里明明是bit 11,怎么说是bit 12呢?mcr 	p15, 0, r0, c1, c0, 0

(1)关于bic和orr指令的介绍,见博客汇编指令——bic(位清除)、orr(位或)。

(2)关于代码的细节内容,待写。

7、识别并暂存启动介质选择

        /* Read booting information */ldr	r0, =PRO_ID_BASEldr	r1, [r0,#OMR_OFFSET]bic	r2, r1, #0xffffffc1

(1)启动介质由SoC的OM0:OM5这6个引脚的高低电平决定。

(2) 由#define PRO_ID_BASE 0xE0000000、#define OMR_OFFSET 0x04得到寄存器的地址为0xE000 0004。这个寄存器会根据OM引脚状况,硬件自动设置值。

(3)上面三行代码后,r2存储了一个数字,下面通过该数字进行启动介质的判断。

//条件编译,省略部分代码/* NAND BOOT */cmp	r2, #0x0		@ 512B 4-cyclemoveq	r3, #BOOT_NANDcmp	r2, #0x2		@ 2KB 5-cyclemoveq	r3, #BOOT_NANDcmp	r2, #0x4		@ 4KB 5-cycle	8-bit ECCmoveq	r3, #BOOT_NANDcmp	r2, #0x6		@ 4KB 5-cycle	16-bit ECCmoveq	r3, #BOOT_NANDcmp	r2, #0x8		@ OneNAND Muxmoveq	r3, #BOOT_ONENAND/* SD/MMC BOOT */ //sd卡启动cmp     r2, #0xcmoveq   r3, #BOOT_MMCSD	/* NOR BOOT */cmp     r2, #0x14moveq   r3, #BOOT_NOR//条件编译,省略部分代码/* Uart BOOTONG failed */cmp     r2, #(0x1<<4)moveq   r3, #BOOT_SEC_DEV  ldr	r0, =INF_REG_BASEstr	r3, [r0, #INF_REG3_OFFSET]    

 (4)通过判断r2中的值,来确定是从哪里启动的。

  • 如果r2中的值为0xc,则从SD卡启动,然后把#BOOT_MMCSD(#define  BOOT_MMCSD  0x3)赋给r3。
  • 注意moveq是否执行,得看前一句的cmp比较的结果是否相同,相同则执行,不同则moveq不会执行。

(5)最后两行中,因为有#define INF_REG_BASE 0xE010F000、#define INF_REG3_OFFSET 0x0c,则合成寄存器的地址为0xE010F00C,然后把r3中的值放入这个寄存器中。

8、第一次设置栈

	/** Go setup Memory and board specific bits prior to relocation.*/ldr	sp, =0xd0036000 /* end of sram dedicated to u-boot */sub	sp, sp, #12	/* set stack */ //sp=sp-12mov	fp, #0bl	lowlevel_init	/* go setup pll,mux,memory */

(1)这次设置栈是在SRAM中设置的,因为当前整个代码还在SRAM中运行,此时DDR还未被初始化还不能用。

(2)之所以要初始化栈,是因为接下来调用的lowlevel_init函数中还要调用其他函数。bl跳转时只会将返回地址存储到LR中,但是只有一个LR,所以在lowlevel_init函数中调用其他函数之前要先将LR入栈,否则函数返回时第一层的返回地址(从lowlevel_init函数退出时)就被覆盖掉了。

(3)栈地址0xd0036000是自己指定的,指定的原则就是这块空间只给栈用,不会被别人占用。

(4)关于sub指令的解释,见ARM的汇编指令【各种指令的解释】。

9、lowlevel_init函数

lowlevel_init函数在uboot\board\samsung\x210\lowlevel_init.S文件中。 

(1)检查复位状态

/* check reset status  */ldr	r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)ldr	r1, [r0]bic	r1, r1, #0xfff6ffffcmp	r1, #0x10000beq	wakeup_reset_precmp	r1, #0x80000beq	wakeup_reset_from_didle

因为复杂CPU支持多种复位状态(冷上电、休眠复位等),因此在复位代码中检查复位状态,判断到底是哪一种。冷上电时DDR需要初始化,而休眠状态下复位不需要再次初始化DDR。

(2)IO状态恢复

	/* IO Retention release */ //IO状态恢复ldr	r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET)ldr	r1, [r0]ldr	r2, =IO_RET_RELorr	r1, r1, r2str	r1, [r0]

(3)关看门狗

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

(4)SRAM、SROM相关的GPIO设置

	/* SRAM(2MB) init for SMDKC110 *//* GPJ1 SROM_ADDR_16to21 */ldr	r0, =ELFIN_GPIO_BASEldr	r1, [r0, #GPJ1CON_OFFSET]bic	r1, r1, #0xFFFFFFldr	r2, =0x444444orr	r1, r1, r2str	r1, [r0, #GPJ1CON_OFFSET]ldr	r1, [r0, #GPJ1PUD_OFFSET]ldr	r2, =0x3ffbic	r1, r1, r2str	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, r2str	r1, [r0, #GPJ4CON_OFFSET]ldr	r1, [r0, #GPJ4PUD_OFFSET]ldr	r2, =(0x3<<8)bic	r1, r1, r2str	r1, [r0, #GPJ4PUD_OFFSET]/* CS0 - 16bit sram, enable nBE, Byte base address */ldr	r0, =ELFIN_SROM_BASE	/* 0xE8000000 */mov	r1, #0x1str	r1, [r0]

(5)开发板的供电锁存

	/* 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	orr	r1, r1, #0x1	str	r1, [r0]

(6)判断当前代码执行位置(判断在SRAM还是DDR中)

	/* 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, =0xff000fffbic	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 system clock */      //如果是冷启动,则要进行时钟的初始化bl system_clock_init/* Memory initialize */     //如果是冷启动,则要进行内存的初始化bl mem_ctrl_asm_init1:                            //如果是休眠恢复,则忽略上面两个操作,进入这里/* for UART */bl uart_asm_init        //初始化串口打印“O”bl tzpc_init           //trust zone的初始化

为什么要判断?因为BL1在SRAM中有一份,在DDR中也有一份。如果是冷启动,则当前代码是在SRAM中运行的BL1;如果是低功耗状态的复位,则当前代码是在DDR中运行的。判断的目的是指导后面代码的运行。比如在lowlevel_init.S中判定当前代码的运行地址,就是为了确定是否执行时钟初始化和初始化DDR的代码。如果当前代码是在SRAM中,说明是冷启动,则需要初始化时钟和DDR;如果当前代码是在DDR中,说明是热启动,则时钟和DDR都不用再次初始化。

(7)时钟初始化函数:system_clock_init

代码见博客:uboot中系统时钟初始化函数:system_clock_init

(8)内存的初始化:mem_ctrl_asm_init

  • 该函数在uboot/cpu/s5pc11x/s5pc110文件中。该函数和裸机中初始化DDR代码是一样的,实际裸机中初始化DDR的代码就是从这里抄的。见博客SDRAM——X210的SDRAM的初始化_天糊土的博客
  • uboot配置值中有一个和裸机中讲的不一样,即DMC0_MEMCONFIG_0。它在裸机中配置值为0x20E01323,在uboot中配置为0x30F01323。
  • 在裸机中DMC0的256 MB内存地址范围是0x20000000-0x2FFFFFFF;在uboot中DMC0的256MB内存地址范围为0x30000000-0x3FFFFFFF。
  • DMC0上允许的地址范围是20000000-3FFFFFFF(一共是512MB),而实际只接了256MB物理内存,SoC允许我们给这256MB挑选地址范围。
  • 在uboot中,可用的物理地址范围为:0x30000000-0x4FFFFFFF,一共512MB,其中30000000-3FFFFFFF为DMC0,40000000-4FFFFFFF为DMC1。
  • 注意条件编译的条件,配置头文件中考虑了不同时钟配置下的内存配置值,主要目的是让不同时钟需求的客户都能找到合适自己的内存配置值。

(9)串口初始化:打印一个“O”

/** 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 UARTldr	r0, =ELFIN_GPIO_BASEldr	r1, =0x22222222str   	r1, [r0, #GPA0CON_OFFSET]ldr     r1, =0x2222str     r1, [r0, #GPA1CON_OFFSET]// HP V210 use. SMDK not use.
#if defined(CONFIG_VOGUES)ldr    r1, =0x100str    r1, [r0, #GPC0CON_OFFSET]ldr    r1, =0x4str    r1, [r0, #GPC0DAT_OFFSET]
#endifldr	r0, =ELFIN_UART_CONSOLE_BASE		@0xEC000000mov	r1, #0x0str	r1, [r0, #UFCON_OFFSET]str	r1, [r0, #UMCON_OFFSET]mov	r1, #0x3str	r1, [r0, #ULCON_OFFSET]ldr	r1, =0x3c5str	r1, [r0, #UCON_OFFSET]ldr	r1, =UART_UBRDIV_VALstr	r1, [r0, #UBRDIV_OFFSET]ldr	r1, =UART_UDIVSLOT_VALstr	r1, [r0, #UDIVSLOT_OFFSET]ldr	r1, =0x4f4f4f4fstr	r1, [r0, #UTXH_OFFSET]		@'O'mov	pc, lr

​(10)pop {pc}返回前通过串口打印“K”

/* check reset status  */ldr	r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET)ldr	r1, [r0]bic	r1, r1, #0xfffeffffcmp	r1, #0x10000beq	wakeup_reset_pre/* ABB disable */ldr	r0, =0xE010C300orr	r1, r1, #(0x1<<23)str	r1, [r0]/* Print 'K' */   //打印一个字符Kldr	r0, =ELFIN_UART_CONSOLE_BASEldr	r1, =0x4b4b4b4bstr	r1, [r0, #UTXH_OFFSET]pop	{pc}

​至此,lowlevel_init函数结束。

10、第二次设置栈

/** Go setup Memory and board specific bits prior to relocation.*/
//第一次设置栈(在SRAM中)ldr	sp, =0xd0036000 /* end of sram dedicated to u-boot */sub	sp, sp, #12	/* set stack */mov	fp, #0bl	lowlevel_init	/* go setup pll,mux,memory *//* To hold max8698 output before releasing power on switch,* set PS_HOLD signal to high*/
//电源锁存ldr	r0, =0xE010E81C  /* PS_HOLD_CONTROL register */ldr	r1, =0x00005301	 /* PS_HOLD output high	*/str	r1, [r0]
//第二次设置栈(在DDR中)/* get ready to call C functions */ldr	sp, _TEXT_PHY_BASE	/* setup temp stack pointer */sub	sp, sp, #12mov	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* is running in ram.*/
//判断当前位置是否在DDR中,不在的话需要重定位ldr	r0, =0xff000fffbic	r1, pc, r0		/* r0 <- current base addr of code */ldr	r2, _TEXT_BASE		/* r1 <- original base addr in ram */bic	r2, r2, r0		/* r0 <- current base addr of code */cmp     r1, r2                  /* compare r0, r1                  */beq     after_copy		/* r0 == r1 then skip flash copy   */

(1)第一次设置栈,是在调用lowlevel_init函数前。那时程序在SRAM中执行,所以在SRAM中分配了一部分内存作为栈。

(2)第二次因为DDR已经被初始化,因此要把栈挪移到DDR中,所以要重新设置栈。实际设置的栈的地址是33E00000,刚好在uboot的代码段的下面紧挨着。因为是满减栈,所以栈向下增长。注意uboot基地址在0x33e00000,向上增长。

(3)为什么要再次设置栈?因为DDR已经初始化了,已经有大片内存可以用了,没必要再把栈放在SRAM中;原来SRAM中内存大小空间有限,栈放在那里要注意不能使用过多,否则栈会溢出。我们及时将栈迁移到DDR中也是为了尽可能避免使用栈时的诸多不便。

(4)奇怪,这里为何还有一个电源锁存,不是在lowlevel_init函数中完成了么?

11、再次判断运行地址是在SRAM中还是DDR中

(1)上次判断运行地址是在SRAM中还是在DDR中,是为了决定是否要执行初始化时钟和DDR的代码,本次判断是为了决定是否进行uboot的重定位。

  • 冷启动时,uboot的前一部分(16kb或者8kb)开机自动从SD卡加载到SRAM中运行,uboot的第二部分(其实第二部分是整个uboot)还躺在SD卡的某个扇区开头的N个扇区中。此时uboot的第一阶段即将结束(第一阶段该做的事基本做完了),结束之前要把第二部分加载到DDR中链接地址处(0x33e00000),这个加载过程就叫重定位。

(2)如下图。

  • D0037488这个内存地址在SRAM中,这个地址中的值是被硬件自动设置的。硬件根据我们实际电路中SD卡在哪个通道中,会将这个地址中的值设置为相应的数字。譬如我们从SD0通道启动时,这个值为EB000000;从SD2通道启动时,这个值为EB200000。
  • 我们确定是从MMCSD启动,因此最终跳转到mmcsd_boot函数中去执行重定位动作,即把SD卡中相应的内容复制到内存中。
  • 真正的重定位是通过调用movi_bl2_copy函数完成的,在uboot/cpu/s5pc11x/movi.c中。此函数包含copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT,CFG_PHY_UBOOT_BASE, 0)函数。其中参数2表示通道2;MOVI_BL2_POS是uboot的第二部分在SD卡中的开始扇区,这个扇区数字必须和烧录uboot时烧录的位置相同;MOVI_BL2_BLKCNT是uboot的长度占用的扇区数;CFG_PHY_UBOOT_BASE是重定位时将uboot的第二部分复制到DDR中的起始地址(33E00000)。

12、配置MMU

  • MMU(memory management unit),内存管理单元。它实际上是SOC中一个硬件单元,它的主要功能就是实现虚拟地址到物理地址的映射。
  • MMU单片在CP15协处理器中进行控制,也就是说要操控MMU进行虚拟地址映射,方法就是对cp15协处理器的寄存器进行编程。

​13、第三次设置栈

(1)这次设置栈还是在DDR中,之前虽然已经在DDR中设置过一次栈了,但是本次设置栈的目的是将栈放在比较合适(安全,紧凑而不浪费内存)的地方。

(2)我们实际将栈设置在uboot起始地址上方2MB处,这样安全的栈空间大小是:2MB-uboot大小-0x1000=1.8MB左右。这个空间既没有太浪费内存,又足够安全。

14、清理bss

注意表示bss段的开头和结尾地址的符号是从链接脚本u-boot.lds得来的。

15、跳转到start_armboot函数

  • start_armboot函数在uboot/lib_arm/board.c中,这是一个C语言实现的函数。这个函数就是uboot的第二阶段。
  • “ldr pc _start_armboot”这句代码的作用就是将uboot第二阶段执行的函数的地址传给pc,实际上就是使用一个远跳转直接跳转到DDR中的第二阶段开始地址处。
  • 远跳转的含义就是这句话加载的地址和当前运行地址无关,而和链接地址有关。因此这个远跳转可以实现从SRAM中的第一阶段跳转到DDR中的第二阶段。
  • 这里这个远跳转就是uboot第一阶段和第二阶段的分界线。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/460782.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习算法之旅

在理解了我们需要解决的机器学习问题之后&#xff0c;我们可以思考一下我们需要收集什么数据以及我们可以用什么算法。本文我们会过一遍最流行的机器学习算法&#xff0c;大致了解哪些方法可用&#xff0c;很有帮助。 机器学习领域有很多算法&#xff0c;然后每种算法又有很多延…

Android Handler的使用方法

如何让程序5秒钟更新一下Title.首先我们看一下习惯了Java编程的人&#xff0c;在不知道Handler的用法之前是怎么样写的程序,代码如下所示: package com.example.androidhandletest; import java.util.Timer;import java.util.TimerTask; import android.os.Bundle;import andro…

windows 下查看进程占用

2019独角兽企业重金招聘Python工程师标准>>> //查找出占用8086端口进程的ID netstat -nao | findstr8086 //本机输出效果为: TCP 0.0.0.0:8086 0.0.0.0:0 LISTENING 804 //很显然&#xff0c;进程ID是804 //找出ID为804的进程名 …

MySQL数据库增删改查

常用的数据类型&#xff1a; int&#xff1a;整数类型&#xff0c;无符号的范围【0&#xff0c;2^32-1】&#xff0c;有符号【-2^31,2^31-1】 float&#xff1a;单精度浮点&#xff0c;4字节64位 double&#xff1a;双精度浮点&#xff0c;8字节64位 char&#xff1a;固定长…

chmod的理解

ll file 共有是十位第一位&#xff1a;如果是 - 表示它是文件第一位&#xff1a;如果是d 表示它是目录剩下的333 分别表示 属主u属组g其他用户o所以如下&#xff1a;转载于:https://blog.51cto.com/zlong37/1567472

中国象棋程序的设计与实现(五)--回答CSDN读者的一些问题

最近写了很多文章&#xff0c;同时&#xff0c;也上传了很多免积分的FansUnion原创的优质资源&#xff0c;有兴趣的同学可以看来我的CSDN博客瞧瞧 http://blog.csdn.net/FansUnion。近期&#xff0c;收到了不少读者的评论、反馈、留言。对于其中的一些问题&#xff0c;我想专门…

第九周项目6-穷举法之年龄几何

张三、李四、王五、刘六的年龄成一等差数列&#xff0c;他们四人的年龄相加是26&#xff0c;相乘是880&#xff0c;求以他们的年龄为前4项的等差数列的前20项。 构建代码&#xff1a; /**Copyright (c) 2014,烟台大学计算机学院*All gight reserved.*文件名称&#xff1a;temp.…

JavaScript操作大全整理(思维导图七--字符串函数)

7. JavaScript 字符串函数 转载于:https://www.cnblogs.com/yuxia/p/3360824.html

进入shell的两种方法

以下内容源于C语言中文网的学习与整理&#xff0c;非原创&#xff0c;如有侵权请告知删除。 方法一&#xff1a;在图形界面中打开终端 在图形界面下&#xff0c;进入 Shell 的方法是使用 Linux 桌面环境中的终端模拟包&#xff0c;也就是我们常说的终端&#xff0c;这样在图形桌…

SQL Server 固定角色

1、 查看固定服务器角色 execute sp_helpsrvrole; 管理&#xff1a; execute master..sp_addsrvrolemember logingNameneeky rolenamesysadmin; go execute master..sp_dropsrvrolemember logingNameneeky rolenamesysadmin; go 2、 查看固定数据库角色成员 execute sp_helprol…

如何在我们项目中利用开源的图表(js chart)

最近觉得应该把自己在技术上的一些心得记录在博客里面跟大家分享&#xff0c;一起讨论&#xff0c;一起成长&#xff01; 这篇随笔主要为介绍chart在项目中的运用&#xff0c;因为在我们看到一些开源的chart时候&#xff0c;是使用纯js 或者建立在一些插件(例如&#xff1a;jqu…

cobbler get-loaders 通过代理下载

2019独角兽企业重金招聘Python工程师标准>>> cobbler 版本是2.6.3&#xff0c;可以通过系统环境变量设置proxy&#xff0c;支持 HTTP_PROXY、HTTPS_PROXY、FTP_PROXY 三个变量。 cobbler 版本是2.6.6时&#xff0c;需要从/etc/cobbler/settings 中增加proxy_url_ex…

分析busybox的源码

以下内容源于网络资源的学习与整理&#xff0c;如有侵权请告知删除。 参考博客 busybox详解_linuxarmsummary的博客-CSDN博客 一、前言 因为uboot给内核传参的bootargs中有“init/linuxrc”这个项目&#xff0c;而由前面的分析可知/linuxrc这个二进制文件位于根文件系统中&…

彻底解决zend studio 下 assignment in condition警告

最近在mac系统下安装zend studio作为php开发工具&#xff0c;把以前的代码导入&#xff0c;发现项目中有很多 “assignment in condition”的警告&#xff0c;造成原因是在条件判断的if、while中使用了如下类似的做法&#xff1a; if ($res $other)while (($row $res->fet…

c# 连接各种数据库 Access、Server等

1.C#连接连接Access程序代码:usingSystem.Data;usingSystem.Data.OleDb;..stringstrConnection"ProviderMicrosoft.Jet.OleDb.4.0;";strConnection"Data SourceC:BegASPNETNorthwind.mdb";OleDbConnection objConnectionnewOleDbConnection(strConnection)…

〖Linux〗Kubuntu设置打开应用时就只在打开时的工作区显示

有没有遇到一种情况&#xff1a; 在工作区1打开了应用程序Google Chrome&#xff1b; 这个时间感觉它打开速度比较慢&#xff0c;就快捷键切换到工作区2了&#xff1b; 结果这个时候&#xff0c;Google Chrome就直接在工作区2打开&#xff0c;多不爽&#xff1f;&#xff01; &…

搭建Spring MVC 4开发环境八步走

Spring MVC作为SpringFrameWork的产品&#xff0c;自诞生之日&#xff0c;就受到广泛开发者的关注&#xff0c;如今Spring MVC在Java中的发展可谓是蒸蒸日上&#xff0c;如今如果再有开发者说&#xff0c;不了解Spring MVC&#xff0c;或许就被人笑掉大牙。煽情的话就不说了&am…

address already in use: jvm_bind

这是用Myeclipse写网上书店时遇到的错误&#xff0c;错误的意思是8080端口被占用&#xff0c;解决问题的方式是找到占用8080端口的进程关闭就可以了&#xff0c; 在dos下输入netstat -ano 即可查看所有的 然后根据PID关闭进程&#xff0c;在任务管理器中&#xff0c;找到进转载…

【linux】学习6

鸟哥13章的东西 shell script速度较慢&#xff0c;适合用于系统管理&#xff0c;但不适合处理大量数值运算 var$((运算内容)) 可以用来做变量的加减乘除求余运算 total$(($firstnum*$secnu)) declare -i total"$firstnum*$secnu" 上面两句功能一样&#xff0c;建议用…

SCCM2012 R2集成WSUS服务器-4:部署软件更新组

在之前的文章中&#xff0c;我们已经创建好的软件更新组&#xff0c;也下载好了补丁&#xff0c;下面我们来部署这些补丁。右击软件更新组&#xff0c;选择”部署“&#xff0c;如图。进入到部署软件更新向导&#xff0c;输入部署的名称&#xff0c;并指定部署的集合。如图。在…