对uboot启动的BL2阶段的主体代码的分析

BL2阶段从BL1阶段调用start_armboot函数完成远跳转至此阶段继续,start_armboot函数与其中调用的函数一起组成了我们BL2阶段的实现,而start_armboot包含在Board.c文件中,所以此份笔记以Board.s中的start_armboot函数作为分析目标。

宏观的分析:

BL2阶段主要是进行开发板外部硬件的初始化、以及软件数据结构的初始化。uboot在启动完后打印出一些信息,然后进入倒数bootdelay秒后执行bootcmd对应的启动命令。如果用户没有进行干涉则会执行bootcmd对应的命令,然后进入自启动内核流程(此后uboot就死掉了),此时用户可以按回车键打断uboot的自启动过程进入uboot的命令行下,然后uboot就会一直工作在命令行下。而uboot的命令行就是一个死循环,循环体内不断地重复:接受、解析、执行命令。  ---执行的快,死得快。

核心的工作:

初始化第一阶段结束还没被初始化的硬件:SOC的外部硬件;uboot本身的一些东西:uboot的命令;环境变量等。

分析的开始:

1.定义了一个二重函数指针类型的变量:

init_fnc_t **init_fnc_ptr;

而这个init_fnc_t类型的变量是在之前被定义:

typedef int (init_fnc_t) (void);

对这个变量在后面会被使用。

2.uboot的全局变量的管理:gd_t 结构体变量:

DECLARE_GLOBAL_DATA_PTR

在Board.c中了一声明个宏,而这个宏的定义如下:

#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

这里的宏定义就是定义了一个名字叫做gd_t的指针类型的全局变量,register将变量放在寄存器中,提升读取、运行的效率,而根据后面gcc支持的一种语法可知将其放在了寄存器r8中,volatile设置变量为可变类型的。这个全局变量实际是一个结构体变量,里面存放着uboot常用的所有全局变量(或者说是所有全局变量的指针)。

对于gd_t内具体的内容如下:

typedef  struct  global_data {bd_t       *bd;unsigned long   flags;unsigned long baudrate;unsigned long  have_console;   /* serial_init() was called */unsigned long reloc_off;  /* Relocation Offset */unsigned long    env_addr;   /* Address  of Environment struct */unsigned long   env_valid;  /* Checksum of Environment valid? */unsigned long   fb_base;    /* base address of frame buffer */#ifdef CONFIG_VFDunsigned char    vfd_type;   /* display type */#endif#if 0unsigned long  cpu_clk;    /* CPU clock in Hz!     */unsigned long bus_clk;phys_size_t ram_size;   /* RAM size */unsigned long reset_status;   /* reset status register at boot */#endifvoid       **jt;       /* jump table */} gd_t;

而其中很重要的一部分关于板子硬件相关的信息又设置在bd_t 这个结构体内:

typedef struct bd_info {int          bi_baudrate;    /* serial console baudrate */unsigned long  bi_ip_addr; /* IP Address */unsigned char   bi_enetaddr[6]; /* Ethernet adress */struct environment_s          *bi_env;ulong            bi_arch_number; /* unique id for this board */ulong         bi_boot_params; /* where this board expects params */struct             /* RAM configuration */{ulong start;ulong size;}            bi_dram[CONFIG_NR_DRAM_BANKS];#ifdef CONFIG_HAS_ETH1/* second onboard ethernet port */unsigned char   bi_enet1addr[6];#endif} bd_t;

3.定义了存储全局变量的地址起始地址,为全局变量分配内存:

在这一步之前,gd_base还只是一个指针,没有没有被分配内存,下面为其分配内存

ulong gd_base;gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);

下面对gd进行实例化:

gd = (gd_t*)gd_base;

内存的分析:
        CFG_UBOOT_BASE :0x33e00000
        CFG_UBOOT_SIZE : 2MB
        CFG_MALLOC_LEN : 896+16=912KB   堆区
        CFG_STACK_SIZE : 512KB             栈区
        gd  sizeof(gd_t) : 4*9=36字节  
        bd  sizeof(bd_t) :  约为44字节

这里使用的内存就是上一个阶段BL1第三次设置的2MB的栈,进过分析大致可知这些全局变量从uboot的起始地址也就是0x33e00000上方0.4MB左右的区域开始向上存储(起始地址向上走200~300KB的内存区域是uboot镜像的存储位置)

4.循环执行函数指的逻辑分析:

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {if ((*init_fnc_ptr)() != 0) {hang ();}

对循环的分析:

(1)循环的初始条件:init_fnc_ptr = init_sequence。这里的init_fnc_ptr 就相当于循环中的i,他为二重指针,由第一步可知指向的函数为 int func(void)型。

对于init_sequence索引可知是一个函数数组,里面存放着一些初始化函数,且都是int func(void)型的:

init_fnc_t *init_sequence[] = {cpu_init,        /* basic cpu dependent setup */#if defined(CONFIG_SKIP_RELOCATE_UBOOT)reloc_init,       /* Set the relocation done flag, mustdo this AFTER cpu_init(), but as soonas possible */#endifboard_init,       /* basic board dependent setup */interrupt_init,        /* set up exceptions */env_init,        /* initialize environment */init_baudrate,      /* initialze baudrate settings */serial_init,       /* serial communications setup */console_init_f,        /* stage 1 init of console */display_banner,        /* say that we are here */#if defined(CONFIG_DISPLAY_CPUINFO)print_cpuinfo,     /* display cpu info (and speed) */#endif#if defined(CONFIG_DISPLAY_BOARDINFO)checkboard,        /* display board info */#endif#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)init_func_i2c,#endifdram_init,        /* configure available RAM banks */display_dram_config,NULL,};

这样一来就相当于将数组内的第一个函数的地址赋给循环变量init_fnc_ptr。

(2)循环终止条件:*init_fnc_ptr

这里借助了字符串的概念,在数组的最后以NULL结束,指针不为NULL时继续循环,当判断到函数指针为NULL时,循环终止。(3)循环体:++init_fnc_ptr。  指针++,相当于执行数组内的下一个函数。

(4)函数的返回值都设置为成功执行返回0,然后通过判断函数的返回值判断函数的执行状态。

void hang (void){puts ("### ERROR ### Please RESET the board ###\n");for (;;);}

如果返回值不为0,打印错误提示信息,进入死循环,挂起启动过程。

5.对循环内的函数的具体分析:(重点)

函数指针数组内的函数都是针对CPU以外,board级别的外部硬件的初始化。

(1)cpu_init:实际内部是空的,在BL1阶段已经完成了CPU级别的底层的初始化;

(2)board_init : 这是针对我们uboot所适配的目标板子的硬件的初始化,其中包括:

int board_init(void)
{DECLARE_GLOBAL_DATA_PTR;
#ifdef CONFIG_DRIVER_SMC911Xsmc9115_pre_init();
#endif#ifdef CONFIG_DRIVER_DM9000dm9000_pre_init();
#endifgd->bd->bi_arch_number = MACH_TYPE;gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);return 0;
}

dm9000网卡的初始化:dm9000_pre_init();

开发板机器码的指定:

gd->bd->bi_arch_number = MACH_TYPE

uboot在启动时会将其作为参数传给内核,内核中的机器码的设置也必须与这里保持一致,否则不能启动内核。

uboot传参地址的指定:

gd->bd->bi_boot_params = (PHYS_SDRAM_1+0x100);

这是uboot中一个很重要的元素,uboot在启动内核传参时会使用三个寄存器r0、r1、r2,而其中的一个寄存器就指向这个bi_boot_params,然后uboot通过读取这个寄存器就相当于读取bi_boot_params内存储的bootargs,最后通过bootargs的传参就可以进行内核的启动。  分析的知这个传参地址是:0x30001000

(3)interrupt_init :

字面上看是中断的初始化,但是实际是进行定时器的初始化,并设置了uboot命令行模式下的使用轮询的方式检测用户命令输入的时间间隔。

timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | TCON_4_UPDATE;/* auto load, start Timer 4 */timers->TCON = (timers->TCON & ~0x00700000) | TCON_4_AUTO | COUNT_4_ON;

在这里设置的时间间隔是10ms

(4)env_init :环境变量相关的初始化  uboot的环境变量

由于uboot支持nand、inand、sd等多种启动方式,所以各种不同的启动介质的env也就放在不同的位置中,而我们这款板子的env_init函数所对应的文件是Env_movi.c文件。

经过基本分析,这个函数只是对内存里维护的那一份uboot的env做了基本的初始化或者说是判定(判定里面有没有能用的环境变量)。当前因为我们还没进行环境变量从SD卡到DDR中的relocate,因此当前环境变量是不能用的。而在start_armboot函数中(776行)调用env_relocate才进行环境变量从SD卡中到DDR中的重定位。重定位之后需要环境变量时才可以从DDR中去取,重定位之前如果要使用环境变量只能从SD卡中去读取。

(5)init_baudrate:波特率的设置

static int init_baudrate (void)
{char tmp[64];  /* long enough for environment variables */int i = getenv_r ("baudrate", tmp, sizeof (tmp));gd->bd->bi_baudrate = gd->baudrate = (i > 0)? (int) simple_strtoul (tmp, NULL, 10): CONFIG_BAUDRATE;return (0);
}

这里通过getenv_r函数读取环境变量baudrate,这个环境变量在bootargs中会有设置,然后放在tmp中,再使用simple_strtoul将字符串类型的环境变量转换为数字类型的具体的波特率的值,最后将这个值传给gd全局变量结构体中负责波特率的成员变量bi_baudrate。

(6)serial_init:串口的初始化

其实在上一个阶段也就是BL1中的lowlevel_init函数中已经进行了串口的初始化,但是对这个函数进行分析可知函数内并未做什么实际的东西而只是利用循环进行了一段的空跑,也就是说串口的初始化还是在BL1阶段完成,这里只是做了一个样子。

(7)console_init_f: 控制台的初始化的第一阶段

int console_init_f (void)
{gd->have_console = 1;#ifdef CONFIG_SILENT_CONSOLEif (getenv("silent") != NULL)gd->flags |= GD_FLG_SILENT;
#endifreturn (0);
}

关于控制台的初始化被分为两个阶段,这个阶段只是做了一些简单的东西。将gd内与控制台相关的成员变量设置为1。

说到这里不得不说说控制台究竟是个什么东西:控制台就是一个用软件虚拟出来的设备,这个设备有一套专用的通信函数(发送、接收···),控制台的通信函数最终会映射到硬件的通信函数中来实现。uboot中实际上控制台的通信函数是直接映射到硬件串口的通信函数中的,也就是说uboot中用没用控制台其实并没有本质差别。

(8)display_banner :uboot启动信息的打印    

static int display_banner (void)
{printf ("\n\n%s\n\n", version_string);debug ("U-Boot code: %08lX -> %08lX  BSS: -> %08lX\n",_armboot_start, _bss_start, _bss_end);
#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */debug("\t\bMalloc and Stack is above the U-Boot Code.\n");
#elsedebug("\t\bMalloc and Stack is below the U-Boot Code.\n");
#endif
#ifdef CONFIG_MODEM_SUPPORTdebug ("Modem Support enabled\n");
#endif
#ifdef CONFIG_USE_IRQdebug ("IRQ Stack: %08lx\n", IRQ_STACK_START);debug ("FIQ Stack: %08lx\n", FIQ_STACK_START);
#endifopen_backlight();//lqm.//open_gprs();return (0);
}

以上是打印的内容,值得思考的是上一步并未完成初始化控制台,但是这里依旧使用完了printf进行输出,进过分析可知printf调用了一个put函数,这个函数会判断控制台是否被初始化,如果未被初始化成功的话就会调用serial_puts(再调用serial_putc直接操作串口寄存器进行内容发送,跟控制台无关),相反如果console初始化好了则调用fputs完成串口发送(这条线才是控制台)。

(9)print_cpuinfo :CPU的有关信息的打印

这部分主要与裸机的寄存器的相关,要进行比较麻烦的推断,这里不做介绍。

(10)checkboard :板子名的打印

int checkboard(void)
{
#ifdef CONFIG_MCP_SINGLE
#if defined(CONFIG_VOGUES)printf("\nBoard:   VOGUESV210\n");
#elseprintf("\nBoard:   X210\n");
#endif //CONFIG_VOGUES
#elseprintf("\nBoard:   X210\n");
#endifreturn (0);
}

通过判断相关宏是否存在来确定当前开发板的名字并进行打印。

(11)init_func_i2c :i2c的初始化

但是经过分析可知,因为此款板子未使用i2c,未定义对应的宏文件,自然也就不会被执行。

(12)dram_init :这里是进行软件方面的初始化:

int dram_init(void)
{DECLARE_GLOBAL_DATA_PTR;gd->bd->bi_dram[0].start = PHYS_SDRAM_1;gd->bd->bi_dram[0].size = PHYS_SDRAM_1_SIZE;#if defined(PHYS_SDRAM_2)gd->bd->bi_dram[1].start = PHYS_SDRAM_2;gd->bd->bi_dram[1].size = PHYS_SDRAM_2_SIZE;
#endif#if defined(PHYS_SDRAM_3)gd->bd->bi_dram[2].start = PHYS_SDRAM_3;gd->bd->bi_dram[2].size = PHYS_SDRAM_3_SIZE;
#endifreturn 0;
}

进行gd结构体中与DRAM相关的成员元素的填充,关于DRAM硬件的初始化在BL1阶段已经完成。

在这里指定了DRAM1的起始地址ox300000000,大小为512MB;DRAM2的起始地址ox400000000,大小为512MB。注:uboot中通过 bdinfo 命令可以得到DRAM的情况

(13)display_dram_config :对上一步填充的值进行打印

for(i=0; i<CONFIG_NR_DRAM_BANKS; i++) {printf ("Bank #%d: %08lx ", i, gd->bd->bi_dram[i].start);print_size (gd->bd->bi_dram[i].size, "\n");}

6.    初始化flash:

#ifndef CFG_NO_FLASH/* configure available FLASH banks */size = flash_init ();display_flash_config (size);

虽然nandflash和norflash都跟着flash,但是一般情况下我们不将nandflash称为flash,所以这里是进行norflash的初始化,但是在S5PV210中并没有norflash,所以这块的代码可以删除。

7.堆内存管理器的初始化:

#ifdef CONFIG_MEMORY_UPPER_CODE /* by scsuh */mem_malloc_init (CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE);

在uboot中维护了一段预留的896KB的堆空间,在这里进行初始化之后就可以使用malloc和free来操作内存。

8.下面针对具体的某个开发板进行开发板独特的初始化:

uboot的Board.c中包含了多个板子独有的初始化(但是经过分析只是进行了MMC相关的初始化),然后通过条件编译选定运行开发板对应的代码。

关于MMC的初始化:

puts ("SD/MMC:  ");mmc_exist = mmc_initialize(gd->bd);

由于我们使用的是sd启动的启动方式,所以在这里打印出sd卡的一些信息,也是作为一个启动信息的展示。

然后在mmc_initialize函数中完成具体的MMC的初始化:

if (board_mmc_init(bis) < 0)cpu_mmc_init(bis);

而mmc_initialize函数中先是使用board_mmc_init函数进行开发板级别的初始化,失败之后再调用cpu_mmc_init函数完成CPU级别的MMC的初始化。这里就体现出了我们uboot中的架构分层思想了,在初始化时先考虑外部board级别的外设的初始化,初始化不成功再去考虑从soc内部初始化。

最后通过计算再打印出MMC控制器所对应的设备(这里是sd卡)的大小信息。

printf("%ldMB\n", (mmc->capacity/(1024*1024/(1<<9))));

9.环境变量的重定位:

在前面的函数指针数组讲过,在哪里进行了环境变量的判定和基本的初始化,但是在此之前仍然不能使用,而这里就是将环境变量的重定位,将环境变量从sd卡中搬移至DRAM。

env_relocate ();

在这个函数中核心的是通过malloc在之前初始化的那段堆区中申请一段内存用存放我们的环境变量。

env_ptr = (env_t *)malloc (CFG_ENV_SIZE);DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);

这里值得一提的是:烧录的阶段之后,我们的sd卡中分别存放着已经烧录了的uboot的镜像+kernel+rootfs,并未指定和烧录环境变量,那么环境变量究竟是怎么存在sd的扇区内的?

进过分析可知在uboot烧录后的第一次启动时,会先从继续从sd的固定分区读取环境变量,但是都区之后经过CRC校验得知sd中无环境变零,然后就会从程序的也就是硬编码中读取环境变量到DRAM中,然后再将环境变量写入sd的那个固定的扇区内,但是具体是在哪里进行sd卡对应扇区的搬移未分析清除;接下来在下一次启动时就可以直接从sd卡的那个扇区读取,最后搬移至DRAM实现重定位。

puts ("*** Warning - bad CRC, using default environment\n\n");

这个就是env_relocate函数中第一次启动时由于sd卡的扇区内无环境变量而打印出来的信息。

env_relocate_spec ();

而这个函数真正实现环境变量从sd卡到DRAM重定位的函数。

10.获取开发板的ip地址,Mac地址:

(1)获取ip地址:

/* IP Address */gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");

通过getenv_IPaddr函数来获取,而参数ipaddr优先从环境变量中获取,如果从环境变量中获取失败则会将其设置为我x210_sd.h中宏定义的ip地址。

而实际上getenv_IPaddr函数内部又是调用string_to_ip来实现:

IPaddr_t getenv_IPaddr (char *var)
{return (string_to_ip(getenv(var)));
}

getenv获取字符串类型的ip地址,然后string_to_ip函数再将其转换成点分十进制类型。

(2)获取Mac地址:

i = getenv_r ("ethaddr", tmp, sizeof (tmp));

通过getenv_r函数优先从环境变量中获取Mac地址。

for (reg = 0; reg < 6; ++reg) {gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;

然后又将这个值储存在gd结构体中。

11.开发板驱动设备的初始化:

devices_init (); /* get the devices list going. */

这里初始化的设备都是驱动设备的初始化,是直接从linux内核中移植过来的,他集中执行各种硬件驱动的init函数。由于驱动源码分析起来的难度较大,在这里就不做陈述了。

12.控制台的第阶段初始化:

#if !defined(CONFIG_SMDK6442)console_init_r ();  /* fully init console as a device */

这里是进行纯软件架构方面的初始化,也就是给console相关的数据结构中填充相应的值。
 具体的代码不做分析,不过对uboot打印的东西做一个展示:

puts ("In:      ");if (stdio_devices[stdin] == NULL) {puts ("No input devices available!\n");} else {printf ("%s\n", stdio_devices[stdin]->name);}puts ("Out:     ");if (stdio_devices[stdout] == NULL) {puts ("No output devices available!\n");} else {printf ("%s\n", stdio_devices[stdout]->name);}puts ("Err:     ");if (stdio_devices[stderr] == NULL) {puts ("No error devices available!\n");} else {printf ("%s\n", stdio_devices[stderr]->name);}

13.中断的初始化:

 /* enable exceptions */enable_interrupts ();

但是由于uboot中未使用中断,所以根据条件编译这里所对应的的函数实际是为空。

#ifdef CONFIG_USE_IRQ
/* enable IRQ interrupts */
void enable_interrupts(void)
{unsigned long temp;__asm__ __volatile__("mrs %0, cpsr\n" "bic %0, %0, #0x80\n" "msr cpsr_c, %0":"=r"(temp)::"memory");
}/** disable IRQ/FIQ interrupts* returns true if interrupts had been enabled before we disabled them*/
int disable_interrupts(void)
{unsigned long old, temp;__asm__ __volatile__("mrs %0, cpsr\n""orr %1, %0, #0xc0\n" "msr cpsr_c, %1":"=r"(old), "=r"(temp)::"memory");return (old & 0x80) == 0;
}
#else
void enable_interrupts(void)
{return;
}

但是通过追踪这个函数就不得不说说uboot中条件编译的两种方式:

(1)通过判断对应宏是否定义来决定函数是否执行,例:

#ifdef AAAAAfunction();  

(2)直接调用函数,然后在真正执行函数时再去根据对应宏是否存在来决定那个函数被执行,而此时的套路是:宏存在则执行函数真正对应的实体含函数,否则执行一个空函数,例:

function();#ifdef AAAAAvoid function(void){........}#elsevoid function(void){return -1;}

14.获取两个与linux内核启动相关的环境变量:

/* Initialize from environment */if ((s = getenv ("loadaddr")) != NULL) {load_addr = simple_strtoul (s, NULL, 16);}
#if defined(CONFIG_CMD_NET)if ((s = getenv ("bootfile")) != NULL) {copy_filename (BootFile, s, sizeof (BootFile));}
#endif

15.    板子正式启动前的lcd显示屏的初始化和logo的显示:

#ifdef CONFIG_MPADextern int x210_preboot_init(void);x210_preboot_init();
#endif

关于lcd显示屏也就是fb设备的初始化在这里不做详细的分析,但是值得一提的是在x210_preboot_init函数中进行了启动过程logo的显示:

display_logo(&s5pv210_fb);

16.uboot启动结束,进入命令模式:

for (;;)
{main_loop ();
}

而在循环内调用main_loop函数不断的采集命令,解析命令。

但如果在规定的bootdelay时间内未输入命令的话就会自动开始加载引导kernel的启动。

自动引导kernel启动的过程如下:

(1)获取启动的命令bootcmd:

s = getenv ("bootcmd");

获取系统的启动命令参数bootcmd,而这个bootcmd追踪得知:

#if defined(CONFIG_BOOTCOMMAND)"bootcmd=" CONFIG_BOOTCOMMAND "\0"
#define CONFIG_BOOTCOMMAND   "movi read kernel 30008000; movi read rootfs 30B00000 300000; bootm 30008000 30B00000"

(2)解析执行bootcmd中的命令:

run_command (s, 0);

这个函数先将bootcmd的命令分开成若干个命令,然后依次按照执行每一个命令的步骤,在管理命令结构体的链表中遍历,然后执行命令。

(3)内核的启动:

内核最终的启动还是依靠执行 bootcmd最后的 bootm 30008000 30B00000命令,而这个函数的具体实现请看bootm函数的实现原理与the_kernel函数指针

如果在bootdaly时间内有命令的输入,则执行执行命令。

对命令模式中其他命令的具体实现原理请移步:uboot的命令实现

END........

uboot启动之BL2阶段的分析1:宏观分析相关推荐

  1. uboot启动之BL1阶段的分析1

    对uboot启动的BL1阶段的主体代码分析1 BL1阶段代码的分析以start.s文件作为主要的目标,此篇博文主要对整个个流程进行分析. 总体分析: BL1阶段的代码固化在IROM中的BL0调用执行, ...

  2. uboot启动流程详细分析(基于i.m6ull)

    uboot介绍 uboot就是一段引导程序,在加载系统内核之前,完成硬件初始化,内存映射,为后续内核的引导提供一个良好的环境.uboot是bootloader的一种,全称为universal boot ...

  3. u-boot启动流程分析

    u-boot启动流程分析 以smdk2410为例,分析u-boot的启动流程.u-boot的启动流程是指从cpu上电开机执行u-boot到u-boot成功加载完操作系统的过程.这一过程可以分为两个阶段 ...

  4. S5PV210 Uboot开发与移植03:Uboot启动流程详解

    目录 1. start.S解析 1.1 uboot入口分析 1.2 头文件包含 1.2.1 config.h 1.2.2 version.h 1.2.3 asm/proc/domain.h 1.2.4 ...

  5. 海思芯片(hi3516dv300)uboot启动过程分析

    1.海思分段式uboot镜像 (1)uboot镜像的生成参考博客:<海思芯片(hi3516dv300)uboot镜像生成过程详解>: (2)海思uboot镜像类似于内核的zImage镜像, ...

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

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

  7. (三) u-boot 启动分析_第一阶段

    参考内容点此跳转 本文重点在于分析 uboot 启动流程以及 uboot 自身的细节,比如栈空间的划分.如何设置 tag .如何添加一个自定义命令等.但是不涉及基本的硬件驱动的分析,比如内存初始化.时 ...

  8. U-Boot 启动过程和源码分析(第一阶段)

    参考:http://blog.csdn.net/hare_lee/article/details/6916325 ******************************************* ...

  9. U-Boot启动阶段修改启动参数方法及分析

    作者:围补 本来启动方式这节不是什么复杂的事儿,不过想简单的说清楚明白,还真是不知道怎么组织.毕竟文字跟有声语言表达有别.但愿简单的东西别让我讲的太复杂! Arm板系统文件一般有三个--bootloa ...

最新文章

  1. 操作系统选择成固定模式 HTML5是潜在方向
  2. 关于C++指针的理解
  3. 中国水务行业运行状况调研与投资前景规划预测报告2022-2027年新版
  4. 数组元素倒置-Java
  5. 常用API-3(System类、Math类、Arrays类、正则表达式)
  6. Python学习笔记——Python的下载与安装
  7. 编程语言-脚本编程-PowerShell相关整理
  8. java 代码解压7z(带密码)转载请注明出处,谢谢
  9. 计算机预览正常打印乱码,打印机打印文件显示乱码该怎么办?
  10. Hvdc-vsc. 基于vsc的柔性直流输电模型 pscad实现
  11. python自动化写作_利用python打造“全自动公文写作神器”之构建公文词库
  12. python画实心圆_任意空实心圆形打印|Python练习系列[8]
  13. chrome js 读取文件夹_javascript – 如何从chrome扩展程序读取文件?
  14. cnn 句向量_快速理解句向量模型,深度好文,一定要看
  15. 超详细的计算机视觉学习书籍pdf汇总(涉及CV、深度学习、多视图几何、SLAM、点云处理等)
  16. Skia深入分析8——Skia的GPU绘图
  17. Flask中使用定时任务
  18. log4cplus:ERROR No appenders could be found for logger (AdSyncNamespace).
  19. 开原框架RxJava
  20. jsonCpp 编译

热门文章

  1. linux系统电脑小白可以用吗,linux小白说说用linux的感受
  2. java 动态切换数据源_Java动态切换数据源(AOP)
  3. 【MySQL】mysql数据库操作指南
  4. java.applet.Applet类
  5. Could not load the Qt platform plugin “xcb“ 问题解决
  6. 2017移动开发者大会汇总【收藏版】
  7. CentOs7下载与安装
  8. Windows fatal exception: access violation / Process finished with exit code -1073741819 (0xC0000005)
  9. input和textarea设置placeholder属性的颜色、字体大小
  10. DOSBox+MASM搭建汇编环境