废弃全志A20广告机变身记(1)

本文详细介绍了U-Boot的启动流程,包括u-bootFITimage、legacyuImage、board_init_f和board_init_r等关键步骤。通过对globaldata、内存管理和设备树的解析,展示了U-Boot如何从二进制文件加载到C环境的建立。此外,还探讨了全志A20广告机的SD卡启动方式,分析了其与普通Soc启动的区别。
摘要由CSDN通过智能技术生成

前言

前几天在咸鱼上面捡到一块全志的A20广告机,总共花费了我50米。板子的电源是12V的,洗洗干净之后发现还能够继续使用,顿时欣喜若狂,因此在这里记录一下这块板子的探索记。
首先感谢这位大佬,他的文章给予了我很多帮助。

u-boot以及SD卡启动探究

uboot FIT image介绍

legacy uImage

1、Linux kernel在ARM架构中引入了device tree,全称是flattened device tree,因此可以简称为FDT。在编译Linux kernel的时候,不需要指定具体的架构或者soc,只需要告诉kernel这次编译需要那些板级和platform即可,最终会生成一个kernel image,以及多个具体的fdt image(dtb文件)。
2、另外从uboot的角度来讲,他要boot一个二进制文件,例如uboot中的kernel image,同时还需要解析里面的数据,包括内部的文件类型(kernel image、dtb文件、ramdisk image等等),还需要了解文件放置的位置,他的加载地址等。除此之外,文件在memory中的执行地址,文件是否被压缩,是否包含有一些完整的CRC校验码等。
3、这种比较老的image文件,它是对应的格式比较简单,就是在二进制文件上面加上一个header,当然这个header是可以在"include/image.h"里面看到他的定义的。这种比较老的image格式文件称之为legacy uImage。
4、legacy uImage使用mkimage工具生成,mkimage工具可以在u-boot source code的tools/mkimage里面找到。同时支持OS Kernel Image、RAMDISK Image等多种格式的image,支持gzip和bzip算法,能够进行CRC32 checksums等等。当然这种比较老的u-boot基本上已经被淘汰了,再去深究意义不大。

FIT uImage

device tree在kernel中普及之后,u-boot也马上跟进了device tree,最终推出了一种全新的image格式,这种格式的文件就是FIT(flattened image tree) uImage,FIT和FDT(flattened device tree)比较类似,利用的语法都是Device Tree Source files语法,生成的iamge文件也和dtb文件基本上一样。
FIT uImage的生成流程可以概括如下
在这里插入图片描述其中的image source file(.its)和device tree source file(.dts)类似,负责描述要生成的image file信息,mkimage和dtc工具可以将.its文件以及对应的image data file打包成一个iamge file,最后将这个file下载到memory中,最后使用bootm命令加载即可。

Image source file语法

image source file语法和device tree source file语法基本上完全是一样的,只不过多了一些images、configurations等特殊的节点。

  1. image节点
    这个节点是指定包含的二进制文件,可以指定多种类型的多个二进制文件,例如在sunxi7i.its文件中包含了kernel image 这个节点。
  2. configuration可以将不同类型的二进制文件,根据不同的场景组成一个一个的配置项。u-boot在boot的时候,以配置项为单位进行加载和执行,这样就可以根据不同的场景,方便后续的配置,实现boot u-boot的功能。
  3. image的编译FIT uImage的编译过程很简单的,根据实际的情况编写对应的image source file之后(假如名称是kernel_fdt.its),在命令中使用mkimage工具进行编译即可mkimage -f kernel_fdt.its kernel_fdt.itb,最后使用dfu工具将生成的idb文件下载到内存的某个指定地址即可。

u-boot平台代码的启动流程分析

在sunxi中,u-boot会从arch/arm/cpu/armv7/start.S的_start接口处开始执行,在后续的u-boot移植中都是按照这个开始的。

.globl	_start
	_start:
	b	reset

start会跳转到reset,而reset部分的代码为

#ifdef CONFIG_SYS_RESET_SCTRL
	bl reset_sctrl
#endif
	/*
	 * Could be EL3/EL2/EL1, Initial State:
	 * Little Endian, MMU Disabled, i/dCache Disabled
	 */
	adr	x0, vectors
	switch_el x1, 3f, 2f, 1f
3:	msr	vbar_el3, x0
	mrs	x0, scr_el3
	orr	x0, x0, #0xf		/* SCR_EL3.NS|IRQ|FIQ|EA */
	msr	scr_el3, x0
	msr	cptr_el3, xzr		/* Enable FP/SIMD */
