. 内存分配相关问题
makefile
uboot 版本信息:U_BOOT_VERSION
uboot 入口地址:CONFIG_SYS_TEXT_BASE
.lds 的选择:CONIFG_STS_LDSCRIPT
mkconfig
config.h
创建符号链接
.lds
指定了uboot的入口:ENTRY(_start)
第 1 阶段 - arch/arm/cpu/armv7/start.S
1. 填充 16 字节校验位
2. 设置异常向量表(4B/item)
实际内容为空,除 reset 异常
0. 复位异常:复位电平有效时,程序跳转到复位处理程序处执行
1. 未定义指令异常:遇到不能处理的指令时,产生未定义指令异常
2. 软件中断异常:执行SWI指令产生的异常
3. 预存指令异常:处理器预取指令的地址不存在,或该地址不允许当前指令访问,产生指令预取终止异常
4. 数据操作异常:处理器数据访问指令的地址不存在时,或该地址不允许当前指令访问时,产生数据中止异常
5. 未使用
6. IRQ:外部中断请求有效,且 CPSR 中的 I 位为 0 时,产生 IRQ 异常
7. FIQ:快速中断请求引脚有效,且 CPSR 中的 F 位为 0 时,产生 FIQ 异常
3. 定义uboot中断处理函数栈
如果 uboot 使用中断
4. 【reset段】禁能 IRQ(普通中断) 和 FIQ(快速中断),使能 SVC 模式(超级用户模式)
禁能目的:异常向量表未初始化,无法执行中断
SVM 模式主要用于 SWI 和 os kernel
5. 初始化相关全局变量
TEXT_BASE:uboot代码的链接地址
CFG_PHY_UBOOT_BASE:
6. 【cpu_init_cp15段】设置 MMU、cache 以及 TLB
关闭 cache:DDR未初始化,从cache中取数据时,会产生异常
关闭 mmu:开启会导致uboot引导过程变得复杂
7. 判断启动介质——PRO_ID_BASE+OMR_OFFSET地址处的寄存器读取启动信息
8. 第一次初始化栈 —— SRAM(cache)
只有一个 lr 寄存器,无法使用嵌套跳转,后续过程需要双层函数调用
9. 【cpu_init_crit段】跳转到 lowlevel_init.S(ddr、clk、pll 初始化)
1. 判断复位类型
冷启动需要初始化DDR,休眠唤醒不需要初始化DDR
2. 关闭看门狗
看门狗就是定期的查看芯片内部的情况,一旦发生错误就向芯片发出重启信号的电路。看门狗命令在程序的中断中拥有最高的优先级。防止程序跑飞。也可以防止程序在线运行时候出现死循环。
3. 开发板供电锁存
4. 检测程序当前运行位置 cache?DDR?
5. 初始化时钟、串口、DDR
6. 跳回到 start.S
10. 再次锁存供电
可能为代码的冗余
11. 第二次初始化栈 —— DDR(内存)
为了即将执行的C程序做准备,位置_TEXT_PHY_BASE(uboot在DDR中的真正物理地址)
12. 再次检测当前程序执行地址(SRAM:DDR?12:14)
13. 通过引脚来判断启动介质
14. 进行uboot重定位
将SD/MMC中的内容移动到DDR
15. 建立虚拟地址映射表并开启MMU
16. 第三次初始化栈 —— DDR
之前栈位置紧挨着uboot,不太合理
17. 清空 bss
18. 跳转至第二阶段
第 2 阶段 —— crt0.S/_main
1. 初始化 board_init_f() 环境并调用
该环境提供了一个栈和存储 GD 数据结构的地方
r9寄存器保存gd结构体的首地址
2. 初始化 recloate_code() 环境并调用
gd 更新
r9 = gd->bd
new GD is below bd
为 relocate 做准备
...
r0 = gd->relocaddr
调用 relocate_code
3. 初始化 board_init_r() 环境并调用
BSS:初始化为0
上电指示灯亮
r0 赋值 gd 指针,r1 赋值 GD_RELOCADDR
一些CPU需要调用 c_runtime_cpu_setup()
无效 icache
更新异常向量表首地址
第 2.5 阶段 —— arch/arm/lib/board.c
1. board_init_f()
1. 初始化全局变量 - gd.monlen、init_sequence 数组,数组元素为函数的指针,这些函数用来初始化各个功能
arch_cpu_init: 可以先写一个空函数启动uboot
timer_init 在lib/time.c中有实现,是空函数,但是有__WEAK关键字,如果自己实现,则会调用自己实现的这个函数
serial_init
gd 中的 have_console 字段就是该函数设置的
console_init_f
dram_init
设置 gd->ram_size
...
2. 初始化 sdram(由顶部向下进行)
(参考内存分配相关问题)
gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */
gd->irq_sp = addr_sp;
gd->bd->bi_baudrate = gd->baudrate;
gd->relocaddr = addr;
gd->start_addr_sp = addr_sp;
gd->reloc_off = addr - _TEXT_BASE;
gd->fdt_blob = new_fdt;
...
2. relocate_code()
环境变量的重定位
3. board_init_r()
gd->flags |= GD_FLG_RELOC; // 标志已经relocate
使能cache
调用板级支持函数—— board_init()
serial_initialize();
对malloc预留的空间初始化,起始地址,结束地址,清空。
接下来的代码是做一些外设的初始化
比如 mmc flash eth,环境变量的设置,还有中断的使能
...
进入死循环
for (;;) {
main_loop();
}
第 3 阶段 —— common/main.c/main_loop()
第一阶段:do_bootm_states() --> bootm_start()
清空 images,标志状态 bootstage_mark_name为bootm_start
第二阶段:do_bootm_states() --> bootm_find_os() --> boot_get_kernel()
boot_get_kernel(): CRC校验image头部,获取image的type
bootm_find_os():获取一些基本的os信息到images结构体中(获取os的 type、comp、end、load addr)
第三阶段:do_bootm_states() --> bootm_find_other()
查询是否有ramdisk
第四阶段:do_boom_states() --> bootm_load_os()
do_bootm_states() --> boot_fn()
获取相应的 boot_fn()即do_bootm_linux()
第五阶段:do_bootm_states() --> do_bootm_linux() --> boot_prep_linux()
为kernel准备参数
第六阶段:do_bootm_states() --> boot_selected_os() --> do_bootm_linux() -c-> boot_jump_linux()
调用kernel_entry()进入内核,将内核参数放在r2(bi_boot_params)
. board_init_f() 中关于sdram的划分 —— 预留
https://blog.csdn.net/skyflying2012/article/details/25804209?spm=1001.2014.3001.5501
1. 将 sdram 顶部一段空间隐藏
gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE;
2. 预留出 tlb 空间
gd->arch.tlb_addr = addr; // tlb 空间存放首地址
gd->arch.tlb_size = 4096 * 4; // 如果打开了 icache 以及 dcache,则预留出 16KB 的 TLB 空间
3. 确定 framebuffer
gd->fb_base = CONFIG_FB_ADDR; // 获取framebuffer大小
gd->fb_base = addr; // frambuffer 首地址
4. 为uboot的code留出空间
addr -= gd->mon_len;addr &= ~(4096 - 1);
5. 预留 malloc space 空间
addr_sp = addr - TOTAL_MALLOC_LEN;
6. bd struct
bd = (bd_t *) addr_sp;
gd->bd = bd; // 全局信息 bd_t 结构体空间的首地址存在 gd->bd
7. gd struct
addr_sp -= sizeof (gd_t);
8. 留出 12 bytes
addr_sp -= 12; // for abort stack
9. 预留 uboot stack space,确定了 addr_sp 位置(之下的空间为栈空间)
interrupt_init();
重定位后,
https://blog.csdn.net/nicholas_duan/article/details/106529673
. /arch/arm/cpu/u-boot.lds 中关于 uboot 镜像划分
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS
{
. = 0x00000000;
. = ALIGN(4);
.text :
{
*(.__image_copy_start)
CPUDIR/start.o (.text*)
*(.text*)
}
. = ALIGN(4);
.rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
. = ALIGN(4);
.data : {
*(.data*)
}
. = ALIGN(4);
. = .;
. = ALIGN(4);
.u_boot_list : {
KEEP(*(SORT(.u_boot_list*)));
}
. = ALIGN(4);
.image_copy_end :
{
*(.__image_copy_end)
}
.rel_dyn_start :
{
*(.__rel_dyn_start)
}
.rel.dyn : {
*(.rel*)
}
.rel_dyn_end :
{
*(.__rel_dyn_end)
}
_end = .;
. = ALIGN(4096);
.mmutable : {
*(.mmutable)
}
.bss_start __rel_dyn_start (OVERLAY) : {
KEEP(*(.__bss_start));
__bss_base = .;
}
.bss __bss_base (OVERLAY) : {
*(.bss*)
. = ALIGN(4);
__bss_limit = .;
}
.bss_end __bss_limit (OVERLAY) : {
KEEP(*(.__bss_end));
}
/DISCARD/ : { *(.dynsym) }
/DISCARD/ : { *(.dynstr*) }
/DISCARD/ : { *(.dynamic*) }
/DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }
/DISCARD/ : { *(.ARM.exidx*) }
/DISCARD/ : { *(.gnu.linkonce.armexidx.*) }
}
. kernel 镜像放在 DDR 什么位置?
do_bootm() 函数校验内核的头部信息,根据头部信息,判断内核格式和进行 CRC 校验。
https://blog.csdn.net/wowricky/article/details/83218356?spm=1001.2014.3001.5501
内核一定要放在链接地址处,链接地址去内核源代码的链接脚本或者Makefile中去查找。
.将 u-boot.bin 从 SD 卡搬运到 DDR 中(relocate.c)
真正的重定位是通过调用 movi_bl2_copy 函数完成的,在uboot/cpu/s5pc11x/movi.c 中,在 lowlevel_init.s 完成 DDR 初始化后调用。
- copy_code_to_dram(void) 搬运代码。
- 修改 Makefile,添加搬运代码
- 修改 lowlevel_init.s 文件,添加跳转到 copy_code_to_dram 代码
- 修改 start.s 代码,从 sram 直接跳转到 sdram
typedef unsigned int u32;
typedef unsigned long ulong;
typedef unsigned int (*copy_sd_mmc_to_mem)
(u32 channel, u32 start_block, unsigned char block_size, u32 *trg, u32 init);
void copy_code_to_dram(void)
{
ulong ch;
ulong dest = 0x34800000;
unsigned int uImage_dest = 30008000;
unsigned int uImage_sec_nu = 1000
u32 ret;
ch = *(volatile u32 *)(0xD0037488);
copy_sd_mmc_to_mem copy_bl2 =
(copy_sd_mmc_to_mem) (*(u32 *) (0xD0037F98));
unsigned int uImage_dest = 0x30008000;
unsigned int uImage_sec_nu = 1000;
ulong dest = 0x34800000;
unsigned int sec_nu = 49;
if (ch == 0xEB000000) {
ret = copy_bl2(0, sec_nu, 128,(u32 *)dest , 0);
ret = copy_bl2(0, sec_nu+128, 128,(u32 *)(dest+0x10000) , 0);
ret = copy_bl2(0, sec_nu+256, 128,(u32 *)(dest+0x20000) , 0);
for(i = 0;i < 5*1024;i++)
{
copy_b12(0,uImage_sec_nu + i*2,2, (unsigned int *)(uImage_dest + i*1024),0);
}
}
}
. uboot 向内核传递启动参数
https://www.cnblogs.com/linfeng-learning/p/9284315.html
https://blog.csdn.net/qq_16933601/article/details/106244510
在跳转到内核以前,uboot 需要对 CPU 寄存器进行设置:
R0=0
R1=machine ID;==arch\arm\include\asm\mach-type.h==
R2=启动参数标记列表在RAM中起始基地址,块内存的基地址 bi_boot_params
重要的数据结构 —— global_data
uboot/include/asm-arm/Global_data.h
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned long baudrate;
unsigned long have_console;
unsigned long reloc_off;
unsigned long env_addr;
unsigned long env_valid;
unsigned long fb_base;
#ifdef CONFIG_VFD
unsigned char vfd_type;
#endif
#if 0
unsigned long cpu_clk;
unsigned long bus_clk;
unsigned long ram_size;
unsigned long reset_status;
#endif
void **jt;
} gd_t;
typedef struct bd_info {
int bi_baudrate;
unsigned long bi_ip_addr;
unsigned char bi_enetaddr[6];
struct environment_s *bi_env;
ulong bi_arch_number;
ulong bi_boot_params;
struct {
ulong start;
ulong size;
}
bi_dram[CONFIG_NR_DRAM_BANKS];
#ifdef CONFIG_HAS_ETH1
unsigned char bi_enet1addr[6];
#endif
} bd_t;
gd 和 bd 初始化
gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
memset((void*)gd, 0, sizeof(gd_t));
gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
memset(gd->bd, 0, sizeof(bd_t));
gd->bd->bi_boot_params = 0x30000100;
gd和bd需要内存,内存当前没有被人管理(因为没有操作系统统一管理内存),大片的DDR内存散放着可以随意使用(只要使用内存地址直接去访问内存即可),在uboot中需要有一个整体规划. 如下:
参数链表必须以 ATAG_CORE 开始,以 ATAG_NONE 结束。(这两个标记本身是一个 32 位的值),其它的参数标记还包括:ATAG_MEM32,ATAG_INITRD,ATAG_RAMDISK,ATAG_COMDLINE 等。**每个参数标记就代表一个参数结构体(ATAG_CORE,ATAG_MEM,ATAG_RAMDISK,ATAG_INITRD32,ATAG_CMDLINE,ATAG_END),由各个参数结构体构成了参数链表。**在整个参数链表中除了参数结构体 ATAG_CORE 和 ATAG_NONE 的位置固定以外,其他参数结构体的顺序是任意的。所以我们在构建各种参数 tag 时,在开始时先要构建一个参数标记为 ATAG_CORE的 tag 结构,标示从这个 tag 结构开始接下来就是参数。参数链表的遍历是通过函数 tag_next(struct*tag) 来实现的。
.uboot 运行中如何得知 DDR 配置信息?
bdinfo 参数可以打印 gd->bd 记录的所有硬件相关的全局变量的值
DRAM bank = 0x00000000
-> start = 0x30000000
-> size = 0x10000000
DRAM bank = 0x00000001
-> start = 0x40000000
-> size = 0x10000000
|