概述:
本文将从两个方面来阐述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启动流程和架构相关推荐

  1. ARMv8架构u-boot启动流程详细分析(一)

    文章目录 1 概述 2 armv8 u-boot的启动 3 u-boot源码整体结构和一些编译配置方式 3.1 编译配置方式 3.2 u-boot源码结构 4 u-boot armv8链接脚本 4.1 ...

  2. uboot流程——uboot启动流程

    [uboot] (第五章)uboot流程--uboot启动流程 2016年11月07日 20:12:07 阅读数:2230 以下例子都以project X项目tiny210(s5pv210平台,arm ...

  3. U-Boot启动流程详解

    参考:U-Boot顶层目录链接脚本文件(u-boot.lds)介绍 作者:一只青木呀 发布时间: 2020-10-23 13:52:23 网址:https://blog.csdn.net/weixin ...

  4. uboot启动流程详解

    要分析boot启动流程,首先要找到程序入口地址,可以通过编译uboot生成u-boot.lds,通过查看链接脚本u-boot.lds知道入口点是 arch/arm/lib/vectors.S 文件中的 ...

  5. U-boot启动流程[一]

    U-boot启动流程[一] 文章目录 U-boot启动流程[一] 1 U-boot总体流程 1.1 不带atf启动 1.2 Atf与U-boot组合方式启动 2 U-boot初始化 2.1 从cpu处 ...

  6. uboot启动Linux内核(一):uboot启动流程

    1. uboot介绍:    uboot是bootloader的一种,是Linux内核的引导启动程序.会初始化嵌入式平台上的一些外设(比如:ddr等),把Linux内核镜像从flash中加载到内存,在 ...

  7. 移植uboot-分析uboot启动流程(详解)

    本节总结: uboot启动流程如下: 1)设置CPU为管理模式 2)关看门狗 3)关中断 4)设置时钟频率 5)关mmu,初始化各个bank 6)进入board_init_f()函数 (初始化定时器, ...

  8. 【正点原子Linux连载】第三十二章 U-Boot启动流程详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  9. U-boot启动流程[三]

    U-boot启动流程[三] 文章目录 U-boot启动流程[三] 1 Linux启动基础镜像 1.1 内核镜像 1.1.1 vmlinux镜像 1.1.2 Image和zImage镜像 1.2 设备树 ...

最新文章

  1. B. Light It Up
  2. Android APK反编译 apktool使用教程
  3. 服务器系统功能描述,Hadoop mapreduce核心功能描述
  4. Linux Shell获取系统资源使用百分比(CentOS)
  5. 可替换元素和非替换元素
  6. 统计长整数n的各位上出现数字1、2、3 的次数
  7. 【Java】Java 线程池 8 大拒绝策略
  8. springboot中如何创建定时任务,以及corn表达式规则
  9. css background-image显示全部_CSS 与网络性能,看了都说好
  10. php 正则表达式 匹配 字符串,PHP 字符串与正则表达式匹配
  11. 【细胞分割】基于matlab GUI分水岭算法细胞分割计数【含Matlab源码 637期】
  12. kali linux 桌面消失_Kali安装好后,需要修改的一些常用配置
  13. 思科CCNP培训中OSPF协议之详细图解-IELAB
  14. 手机浏览器/H5页面实现打开微信代码 引导关注公众号
  15. 编译原理 自下而上分析题型
  16. PhpBazar adid SQL注入漏洞
  17. body加背景图片没反应_css设置背景图片不显示问题
  18. 网站服务器商标属于哪类,网络平台商标注册属于什么类别?-商标分类表-猪八戒知识产权...
  19. 用Python画小篮子
  20. lua pairs与ipairs区别

热门文章

  1. Python:暴力破解密码 - 压缩包、web实战
  2. 智能门锁里的大市场,凯迪仕签署上市辅导协议,冲击A股上市
  3. Lync2010客户端无法登录Lync Server 2013
  4. 电脑报网站使用盗版软件?
  5. chm文件打开是空白的
  6. D-ID生成式人工智能视频合成技术,将原创视频内容变得唾手可得
  7. 在python里面使用you-get批量下载哔哩哔哩视频
  8. .NET程序集版本搜索分析
  9. 计算机视觉用于图像识别的难点在哪?
  10. Burpsuite专题学习指南