uboot启动流程和架构
概述:
本文将从两个方面来阐述uboot:
1、启动流程
2、架构
一、uboot流程图:
从上图中看到红色1,2,3,4,5,7,8,9的标号,下面分别说明每个过程:
1、启动入口:
(1)确定链接脚本文件:
在根目录下Makefile下LDSCRIPT宏值,就是指定链接脚本(如:arch/arm/cpu/u-boot.lsds)路径的
(2)从脚本文件找入口:
在链接脚本中可以看到ENTRY()指定的入口,如:ENTRY(_start),_start就是入口(3)链接脚本简要分析:
.........ENTRY(_start)SECTIONS{.=0x00000000;.=ALIGN(4);.text:{*(.__image_copy_start)*(.vectors)CPUDIR/start.o (.text*)*(.text*)}}.........
像*(.__image_copy_start)这种,指定将标记为这个标记的代码段编译链接放入这个地方,上面的事例表示0x00000000地址(在编译完,生成的System.map文件中可以看到),在汇编中可以用如下方式将一段代码做标记(直到遇到下一个section为止,要是没有就到文件结束):
.section ".__image_copy_start", "ax"
在C文件中可以用如下方式,把一个变量或者函数标记到指定段:
char __image_copy_start[0] __attribute__((section(".__image_copy_start")));
在本人手中的例子,被编译的文件中没有__image_copy_start这样标记的代码段,只能找.vectors,在arch/arm/lib/vectors.S开头地方,找到被.vectors标记的代码段,并且_start也是位于有效代码第一行,最终启动入口位置确定了(假设_start没有位于vectors.S有效代码第一行,更或者,在start.o中,而ENTRY()又指定ENTRY(_start),会是什么结果?)。
2、初始化:
(1)启动过程中memory变化:
如上图:
a、上电,首先在CPU片上的bootrom空间里运行(CPU出厂就固化的code),检查外部flash(nand , emmc , nor , sdmmc等,看CPU支持情况):
——检测到,就会读取flash中前2K code到片上Sram,然后由这2K code负责将flash中全部code加载到外部RAM(EXT RAM),最终跳转到外部ram运行,然后继续初始化环境,加载系统等。
——检测不到外部flash,或者在外部flash检测不到有效bank,则会初始化USB(视具体板子),启动下载,等待img数据。
(验证:本人,验证结果是,在uboot前2k,里面取出的PC值,始终是外部RAM所在的地址空间,但是各种资料都描述有片上2K RAM)
(2)初始化过程,分board_init_f前,board_init_f,relocate, board_init_r这四个过程描述:
- a、board_init_f前,做的都是关于CPU体系结构相关的初始化,对于做电子产品开发的,基本都不会去修改,就简单描述,首先,8个异常入口:
b reset
b pc, _undefined_instuction
b pc, _software_interrupt
b pc, _prefetch_abort
b pc, _data_abort
b pc, _not_used
b pc, _irq
b pc, _fiq
从上面字面意思大概也知道,重点关注下reset(定位reset时,结合链接脚本文件很容易找到),reset首先做的是将FIQ,IRQ关闭,将CPU(arm)模式设置成SVC32(保护模式),然后配置CP15协处理器,CP15作用:
- 第一,配置异常入口,以前都认为CPU发生reset异常后,都是PC为0x0地址,
然而,通过配置CP15后,可以修改:
ldr r0, =_start
mcr p15,0,r0,c12,c0,0
如上,之后,再出现reset异常后,PC就是跳到_start处,不再是0x0地址
- 第二,cpu_init_cp15在这个函数里面,配置(CP15配置)cache,MMU,TLBS,I-cache,L2 cache。
CP15完成配置,cpu_init_crit会调用lowlevel_init,这个一般定义板级初始化,需要很早初始化的,主要初始化pll,mux,memory(ddr)。
cpu_init_crit返回后,进入_main,这个_main入口有点难找,搜索会出来一大堆,我的是arm体系的,在arch/arm/lib/crt0.S里面找到的,从_main开始到board_init_f都是对ram空间的划分,有个重点宏CONFIG_SYS_INIT_SP_ADDR需要根据具体硬件RAM空间划分配置,例如,我的板子2G RAM空间映射到0x0地址开始2G地址空间,厂商将128M空间到0x0地址作为系统用,因此CONFIG_SYS_INIT_SP_ADD=0x0+SZ128M。
在_main第一条指令:
ldr sp, =(CONFIG_SYS_INIT_SP_ADD)
然后,对sp按照8字节对齐处理后,将SP-sizeof(gd_t):
sub sp, sp, #GD_SIZE
GD_SIZE定义在include/generated/generic-asm-offsets.h里面,从这里面看:
#define GD_SIZE 232
gd_t结构体size与结构体系有关,这里写死,估计是厂商根据自己板子cpu体系算过。
这里sp是按照向下生长的,这条指令后sp,再通过:
mov r9, sp
这样r9就指向gd全局变量起始地址了(空间就是从gd到CONFIG_SYS_INI_SP_ADD),至于为何这么说,且看下面:
gd_t(通用)定义在include/asm-generic/global_data.h,然后在gd_t结构体重嵌入了与体系有关的struct arch_global_data,我的是arm的,所以在arch/arm/include/asm/global_data.h中定义了arch_global_data,在这个头文件中还发现了:
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm("r9")
结合上面r9的值,可以确定gd=CONFIG_SYS_INIT_SP_ADD-sizeof(gd_t)。
然后_main完成r9赋值后,要对gd这段空间清零处理,清零完成后就进入board_init_f函数。
b、board_init_f中,主要是做了gd成员值的初始化,这些成员值,最后会为copy code提供地址和size。
最终划分的空间大致情况(根据firefly-rk3288,2G的ram空间,图子格子大小不代表ram空间大小),整理如下图:
更详细的分析图(relocaddr表示的是gd->relocaddr,基于firefly-rk3288开发板分析的):
图看不清楚的可以百度盘下载看。
各分区说明:
tlb: 一种cach,百度吧,我没很明白
globa buff:firefly-rk3288用作flash管理buff(nand/emmc坏块等)
boot /fastboot buff:
fastboot log buff:
uboot code data&bss:relocate后uboot code会copy到此处运行
malloc: malloc()分配的内存空间
bd_t: DDR信息,即几个bank,每个bank多大(bank信息的获取,就要看具体的板子 了,例如,我的firefly-rk3288,做得够隐秘了,代码(函数dram_init_banksize())分析看,将信息放在了0x0+32M的地方,然后用这个函数解析存入gd->bd->rk_dram里面,bd_t是一个与CPU体系结构相关的,也是可以根据在基础上增加成员)
gd_t:new gd所在空间,原来在r9指定的gd空间,会copy到此处后,在board_init_f调用完成后由汇编把r9修改指向这块空间
在board_init_f中也有对serial,console初始化:
serial_init()在这里的初始化,做两件事,
首先,初始化gd->flags|=GD_FLG_SERIAL_READY表示serial以就绪
然后,调用具体serial初始化函数,将所用的serial端口和波特率等设置到就绪状态。
console_init_f()之中了一件事情,就是将gd->have_console=1,表示有控制台。
board_init_f最后调用setup_reloc()两个很重要的动作:
gd->reloc_off = gd->relocaddr - CONFIG_SYS_TEXT_BASE;
memcpy(gd->newd_gd,(char *)gd,sizeof(gd_t))
重要的宏CONFIG_SYS_TEXT_BASE,可以说虚拟地址空间,编译的时候,uboot会根据这值来链接。实际真实的硬件RAM起始地址和CONFIG_SYS_TEXT_BASE差,就是虚拟地址和物理地址偏移量。在这里,relocate后uboot在实际的物理地址gd->relocaddr(起始地址),CONFIG_SYS_TEXT_BASE是虚拟地址的起始,差值自然就是relocate后虚拟地址和物理地址的偏移量。
然后将重要的gd数据从(CONFIG_SYS_INIT_SP_ADD-SZ128M,CONFIG_SYS_INIT_SP_ADD)区间,copy到新的new gd区间
c、relocate:
这部分就是将,当前uboot运行空间的code,copy到上面“uboot code data&bss”空间里,即gd->relocaddr地址处,接着上面的运行。具体可以看这个博客地址:uboot的relocation原理详细分析
d、board_init_r做的事情主要概括如下:
- 标记uboot启动已经到了board_init_r阶段
- 设置machine id & 启动参数存放位置
- 最后硬件的初始化
- Flash接口初始化
- 环境变量最后的初始化
- console最后初始化
- 检查key是否进入下载模式
进入main loop或者直接启动系统
检查key是否进入下载模式、进入main loop或者直接启动系统放到流程图中3、4、5、6、7、8、9降
标记uboot启动已经到了board_init_r阶段:
看下面代码很好理解:
static int initr_reloc(void)
{gd->flags |= GD_FLG_RELOC;bootstage_mark_name(BOOTSTAGE_ID_START_UBOOT_R,"board_init_r");
}
设置machine id & 启动参数存放位置
如下就是对gd两个重要成员的初始化:
int board_init(void)
{gd->bd->bi_arch_number = MACH_TYPE_XX;gd->bd->bi_boot_params = PHYS_SDRAM;
}
gd->bd->bi_arch_number作为machine id通过R1传递到linux kernel,也就是linux kernel会将这个值与宏MACHINE_START定义的nr比较,要相同,才能通过linux kernel的检查,否则会导致系统不能启动,这是旧版linux方式,新版的用宏DT_MACHINE_START,从宏定义也可以看出nr已无作用,这两个宏都定义在linux根目录下arch/arm/include/asm/mach/arch.h:
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \__used \__attribute__((__section__(".arch.info.init"))) = { \.nr = MACH_TYPE_##_type, \.name = _name,#define MACHINE_END \
};#define DT_MACHINE_START(_name, _namestr) \
static const struct machine_desc __mach_desc_##_name \__used \__attribute__((__section__(".arch.info.init"))) = { \.nr = ~0, \.name = _namestr,
现在来看下linux是怎么来兼容新旧两种方式的,在linux根目录下init/main.c中start_kernel()调用setup_arch():
void __init setup_arch(char **cmdline_p)
{const struct machine_desc *mdesc;setup_processor();mdesc = setup_machine_fdt(__atags_pointer);if (!mdesc)mdesc = setup_machine_tags(__atags_pointer, __machine_arch_type);machine_desc = mdesc;.....................
}
setup_machine_fdt()就是新版,参数__atags_pointer就是r2传过来的地址(下面会讲到uboot对r2的处理),匹配的方式就是r2地址开始的内存中,前4byte必须是0xd00dfeed:
#define FDT_MAGIC 0xd00dfeed
头部匹配通过后,开始匹配compatible属性,r2是dtb存放的内存起始地址,在dtb中有这么样值:
compatible = "rockchip,rk3288"
在r2中搜索这个属性,然后将属性值与linux中DT_MACHINE_START定义的dt_compat值对比,相同才最终匹配成功。DT_MACHINE_START定义变量都存储在.arch.info.init段中,搜索这段中的dt_compat就可以了。匹配成功后,会搜索dtb中如下节点:
chosen{bootargs = "console=ttyS2 init=/init initrd=0x62000000,0x00800000";
};
搜索到后,将bootargs的值copy到boot_command_line中,到此linux新版获取启动参数方式成功。
当linux新版获取启动参数方式失败,调用setup_machine_tags(__atags_pointer, __machine_arch_type)用旧版获取启动参数的方式,形参__atags_pointer当然就是r2的值,__machine_arch_type就是machine id,在哪里获取?
在linux根目录arch/arm/boot/compressed/misc.c中:
decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p,unsigned long free_mem_ptr_end_p,int arch_id)
{.........__machine_arch_type = arch_id;.........
}
参数arch_id又是哪里传入的:
在linux根目录下arch/arm/boot/compressed/head.S中:
/** The C runtime environment should now be setup sufficiently.* Set up some pointers, and start decompressing.* r4 = kernel execution address* r7 = architecture ID* r8 = atags pointer*/mov r0, r4mov r1, sp @ malloc space above stackadd r2, sp, #0x10000 @ 64k maxmov r3, r7bl decompress_kernelbl cache_clean_flushbl cache_offmov r1, r7 @ restore architecture numbermov r2, r8 @ restore atags pointer
根据汇编和C互相调用时,传参数的规定,arch_id就等于r3的值,r3==r7,根据注释r7是architecture ID,至于r7什么时候得到,我就不继续追下去了,有兴趣的可以跟踪下代码。接下来就是用__machine_arch_type与MACHINE_START定义的nr比较,相等就匹配成功,获取linux中struct machine_desc结构体变量(描述cpu相关的):
/** locate machine in the list of supported machines.*/for_each_machine_desc(p)if (machine_nr == p->nr) {pr_info("Machine: %s\n", p->name);mdesc = p;break;}
到此新旧两种获取启动参数的方式结束。我另外一篇博客:linux之early_param()和__setup对跟踪启动参数作用或许在理解上有一定帮助
回到uboot:
在arch/arm/lib/bootm.c中函数boot_jump_linux()如下代码,将bi_arch_number传到r1,然后传到linux:
static void boot_jump_linux(bootm_headers_t *images, int flag)
{unsigned long machid = gd->bd->bi_arch_number;unsigned long r2;void (*kernel_entry)(int zero, int arch, uint params);kernel_entry = (void (*)(int,int,uint))images->ep;if(IMAGE_ENABLE_OF_LIBFDT && images->ft_len)r2 = (unsigned long)images->ft_addr;elser2 = gd->bd->bi_boot_params;kernel_entry(0,machid,r2);
}
images->ep就是linux在ram里面起始地址,地址直接赋值给一个函数指针,然后就当一个函数调用。uboot和linux传参,规定函数的第一个参数为0(r0 = 0),第2个参数machine id(r1= machine id),第三个参数为命令行起始地址(r2 = params addr)。
(c语言中具体地址当作函数调用,参数一般都是第一个对应r0,第2个对应r1…依次类推,这也是c调用汇编的规定)
关于MACH_TYPE_XX生成,由linux kernel一个awk脚本arch/arm/tools/gen-mach-types在编译的时候,会根据在同目录下的mach-types(修改这个文件,就可以在mach-types.h下生成形如MACH_TYPE_XX宏)文件生成linux根目录下include/generated/mach-types.h。与uboot根目录下arch/arm/include/asm/mach-types.h很相似,但在uboot下面没找到生成的脚本(个人感觉从linux下面copy过去,哈哈)。
gd->bd->bi_arch_number,gd->bd->bi_boot_params这个启动参数的方式是linux老式传参方式了,但新版本linux都是兼容的,bi_boot_params地址一般都设置在靠近ram起始地址(fireflye-rk3288设置在0x0+0x88000)。
uboot对新版命令行传参方式的支持:
在arch/arm/lib/bootm.c中boot_prep_linux()中有调用到image_setup_linux(bootm_headers_t *images) ,函数参数struct boot_headers_t结构体中成员ft_addr,ft_len就是新版参数的关键。ft_addr是dtb装载到ram中的地址,dtb可以从两个地方resource.img和boot.img装载,
boot.img是android中用到的,在android中有mkbootimg工具,将zImage(Linux),ramdisk,second stage(可选,可以将dtb放到这段)。这样,uboot就可以从boot.img中解析出sencond stage加载到内存,并将地址赋给fd_addr。关于boot.img和工具mkbootimg理解,可以参考这篇博客:通过分析mkbootimg源代码了解boot.img文件结构
dtb是linux根目录下scripts/dts/dtc(dtc工具编译的时候会产生)工具,将dts生成dtb二进制文件。dtc的工具使用如下:
dts编译成二进制文件dtb:
scripts/dtc/dtc -I dtb -O dts ./product1.dtb -o ./my.dts
dtb反编译成dts:
scripts/dtc/dtc -I dts -O dtb ./a.dts -o ./b.dtb
dts中命令行参数放在如下的节点中:
chosen{bootargs = "console=ttyS2 init=/init initrd=0x62000000,0x00800000";
};
从这个获取resource.img启动参数,在uboot根目录下common/resource.c,include/resource.h来看:
struct resource_content:这个结构体,是解析时的内存结构
struct resource_ptn_header:这个结构体,是存在于resource.img开始地方,即文件头
struct index_tbl_entry:存在于resource.img每个数据块的头
如下resource.img在nand/emmc结构示意图:
捣事的CSDN,写完整的博客,却不见了,这是我写到一半时保存在自己电脑上的先放这,心好累,以后有时间在看着补全吧,心累啊
uboot启动流程和架构相关推荐
- ARMv8架构u-boot启动流程详细分析(一)
文章目录 1 概述 2 armv8 u-boot的启动 3 u-boot源码整体结构和一些编译配置方式 3.1 编译配置方式 3.2 u-boot源码结构 4 u-boot armv8链接脚本 4.1 ...
- uboot流程——uboot启动流程
[uboot] (第五章)uboot流程--uboot启动流程 2016年11月07日 20:12:07 阅读数:2230 以下例子都以project X项目tiny210(s5pv210平台,arm ...
- U-Boot启动流程详解
参考:U-Boot顶层目录链接脚本文件(u-boot.lds)介绍 作者:一只青木呀 发布时间: 2020-10-23 13:52:23 网址:https://blog.csdn.net/weixin ...
- uboot启动流程详解
要分析boot启动流程,首先要找到程序入口地址,可以通过编译uboot生成u-boot.lds,通过查看链接脚本u-boot.lds知道入口点是 arch/arm/lib/vectors.S 文件中的 ...
- U-boot启动流程[一]
U-boot启动流程[一] 文章目录 U-boot启动流程[一] 1 U-boot总体流程 1.1 不带atf启动 1.2 Atf与U-boot组合方式启动 2 U-boot初始化 2.1 从cpu处 ...
- uboot启动Linux内核(一):uboot启动流程
1. uboot介绍: uboot是bootloader的一种,是Linux内核的引导启动程序.会初始化嵌入式平台上的一些外设(比如:ddr等),把Linux内核镜像从flash中加载到内存,在 ...
- 移植uboot-分析uboot启动流程(详解)
本节总结: uboot启动流程如下: 1)设置CPU为管理模式 2)关看门狗 3)关中断 4)设置时钟频率 5)关mmu,初始化各个bank 6)进入board_init_f()函数 (初始化定时器, ...
- 【正点原子Linux连载】第三十二章 U-Boot启动流程详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0
1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...
- U-boot启动流程[三]
U-boot启动流程[三] 文章目录 U-boot启动流程[三] 1 Linux启动基础镜像 1.1 内核镜像 1.1.1 vmlinux镜像 1.1.2 Image和zImage镜像 1.2 设备树 ...
最新文章
- B. Light It Up
- Android APK反编译 apktool使用教程
- 服务器系统功能描述,Hadoop mapreduce核心功能描述
- Linux Shell获取系统资源使用百分比(CentOS)
- 可替换元素和非替换元素
- 统计长整数n的各位上出现数字1、2、3 的次数
- 【Java】Java 线程池 8 大拒绝策略
- springboot中如何创建定时任务,以及corn表达式规则
- css background-image显示全部_CSS 与网络性能,看了都说好
- php 正则表达式 匹配 字符串,PHP 字符串与正则表达式匹配
- 【细胞分割】基于matlab GUI分水岭算法细胞分割计数【含Matlab源码 637期】
- kali linux 桌面消失_Kali安装好后,需要修改的一些常用配置
- 思科CCNP培训中OSPF协议之详细图解-IELAB
- 手机浏览器/H5页面实现打开微信代码 引导关注公众号
- 编译原理 自下而上分析题型
- PhpBazar adid SQL注入漏洞
- body加背景图片没反应_css设置背景图片不显示问题
- 网站服务器商标属于哪类,网络平台商标注册属于什么类别?-商标分类表-猪八戒知识产权...
- 用Python画小篮子
- lua pairs与ipairs区别