#ifdef COUNTER_FREQUENCY
	ldr	x0, =COUNTER_FREQUENCY
	msr	cntfrq_el0, x0		/* Initialize CNTFRQ */
#endif
	b	0f
2:	msr	vbar_el2, x0
	mov	x0, #0x33ff
	msr	cptr_el2, x0		/* Enable FP/SIMD */
	b	0f
1:	msr	vbar_el1, x0
	mov	x0, #3 << 20
	msr	cpacr_el1, x0		/* Enable FP/SIMD */
0:

	/* Apply ARM core specific erratas */
	bl	apply_core_errata

	/*
	 * Cache/BPB/TLB Invalidate
	 * i-cache is invalidated before enabled in icache_enable()
	 * tlb is invalidated before mmu is enabled in dcache_enable()
	 * d-cache is invalidated before enabled in dcache_enable()
	 */

	/* Processor specific initialization */
	bl	lowlevel_init

#ifdef CONFIG_ARMV8_MULTIENTRY
	branch_if_master x0, x1, master_cpu

	/*
	 * Slave CPUs
	 */
slave_cpu:
	wfe
	ldr	x1, =CPU_RELEASE_ADDR
	ldr	x0, [x1]
	cbz	x0, slave_cpu
	br	x0		/* branch to the given address */
master_cpu:
	/* On the master CPU */
#endif /* CONFIG_ARMV8_MULTIENTRY */

	bl	_main

在这里主要的工作是reset SCTRL寄存器,具体的可以参考reset_sctrl函数,由CONFIG_SYS_RESET_SCTRL控制,一般这里是不用打开的。
之后会调用lowlevel_init函数,具体的功能解释参考u-boot的readme文档,如果是多CPU的场景,处理其他CPU的boot由相应的CONFIG_ARM_MULTIENTRY控制。最后跳转到arm公共的_main中执行。
此外_main为ctr0,ctr0是C Runtimr Startup Code的简称,意思就是运行C代码之前的准备工作。关于_main函数,ctr0中有详细的介绍。

ctr0功能概括

首先,ctr0主要为C代码的运行配置环境,为调用board_init_f函数做接口的准备,设置堆栈(为C语言函数的调用做准备,堆栈是必须的)。如果当前编译的是SPL(由CONFIG_SPL_BUILD定义),可以单独定义堆栈基地址(CONFIG_SPL_STACK),否则通过CONFIG_SYS_INIT_SP_ADDR定义堆栈基地址。
调用board_init_f_alloc_reserve接口,从堆栈开始的地方,为大名鼎鼎的GD(global data)数据分配空间。
调用board_init_f_reserve接口对GD进行初始化。一般对于编程而言,需要进行点灯操作来确定u-boot是否正确的运行了,在确定正确运行之后再进行后续的DRAM、DDR和system范围内的RAM等,当然这里的步骤都是针对SPL模式而言,对于传统模式类似。之后还需要计算代码后续还需要的一些参数,包括relocation destinationthe future stack、the future GD location等。如果是SPL模式,则会在_main函数结束之后直接返回,如果是正常的u-boot,则会继续执行后续的操作。
接下来就是清除BBS段,调用board_init_r函数,执行后续的操作。

u-boot小结

SPL是secondary program Loader的简称,之所以称为secondary是相对于ROM code而言的。SPL是u-boot中独立的一个代码分支机构,由CONFIG_SPL_BUILD配置项控制,是为了在正常的u-boot image之外,提供一个独立的,小size的SPL image。通常是用于那些SRAM比较小、无法直接装载并运行整个u-boot的平台。
如果使用了SPL功能,则u-boot的启动流程为
(1)、ROM code 加载SPL并运行。
(2)、SPL进行必要的初始化操作,加载u-boot并运行。
(3)、u-boot后续操作。
而整个配置过程所使用到的一些宏定义可以总结如下

CONFIG_SYS_RESET_SCTRL,控制是否在启动的时候reset SCTRL寄存器,一般不需要打开。
CONFIG_ARM_ERRATA_XXX,控制ARM core的勘误信息,一般不需要打开。
CONFIG_GICV2、CONFIG_GICV3,控制GIC的版本,用到的时候再说明。
CONFIG_ARMV8_MULTIENTRY,控制是否在u-boot中使用多CPU,一般不需要。
CONFIG_SPL_BUILD,是否是能SPL的编译,需要的话可以打开。
CONFIG_SPL_STACK,如果配置了CONFIG_SPL_BUILD,是否为SPL image配置单独的stack(SP基址),如果需要,通过该配置项配置,如果不需要,则使用CONFIG_SYS_INIT_SP_ADDR。
CONFIG_SYS_INIT_SP_ADDR,配置u-boot的stack(SP基址),对于u-boot功能来说,必须提供。

