uboot学习之三-----uboot启动第一阶段--start.S之二
start.s启动第一阶段
----------------------------------------------------------------------------------------------------------------------------------------------------------
①设置CPU运行SVC模式,禁止IRQ和FIQ
②设置L2 L1 cache 和MMU
③读取启动信息
④读取启动方式
⑤第一次设置栈,因为要调用lowlevel_init.S,其内部需要再次调用函数
----------lowlevel_init.S----------
一:检查复位状态
二:IO状态恢复,关看门狗,SRAM和SROM相关gpio设置,供电锁存
三:来判定当前运行在SRAM(不相等)中还是DDR(相等)中,然后来决定是否跳过是时钟初始化部分和DDR 初始化
四:system_clock_init 初始化系统时钟
五:mem_ctrl_asm_init 初始化内存
六:uart_asm_init这个函数用来初始化,初始化完之后通过串口发送一个大写的O,这里是为了做阶段性的调试
七:tzpc_inittrustzone,可信任区域的初始化
八: pop {pc}返回前通过串口打印"K"
-----------返回start.S-----------
⑥第二次设置栈(DDR中的栈)
⑦再次判断当前地址以决定是否重定位
⑧虚拟地址映射
⑨第三次设置栈
⑩清bss
⑪ldr pc, _start_armboot
----------------------------------------------------------------------------------------------------------------------------------------------------
start.S中真正的复位代码:
①设置CPU运行SVC模式,禁止IRQ和FIQ
/* * the actual reset code */ reset: /* * set the cpu to SVC32 mode and IRQ & FIQ disable */ msr cpsr_c, #0xd3 @ I & F disable, Mode: 0x13 - SVC
msr cpsr_c, #0xd3 代表:将CPU设置为禁止IRQ,FIQ, ARM状态,SVC模式。
其实我们arm CPU默认的就是SVC模式的,但是这里还是用软件将其置为SVC模式,整个uboot也一直工作在SVC模式。用软件来设置只是为了不管硬件的预设。
②设置L2 L1 cache 和MMU(与CPU有关,不用去细看,了解即可,抓大放小)
一:L2 cache
(1)bl disable_l2cache //禁止L2 cache
(2)bl set_l2cache_auxctrl_cycle //也是L2 cache的设置
(3)bl enable_l2cache //使能L2 cache
二:刷新L1的icache和dcache
/* * Invalidate L1 I/D */ mov r0, #0 @ set up for MCR mcr p15, 0, r0, c8, c7, 0 @ invalidate TLBs mcr p15, 0, r0, c7, c5, 0 @ invalidate icache
三:关闭MMU
为什么要关闭MMU呢?因为一开始还没有做虚拟地址映射,现在还不能操作相关的地址,等我们后面需要的时候再打开。
/* * 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
③读取启动信息
/* Read booting information */ ldr r0, =PRO_ID_BASE ldr r1, [r0,#OMR_OFFSET] bic r2, r1, #0xffffffc1
查询数据手册,并未查到0XE0000004,三星没有给出这方面的资料。
(1)根据这个代码表示,这个寄存器存了一个很重要的信息,就是OMPin的信息,从哪里启动是由OM5:0M0这六个引脚的高低电平决定的。实际上在210内部有一个寄存器这个寄存器的地址是0XE00000004,这个寄存器的值是硬件根据OM引脚的设置而自动设置的,这个值反映的就是OM引脚的接法,也就是真正的启动介质是啥,
(2)我们代码中可以读取这个寄存器的值,来确定我们当前选中的启动介质是nand SD 还是inand 或者其他的。但是这个寄存器是被限制的,我们拿不到很底层的内容,只有三星的工程师才有。
(3)start.S经过上面几步,r2就是记录了一个数,这个数字等于某个特定值时表示从哪个部分启动...
④读取启动方式
/* NAND BOOT */ cmp r2, #0x0 @ 512B 4-cycle 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
例如从SD卡启动: moveq r3, #BOOT_MMCSD 这个在SD启动时,会被执行,因此执行完这一段代码后r3中存储了0xc,以后备用
⑤第一次设置栈,因为要调用lowlevel_init.S,其内部需要再次调用函数
/* * 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 */ mov fp, #0 bl 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] /* get ready to call C functions */ ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer */ sub sp, sp, #12 mov fp, #0 /* no previous frame, so fp=0 */
(1)这里是第一次设置栈,这次设置栈是在SRAM中设置的,因为当前整个代码还在SRAM中运行,此时DDR还未初始化还不能用,这是只有内部SRAM可以用。这个栈的地址0xd0036000,指定的原则是这块代码只能给栈用,不会被别人占用。
(2)我们在调用函数前初始化栈,主要原因是在被调用函数的内部还要再次调用函数,而bl只会将返回地址存储到lr中,但是我们只有一个lr,所以在第二层调用函数前要先将lr入栈,否则函数返回时,第一层地址就丢了。
接下来部分的代码都是lowevel_init.S
使用sourceinsight的搜索功能找到lowlevel_init函数真正的地方在/uboot/board/samsung/x210/lowevel_init.S中,
.globl lowlevel_init lowlevel_init: push {lr} /* check reset status */ ldr r0, =(ELFIN_CLOCK_POWER_BASE+RST_STAT_OFFSET) ldr r1, [r0] bic r1, r1, #0xfff6ffff cmp r1, #0x10000 beq wakeup_reset_pre cmp r1, #0x80000 beq wakeup_reset_from_didle
一:检查复位状态
(1)复杂CPU允许多种复位情况,譬如直接冷上电,热启动,睡眠状态下的唤醒,这些情况都属于复位,所以我们在复位代码中要去检测复位状态,来判断到底是哪种情况。
(2)判断哪种复位的意义在于:冷上电时,DDR是需要初始化,而热启动和睡眠状态的唤醒时不需要初始化DDR的。
/* IO Retention release */ ldr r0, =(ELFIN_CLOCK_POWER_BASE + OTHERS_OFFSET) ldr r1, [r0] ldr r2, =IO_RET_REL orr r1, r1, r2 str r1, [r0]
二:IO状态恢复
IO状态复位和检查复位状态和主线启动代码无关,
/* Disable Watchdog */ ldr r0, =ELFIN_WATCHDOG_BASE /* 0xE2700000 */ mov r1, #0 str r1, [r0]
关看门狗
/* SRAM(2MB) init for SMDKC110 */ /* 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]
一些SRAM和SROM相关的GPIO设置
与主线程无关不用管
/* 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]
供电锁存的代码。
三:来判定当前运行在SRAM(不相等)中还是DDR(相等)中,然后来决定是否跳过是时钟初始化部分和DDR 初始化
/* when we already run in ram, we dont need to relocate U-Boot. * and actually, memory controller must be configured before U-Boot * is running in ram. */ ldr r0, =0xff000fff 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 system clock */ bl system_clock_init /* Memory initialize */ bl mem_ctrl_asm_init 1: /* for UART */ bl uart_asm_init
(1) 判断当前代码执行的位置,110-115行,判断当前的代码是否需要重定位,判断当前代码是在SRAM中还是在DDR中,
为什么需要这个判断?
原因1:BL1(uboot的前一部分)是在SRAM中有一份,在DDR中也有一份,因此如果是冷启动,当前的代码应该是在SRAM中运行的BL1,如果是低功耗状态下的复位这时候 应该就是在DDR中运行的。
原因2:我们判定当前运行代码的地址是有用的,可以指导后面代码的运行。譬如在lowlevel_init.S中,判定当前代码的运行地址,就是为了确定要不要执行时钟初始化和DDR初始化的代码。如果当前代码在SRAM中,说明是冷启动,那么时钟和DDR初始化都要执行,如果是DDR中运行,说明是热启动,就不需要执行时钟和DDR的初始化。
(2)bic r1, pc, r0 /* r0 <- current base addr of code */
将pc的值中的某些比特位清零,剩下一些特殊的比特位赋值给r1(r0中为1的那些位清零)相当于 r1 = pc & ~(ff000fff)
(3) ldr r2, _TEXT_BASE /* r1 <- original base addr in ram */ 链接地址加载到r2,然后将r2的相应位清零,剩下特定位。
(4)最后比较r1和r2
总结:读取当前运行地址和连接地址,然后处理两个地址后对比是否相等,来判定当前运行在SRAM(不相等)中还是DDR(相等)中,然后来决定是否跳过是时钟初始化部分和DDR 初始化
四:system_clock_init 初始化系统时钟
搜索到在当前代码的第205行,一直到第385行。这个初始化时钟比较完整,比较详细,在x210_sd.h中300行到428行,都是和时钟相关的配置值。这些宏定义就决定了210的时钟配置值是多少。如果移植时需要更改CPU的时钟设置,根本不需要动代码,只是需要在x210_sd.h中去更改宏定义就好了。(待完善,时钟部分详细的分析)
五:mem_ctrl_asm_init 初始化内存
这个函数就是用来初始化DDR动态内存的,函数的位置在uboot/cpu/s5pc11x/s5pc110/cpu_init.S
总结:这个函数和裸机中的DDR初始化代码和这里一样。配置值是在x210_sd.h中,配置了。
配置值中其他配置值参考裸机中的解释。
有一个不一样, DMC0_MEMCONFIG_0
裸机中配置为0X20E01323;在uboot中配置为0x30F01313;关键是前面是20还是30;这个配置不同导致结果不同,在裸机中,DMC0的256MB内存地址范围是0X20000000-0X2FFFFFFF,而在uboot中内存地址范围为0X30000000-0X3FFFFFFF;
(2)之前在裸机中我们配置为2开头的地址,当时并没有讲3开头的地址,从分析九鼎移植的uboot可以看出:DMC0允许的地址范围为0X20000000-0X3FFFFFFF(一共是512MB),而我们实际直接了256MB物理内存,SOC允许我们给这256MB挑选地址范围。
总结:在uboot中,可用的物理地址范围为:0X30000000-0X4FFFFFFF;一共512MB,其中0X30000000-0X3FFFFFFF为DMC0,0X40000000-0X4FFFFFFF为DMC1
(3)我们需要的内存配置值在x210_sd.h的438行到468行之间,注意条件编译的条件,配置头文件中考虑了不同时钟配置下的内存配置值。这样的主要目的是让不同时钟需求的客户都能找到合适自己的内存的配置值。
(4)uboot中DMC0和DMC1都工作了,所以在裸机中只要把uboot配置值和配置代码全部移植过去,应该是能够让DMC0和DMC1都工作的。
六:uart_asm_init
这个函数用来初始化,初始化完之后通过串口发送一个大写的O,这里是为了做阶段性的调试,lowlevel_init.S中的392行到432行,
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] // 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' mov pc, lr
七:tzpc_init
trustzone,可信任区域的初始化,可以搜索什么叫trustzone,ARM在2003年推出了trustzone;
八: pop {pc}
返回前通过串口打印"K"
如果lowlevel_init.S执行完了如果没错那么就会打印OK字样,这应该是uboot中输出的最早的字样,
大总结:lowlevel_init.S总共做了哪些事情?
lowlevel_init.S做了哪些事情:
检查复位状态,IO恢复,关看门狗,开发板供电锁存,判断代码运行地址,时钟初始化,DDR初始化, 串口初始化并打印O,tzpc初始化,打印'K'的字样,
这里面值得关注的:关看门狗,开发板供电锁存,判断代码运行地址,时钟初始化,DDR初始化,打印'K'的字样。
从这里回到start.S
⑥第二次设置栈(DDR中的栈)
(1)再一次开发板供电锁存(start.S中第289-294)
/* 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]
这一次设置供电锁存无意义。但是没错。
(2)第二次设置栈; 第一次设置栈的时候是在上面第⑤(284-287行)点,那时候DDR尚未初始化,程序是在SRAM中运行的,所以在SRAM中分配了一部分内存作为栈,这一次因为DDR已经初始化了(在lowlevel_init分析的: 五:mem_ctrl_asm_init 初始化内存这个函数就是用来初始化DDR动态内存的,函数的位置在uboot/cpu/s5pc11x/s5pc110/cpu_init.S),因此要把栈挪移到DDR中,所以要重新设置栈,这是第二次设置栈了(在start.S中296-299行)
/* get ready to call C functions */ ldr sp, _TEXT_PHY_BASE /* setup temp stack pointer */ sub sp, sp, #12 mov fp, #0 /* no previous frame, so fp=0 */
_TEXT_PHY_BASE 实际值 0x33e00000,这里栈的地址是刚好在uboot的下面紧挨着,
(3)为什么要第二次设置栈?因为DDR已经初始化了,已经有一大片的内存已经初始化了,没必要再把栈放在SRAM中,SRAM中的栈比较小,内存空间有限,栈放在这里,不能使用过多的栈,否则会溢出,SRAM外部什么都没有,溢出后果不堪设想,所以将栈迁移到DDR来,为了避免使用栈的时候会溢出。
⑦再次判断当前地址以决定是否重定位
在start.S中的301-310行
/* when we already run in ram, we dont need to relocate U-Boot. * and actually, memory controller must be configured before U-Boot * is running in ram. */ ldr r0, =0xff000fff 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 after_copy /* r0 == r1 then skip flash copy */
(1)再次用相同的代码判断运行地址是在SRAM中还是在DDR中,不过本次判断的目的不同(上次判断是为了决定是否要初始化时钟和DDR,也就是上面lowlevel_init.S的第三点)这次判断是为了决定uboot是否需要重定位
(2)冷启动时,uboot的前一部分是16kb或者8kb,开机自动从SD卡加载到SRAM中,正在运行,uboot第二部分(整个uboot)还在SD卡中的某个扇区开头的N个扇区中,本次第一阶段已经差不多结束了,uboot第一阶段的事基本做完了,结束之前,要把第二部分加载到DDR中链接地址处也就是0x33e00000,整个加载的过程就叫---重定位。
如果需要重定位怎么重定位?
start.S第312-354行:
#if defined(CONFIG_EVT1) /* If BL1 was copied from SD/MMC CH2 */ ldr r0, =0xD0037488 ldr r1, [r0] ldr r2, =0xEB200000 cmp r1, r2 beq mmcsd_boot #endif ldr r0, =INF_REG_BASE 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 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
(1)0xD0037488这个内存地址在SRAM中,这个地址中的值是被硬件自动设置的,硬件根据我们实际电路中,SD卡在哪个通道中会将这个地址中的值设置为相应的数字,譬如我们从sd0启动时,这个值为EB000000,如果是从sd2启动时,这个值为EB200000;如果相等就是从sd2来启动的;
(2)我们在start.S的第260行确定了从MMCSD启动(也是上面start.S分析中的第④点),将其放在r3里,然后又在278行将#BOOT_MMCSD写入了INF_REG3寄存器中存储着,然后又在第322将其读出来,再和#BOOT_MMCSD比较,确定是从MMCSD启动,最终跳转到mmcsd_boot函数中执行重定位,
mmcsd_boot: #if DELETE ldr sp, _TEXT_PHY_BASE sub sp, sp, #12 mov fp, #0 #endif bl movi_bl2_copy b after_copy
这个函数中,先是已经删除了设置栈,因为在其他地方已经设置了,然后跳转到了movi_bl2_copy这个函数,这个函数是一个C语言的函数,在uboot/cpu/s5pc11x/Movi.c中
typedef u32(*copy_sd_mmc_to_mem)
(u32 channel, u32 start_block, u16 block_size, u32 *trg, u32 init);
void movi_bl2_copy(void) { ulong ch; #if defined(CONFIG_EVT1) ch = *(volatile u32 *)(0xD0037488); copy_sd_mmc_to_mem copy_bl2 = (copy_sd_mmc_to_mem) (*(u32 *) (0xD0037F98)); #if defined(CONFIG_SECURE_BOOT) ulong rv; #endif #else ch = *(volatile u32 *)(0xD003A508); copy_sd_mmc_to_mem copy_bl2 = (copy_sd_mmc_to_mem) (*(u32 *) (0xD003E008)); #endif u32 ret; if (ch == 0xEB000000) { ret = copy_bl2(0, MOVI_BL2_POS, MOVI_BL2_BLKCNT, CFG_PHY_UBOOT_BASE, 0); #if defined(CONFIG_SECURE_BOOT) /* do security check */ rv = Check_Signature( (SecureBoot_CTX *)SECURE_BOOT_CONTEXT_ADDR, (unsigned char *)CFG_PHY_UBOOT_BASE, (1024*512-128), (unsigned char *)(CFG_PHY_UBOOT_BASE+(1024*512-128)), 128 ); if (rv != 0){ while(1); } #endif } else if (ch == 0xEB200000) { ret = copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT, CFG_PHY_UBOOT_BASE, 0); #if defined(CONFIG_SECURE_BOOT) /* do security check */ rv = Check_Signature( (SecureBoot_CTX *)SECURE_BOOT_CONTEXT_ADDR, (unsigned char *)CFG_PHY_UBOOT_BASE, (1024*512-128), (unsigned char *)(CFG_PHY_UBOOT_BASE+(1024*512-128)), 128 ); if (rv != 0) { while(1); } #endif } else return; if (ret == 0) while (1) ; else return; }
这个函数上来一开始是读出0xD0037488里的值,这里面存的是选择通道(irom application note),
然后将copy_sd_mmc_to_mem copy_bl2 = (copy_sd_mmc_to_mem) (*(u32 *) (0xD0037F98));这里将sdmmc拷贝到ddr中去,调用了封装好的函数,然后后面又判断ch == 0xEB200000时, ret = copy_bl2(2, MOVI_BL2_POS, MOVI_BL2_BLKCNT, CFG_PHY_UBOOT_BASE, 0);这几个参数的意思
2:表示第二个通道;MOVI_BL2_POS:表示uboot的第二部分在SD卡中的开始扇区,这个扇区数字必须和烧录uboot时的位置相同,MOVI_BL2_BLKCNT:uboot长度占用的扇区数,CFG_PHY_UBOOT_BASE:是重定位时,将uboot复制到DDR的起始地址(33E00000)
⑧虚拟地址映射
在上面movi_bl2_copy重定位完了以后,跳转到after_copy,这里后面就是虚拟地址映射部分
after_copy: #if defined(CONFIG_ENABLE_MMU) enable_mmu: /* enable domain access */ ldr r5, =0x0000ffff mcr p15, 0, r5, c3, c0, 0 @load domain access register
(1)使能域访问(cp15的c3寄存器)
cp15协处理器内部有c0到c15共16个寄存器,这些寄存器每一个都有自己的作用,可以通过mcr和mrc来访问,c3寄存器在MMU中的作用是控制域访问,
(2)设置TTB(cp15的c2寄存器)
/* 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
TTB就是translation table base,就是转换表基地址,转换表是虚拟地址对应到物理地址的那张表,转换表分两部分,表索引和表项,表索引对应虚拟地址,表项对应物理地址;一对表索引和表项组成一个转换表单元,能够对一个内存块进行虚拟地址转换(映射中基本规定中规定了内存映射和管理单元是以块为单位的,至于有多大,要看mmu的支持和用户自己的选择,在arm中,支持3种块大小,分别是细表1KB 粗表4KB 段1MB)。真正的转换表就是由若干个转换表的单元构成的,每个单元负责一个内存块,最后总体的转换表负责整个内存空间的映射,0-4G的范围。整个建立虚拟地址映射的主要工作就是建立这张转换表,转换表放置在内存中,放置时要求起始地址在内存中要xx位对齐,转换表不需要软件去干涉使用,而是将基地址TTB设置到cp15的c2寄存器中,然后MMU工作时,会自动去查表。
(3)使能MMU单元(cp15的c1寄存器)
/* Enable the MMU */ mmu_on: mrc p15, 0, r0, c1, c0, 0 orr r0, r0, #1 mcr p15, 0, r0, c1, c0, 0 nop nop nop nop #endif
cp15的c1寄存器的bit0控制MMU开关,只要将这一个bit置1即可开启MMU。开启MMU之后,上层软件层的地址就必须经过TTB的一个转换,才能发送到下层的物理层去
(4)转换表分析
通过符号查找到,确定转换表放在lowlevel_init.S的593行。
宏观理解转换表:整个转换表可以看作是一个int类型的数组,数组中的一个元素就是一个表索引和表项的单元,数组中的元素值就是表项,数组的下标就是表索引
ARM的段式映射中长度为1MB,因此一个映射单元只能管1MB内存,那我们整个4G的范围需要 4G/1MB=4096个映射单元,也就是说这个数组的元素个数是4096.实际上我们做的时候,并没有依次单个处理这4096个单元,而是把4096个分成了几部分,然后每部分用for循环做相同的处理
VA PA length
0-10000000 0-10000000 256MB
10000000-20000000 0 256MB
20000000-60000000 20000000-60000000 1GB 512-1.5G
60000000-80000000 0 512MB 1.5G-2G
80000000-b0000000 80000000-b0000000 768MB 2G-2.75G
b0000000-c0000000 b0000000-c0000000 256MB 2.75G-3G
c0000000-d0000000 30000000-40000000 256MB 3G-3.25G
d-完 d-完 768MB 3.25G-4G
DRAM有效范围:
DMC0: 0x30000000-0x3FFFFFFF
DMC1: 0x40000000-0x4FFFFFFF
结论:虚拟地址映射只是把虚拟地址的c0000000开头的256MB映射到了DMC0的30000000开头的256MB物理内存上去了。其他的虚拟地址空间根本没动,还是原样映射的。
思考:为什么配置时将链接地址设置为c3e00000,因为这个地址将来会被映射到33e00000这个物理地址。
⑨第三次设置栈
这一次设置栈是在DDR中,之前虽然已经在DDR中设置过一次栈,但是本次设置栈的目的是将栈放在比较合适(安全,紧凑而不浪费内存)的地方,我们实际将栈设置在uboot起始地址上方2MB处,这样我们安全的栈空间-uboot大小-0x10000 ,大概1.8MB左右,这个空间既没有浪费内存,又很安全,
⑩清bss
清理bss段代码,注意bss段开头和结尾地址的符号,是从链接脚本,uboot.lds得来的。
⑪ldr pc, _start_armboot
start_armboot这个函数在uboot/lib_arm/board.c中,这是一个C语言的函数 ,这个函数就是uboot的第二阶段,这句代码就是将uboot第二阶段的函数地址传给pc,实际上就是使用了远跳转,直接跳转到DDR中的第二阶段开始地址处。
远跳转的意思是:这段代码加载的地址和当前的运行地址无关,而和链接地址有关,因此这个远跳转可以实现从SRAM中的第一阶段跳转到DDR的第二阶段。这个跳转就是第一阶段和第二阶段的分界线