uboot启动第一阶段详解——汇编代码部分start.S

2023-11-05

前言

uboot启动第一阶段是用汇编语言实现的,大部分都是Soc内部的初始化,可以理解成一些通用的初始化,只要使用该款Soc,第一阶段的初始化流程基本是一样的。不直接用C语言进行初始化是因为,C语言运行需要一定的环境,比如栈的设置,而汇编代码刚好可以为C语言的运行初始化环境。在汇编代码的结束阶段就是跳转到C语言实现的函数继续进行初始化。

1、找到uboot的入口

uboot是个裸机代码,用汇编代码和C语言代码共同组成,和平时我们写的应用层的C语言程序不一样,uboot的入口不是main函数。uboot的入口可以在链接脚本(ENTRY(_start))中得到,_start标号处就是uboot启动的第一句代码。详细介绍可以看博客:《u-boot的链接脚本分析》

2、16字节的头信息填充

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

S5PV210的uboot要求有16字节的头信息,里面会放一些校验消息,其他平台会不会要求16字节的头不太确定。这里只是定义了4个int型变量,具体的内容不重要,作用就是先预留出16字节的头空间,后面会去填充。

3、构建异常向量表

.globl _start
_start: b	reset //重启异常
	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//快速中断

异常向量表在内存的地址时可以配置的,只需要提前在异常向量表的地址处存入各种异常的处理函数地址,当异常发生时硬件会自动跳转到该种异常的异常向量表去执行。这里是将各种异常的处理函数存入异常向量表,实际的异常处理函数是空的,因为uboot的主要作用是启动内核,各种异常等内核去处理。如果uboot在启动中遇到异常就直接挂掉或者重启。更详细的细节参考:《ARM的37个寄存器和异常处理机制详解》

4、定义全局变量

.global _end_vect
_end_vect:

	.balignl 16,0xdeadbeef
	
_TEXT_BASE:
	.word	TEXT_BASE //uboot的链接地址

_TEXT_PHY_BASE:
	.word	CFG_PHY_UBOOT_BASE//uboot所在的物理地址

.globl _armboot_start
_armboot_start:
	.word _start //_start标号的地址,也就是uboot执行第一句代码的地址

.globl _bss_start	//bss段的开始,后面清除bss段需要用
_bss_start:
	.word __bss_start

.globl _bss_end
_bss_end:
	.word _end//bss段的结束

#if defined(CONFIG_USE_IRQ)
/* IRQ stack memory (calculated at run-time) */
.globl IRQ_STACK_START
IRQ_STACK_START:
	.word	0x0badc0de //IRQ模式下的栈空间地址

/* IRQ stack memory (calculated at run-time) */
.globl FIQ_STACK_START
FIQ_STACK_START:
	.word 0x0badc0de//FIQ模式下的栈空间地址
#endif

这些全局变量主要是uboot启动要用到的一些特殊地址,要根据编译脚本和链接脚本进行分析。
.balignl 16,0xdeadbeef:balignl 是对齐指令,该语句的作用是后面的代码按16字节对齐,如果没有对齐则用0xdeadbeef进行填充。

5、设置CPU为管理员模式(SVC)并禁止IRQ、FIQ

	@;mrs	r0,cpsr
	@;bic	r0,r0,#0x1f
	@;orr	r0,r0,#0xd3
	@;msr	cpsr,r0
	msr	cpsr_c, #0xd3		@ I & F disable, Mode: 0x13 - SVC

SVC模式下有特权,不然uboot初始化有些操作不被允许,整个uboot都是在SVC模式下,禁止IRQ和FIQ是因为uboot主要作用是启动内核,不需要也不想被中断打扰。整个设置操作cpsr寄存器,具体设置步骤要查询cpsr的位定义。

6、初始化l2cache

bl	disable_l2cache
bl	set_l2cache_auxctrl_cycle
bl	enable_l2cache

初始化l2cache,具体作用不了解。

7、禁止L1 iCache和dCache

   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

禁止掉TLB和icache,此时DDR还没有初始化,TLB(页表转换)和icache都用不上,相关的设置都是操作协处理器。

8、禁止MMU和cache

        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

设置协处理器cp15的c1寄存器,经典的"读-改-写"三部曲完成设置。