uboot Generic Board部分

u-boot启动中的具体板型包括board_init_rboard_init_f里面的内容,这里面的整个过程会持续到main_loop,也就是传说中的命令行。
u-boot的策略就是声明一系列的API(如low_level_init、board_init_f、board_init_r等等),并在u-boot的核心控制逻辑中调用他们。而作为平台开发者所做的事就是根据所需,实现他们。
与此同时,为了减少开发的工作量,u-boot为大部分API提供了通用的实现(一般是通过CONFIG配置项或者若定义去控制是否实现编译)。以board_init_fboard_init_r两个板级初始化接口为例,u-boot分别在common/board_f.ccommon/board_r.c里面进行了实现。

ifndef CONFIG_SPL_BUILD
…
# boards
obj-$(CONFIG_SYS_GENERIC_BOARD) += board_f.o
obj-$(CONFIG_SYS_GENERIC_BOARD) += board_r.o
…
endif # !CONFIG_SPL_BUILD 

这两个通用的接口是由CONFIG_SYS_GENERIC_BOARD配置项控制的,并且只会在u-boot image中被编译。再通过arch/Kconfig中ARM平台有关的配置可知。

config ARM
        bool "ARM architecture"
        select CREATE_ARCH_SYMLINK
        select HAVE_PRIVATE_LIBGCC if !ARM64
        select HAVE_GENERIC_BOARD
        select SYS_GENERIC_BOARD
        select SUPPORT_OF_CONTROL

ARM平台已经自动使能了CONFIG_SYS_GENERIC_BOARD配置,因此u-boot image有关的板级启动流程是由Generic Board的代码实现的。

_main函数

_main函数的分析已经进行过了,主要是设置初始化的堆栈,对应的基地址是由CONFIG_SYS_INIT_SP_ADDR定义。
其次是分配global data所需的空间,将堆栈设置为16bits对齐之后,调用board_init_f_alloc_reserve接口,从堆栈开始的地方,为u-bootglobal data(struct global_data)分配空间。如下

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

需要注意的是,如果定义了CONFIG_SYS_MALLOC_F_LEN之后,则会预先留出early malloc所需的空间。
global data的空间分配之后,调用board_init_f_init_reserve,初始化global data。所谓的初始化,无非就是一些清零的操作,不过有几个地方需要注意
(1)、如果不是ARM平台(!CONFIG_ARM),则可以调用arch_setup_gd接口,进行arch级别的设置。当然前提是对应的arch应该有这个接口。
(2)、如果定义了CONFIG_SYS_MALLOC_F,则会初始化gd->malloc_base
执行前置(front)操作,调用board_init_f接口,执行前置的初始化操作。
然后执行relocation操作,清除BBS段,执行后续的rear初始化操作。

global data思考

u-boot是一个bootloader,有些情况下,他可能只位于系统的只读存储器(ROM或者FLASH)中,并从哪里开始执行,因此我们要怎么处理呢?
因此这种情况下,在执行u-boot的期间(在将自己copy到可读的存储器之前),他所在的存储空间是不可写的,这样会有两个问题
(1)、堆栈无法使用,无法执行函数的调用,即无法使用C环境。
(2)、没有data段(或者正确初始化data段)可用,不同的函数或者代码之间无法通过全局变量的形式共享数据。
对于问题(1),通常的解决方案是:
u-boot运行起来之后,在那些不需要执行任何初始化动作即可使用的、可读写的存储区域,开辟一段堆栈(stack)空间。
一般来说,大部分的平台如ARM平台,都有自己的SRAM可以作为堆栈空间。如果实在不行,也可以借助CPUdata cache的方法。
对于问题(2),通常的解决是:
首先,对于开发者而言,在u-bootcopy到可读写的RAM之前,永远都不要使用全局变量。其次就是在relocation之前,不同模块之间确实有通过全局全局变量传参数的需求时,可以考虑通过global data实现。

gloabal data

为了在relocation前通过全局变量的形式传递数据,u-boot设计了一套巧妙的方法。首先,定义一个struct global_data类型的数据结构,里面保存了各色各样需要传递的数据,这里对应的数据类型在”include/asm-generic/global_data.h”中。
堆栈配置好之后,在堆栈开始的位置为struct global_data预留空间,并将开始地址(也就是一个struct global_data指针)保存在一个寄存器中,后续的传递都是通过保存在寄存器中的指针实现的。对于arm64平台而言,该指针保存在了X18寄存器中。

    bl      board_init_f_alloc_reserve
    mov     sp, x0
    /* set up gd here, outside any C code */
    mov     x18, x0
    bl      board_init_f_init_reserve

