关键词:
Linux系统移植:U-Boot 启动流程(上)
一、reset 函数源码详解
根据链接文件,可以知道 U-Boot 的入口点是 arch/arm/lib/vectors.S 文件中的 _start,函数代码第一句是跳转到 reset 函数
_start:
#ifdef CONFIG_SYS_DV_NOR_BOOT_CFG
.word CONFIG_SYS_DV_NOR_BOOT_CFG
#endif
b reset
...
reset 函数在 arch/arm/cpu/armv7/start.S 里面,代码如下,函数跳转到了 save_boot_params
reset:
/* Allow the board to save important registers */
b save_boot_params
save_boot_params 函数又跳了一次,到了 save_boot_params_ret
ENTRY(save_boot_params)
b save_boot_params_ret @ back to my caller
save_boot_params_ret 函数如下:
save_boot_params_ret:
/*
* disable interrupts (FIQ and IRQ), also set the cpu to SVC32 mode,
* except if in HYP mode already
*/
mrs r0, cpsr
and r1, r0, #0x1f @ mask mode bits
teq r1, #0x1a @ test for HYP mode
bicne r0, r0, #0x1f @ clear all mode bits
orrne r0, r0, #0x13 @ set SVC mode
orr r0, r0, #0xc0 @ disable FIQ and IRQ
msr cpsr,r0
此处代码读取寄存器 cpsr 中的值,并保存到 r0 寄存器,然后将寄存器 r0 中的值与 0X1F 进行与运算,结果保存到 r1 寄存器,这段代码就是提取 cpsr 的 bit0~bit4 这 5 位,获得处理器的工作模式:
M[4:0] | 模式 |
---|---|
10000 | User(usr) |
10001 | FIQ(fiq) |
10010 | IRQ(irq) |
10011 | Supervisor(svc) |
10110 | Monitor(mon) |
10111 | Abort(abt) |
11010 | Hyp(hyp) |
11011 | Undefined(und) |
11111 | System(sys) |
然后代码判断 r1 寄存器的值是否等于 0X1A,判断当前处理器模式是否处于 Hyp 模式,如果不为 Hyp 模式的话就将 r0 寄存器的 bit0~5 进行清零
后面的代码将 r0 的寄存器的值与 0x13 进行或运算,设置处理器进入 SVC 模式,r0 寄存器的值再与 0xC0 进行或运算,因为 cpsr 的 I 为和 F 位分别控制 IRQ 和 FIQ 这两个中断的开关,设置为 1 就关闭了 FIQ 和 IRQ,然后将 r0 寄存器写回到 cpsr 寄存器中
所以这段代码的功能就是设置 CPU 处于 SVC32 模式,并且关闭 FIQ 和 IRQ 这两个中断
后面的代码
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
/* Set V=0 in CP15 SCTLR register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTLR Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTLR Register
/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif
判断没有定义 CONFIG_OMAP44XX 和 CONFIG_SPL_BUILD 的话条件成立,执行下面的代码,读取 CP15 协处理器中 c1 寄存器的值到 r0 寄存器中,这里读取的是协处理器 SCTLR 寄存器的值,然后用 CR_V 清除 SCTLR 寄存器中的 bit13 位
此位是向量表控制位,当为 0 的时候向量表基地址为 0X00000000,软件可以重定位向量表。为 1 的时候向量表基地址为 0XFFFF0000,软件不能重定位向量表。这里将 V 清零,为了接下来的向量表重定位,然后将 r0 寄存器的值重写写入到寄存器 SCTLR 中
后面的代码设置 r0 寄存器的值为 _start,_start 就是整个uboot 的入口地址,其值为 0X87800000,相当于 uboot 的起始地址,因此 0x87800000 也是向量表的起始地址
后面的代码分别调用函数 cpu_init_cp15、cpu_init_crit 和 _main 函数
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif
bl _main
cpu_init_cp15 用来设置 CP15 相关的内容,初始化协处理器,具体代码可以在 start.S 里面找
而函数 cpu_init_crit 内部仅仅是调用了函数 lowlevel_init,下面分析一下 lowlevel_init 函数
二、lowlevel_init 函数详解
函数 lowlevel_init 在文件 arch/arm/cpu/armv7/lowlevel_init.S 中定义,代码如下:
ENTRY(lowlevel_init)
/*
* Setup a temporary stack. Global data is not available yet.
*/
ldr sp, =CONFIG_SYS_INIT_SP_ADDR
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#ifdef CONFIG_SPL_DM
mov r9, #0
#else
/*
* Set up global data for boards that still need it. This will be
* removed soon.
*/
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata
#else
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
#endif
#endif
/*
* Save the old lr(passed in ip) and the current lr to stack
*/
push ip, lr
/*
* Call the very early init function. This should do only the
* absolute bare minimum to get started. It should not:
*
* - set up DRAM
* - use global_data
* - clear BSS
* - try to start a console
*
* For boards with SPL this should be empty since SPL can do all of
* this init in the SPL board_init_f() function which is called
* immediately after this.
*/
bl s_init
pop ip, pc
ENDPROC(lowlevel_init)
代码就是设置 sp 指向 CONFIG_SYS_INIT_SP_ADDR,CONFIG_SYS_INIT_SP_ADDR 在 include/configs/mx6ullevk.h 文件中,定义如下:
#define CONFIG_SYS_INIT_RAM_ADDR IRAM_BASE_ADDR
#define CONFIG_SYS_INIT_RAM_SIZE IRAM_SIZE
#define CONFIG_SYS_INIT_SP_OFFSET \\
(CONFIG_SYS_INIT_RAM_SIZE - GENERATED_GBL_DATA_SIZE)
#define CONFIG_SYS_INIT_SP_ADDR \\
(CONFIG_SYS_INIT_RAM_ADDR + CONFIG_SYS_INIT_SP_OFFSET)
IRAM_BASE_ADDR 和 IRAM_SIZE 在文件 arch/arm/include/asm/arch-mx6/imx-regs.h 中有定义,用于设置 ocram 的首地址和大小
#define IRAM_BASE_ADDR 0x00900000
#if !(defined(CONFIG_MX6SX) || defined(CONFIG_MX6UL) || \\
defined(CONFIG_MX6SLL) || defined(CONFIG_MX6SL))
#define IRAM_SIZE 0x00040000
#else
#define IRAM_SIZE 0x00020000
#endif
所以
CONFIG_SYS_INIT_RAM_ADDR = IRAM_BASE_ADDR = 0x00900000。
CONFIG_SYS_INIT_RAM_SIZE = 0x00020000 =128KB
而 GENERATED_GBL_DATA_SIZE 在 include/generated/generic-asm-offsets.h 中有定义
#define GENERATED_GBL_DATA_SIZE 256
所以设置后 SP 如下,此时 sp 指向 0X91FF00,这属于 IMX6UL/IMX6ULL 的内部 ram
后面的代码进行8字节对齐:
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
然后 sp 指针减去 GD_SIZE,GD_SIZE 同样在 generic-asm-offsets.h 中定了,大小为 248,然后同样做8字节对齐:
#ifdef CONFIG_SPL_BUILD
ldr r9, =gdata
#else
sub sp, sp, #GD_SIZE
bic sp, sp, #7
mov r9, sp
#endif
bic sp, sp, #7 实现8位对齐的原理就是将最低三位清零因为 #7 对应 (0111),清除后就可以被 8 (1000)整除,不过前提是栈地址要向下生长,这样被清除的地址不会与数据冲突
后面的代码将 sp 地址保存在 r9 寄存器中,然后将 ip 和 lr 压栈,然后调用函数 s_init,调用完成后,将入栈的 ip 和 lr 进行出栈,并将 lr 赋给 pc
三、s_init 函数详解
s_init 函数定义在文件 arch/arm/cpu/armv7/mx6/soc.c 中,函数部分代码如下:
void s_init(void)
struct anatop_regs *anatop = (struct anatop_regs *)ANATOP_BASE_ADDR;
struct mxc_ccm_reg *ccm = (struct mxc_ccm_reg *)CCM_BASE_ADDR;
u32 mask480;
u32 mask528;
u32 reg, periph1, periph2;
if (is_cpu_type(MXC_CPU_MX6SX) || is_cpu_type(MXC_CPU_MX6UL) ||
is_cpu_type(MXC_CPU_MX6ULL) || is_cpu_type(MXC_CPU_MX6SLL))
return;
//代码省略...
代码判断当前 CPU 类型,如果 CPU 为 MX6SX、MX6UL、MX6ULL 或 MX6SLL 中的任意 一 种 , 那么就会直接返回:
if (is_cpu_type(MXC_CPU_MX6SX) || is_cpu_type(MXC_CPU_MX6UL) ||
is_cpu_type(MXC_CPU_MX6ULL) || is_cpu_type(MXC_CPU_MX6SLL))
return;
所以对 I.MX6UL/I.MX6ULL 来说,s_init 就是个空函数,所以到此 lowlevel_init 函数执行完成了,执行如下:
下一个要执行的函数就是 _main 函数
四、_main 函数详解
_main 函数定义在文件 arch/arm/lib/crt0.S 中
代码首先设置 sp 指针为 CONFIG_SYS_INIT_SP_ADDR,就是 sp 指向 0X0091FF00,然后 sp 做 8 字节对齐,然后读取 sp 到寄存器 r0 里面,调用 board_init_f_alloc_reserve 函数,r0 中的 sp 值作为函数参数,函数调用后返回值再通过 r0 传给 sp :
#if defined(CONFIG_SPL_BUILD) && defined(CONFIG_SPL_STACK)
ldr sp, =(CONFIG_SPL_STACK)
#else
ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)
#endif
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#endif
mov r0, sp
bl board_init_f_alloc_reserve
mov sp, r0
board_init_f_alloc_reserve 函数定义在文件 common/init/board_init.c,代码如下:
ulong board_init_f_alloc_reserve(ulong top)
/* Reserve early malloc arena */
#if defined(CONFIG_SYS_MALLOC_F)
top -= CONFIG_SYS_MALLOC_F_LEN;
#endif
/* LAST : reserve GD (rounded up to a multiple of 16 bytes) */
top = rounddown(top-sizeof(struct global_data), 16);
return top;
函数 board_init_f_alloc_reserve 主要是留出早期的 malloc 内存区域和 gd 内存区域,malloc 的大小 CONFIG_SYS_MALLOC_F_LEN=0X400( 在 文 件include/generated/autoconf.h 中 定 义 )
执行完成后,函数的指针如下:
后面的代码将 r0 寄存器的值写到寄存器 r9 里面
/* set up gd here, outside any C code */
mov r9, r0
r9 寄存器存放着全局变量 gd 的地址,gd 是一个指向 gd_t 的指针,gd_t 是个结构体,部分定义如下:
typedef struct global_data
bd_t *bd;
unsigned long flags;
unsigned int baudrate;
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
/* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
unsigned long pci_clk;
unsigned long mem_clk;
#if defined(CONFIG_LCD) || defined(CONFIG_VIDEO)
unsigned long fb_base; /* Base address of framebuffer mem */
#endif
//.......................//
后面的代码调用函数 board_init_f_init_reserve ,此函数用于初始化 gd,其实就是清零处理,此函数还设置了
gd->malloc_base 为 gd 基地址+gd 大小=0X0091FA00+248=0X0091FAF8,在做 16 字节对齐,最终 gd->malloc_base=0X0091FB00,这个也就是 early malloc 的起始地址
bl board_init_f_init_reserve
然后设置 R0 为 0,再调用 board_init_f 函数,此函数定义在文件 common/board_f.c 中,主要用来初始化 DDR、定时器、完成代码拷贝等等操作
mov r0, #0
bl board_init_f
后面的代码如下:
#if ! defined(CONFIG_SPL_BUILD)
/*
* Set up intermediate environment (new sp and gd) and call
* relocate_code(addr_moni). Trick here is that we'll return
* 'here' but relocated.
*/
ldr sp, [r9, #GD_START_ADDR_SP] /* sp = gd->start_addr_sp */
#if defined(CONFIG_CPU_V7M) /* v7M forbids using SP as BIC destination */
mov r3, sp
bic r3, r3, #7
mov sp, r3
#else
bic sp, sp, #7 /* 8-byte alignment for ABI compliance */
#endif
ldr r9, [r9, #GD_BD] /* r9 = gd->bd */
sub r9, r9, #GD_SIZE /* new GD is below bd */
adr lr, here
ldr r0, [r9, #GD_RELOC_OFF] /* r0 = gd->reloc_off */
add lr, lr, r0
#if defined(CONFIG_CPU_V7M)
orr lr, #1 /* As required by Thumb-only */
#endif
ldr r0, [r9, #GD_RELOCADDR] /* r0 = gd->relocaddr */
b relocate_code
here:
/*
* now relocate vectors
*/
bl relocate_vectors
/* Set up final (full) environment */
bl c_runtime_cpu_setup /* we still call old routine here */
#endif
代码先获取 gd->start_addr_sp 的值赋给 sp,这里相当于设置 sp=gd->start_addr_sp=0X9EF44E90,0X9EF44E90 是 DDR 中的地址,说明新的 sp 和 gd 将会存放到 DDR 中,而不是内部的 RAM 了
然后借助 r3 寄存器对 sp 做 8 字节对齐,之后获取 gd->bd 的地址赋给 r9,此时 r9 存放的是老的 gd,这里通过获取 gd->bd 的地址来计算出新的 gd 的位置
lr 寄存器的值加上 r0 寄存器的值,重新赋值给 lr 寄存器。因为接下来要重定位代码,也就是把代码拷贝到新的地方去(现在的 uboot 存放的起始地址为 0X87800000,下面要将 uboot 拷贝到 DDR 最后面的地址空间出,将 0X87800000 开始的内存空出来),其中就包括 here,因此 lr 中的 here 要使用重定位后的位置
之后读取 gd->relocaddr 的值赋给 r0 寄存器,此时 r0 寄存器就保存着 uboot 要拷贝的目的地址,作为参数,调用函数 relocate_code(代码重定位函数),执行完成后调用函数 c_runtime_cpu_setup 用来配置协处理器
下面的代码用来清除 BSS 段
ldr r0, =__bss_start /* this is auto-relocated! */
#ifdef CONFIG_USE_ARCH_MEMSET
ldr r3, =__bss_end /* this is auto-relocated! */
mov r1, #0x00000000 /* prepare zero to clear BSS */
subs r2, r3, r0 /* r2 = memset len */
bl memset
#else
ldr r1, =__bss_end /* this is auto-relocated! */
mov r2, #0x00000000 /* prepare zero to clear BSS */
clbss_l:cmp r0, r1 /* while not at end of BSS */
#if defined(CONFIG_CPU_V7M)
itt lo
#endif
strlo r2, [r0] /* clear 32-bit BSS word */
addlo r0, r0, #4 /* move to next */
blo clbss_l
#endif
后面的代码
mov r0, r9 /* gd_t */
ldr r1, [r9, #GD_RELOCADDR] /* dest_addr */
设置函数 board_init_r 的两个参数,然后调用函数:
#if defined(CONFIG_SYS_THUMB_BUILD)
ldr lr, =board_init_r /* this is auto-relocated! */
bx lr
#else
ldr pc, =board_init_r /* this is auto-relocated! */
#endif
/* we should not return here. */
#endif
然后 _main 函数就调用结束了,在 _main 函数里面调用了 board_init_f、relocate_code、relocate_vectors 和 board_init_r 这 4 个函数,下一节分析一下这四个函数功能
总结一下这段代码的执行流程就是
- 上电启动后,代码执行到 _start 函数,调用 reset 函数,reset 的函数目的是将处理器设置为SVC模式,并且关闭FIQ和IRQ,然后设置中断向量以及初始化 CP15 协处理器
- 然后 lowlevel_init 设置SP指针、R9 寄存器(gd 结构体指针值)
- 之后在 _main 函数里面修改 SP 指针,重定位代码,以及初始化 BSS 段
linux系统移植:u-boot启动流程(中)(代码片段)
Linux系统移植:U-Boot启动流程(中)一、board_init_f函数详解board_init_f函数是_main函数初始化中调用的重要函数之一,函数主要有两个工作:初始化一系列外设,比如串口、定时器,打印一些消息初始化gd... 查看详情
linux系统移植:u-boot常用指令(上)(代码片段)
文章目录Linux系统移植:U-Boot常用指令(上)一、U-Boot命令行二、U-Boot常用命令(上)2.1help帮助命令2.2信息查询命令2.3环境变量修改命令2.4内存操作命令2.5网络操作命令Linux系统移植:U-Boot常用指令(... 查看详情
linux系统移植:正点原子u-boot移植(代码片段)
文章目录Linux系统移植:正点原子U-Boot移植一、What‘sU-Boot?1.1U-Boot简介1.2U-Boot选择二、正点原子U-Boot编译2.1编译环境2.2编译脚本三、U-Boot烧写与启动Linux系统移植:正点原子U-Boot移植一、What‘sU-Boot?1.1U-Boot简介Linux系统启... 查看详情
linux系统移植博文导航
Linux系统移植专栏更新很久了,博主今天就把以前的博文整理一下,希望对感兴趣的朋友有所帮助,在此感谢CSDN这个平台给出了这个一个交流的机会,也感谢大家的支持。Linux相关的网站U-BootLinux内核busyboxLinux系统移植Linux系统移... 查看详情
linux系统移植:u-boot顶层makefile分析(上)(代码片段)
目录Linux系统移植:U-Boot顶层Makefile分析(上)一、版本号二、传递变量到子make三、命令输出四、静默输出五、编译输出目录六、代码检查七、模块编译八、获取主机架构和系统九、设置目标架构、交叉编译器和配置... 查看详情
u-boot-2016.07移植(第一篇)初步分析
U-BOOT-2016.07移植 (第一篇) 初步分析目录U-BOOT-201607移植 第一篇 初步分析目录编译和移植环境更新交叉编译工具1下载arm-linux-gcc4432安装arm-linux-gcc443安装环境Ubuntu910下载u-boot-201607并解压分析顶层Makefile1找出目标依赖关系2总结... 查看详情
正点原子i.mx6u-mini移植篇u-boot移植过程详解(代码片段)
...以后Linux还不能正常启动,还需要再移植一个根文件系统(rootfs),根文件系统里面包含了一些最常用的命令和文件。所以U-Boot、Linuxkernel和rootfs这三者一起构成了一个完整的Linux系统,一个可以正常使用、功能完善的Linu... 查看详情
成为linux程序员需要学习啥
1第一部分:LINUX平台搭建与环境熟悉 了解Linux系统;区分各种版本的Linux系统,以便于拓展Linux视野。1、Linux简介;2、Linux系统的主要特点;3、Linux的组成;4、主要的Linux版本;5、嵌入式Linux简介与发展第二部分虚拟机安装和LINU... 查看详情
linux学习:uboot移植
...nbsp;2)第二阶段的功能初始化本阶段使用的硬件设备检测系统内存映射将内核从Flash读取到RAM中为内核设置启动参数启动内核关于uboot启动分析以下这条链接写的不错,使用的是MINI2440平台: http://w 查看详情
linux系统移植:u-boot顶层makefile分析(下)(代码片段)
目录Linux系统移植:U-Boot顶层Makefile分析(下)一、调用scripts/Kbuild.include二、导出交叉编译工具变量设置三、导出其他变量四、makexxx_config过程五、make过程Linux系统移植:U-Boot顶层Makefile分析(下)继续沿着... 查看详情
linux系统移植:u-boot常用指令(下)(代码片段)
文章目录Linux系统移植:U-Boot常用指令(下)一、U-Boot常用命令(上)1.1EMMC和SD卡命令1.2FAT格式文件系统命令1.3EXT格式文件系统命令1.4NAND操作命令1.5BOOT操作命令1.6其他常用命令Linux系统移植:U-Boot常用指令... 查看详情
怎样移植u-boot和linux到s3c2440开发板
...启动。本文就介绍如何实现该功能,并组成一个最简单的系统,这不仅要移植uboot,还要移植linux内核及创建一个根文件系统。首先我们对nandflash进行分区,规划好每个文件存放在nandflash的位置。下面是nandflash的分区:第0分区:0... 查看详情
linux系统移植:u-boot链接脚本(代码片段)
文章目录Linux系统移植:U-Boot链接脚本一、u-boot.lds介绍二、u-boot.lds分析Linux系统移植:U-Boot链接脚本一、u-boot.lds介绍前面提到的U-Boot的本质就是一个大的裸机程序,执行的时候需要先找到程序入口,而程序的链接... 查看详情
linux系统移植:nxp官板uboot移植(代码片段)
文章目录Linux系统移植:NXP官板uboot移植一、获取官板U-Boot二、编译下载U-Boot源码三、官方驱动验证Linux系统移植:NXP官板uboot移植移植NXP的IMX6ULL官板U-Boot到原子的开发板一、获取官板U-Boot去NXP官网下载评估板的U-Boot,... 查看详情
u-boot移植---代码修改---时钟修改sdram
...u-boot。一、时钟修改 在代码流程分析中,我们知道,系统的启动是:设置CPU为管理员模式关闭看门狗屏蔽中断设置启动参数:时钟 FCLK:HCLK:PCLK=1:2:4 FCLK=120MHZflushv4I/DcachesdisableMMUstuffandc 查看详情
linux系统移植:u-boot工程创建(代码片段)
文章目录Linux系统移植:U-Boot工程创建一、U-Boot文件目录二、U-Boot目录解析2.1arch文件夹2.2board文件夹2.3config文件夹2.4.u-boot.xxx_cmd文件2.5顶层Makefile文件2.6u-boot.xxx文件2.7.config文件2.8README文件三、U-Boot过程创建3.1打开工程文件夹3... 查看详情
嵌入式linux第二部分-裸机开发/系统移植/驱动开发/内核开发
...。主要涉及Linux环境配置,嵌入式Linux裸机开发,Linux文件系统及系统移植,驱动开发等部分。目前持续更新中,更新时间:2022年11月13日【嵌入式Linux】裸机开发篇LinuxC语言及Makefile基础【嵌入式Linux】1.shell概念及常用命令行【嵌... 查看详情
如何往riscv上移植linux
参考技术A步骤一:编译生成u-boot.elf文件使用gitclone命令从github上下载u-boot源码,注意使用主分支(master),使用tar命令对下载的压缩文件解压,tarzxvfu-boot-digilent-2012.04-digilent-13.01.tar.gz。如果下载的是zip文件用unzipu-boot-digilent-2012.0... 查看详情