9、读取并判断启动方式

        ldr	r0, =PRO_ID_BASE
        ldr	r1, [r0,#OMR_OFFSET]
        bic	r2, r1, #0xffffffc1//只保留特定位数据

将[PRO_ID_BASE+OMR_OFFSET]地址处的数据读出来,并将特定几位的数据赋值给r2。详细信息看博客:《u-boot中如何确定启动方式》

10、设置栈并执行lowlevel_init函数

	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 */

(1)设置栈就是将sp寄存器中写入分配的栈空间的地址,ARM默认是满减栈;此时DDR还没有初始化,所以栈空间是在iRAM中。这里必须设置栈的原因是,后面要开始调用汇编函数,虽然LR寄存器能保存函数返回地址,但是只有一个LR寄存器,不能实现函数的多级调用。
(2)lowlevel_init函数主要功能有设置时钟系统、初始化内存;参考博客:《uboot启动——lowlevel_init函数详解》

11、上电锁存

	/* 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]

作用是开发板上电后电源按键弹起开发板也不会断电。详情参见:《开发板的上电锁存》

12、第二次设置栈

	/* 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 */

此时DDR已经初始化完成,将栈空间设置在DDR中,因为iRAM中的空间太小了,这也是为调用C语言做准备。

13、判断当前是在IRAM还是在DDR中运行

	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   */

原理就是从PC寄存器中读出当前的运行地址,然后运行地址和链接地址的某几位高位进行比较。如果相等,说明uboot已经进行了重定位,此次启动可能是从休眠状态启动,不用再重定位BL2;如果不相等,说明此时还运行在iRAM中,要进行BL2的重定位。

14、判断当前是否从SD卡通道2启动

#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

从[0xD0037488]地址处读取数据,然后与0xEB200000进行比较,如果相等则说明是从SD卡通道2启动,跳转到mmcsd_boot函数指定,进行BL2的重定位,第15步会被跳过。[0xD0037488]是个特殊地址,硬件会根据启动方式自动去写入数据,这是S5PV210芯片的特性。

15、根据启动方式读取BL2

	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

根据不同的启动方式,调用不同启动介质的启动函数,要结合第九步进行分析。

16、打开MMU

#if defined(CONFIG_ENABLE_MMU)
enable_mmu:
	/* enable domain access */
	ldr	r5, =0x0000ffff
	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

	/* 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

详解介绍参考博客:《嵌入式开发(S5PV210)——u-boot中开启MMU》

17、第三次设置栈

ldr	sp, =(CFG_UBOOT_BASE + CFG_UBOOT_SIZE - 0x1000)
sub	sp, r0, #12		/* leave 3 words for abort-stack    */

此时uboot启动第一阶段马上就要结束了,DDR已经初始化,uboot中对内存是有规划的使用,这里会给栈一个够用的空间。

18、清bss段

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...                    */
	add	r0, r0, #4
	cmp	r0, r1
	ble	clbss_l

汇编实现的循环语句将bss段清零。

19、跳转到C语言执行第二阶段初始化

ldr	pc, _start_armboot

_start_armboot:
	.word start_armboot

跳转到start_armboot函数执行,这是用C语言实现的函数,接下来就是uboot用C语言进行的第二阶段的启动。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

uboot启动第一阶段详解——汇编代码部分start.S 的相关文章

随机推荐

  • 14 C语言进阶自定义类型详解

    自定义类型 结构体 枚举 联合 大纲 结构体 结构体类型的声明 结构的自引用 结构体变量的定义和初始化 结构体内存对齐 结构体传参 结构体实现位段 位段的填充 可移植性 枚举 枚举的定义 枚举的优点 枚举的使用 联合 联合类型的定义 联合的
  • 敏捷测试的“三板斧“

    什么是三板斧 可灰度 任何变更 都必须是可以灰度的 即控制变更的生效范围 先做小范围变更 验证通过之后才扩大范围 可监控 在灰度的过程中 必须能做到可监控 能了解到变更之后对系统的应用 可回滚 当通过监控发现变更后会引发问题时 还需要有方法
  • 八进制数的表达方法!八进制数在转义符中的使用!

    C C 语言中 如何表达一个八进制数呢 如果这个数是 876 我们可以断定它不是八进制数 因为八进制数中不可能出7以上的阿拉伯数字 但如果这个数是123 是567 或12345670 那么它是八进制数还是10进制数 都有可能 所以 C C
  • Spring学习笔记 搭建环境

    现在开始我们就要开始学习Spring框架了 首先要做的事情就是搭建Spring环境 为了让我们关注于Spring的功能 我在这里使用Spring Initializer 它会自动为我们创建一个包含了Spring依赖的项目 让我们能直接快速开
  • 试题 算法训练 二进制数数

    问题描述 给定L R 统计 L R 区间内的所有数在二进制下包含的 1 的个数之和 如5的二进制为101 包含2个 1 输入格式 第一行包含2个数L R 输出格式 一个数S 表示 L R 区间内的所有数在二进制下包含的 1 的个数之和 样例
  • linux 防火墙打开5432 端口,在ubuntu上打开端口5432

    我试图使用ufw使用sudo ufw allow 5432 tcp在ubuntu上打开端口5432 然后我使用nmap来查看端口5432是否已打开 我得到了这个 root domain sudo nmap sS O 127 0 0 1 St
  • Linux部署kettle并设置定时任务

    一 安装Kettle linux中使用kettle时首先需要jdk环境 这里就不概述linux中jdk的安装与配置了 1 首先将kettle压缩包放入linux并解压 unzip data integration zip kettle安装路
  • C++:理解this指针

    详情见 gt https blog csdn net keneyr article details 111758870
  • STM32 DAC 学习笔记

    本文基于SYM32F4 部分参数说明参考标准库 DAC简介 作用就是把输入的数字编码 转换成对应的模拟电压输出 DAC 可以按 8 位或 12 位模式进行配置 并且可与 DMA 控制器配合使用 在 12 位模式下 数据可以采用左对齐或右对齐
  • ASP.NET MVC Note1

    学习ASP NET MVC第一件事应该就是Route 下面的图摘自的Dino的书 由此粗略的学习可以归结于理解Routing Http Module和Http Handler Route Handler是Route Module的一部分 p
  • GAMES101回顾 -- Shading

    Shading 定义 将材质作用于对象的流程 Z Buffer 帧缓存 Frame Buffer Frame Buffer是一个用于存储图像像素数据的内存区域 它通常由一个二维数组表示 每个元素对应屏幕上的一个像素 Frame Buffer
  • c语言任意两个整数相减_c语言 大整数相减

    include include include include cin输入cout输出usingnamespacestd intmain void inta b c d e o f 210 0 h 210 0 g 210 0 include
  • Unity3D 中旋转和变换

    Transform 变换 是场景中最常打交道的类 用于控制物体的位移 旋转 缩放等功能 Transform Class inherits from Component IEnumerable Position rotation and sc
  • 小程序 bindtouchmove 使用拖动按钮 页面跟着滑动并拖动卡顿感 问题

    修改 bindtouchmove buttonMove 为 catchtouchmove buttonMove 使用touchmove监听滑动要更新视图层 导致动画卡顿 阻止冒泡可以解决
  • centos7下javac:未找到命令

    如果你配置了jdk的环境变量之后 如果输入javac 报下面错误 则你可以在终端使用命令 yum install java devel 完美解决
  • Monaco-Editor在Vue中使用(实现代码编辑与diff代码比较)

    Monaco在线代码编辑器使用总结 1 什么是Monaco Monaco编辑器是为VS代码提供支持的代码编辑器 官方API文档 2 Monaco Editor安装及使用 2 1安装 npm install monaco editor sav
  • Eu63-Collecter Euromap63协议采集接口实现

    Eu63 Collecter Euromap63协议采集接口实现 项目仓库 https github com tang0 0 Eu63 Collecter Euromap 63协议认识 https blog csdn net lblmlms
  • mysql单表数据过大的处理方法

    优先考虑表分区 其次考虑分表 最后考虑分库
  • ChatGPT与医学的结合:揭示未来医疗保健的潜力

    随着科技的不断发展 聊天机器人在诸多领域中扮演着越来越重要的角色 特别是在医学领域 通过将其与ChatGPT相结合 我们可以为医疗保健体系带来许多改变和益处 本文将探讨如何将ChatGPT应用于医学领域 以及这种结合所带来的潜在好处 1 患
  • uboot启动第一阶段详解——汇编代码部分start.S

    前言 uboot启动第一阶段是用汇编语言实现的 大部分都是Soc内部的初始化 可以理解成一些通用的初始化 只要使用该款Soc 第一阶段的初始化流程基本是一样的 不直接用C语言进行初始化是因为 C语言运行需要一定的环境 比如栈的设置 而汇编代