上面的board_init_f_alloc_reserve的返回值(x0)就是global data的指针。

/* arch/arm/include/asm/global_data.h */

#ifdef __clang__

#define DECLARE_GLOBAL_DATA_PTR
#define gd      get_gd()

static inline gd_t *get_gd(void)
{
        gd_t *gd_ptr;

#ifdef CONFIG_ARM64
        …
        __asm__ volatile("mov %0, x18\n" : "=r" (gd_ptr));
#else}

#else

#ifdef CONFIG_ARM64
#define DECLARE_GLOBAL_DATA_PTR         register volatile gd_t *gd asm ("x18")
#else#endif 

global data准备好之后,u-boot会执行前置的板级初始化操作,即board_init_f。所谓的前置的初始化动作,主要是relocation之前的初始化操作,也就是说在执行board_init_f的时候,u-boot很有可能还在只读的存储器中。
对于ARM等平台而言,u-boot提供了一个通用的board_init_f接口,该接口使用u-boot惯用的设计思路——u-boot将需要在board_init_f中初始化的内容,抽象为系列的API。这些APIu-boot声明,由平台开发者根据实际情况来实现。
board_init_f位于common/board_f.c中,他的接口实现非常简单,如下

  void board_init_f(ulong boot_flags)
    {
    …

            gd->flags = boot_flags;
            gd->have_console = 0;

            if (initcall_run_list(init_sequence_f))
                    hang();}

global data进行初始化之后,调用位于init_sequence_f数组中的各种初始化API
trace_early_initCONFIG_TRACE配置项控制,暂时可以不用关心。
initf_malloc 如果定义了CONFIG_SYS_MALLOC_F_LEN,则调用initf_malloc,初始化和malloc有关的global data
arch_cpu_init cpu级别的初始化操作,可以在需要的时候由CPU有关的code实现。
initf_dm driver model有关的初始化操作,如果定义了CONFIG_DM,则调用dm_init_and_scan初始化并扫描系统所有的device,如果定义了CONFIG_TIMER_EARLY,调用dm_timer_init初始化driver model所需的timer
board_early_init_f 如果定义了CONFIG_BOARD_EARLY_INIT_F,则调用board_early_init接口,以完成特定的功能。
timer_init 初始化系统的timer
get_clocks 获取当前CPUBUS的时钟频率,并保存在global data中。
env_init 初始化环境变量有关的逻辑。
init_board_rate 获取当前使用的波特率,可以有两个途径,从baudrate中获取或者从CONFIG_BAUDRATE配置中获取。
serial_init 初始化serial,包括u-boot serial core以及具体的serial driver,该函数执行之后,串口可用。
console_init_f配置内容如下



    /* Called before relocation - use serial functions */
    int console_init_f(void)
    {
            gd->have_console = 1;

    #ifdef CONFIG_SILENT_CONSOLE
            if (getenv("silent") != NULL)
                    gd->flags |= GD_FLG_SILENT;
    #endif

            print_pre_console_buffer(PRE_CONSOLE_FLUSHPOINT1_SERIAL);

            return 0;
    }

初始化系统的控制台之后串口可以输出,相应的配置项为CONFIG_SILENT_CONSOLE
fdtdec_prepare_fdt 如果定义了CONFIG_OF_CONTROL,调用fdtdec_prepare_fdt接口,准备device tree有关的内容。

display_options/display_text_info/print_cpuinfo/show_board_info

这个文件是通过控制台显示一些信息,可以用于debug
misc_init_f 如果使能了CONFIG_MISC_INIT_F,则调用misc_init_f执行misc driver有关的初始化内容。
init_func_i2c 如果使能了CONFIG_HARD_I2C或者CONFIG_SYS_I2C,则调用init_func_i2c执行i2c driver有关的初始化。
init_func_spi 如果使能了CONFIG_HARD_SPI,则调用init_func_spi执行spi driver有关的初始化。
announce_dram_init 宣布要进行DDR的初始化动作了。
dram_init 调用dram_init接口,初始化系统中的DDR,dram_init应该由平台相关的代码实现。另外,如果DDRSPL中已经实现过了,则不需要重新初始化,只需要将DDR信息保存在global data中即可——gd->ram_size=?
testdram 如果定义了CONFIG_SYS_DRAM_TEST,则会调用testdram执行DDR的测试操作,可以在开发阶段打开,系统稳定之后再关闭即可。
relocate完成之后,真正的C语言运行环境才算建立起来,接下来会执行后续的板级初始化操作,即board_init_r函数。board_init_rboard_init_f的设计思路基本上一样,也有一个很长的初始化序列——init_sequence_r,该序列包含下面的初始化函数,逻辑比较简单,这里不做详细的介绍,权当是index吧。

initr_trace,初始化并使能u-boot的tracing system,涉及的配置项有CONFIG_TRACE。
initr_reloc,设置relocation完成的标志。
initr_caches,使能dcache、icache等,涉及的配置项有CONFIG_ARM。
initr_malloc,malloc有关的初始化。
initr_dm,relocate之后,重新初始化DM,涉及的配置项有CONFIG_DM。
board_init,具体的板级初始化,需要board代码根据需要实现,涉及的配置项有CONFIG_ARM。
set_CPU_clk_info,initialize clock framework,涉及的配置项有CONFIG_CLOCKS。
initr_serial,重新初始化串口(暂时不知道意义)。
initr_announce,宣布已经在RAM中执行,会打印relocate后的地址。
board_early_init_r,由板级代码实现,涉及的配置项有CONFIG_BOARD_EARLY_INIT_R。
arch_early_init_r,由arch代码实现,涉及的配置项有CONFIG_ARCH_EARLY_INIT_R。
power_init_board,板级的power init代码,由板级代码实现,例如hold住power。
init_flash、init_nand、init_onenand、initr_mmc、initr_dataflash,各种flash设备的初始化。
initr_env,环境变量有关的初始化。
initr_secondary_cpu,初始化其他的CPU core。
stdio_add_devices,各种输入输出设备的初始化,如LCD driver等。
interrupt_init 中断有关的初始化。
initr_enable_interrupts,使能系统中断,涉及的配置项有CONFIG_ARM(ARM平台在u-boot是在开中断的情况下运行的)。
initr_status_led 状态指示LED的初始化,涉及的配置项有CONFIG_STATUS_LED、STATUS_LED_BOOT。
initr_ethaddr Ethernet的初始化,涉及配置项有CONFIG_CMD_NET。
board_late_init 由板级代码实现,涉及的配置项有CONFIG_BOARD_LATE_INIT。
run_main_loop/main_loop、执行到main_loop开始命令行的操作

SD卡启动探究

前面已经详细的介绍了u-boot的整个启动过程以及启动流程,但是这对于手中的这块废板子而言终究还是纸上得来终觉浅。
前面谈到的所有u-boot都是假设于在某一个地址上面启动的,实际使用中u-boot的启动位置可以是内部的EMMC、NAND FLASH、NOR FLASH或者外部的SD卡。具体是那个启动地址还需要根据实际的处理器型号来进行确立。
A20的u-boot启动方式和其他Soc的启动方式有很大的区别,相对于其他Soc的启动,增加了一个8kb字节的启动段。
在这里插入图片描述
在这里插入图片描述
因此对于普通的SPL启动而言,只需要把对应的uboot放在地址上即可。

总结

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1、安装PC上的升级工具Livesuite.exe。      2、打开Livesuit      找到升级工具文件双击打开,如下图所示:      3、选择固件      点击上图的“否”关闭用户向导升级,并点击“选择固件”按钮选择后缀名为.img文件,如下图:      4、关闭电源      确保将I130关机(开机状态下,长按电源键8秒钟以上可强制断电关机)      5、按下组合键开始升级      按住I130平板的任意键(除了电源键,推荐音量+)不放开,再通过USB数据线连上电脑,然后快速连续点击电源键5-6次,记住此时千万不能松开开始按住的按键,直到电脑上出现下图界面时,松开所有键开始升级。      (注:如果此时弹出安装驱动程序的对话框,请把路径指向Livesuit安装目录下的UsbDriver文件夹,并按下一步提示完成驱动程序的安装)。      6、选择强制格式化升级      Livesuit提示“是否强制格式化”,点击“是”,则原先安装的APK全部被删除,点击否则原装APK不会删除。      推荐强制格式化,进行彻底更新升级!否则可能更新不彻底而变砖!      7、升级开始      弹出“确定要强制格式化”对话框,选择“是”,升级开始,如下图:      8、升级完成      固件更新过程大约持续3分半钟左右,请耐心等待,直到提示“升级成功”。   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值