uboot start_armboot函数 第二阶段代码分析

news2024/11/16 21:55:52

1.1、start_armboot函数简介

这个函数整个构成了uboot启动的第二阶段。

1.2、uboot第二阶段做的事情

uboot第一阶段主要就是初始化了SoC内部的一些部件(譬如看门狗、时钟、串口…),然后初始化DDR并且完成重定位。那么,uboot的第二阶段就是要初始化剩下的还没被初始化的硬件。主要是SoC外部硬件(譬如iNand、网卡芯片…)、以及uboot本身的一些东西(uboot的命令集、环境变量…),最终初始化完成后进入uboot的命令行准备接受命令、然后解析命令、执行命令。

1.3、uboot第二阶段完结于何处?

uboot启动后会自动打印出很多信息,这些信息是uboot第一阶段和第二阶段初始化时打印出来的调试信息。然后uboot倒数bootdelay秒然后执行bootcmd对应的启动命令,启动内核后,uboot就死掉了,在倒数bootdelay秒之前用户可以按下回车键打断uboot的自动启动进入uboot的命令行下,然后uboot就一直工作在命令行下。uboot的命令行是一个死循环,在start_armboot函数的最后可以看到,

for(;;)
    mainloop();

循环体内不断重复:接收命令、解析命令、执行命令。这就是uboot最终的归宿。

2.1、init_fnc_t

(1)typedef int (init_fnc_t) (void); 这是一个函数类型
(2)init_fnc_ptr是一个二重函数指针,二重指针的作用有2个,第一个是用来指向一重指针,第二个是用来指向指针数组。后续得知init_fuc_ptr指向了一个函数指针数组,数组中的每个元素都是函数名。

2.2、DECLARE_GLOBAL_DATA_PTR

(1)#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm (“r8”)
定义了一个指针类型的全局变量名字叫gd,占4字节。用volatile修饰表示防止编译器优化,用register修饰表示这个变量要尽量放到寄存器中,后面的asm(“r8”)是gcc支持的一种语法,意思就是要把gd放到寄存器r8中。
这个全局变量gd(global data的简称)是一个结构体指针,里面有成员变量,每一个成员变量都可以看成是一个全局变量,在uboot程序中经常被访问,因此放在register中提升运行效率

(2)gd_t定义在include/asm-arm/global_data.h中。

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? *//*是布尔型,环境变量是否可以使用,环境变量从SDcard送到DDR中建立,需要一段过程*/
    unsigned long    fb_base;    /* base address of frame buffer */    /*缓存地址*/

    void        **jt;        /* jump table */    /*跳转表*/
} gd_t;
typedef struct bd_info {
    int            bi_baudrate;    /* serial console baudrate *//*串口控制台波特率,和外面那个一致*/
    unsigned long    bi_ip_addr;    /* IP Address */
/*开发板IP地址*/
    unsigned char    bi_enetaddr[6]; /* Ethernet adress *//*MAC地址*/
    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];    /*DDR内存分布的信息*/

} bd_t;

gd_t中定义了很多全局变量,都是整个uboot使用的;其中有一个bd_t类型的指针,指向一个bd_t类型的变量,这个bd是承载开发板的板级信息的结构体,里面有不少硬件相关的参数,譬如波特率、IP地址、机器码、DDR内存分布信息。

2.2.1、为gd、bd分配内存

(1)DECLARE_GLOBAL_DATA_PTR只是定义了一个指针gd,并没有被分配内存,是一个野指针。gd和bd需要分配内存,内存当前没有人管理(因为没有操作系统统一管理内存),大片的DDR内存散放着可以随意使用(只要使用内存地址直接去访问内存即可),因此要分配内存,只需赋予其一个内存地址即可。但是因为uboot中后续很多操作还需要大片的连着内存块,出于紧凑排布的原则。我们在uboot中需要有一个整体规划。

2.2.2、内存排布

(1)uboot区 CFG_UBOOT_BASE-xx(长度为uboot的实际长度)
(2)堆区 长度为CFG_MALLOC_LEN,实际为912KB
(3)栈区 长度为CFG_STACK_SIZE,实际为512KB
(4)gd 长度为sizeof(gd_t),实际36字节
(5)bd 长度为sizeof(bd_t),实际为44字节左右
(6)内存间隔 为了防止高版本的gcc的优化造成错误。

ulong gd_base;    /*gd在内存中分配的起始地址*/

    gd_base = CFG_UBOOT_BASE + CFG_UBOOT_SIZE - CFG_MALLOC_LEN - CFG_STACK_SIZE - sizeof(gd_t);
    /*
     *     gd_base = 33e00000 + 2MB - 912KB -512KB - 36B
     */
    gd = (gd_t*)gd_base;//强制类型转换,给gd分配内存
        
    /* compiler optimization barrier needed for GCC >= 3.4 */
    __asm__ __volatile__("": : :"memory");//为了防止高版本的gcc的优化造成错误。

    memset ((void*)gd, 0, sizeof (gd_t));
    gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));//给bd分配内存,位置在(gd_base - 44B)处
    memset (gd->bd, 0, sizeof (bd_t));

3.1、for循环遍历init_sequence

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_sequenc的每个元素都是函数,这些函数的特征是int (init_fnc_t) (void); 这次遍历的目的是依次执行这个数组中的所有函数,完成uboot第二阶段相关的硬件初始化。

typedef int (init_fnc_t) (void);

int print_cpuinfo (void); /* test-only */

init_fnc_t *init_sequence[] = {
    cpu_init,        /* basic cpu dependent setup */
#if defined(CONFIG_SKIP_RELOCATE_UBOOT)
    reloc_init,        /* Set the relocation done flag, must
                   do this AFTER cpu_init(), but as soon
                   as possible */
#endif
    board_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,
#endif
    dram_init,        /* configure available RAM banks */
    display_dram_config,
       NULL,
};

(2)如何遍历一个函数指针数组?
有2种方法:
第一种是用数组下标去遍历,用数组元素个数来截至。
第二种是在数组的末尾放一个标志NULL,直到遍历到NULL才结束循环。(类似字符串末尾的’\0’标志)这种方法的优势是不用事先统计数组有多少个元素。

(3)循环体

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

init_sequence数组里的函数的返回值定义方式一样的,都是:函数执行正确时返回0,不正确时返回-1。所以当我们在遍历过程中有一个函数返回值不等于0,则说明我们的板级硬件有问题,因此必须要终止uboot的启动,调用hang()挂起。

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

3.2、cpu_init

int cpu_init (void)
{
    /*
     * setup up stacks if necessary
     */
#ifdef CONFIG_USE_IRQ
    IRQ_STACK_START = _armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4;
    FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ;
#endif
    return 0;
}

(1)看这个函数名应该是要进行cpu内部的初始化,cpu内相关的初始化在start.S第一阶段就完成了,CONFIG_USE_IRQ并未定义,所以这里是空的。

3.3、board_init

int board_init(void)
{
    DECLARE_GLOBAL_DATA_PTR;    //DECLARE_GLOBAL_DATA_PTR在这里声明是为了后面使用gd方便。。


#ifdef CONFIG_DRIVER_DM9000
    dm9000_pre_init();
#endif

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

    return 0;
}

(1)board_init在uboot/board/samsung/x210/x210.c中,这个函数做的事情从名字就知道是x210开发板相关的初始化。

(2)DECLARE_GLOBAL_DATA_PTR在这里声明是为了后面使用gd方便。可以看出把gd的声明定义成一个宏的后我们就可以到处去使用gd了,而不需要把这个变量放到.h头文件中去包含,使用更方便。

(3)网卡初始化。CONFIG_DRIVER_DM9000这个宏是x210_sd.h中定义的,这个宏用来配置开发板的网卡。dm9000_pre_init函数就是DM9000网卡的初始化函数。开发板移植uboot时,如果要移植网卡,主要的工作就在这里。

(4)dm9000_pre_init这个函数的工作主要是DM9000这个网卡的GPIO和端口的配置,而不是驱动。因为网卡的驱动都是现成的正确的,移植的时候驱动是不需要改动的,关键是这里的基本初始化不能出错。因为这些基本初始化是硬件相关的。

3.3.1、gd->bd->bi_arch_number

(1)board_info中的一个重要变量,含义是:开发板的机器码。所谓机器码就是uboot官方给这个开发板定义的一个唯一编号,在linux内核中也有个机器码,所以机器码是用于开发板和linux内核之间的适配,开发板的机器码通常维护在这个开发板移植的uboot当中,也就是我们这里定义的bi_arch_number,只有当两者机器码相等才能启动内核,否则就不启动;嵌入式设备中每个设备的硬件都是高度定制化的,不像PC机那样通用,这样就导致硬件和软件不能适配使用。

(2)MACH_TYPE在x210_sd.h中定义,说明当前开发板对应的编号是2456。将来这个开发板上面移植的linux内核中的机器码也必须是2456,否则就启动不起来。

(3)uboot中配置的这个机器码,会作为uboot给linux内核的传参的一部分传给linux内核,内核启动过程中会比对这个接收到的机器码,和自己本身的机器码相对比,如果相等就启动,如果不等就不启动。这个机器码是在linux内核中比对的,uboot中并不比对,uboot只是收好,在将来传参给linux内核。

(4)理论上来说,一个开发板的机器码不能自己随便定,只有uboot官方能发放,但是国内出于英文水平低的原因并没有向uboot官方申请,都是自己随便编号的,只要保证uboot和kernel中的编号是一致的,就不影响自己的开发板启动。

3.3.2、gd->bd->bi_boot_params

(1)board_info中的一个重要变量,bi_boot_params表示uboot给linux kernel传参的参数的内存地址。也就是说uboot会事先将准备好的参数(字符串bootargs)放到bi_boot_params地址处,然后uboot启动内核时通过r0、r1、r2寄存器来传递bi_boot_params这个内存地址给内核,内核通过这些寄存器得到参数存放的地址,进而找到uboot传递的参数。

(2)经过计算得知:X210中bi_boot_params的值为0x30000100,这个内存地址被分配用来做内核传参。所以在uboot的其他地方使用内存时需要注意,千万不敢把这里给淹没了。

3.3.3、PHYS_SDRAM_1涉及DDR的配置信息:

(1)注意:这里的DDR配置信息和汇编阶段lowlevel_init中初始化DDR是不同的。当时是硬件的初始化,目的是让DDR可以开始工作。现在是软件结构中一些DDR相关的属性的配置、地址的设置,是纯软件层面的,目的是为了告诉uboot本身,这块开发板接了一块什么样的内存。

(2)软件层次配置DDR的原因:对于uboot来说,他并不知道开发板上接了几片DDR,每一片的长度大小、起始地址等信息;因此必须由程序员告知,程序员在移植uboot到开发板时,在x210_sd.h中使用宏定义去配置出来板子上DDR内存的信息,然后uboot只需读取这些信息即可。(实际上还有另外一条思路:就是uboot通过代码读取硬件信息来知道DDR配置,但是uboot没有这样。实际上PC的BIOS采用的是这种)

(3)x210_sd.h的496行到501行中使用了标准的宏定义来配置DDR相关的参数。主要配置了这么几个信息:有几片DDR内存、每一片DDR的起始地址、长度。这里的配置信息我们在uboot代码的其它地方中使用。

#define CONFIG_NR_DRAM_BANKS    2          /* we have 2 bank of DRAM */
#define SDRAM_BANK_SIZE         0x10000000    /* 512 MB lqm*/
//#define SDRAM_BANK_SIZE         0x20000000    /* 1GB lqm*/
#define PHYS_SDRAM_1            MEMORY_BASE_ADDRESS /* SDRAM Bank #1 */
#define PHYS_SDRAM_1_SIZE       SDRAM_BANK_SIZE
#define PHYS_SDRAM_2            MEMORY_BASE_ADDRESS2 /* SDRAM Bank #2 */
#define PHYS_SDRAM_2_SIZE       SDRAM_BANK_SIZE

3.4、interrupt_init

int interrupt_init(void)
{

    S5PC11X_TIMERS *const timers = S5PC11X_GetBase_TIMERS();

    /* use PWM Timer 4 because it has no output */
    /* prescaler for Timer 4 is 16 */
    timers->TCFG0 = 0x0f00;
    if (timer_load_val == 0) {
        /*
         * for 10 ms clock period @ PCLK with 4 bit divider = 1/2
         * (default) and prescaler = 16. Should be 10390
         * @33.25MHz and  @ 66 MHz
         */
        timer_load_val = get_PCLK() / (16 * 100);
    }

    /* load value for 10 ms timeout */
    lastdec = timers->TCNTB4 = timer_load_val;
    /* auto load, manual update of Timer 4 */
    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;
    timestamp = 0;


    return (0);
}

(1)看函数名是和中断初始化有关的,但是实际上不是,实际上这个函数是用来初始化定时器Timer4.

(2)210中共有5个PWM定时器。其中Timer0-timer3都有PWM输出引脚。而Timer4没有引脚,也没有TCMPB输出比较寄存器,因此无法输出PWM波形,只能用来做计时。

(3)Timer4用作计时主要使用到2个寄存器:TCNTB4、TCNTO4。TCNTB中存放的数用作定时次数(每一次时间是由时钟决定的,而时钟是由2级时钟分频器决定的)。我们定时时只需要把定时时间/基准时间=数,将这个数放入TCNTB中即可;我们通过TCNTO寄存器即可知道当前定时时间走到哪里了。

(4)定时器Timer4是没有中断支持的,所以CPU只能使用轮询方式来不断查看TCNTO寄存器才能知道定时时间到了没。uboot中的bootdelay倒计时就是通过Timer4来实现的,然后通过检测按键是否要进入uboot命令行。和轮询方式处理按键是一个道理。

(5)interrupt_init函数将Timer4设置为定时10ms。get_PCLK函数获取系统设置的PCLK_PSYS时钟频率66MHz,然后设置TCFG0和TCFG1寄存器进行16分频和1分频,然后计算出要将定时设置为10ms时需要向TCNTB中写入的值是66MHz/(16x100),将其写入TCNTB,然后设置为auto reload模式并打开定时器开始计时。

总结:在这个函数中采用结构体的方式来访问寄存器。

3.5、env_init

(1)env_init,看函数名就知道是和uboot自身环境变量的初始化有关。
在这里插入图片描述

(2)为什么有很多env_init函数,主要原因是uboot支持多种的启动介质(譬如norflash、nandflash、inand、sd卡·····),一般从哪里启动就会把环境变量env放到哪里,我们SD卡启动时,环境变量被放在SD中;由于各种存储介质接口标准不同,所以存取操作env的时序也是不一样的。因此uboot支持了各种不同介质中env的操作方法。所以有好多个env_xx开头的c文件,其中都实现了env_init函数。实际使用哪一个要根据自己开发板使用的哪种存储介质启动来定,这些文件只有一个会起作用,通过x210_sd.h中配置的宏来决定谁被包含,对于x210来说,我们应该看env_movi.c中的函数。

uchar env_get_char_spec (int index)
{
    return ( *((uchar *)(gd->env_addr + index)) );
}

int env_init(void)
{
#if defined(ENV_IS_EMBEDDED)
    ulong total;
    int crc1_ok = 0, crc2_ok = 0;
    env_t *tmp_env1, *tmp_env2;

    total = CFG_ENV_SIZE;

    tmp_env1 = env_ptr;
    tmp_env2 = (env_t *)((ulong)env_ptr + CFG_ENV_SIZE);

    crc1_ok = (crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc);
    crc2_ok = (crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc);

    if (!crc1_ok && !crc2_ok)
        gd->env_valid = 0;
    else if(crc1_ok && !crc2_ok)
        gd->env_valid = 1;
    else if(!crc1_ok && crc2_ok)
        gd->env_valid = 2;
    else {
        /* both ok - check serial */
        if(tmp_env1->flags == 255 && tmp_env2->flags == 0)
            gd->env_valid = 2;
        else if(tmp_env2->flags == 255 && tmp_env1->flags == 0)
            gd->env_valid = 1;
        else if(tmp_env1->flags > tmp_env2->flags)
            gd->env_valid = 1;
        else if(tmp_env2->flags > tmp_env1->flags)
            gd->env_valid = 2;
        else /* flags are equal - almost impossible */
            gd->env_valid = 1;
    }

    if (gd->env_valid == 1)
        env_ptr = tmp_env1;
    else if (gd->env_valid == 2)
        env_ptr = tmp_env2;
#else /* ENV_IS_EMBEDDED */
    gd->env_addr  = (ulong)&default_environment[0];
    gd->env_valid = 1;
#endif /* ENV_IS_EMBEDDED */

    return (0);
}

(3)经过基本分析,这个函数只是对判定内存里面有没有能用的环境变量。当前因为我们还没将环境变量从SD卡复制到DDR中的,因此当前环境变量都是不能用的。在start_armboot函数中776行处调用了env_relocate才将环境变量从SD卡中复制到DDR中,复制之后才能使用环境变量,否则就得读取SD卡中的环境变量。

3.6、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);
}
int getenv_r (char *name, char *buf, unsigned len)
{
    int i, nxt;

    for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
        int val, n;

        for (nxt=i; env_get_char(nxt) != '\0'; ++nxt) {
            if (nxt >= CFG_ENV_SIZE) {
                return (-1);
            }
        }
        if ((val=envmatch((uchar *)name, i)) < 0)
            continue;
        /* found; copy out */
        n = 0;
        while ((len > n++) && (*buf++ = env_get_char(val++)) != '\0')
            ;
        if (len == n)
            *buf = '\0';
        return (n);
    }
    return (-1);
}

(1)init_baudrate是配置串口波特率的函数,并非初始化串口的函数,串口初始化早在第一阶段完成,这里只是纯软件结构,告诉uboot我使用的串口波特率是多少。

(2)getenv_r函数用来读取环境变量“baudrate”的值(注意读取到的不是int型而是字符串类型),然后用simple_strtoul函数将字符串转成数字格式的波特率。

(3)这个函数的工作是,先通过getenv_r函数读取环境变量"baudrate"的值,如果读取成功,则记录在gd->baudrate和gd->bd->bi_baudrate中;如果读取不成功,则将x210_sd.h中的CONFIG_BAUDRATE宏(值为115200)作为记录。从这可以看出:环境变量的优先级是高于配置值的。

3.7、serial_init

void serial_setbrg(void)
{
    DECLARE_GLOBAL_DATA_PTR;

    int i;
    for (i = 0; i < 100; i++);
}

/*
 * Initialise the serial port with the given baudrate. The settings
 * are always 8 data bits, no parity, 1 stop bit, no start bits.
 *
 */
int serial_init(void)
{
    serial_setbrg();

    return (0);
}

(1)serial_init看函数名是初始化串口的函数,但汇编阶段便已经初始化过串口,这个函数的路径在uboot/cpu/s5pc11x/serial.c中,函数体中什么也没做,是空的。

3.8、console_init_f

(1)console_init_f是console(控制台)的第一阶段初始化。_f表示是第一阶段初始化,_r表示第二阶段初始化。有时候初始化函数不能一次一起完成,中间必须要夹杂一些代码,因此将完整的一个模块的初始化分成了2个阶段。(我们的uboot中start_armboot的826行进行了console_init_r的初始化)

int console_init_f (void)
{
    gd->have_console = 1;

    return (0);
}

(2)console_init_f在uboot/common/console.c中,仅仅是对gd->have_console设置为1而已,其他事情都没做,因此现在控制台还是不能用的,

3.9、display_banner

(1)display_banner用来串口输出显示uboot的logo

//board.c 59行 
#undef DEBUG   

//由于未定义DEBUG宏;因此display_banner()函数中的debug全部无效
//board.c 304行
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");
#else
    debug("\t\bMalloc and Stack is below the U-Boot Code.\n");
#endif
#ifdef CONFIG_MODEM_SUPPORT
    debug ("Modem Support enabled\n");
#endif
#ifdef CONFIG_USE_IRQ
    debug ("IRQ Stack: %08lx\n", IRQ_STACK_START);
    debug ("FIQ Stack: %08lx\n", FIQ_STACK_START);
#endif
    open_backlight();//lqm.打开LCD背光
    //open_gprs();

    return (0);
}

(2)display_banner中使用printf函数向串口输出了version_string这个字符串。但是此时控制台并没有初始化好,怎么就能使用了呢?

//console.c 217行
//可以看出printf函数最后调用puts函数
void printf (const char *fmt, ...)
{
    va_list args;
    uint i;
    char printbuffer[CFG_PBSIZE];

    va_start (args, fmt);

    /* For this to work, printbuffer must be larger than
     * anything we ever want to print.
     */
    i = vsprintf (printbuffer, fmt, args);
    va_end (args);

    /* Print the string */
    puts (printbuffer);
}

//console.c 201行
//puts函数中判断当前console控制台是否初始化好
void puts (const char *s)
{
#ifdef CONFIG_SILENT_CONSOLE
    if (gd->flags & GD_FLG_SILENT)
        return;
#endif

    if (gd->flags & GD_FLG_DEVINIT) {//当前console控制台是否初始化好
                //GD_FLG_DEVINIT在global_data.h 61行定义
        /* Send to the standard output */
        fputs (stdout, s);
    } else {
                //serial_puts ()在serial.c 146行定义                                                                                         
        /* Send directly to the handler */
        serial_puts (s);
    }
}
//================================================================
//global_data.h 61行定义
#define    GD_FLG_DEVINIT    0x00002    /* Devices have been initialized    */

//serial.c 146行
void serial_puts(const char *s)
{
    while (*s) {
        serial_putc(*s++);
    }
}
//serial.c 112行
void serial_putc(const char c)
{
    S5PC11X_UART *const uart = S5PC11X_GetBase_UART(UART_NR);

#ifdef CONFIG_MODEM_SUPPORT
    if (be_quiet)
        return;
#endif

    /* wait for room in the tx FIFO */
    while (!(uart->UTRSTAT & 0x2));

#ifdef CONFIG_HWFLOW
    /* Wait for CTS up */
    while (hwflow && !(uart->UMSTAT & 0x1));
#endif

    uart->UTXH = c;            //直接操作的是串口的寄存器

    /* If \n, also do \r */
    if (c == '\n')
        serial_putc('\r');
}

(3)通过追踪printf的实现,发现printf->puts->serial_puts->serial_putc->uart->UTXH = c; ,而puts函数中会判断当前uboot中console有没有被初始化好。如果console初始化好了则调用fputs完成串口发送(这条线才是控制台);如果console尚未初始化好则会调用serial_puts(再调用serial_putc直接操作串口寄存器进行内容发送)。

(4)控制台也是通过串口输出,非控制台也是通过串口输出。其实控制台只是软件虚拟出来的设备,这个设备有一套专用的通信函数(在uboot中,发送fputs、接收fgets),fputs封装了serial_puts,所以在uboot中两者最终都是使用硬件串口的通信函数,并无本质区别。但是在别的体系中,控制台的通信函数可以做中间优化,譬如缓冲机制,增设一个缓冲区来保存要输出的数据,这样CPU就不用盯着串口输出一个一个的字符,只需将要输出的数据放到缓冲区中,让串口自己打印,而CPU可以去做别的事情。(操作系统中的控制台都使用了缓冲机制,所以有时候我们printf了内容但是屏幕上并没有看到输出信息,就是因为被缓冲了。我们输出的信息只是到了console的buffer中,buffer还没有被刷新到硬件输出设备上,尤其是在输出设备是LCD屏幕时,这种优势极其明显,我们并不是每次输出一个字符然后就要刷新整个显存,而是将要输出的字符串都放到缓存buffer中,然后一起刷新到显存中去。)

(5)U_BOOT_VERSION在uboot源代码中找不到定义,实际上它的定义在include/version_autogenerated.h中用一个宏定义U_BOOT_VERSION来实现,version_autogenerated.h是Makefile编译的时候自动生成的,由Makefile创建,这个宏的值来自Makefile的变量。

3.10、print_cpuinfo

(1)uboot启动过程中控制台显示的:
CPU: S5PV210@1000MHz(OK)
APLL = 1000MHz, HclkMsys = 200MHz, PclkMsys = 100MHz
MPLL = 667MHz, EPLL = 96MHz
HclkDsys = 166MHz, PclkDsys = 83MHz
HclkPsys = 133MHz, PclkPsys = 66MHz
SCLKA2M = 200MHz
Serial = CLKUART
这些信息都是print_cpuinfo打印出来的。

//speed.c 228行
int print_cpuinfo(void)
{
    uint set_speed;
    uint tmp;
    uchar result_set;

#if defined(CONFIG_CLK_533_133_100_100)
    set_speed = 53300;
#elif defined(CONFIG_CLK_667_166_166_133)
    set_speed = 66700;
#elif defined(CONFIG_CLK_800_200_166_133)
    set_speed = 80000;
#elif defined(CONFIG_CLK_1000_200_166_133)   //x210_sd.h中配置的是这个
    set_speed = 100000;
#elif defined(CONFIG_CLK_1200_200_166_133)
    set_speed = 120000;
#else
    set_speed = 100000;
    printf("Any CONFIG_CLK_XXX is not enabled\n");
#endif
    tmp = (set_speed / (get_ARMCLK()/1000000));
         /*  get_ARMCLK()根据我们时钟配置的寄存器,软件计算出我们设置的时钟频率
          *  get_ARMCLK() = 10^9
          *  tmp = 100000/ (10^9/10^6) = 100000/1000 = 100
          */

    if((tmp < 105) && (tmp > 95)){    //tmp = 100
        result_set = 1;    //result_set = 1
    } else {
        result_set = 0;
    }

#ifdef CONFIG_MCP_SINGLE
    printf("\nCPU:  S5PV210@%ldMHz(%s)\n", get_ARMCLK()/1000000, ((result_set == 1) ? "OK" : "FAIL"));
#else
    printf("\nCPU:  S5PC110@%ldMHz(%s)\n", get_ARMCLK()/1000000, ((result_set == 1) ? "OK" : "FAIL"));
#endif
    printf("        APLL = %ldMHz, HclkMsys = %ldMHz, PclkMsys = %ldMHz\n",
            get_FCLK()/1000000, get_HCLK()/1000000, get_PCLK()/1000000);
#if 1
    printf("    MPLL = %ldMHz, EPLL = %ldMHz\n",
            get_MPLL_CLK()/1000000, get_PLLCLK(EPLL)/1000000);
    printf("               HclkDsys = %ldMHz, PclkDsys = %ldMHz\n",
            get_HCLKD()/1000000, get_PCLKD()/1000000);
    printf("               HclkPsys = %ldMHz, PclkPsys = %ldMHz\n",
            get_HCLKP()/1000000, get_PCLKP()/1000000);
    printf("               SCLKA2M  = %ldMHz\n", get_SCLKA2M()/1000000);
#endif
    puts("Serial = CLKUART ");

    return 0;
}

3.11、checkboard

(1)checkboard这个函数的作用就是检查当前开发板是哪个开发板并且打印出开发板的名字。
在这里插入图片描述

3.12、init_func_i2c

在这里插入图片描述
在这里插入图片描述
(1)这个函数实际没有被执行,X210的uboot中并没有使用I2C。如果将来我们的开发板要扩展I2C来接外接硬件,则在x210_sd.h中配置相应的宏即可开启。

3.13、uboot学习实践

(1)对uboot源代码进行完修改(修改内容根据自己的理解和分析来修改)
这里只是修改了版本号和开发板信息
(2)make distclean然后make x210_sd_config然后make
(3)编译完成得到u-boot.bin,然后去烧录。在linux下使用dd命令来烧录。
(4)烧写过程:
第一步:进入sd_fusing目录下
第二步:make clean
第三步:make(必须保证当前虚拟机是32位的)
第四步:插入sd卡,ls /dev/sd*得到SD卡在ubuntu中的设备号(一般是/dev/sdb,注意SD卡要连接到虚拟机ubuntu中,不要接到windows中)
第五步:./sd_fusing.sh /dev/sdb完成烧录(注意不是sd_fusing2.sh)
在这里插入图片描述
(5)总结:uboot就是个庞大点复杂点的裸机程序而已,我们完全可以对他进行调试。调试的方法就是按照上面步骤,根据自己对代码的分析和理解对代码进行更改,然后重新编译烧录运行,根据运行结果来学习。

3.14、dram_init、

(1)在汇编阶段已经初始化过DDR否则也无法进行relocate,这里只是纯软件的配置,告知uboot自己用的是什么内存,有几片、起始地址、长度大小。以便uboot之后使用内存,譬如说给mm命令使用。
在这里插入图片描述
(2)dram_init都是在给gd->bd里面关于DDR配置部分的全局变量赋值,让gd->bd数据记录下当前开发板的DDR的配置信息。

(3)从代码来看,其实就是初始化gd->bd->bi_dram这个结构体数组。

3.15、display_dram_config

在这里插入图片描述
(1)这个函数的作用是打印显示dram的配置信息。

(2)启动信息中的:(DRAM: 512 MB)就是在这个函数中打印出来的。

(3)思考:如何在uboot命令行中得知uboot的DDR配置信息?uboot中有一个命令叫bdinfo,这个命令可以打印出gd->bd中记录的所有硬件相关的全局变量的值,因此可以得知DDR的配置信息。
DRAM bank = 0x00000000
-> start = 0x30000000
-> size = 0x10000000
DRAM bank = 0x00000001
-> start = 0x40000000
-> size = 0x10000000

3.16、init_sequence总结

(1)都是板级硬件的初始化以及gd、gd->bd中的数据结构的初始化。譬如:
cpu_init:空
board_init:网卡初始化、机器码(gd->bd->bi_arch_number)、内核传参DDR地址(gd->bd->bi_boot_params)
interrupt_init:Timer4初始化为10ms一次
env_init:检查可用的环境变量,没有什么实质性的东西,此时并未将环境变量从SD卡复制到内存
init_baudrate:波特率设置(gd->bd->bi_baudrate和gd->baudrate)
serial_init:空
console_init_f:console第一阶段初始化(gd->have_console设置为1)
display_banner:打印uboot的版本信息
print_cpuinfo:打印时钟相关设置信息
checkboard:检查并打印当前开发板名字
init_func_i2c:空
dram_init:DDR配置信息初始化(gd->bd->bi_dram)
display_dram_config:打印DDR总容量。

4.1、CFG_NO_FLASH

(1)flash_init执行的是开发板中对应的NorFlash的初始化、display_flash_config打印的也是NorFlash的配置信息(Flash: 8 MB就是这里打印出来的)。但是实际上X210中是没有Norflash的。所以这两行代码是可以去掉的,加上CONFIG_NOFLASH宏之后就可去掉,发现编译出错,说明代码移植的不好,那个文件的包含没有被这个宏控制。于是乎移植的人就直接放这没管。
在这里插入图片描述
(2)CONFIG_VFD和CONFIG_LCD是显示相关的,这个是uboot中自带的LCD显示的软件架构。但是实际上我们用LCD而没有使用uboot中设置的这套软件架构,我们自己在后面自己添加了一个LCD显示的部分。

4.2、mem_malloc_init

在这里插入图片描述
(1)mem_malloc_init函数用来初始化uboot的堆管理器。
在这里插入图片描述
(2)uboot中自己维护了一段堆内存,肯定自己就有一套代码来管理这个堆内存。有了这些东西就可以在uboot中使用malloc、free这套机制来申请内存和释放内存。我们在DDR内存中给堆预留了912KB的内存。

4.3、开发板独有初始化:mmc初始化

(1)从536到768行为开发板独有的初始化。意思是三星用一套uboot同时满足了好多个系列型号的开发板,然后在这里把不同开发板自己独有的一些初始化写到了这里。用#if条件编译配合CONFIG_xxx宏来选定特定的开发板。

(2)X210相关的配置在599行到632行。

#if defined(CONFIG_X210)
#if defined(CONFIG_GENERIC_MMC)
        puts ("SD/MMC:  ");
        mmc_exist = mmc_initialize(gd->bd);
        if (mmc_exist != 0)
        {
            puts ("0 MB\n");
#ifdef CONFIG_CHECK_X210CV3
            check_flash_flag=0;//check inand error!
#endif
        }
#ifdef CONFIG_CHECK_X210CV3
        else
        {
            check_flash_flag=1;//check inand ok! 
        }
#endif
    #endif

    #if defined(CONFIG_MTD_ONENAND)
        puts("OneNAND: ");
        onenand_init();
        /*setenv("bootcmd", "onenand read c0008000 80000 380000;bootm c0008000");*/
    #else
        //puts("OneNAND: (FSR layer enabled)\n");
    #endif

    #if defined(CONFIG_CMD_NAND)
        puts("NAND:    ");
        nand_init();
    #endif

#endif /* CONFIG_X210 */

(3)mmc_initialize看名字就应该是MMC相关的一些基础的初始化,其实就是用来初始化SoC内部的SD/MMC控制器的。函数在uboot/drivers/mmc/mmc.c 1177行。

int mmc_initialize(bd_t *bis)
{
    struct mmc *mmc;
    int err;

    INIT_LIST_HEAD(&mmc_devices);//初始化内核链表,用于记录当前系统所有MMC设备
    
     cur_dev_num = 0;//当前设备的编号:0,可选0~3,对应SDcare四个通道

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

#if defined(DEBUG_S3C_HSMMC)
    print_mmc_devices(',');
#endif

#ifdef CONFIG_CHECK_X210CV3
    mmc = find_mmc_device(1);//lqm
#else
    mmc = find_mmc_device(0);
#endif
    if (mmc) {
        err = mmc_init(mmc);
        if (err)
            err = mmc_init(mmc);
        if (err) {
            printf("Card init fail!\n");
            return err;
        }
    }
    printf("%ldMB\n", (mmc->capacity/(1024*1024/(1<<9))));
    return 0;
}

(4)uboot中对硬件的操作(譬如网卡、SD卡···)都是借用的linux内核中的驱动来实现的,uboot根目录底下有个drivers文件夹,这里面放的全都是从linux内核中移植过来的各种驱动源文件。

(5)mmc_initialize是具体硬件架构无关的一个MMC初始化函数,所有的使用了这套架构的代码都调用这个函数来完成MMC的初始化。mmc_initialize中再调用board_mmc_init或者cpu_mmc_init来完成具体的硬件的MMC控制器初始化工作。
在这里插入图片描述

调用board_mmc_init时实际调用的是_def_c_init,board_mmc_init是函数_def_mmc_init起的一个别名,在这里board_mmc_init函数返回值被定为-1,严格小于0,原因是SD控制器是集成在SOC里面的,和CPU待在一起,属于CPU级的,而不是board板级。

(6)cpu_mmc_init在uboot/cpu/s5pc11x/cpu.c中,这里面又间接的调用了drivers/mmc/s3c_mmcxxx.c中的驱动代码来初始化硬件MMC控制器。这里面分层很多,
cpu_mmc_init->smdk_s3c_hsmmc_init->s3c_hsmmc_initialize->drivers/mmc/s3c_mmcxxx.c中的驱动代码

/*
 * Initializes on-chip MMC controllers.
 * to override, implement board_mmc_init()
 */
int cpu_mmc_init(bd_t *bis)
{
#ifdef CONFIG_S3C_HSMMC
    setup_hsmmc_clock();            //SD控制器时钟相关初始化
    setup_hsmmc_cfg_gpio();            //SD控制器GPIO相关初始化
    return smdk_s3c_hsmmc_init();
#else
    return 0;
#endif
}

4.4、env_relocate

void env_relocate (void)
{
        /*    



    
     * We must allocate a buffer for the environment
     */
//1.给环境变量分配内存区域,大小为CFG_ENV_SIZE(4G)
    env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
//2.判断当前环境变量是否可用,第一次由于环境变量尚未重定位到DDR中,所以是不能用的。    


    if (gd->env_valid == 0) {

        puts ("*** Warning - bad CRC, using default environment\n\n");
                //打印一串调试信息
        show_boot_progress (-60);
                //也是调试信息,说明当前uboot进行到了百分之多少  

        set_default_env();
 /* uboot代码中默认封装了一套环境变量,将其memcpy到第一步申请的内存中 
  * 计算CRC校验保证下次使用不会错误
  * 设置gd->env_valid = 1;表示当前内存中有环境变量,可以使用了
  */                                                              
    }
    else {
 // 由于第一次gd->env_valid = 1,第二次进来这里进行重定位。
        env_relocate_spec ();
    }
 //3.设置环境变量所在的env分区的内存地址
    gd->env_addr = (ulong)&(env_ptr->data);


}

(1)env_relocate是环境变量的重定位,完成从SD卡中将环境变量读取到DDR中的任务。

(2)环境变量到底从哪里来?SD卡中有一些(8个)独立的扇区作为环境变量存储区域的。但是我们烧录/部署系统时,我们只是烧录了uboot分区、kernel分区和rootfs分区,根本不曾烧录env分区。所以当我们烧录完系统第一次启动时ENV分区是空的,本次启动uboot尝试去SD卡的ENV分区读取环境变量时失败(读取回来后进行CRC校验时失败),我们uboot选择从uboot内部代码中设置的一套默认的环境变量出发来使用(这就是默认环境变量);这套默认的环境变量在本次运行时会被读取到DDR中的环境变量中,然后被写入(也可能是你saveenv时写入,也可能是uboot设计了第一次读取默认环境变量后就写入)SD卡的ENV分区。然后下次再次开机时uboot就会从SD卡的ENV分区读取环境变量到DDR中,这次读取就不会失败了。

(3)真正的从SD卡到DDR中重定位ENV的代码是在env_relocate_spec内部的movi_read_env完成的。

//位置是uboot/common/env_movi.c 93行
void env_relocate_spec (void)
{
#if !defined(ENV_IS_EMBEDDED)
    uint *magic = (uint*)(PHYS_SDRAM_1);

    if ((0x24564236 != magic[0]) || (0x20764316 != magic[1]))
        movi_read_env(virt_to_phys((ulong)env_ptr));

    if (crc32(0, env_ptr->data, ENV_SIZE) != env_ptr->crc)
        return use_default();
#endif /* ! ENV_IS_EMBEDDED */
}
//位置是uboot/cpu/s5pc11x/movi.c 112行 
void movi_read_env(ulong addr)
{
    movi_read(raw_area_control.image[2].start_blk,
          raw_area_control.image[2].used_blk, addr);
}

4.5、IP地址、MAC地址的确定

x210_sd.h第231行开始,是uboot代码封装的关于IP、MAC地址等的默认环境变量

#define CONFIG_BOOTARGS “console=ttySAC2,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3”
//#define CONFIG_BOOTARGS “console=ttySAC0,115200 root=/dev/mmcblk0p2 rw init=/linuxrc rootfstype=ext3”
#define CONFIG_ETHADDR 00:40:5c:26:0a:5b
#define CONFIG_NETMASK 255.255.0.0
#define CONFIG_IPADDR 192.168.1.88
#define CONFIG_SERVERIP 192.168.1.102
#define CONFIG_GATEWAYIP 192.168.0.1

(1)开发板的IP地址数据结构是在gd->bd中维护的,来源于环境变量ipaddr。getenv函数用来获取字符串格式的IP地址,然后用string_to_ip将字符串格式的IP地址转成字符串格式的点分十进制格式。

(2)IP地址由4个0-255之间的数字组成,因此一个IP地址在程序中最简单的存储方法就是一个unsigend int。但是人类容易看懂的并不是这种类型,而是点分十进制类型(192.168.1.2)。这两种类型可以互相转换。

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

IPaddr_t getenv_IPaddr (char *var)
{
    return (string_to_ip(getenv(var)));
}
/* MAC Address */
    {
        int i;
        ulong reg;
        char *s, *e;
        char tmp[64];

        i = getenv_r ("ethaddr", tmp, sizeof (tmp));
        s = (i > 0) ? tmp : NULL;

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

4.6、devices_init

(1)devices_init看名字是设备的初始化。这里的设备指的就是开发板上的硬件设备。和前面的各种硬件设备初始化不同的是,放在这里初始化的设备都是驱动设备,这个函数本来就是从驱动框架中衍生出来的。uboot中很多设备的驱动是直接移植linux内核的(譬如网卡、SD卡),linux内核中的驱动都有相应的设备初始化函数。linux内核在启动过程中就有一个devices_init(名字不一定完全对,但是差不多),作用就是集中执行各种硬件驱动的init函数。

(2)uboot的这个函数其实就是从linux内核中移植过来的,它的作用也是去执行所有的从linux内核中继承来的那些硬件驱动的初始化函数。

4.7、jumptable_init

void jumptable_init (void)
{
    int i;

    gd->jt = (void **) malloc (XF_MAX * sizeof (void *));
    for (i = 0; i < XF_MAX; i++)
        gd->jt[i] = (void *) dummy;

    gd->jt[XF_get_version] = (void *) get_version;
    gd->jt[XF_malloc] = (void *) malloc;
    gd->jt[XF_free] = (void *) free;
    gd->jt[XF_getenv] = (void *) getenv;
    gd->jt[XF_setenv] = (void *) setenv;
    gd->jt[XF_get_timer] = (void *) get_timer;
    gd->jt[XF_simple_strtoul] = (void *) simple_strtoul;
    gd->jt[XF_udelay] = (void *) udelay;
    gd->jt[XF_simple_strtol] = (void *) simple_strtol;
    gd->jt[XF_strcmp] = (void *) strcmp;
#if defined(CONFIG_I386) || defined(CONFIG_PPC)
    gd->jt[XF_install_hdlr] = (void *) irq_install_handler;
    gd->jt[XF_free_hdlr] = (void *) irq_free_handler;
#endif    /* I386 || PPC */
#if defined(CONFIG_CMD_I2C)
    gd->jt[XF_i2c_write] = (void *) i2c_write;
    gd->jt[XF_i2c_read] = (void *) i2c_read;
#endif
}

(1)jumptable跳转表,jt本身是一个函数指针数组,里面记录了很多工具函数的函数名,譬如堆的申请和释放的函数、字符串转换函数、环境变量相关的函数、I2c读写相关的函数等。看这阵势是要实现一个函数指针到具体函数的映射关系,将来通过跳转表中的函数指针就可以执行具体的函数。这个其实就是在用C语言实现面向对象编程。在linux内核中有很多这种技巧。但是这个跳转表在uboot中只有赋值,而从未被使用,所以根本无用,这里可以忽略。

4.8、console_init_r

(1)console_init_f是控制台的第一阶段初始化,console_init_r是第二阶段初始化。实际上第一阶段初始化并没有实质性工作,第二阶段初始化才进行了实质性工作。

(2)uboot中有很多同名函数,使用SI工具去索引时经常索引到不对的函数处(回忆下当时start.S中找lowlevel_init.S时,自动索引找到的是错误的,真正的反而根本没找到。)

(3)console_init_r就是console的纯软件架构方面的初始化(说白了就是去给console相关的数据结构中填充相应的值),所以属于纯软件配置类型的初始化。

(4)uboot的console实际上并没有干有意义的转化,它就是直接调用的串口通信的函数。所以用不用console实际并没有什么分别。(在linux内console就可以提供缓冲机制等不用console不能实现的东西)。

4.9、enable_interrupts

(1)看名字应该是中断初始化代码。但因为我们uboot中没有使用中断,因此没有定义CONFIG_USE_IRQ宏,因此我们这里这个函数是个空壳子。

(2)uboot中经常出现一种情况就是根据一个宏是否定义了来条件编译决定是否调用一个函数内部的代码。uboot中有2种解决方案来处理这种情况:方案一:在调用函数处使用条件编译,然后函数体实际完全提供代码。方案二:在调用函数处直接调用,然后在函数体处提供2个函数体,一个是有实体的一个是空壳子,用宏定义条件编译来决定实际编译时编译哪个函数进去。

4.10、loadaddr、bootfile两个环境变量

(1)定义在uboot/common/cmd_bootm.c,所以这两个环境变量都是内核启动有关的,在启动linux内核时会参考这两个环境变量的值。

4.11、board_late_init

(1)这个函数的作用是开发板后续硬件的初始化,前面该初始化的都初始化过了。侧面说明了开发板级别的硬件软件初始化即将告一段落了。

(2)对于X210来说,这个函数是空的。

4.12、eth_initialize

(1)看名字应该是网卡相关的一些初始化。这里不是SoC与网卡芯片连接时SoC这边的初始化,而是网卡芯片本身的一些初始化。

(2)对于X210(DM9000)来说,这个函数是空的。X210的网卡IO端口的初始化在board_init函数中实现,网卡芯片的初始化在驱动中。
0

4.13、x210_preboot_init(LCD和logo显示)

(1)uboot命令行在启动起来之前的LCD初始化,以及LCD屏幕上的logo显示。

4.14、check menukey to update from sd

/* check menukey to update from sd */
    extern void update_all(void);
    if(check_menu_update_from_sd()==0)//update mode
    {
        puts ("[LEFT DOWN] update mode\n");
        run_command("fdisk -c 0",0);
        update_all();
    }
    else
        puts ("[LEFT UP] boot mode\n");

(1)uboot启动的最后阶段设计了一个自动更新的功能。就是:我们可以将要升级的镜像放到SD卡的固定目录/x210目录中,然后开机时在uboot启动的最后阶段检查升级标志(是一个按键。按键中标志为"LEFT"的那个按键,这个按键如果按下则表示update mode,如果启动时未按下则表示boot mode)。如果进入update mode则uboot会自动从SD卡中读取镜像文件然后烧录到iNand中;如果进入boot mode则uboot不执行update,直接启动正常运行。

(2)这种机制能够帮助我们快速烧录系统,常用于量产时用SD卡进行系统烧录部署。

4.15、死循环mainloop函数功能

(1)解析器
(2)开机倒数自动执行
(3)命令补全

4.16、uboot启动2阶段总结

1)第二阶段主要是对开发板级别的硬件、软件数据结构进行初始化。

2)

init_sequence                	遍历以下函数
	cpu_init                    	空的
	board_init                    	网卡、机器码、内存传参地址
		dm9000_pre_init           		网卡
		gd->bd->bi_arch_number      	机器码
		gd->bd->bi_boot_params      	内存传参地址
	interrupt_init                定时器4的初始化
	env_init                      没什么实际的事情,判断当前可以用的环境变量
	init_baudrate                 gd数据结构中波特率
	serial_init                   空的,第一阶段初始化过
	console_init_f                基本是空的,一条gd数据结构赋值
	display_banner               打印uboot版本信息
	print_cpuinfo                打印CPU时钟设置信息
	checkboard                   检验并打印开发板名字
	dram_init                    gd数据结构中DDR信息
	display_dram_config         打印DDR配置信息
mem_malloc_init            	初始化uboot的自己维护的堆内存
mmc_initialize              SD/emmc控制器和SD卡的初始化
env_relocate                环境变量重定位
gd->bd->bi_ip_addr          ip地址,gd数据结构赋值
gd->bd->bi_enetaddr         mac地址,gd数据结构赋值
devices_init                基本空的,从linux内核中搬过来的,但我们前面的硬件基本都初                始化差不多了
jumptable_init              跳转表,uboot定义了却不用,不用特别关注的
console_init_r              真正的控制台初始化,有没有控制台没什么区别,不用特别关注
enable_interrupts           空的,uboot中不使用中断
loadaddr、bootfile          两个要传给内核的参数,环境变量读出初始化成全局变量
board_late_init             空的,后续要添加硬件,可以往这里添加
eth_initialize              空的,一些网卡比较深层次的东西,看不懂
x210_preboot_init           LCD初始化和显示logo
check_menu_update_from_sd	检查自动更新,实现量产功能
main_loop                	主循环

3)启动过程特征总结
(1)第一阶段为汇编阶段、第二阶段为C阶段
(2)第一阶段在SRAM中、第二阶段在DRAM中
(3)第一阶段注重SoC内部硬件、第二阶段注重SoC外部Board上面

4)移植时的注意点
(1)x210_sd.h头文件中的宏定义
(2)特定硬件的初始化函数位置(譬如网卡)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/487071.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Win10配置ESP32-IDF+VSCode开发环境

一、安装包下载&#xff1a; Git&#xff1a;Git for WindowsPython&#xff1a;Download Python | Python.org以Windows x86-64开头的是 64 位的 Python 安装程序&#xff1b;以Windows x86开头的是 32 位的 Python 安装程序。ESP-IDF&#xff08;选择Offline版本&#xff09…

【微机原理】8088/8086的寻址方式

目录 一.指令的组成 二.操作数的寻址方式 1.立即数寻址 2.寄存器寻址方式 3.存储器寻址方式 &#xff08;1&#xff09;直接寻址 &#xff08;2&#xff09;寄存器间接寻址 &#xff08;3&#xff09;寄存器相对寻址方式 &#xff08;4&#xff09;基址变址寻址方式&#xff08…

基于zookeeper实现分布式锁

目录 zookeeper知识点复习 相关概念 java客户端操作 实现思路分析 基本实现 初始化链接 代码落地 优化&#xff1a;性能优化 实现阻塞锁 监听实现阻塞锁 优化&#xff1a;可重入锁 zk分布式锁小结 zookeeper知识点复习 Zookeeper&#xff08;业界简称zk&#xff…

【Linux】多路转接--select、poll、epoll,非阻塞等待

1.IO的概念 IO等拷贝数据 等&#xff1a;发送缓冲区满了或者接受缓冲区没有数据&#xff0c;就需要等待 高效IO就是&#xff1a;减少单位时间内,"等"的比重 2. 阻塞IO和非阻塞IO 2.1.阻塞IO 阻塞等待会在read的地方等待 #include <iostream> #include &l…

JavaScript实现输入数字,输出是几月份的代码

以下为实现输入数字&#xff0c;输出是几月份的代码和运行截图 目录 前言 一、实现输入数字&#xff0c;输出是几月份的 1.1 运行流程及思想 1.2 代码段 1.3 JavaScript语句代码 1.4 运行截图 前言 1.若有选择&#xff0c;您可以在目录里进行快速查找&#xff1b; 2.本…

1699_simulink代码生成配置初级方案

全部学习汇总&#xff1a; GreyZhang/g_matlab: MATLAB once used to be my daily tool. After many years when I go back and read my old learning notes I felt maybe I still need it in the future. So, start this repo to keep some of my old learning notes servral …

数据库篇:初始化、建表、配置及调用

微信小程序云开发实战-答题积分赛小程序 数据库篇:初始化、建表、配置及调用 开通云开发服务 点击【云开发】,开通云开发服务; 开通服务完成后,方可继续往下操作; 题库数据表初始化 创建数据表 点击【数据库】,然后点击【+】创建数据表;

彻底告别手动配置任务,魔改xxl-job!

分析 改造 1、接口调用 2、创建新注解 3、自动注册核心 4、自动装配 测试 测试后 XXL-Job是一款非常优秀的任务调度中间件&#xff0c;其轻量级、使用简单、支持分布式等优点&#xff0c;被广泛应用在我们的项目中&#xff0c;解决了不少定时任务的调度问题。不仅如此&a…

RabbitMQ 简单模型

MQ引言 1.1 什么是MQ ​ MQ(Message Quene) : 翻译为消息队列,通过典型的生产者和消费者模型,生产者不断向消息队列中生产消息&#xff0c;消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的&#xff0c;而且只关心消息的发送和接收&#xff0c;没有业务逻辑的侵…

chatGPT+Midjourney制作绘画本

chatGPTMidjourney制作绘画本 灵感来源&#xff1a;https://www.bilibili.com/video/BV1N24y1F7ga/?spm_id_from888.80997.embed_other.whitelist&vd_source6dd97671c42eb7cf111063714216bd0b 最终效果&#xff1a; 绘本故事 故事塑造能力弱的人可以使用chatGPT来帮助编…

wait/waitpid函数等待子进程状态发生改变

&#x1f38a;【进程通信与并发】专题正在持续更新中&#xff0c;进程&#xff0c;线程&#xff0c;IPC&#xff0c;线程池等的创建原理与运用✨&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列…

【自看】2023前端面试上岸手册——VUE部分

目录 Vue 的基本原理双向数据绑定的原理MVVM、MVC、MVP 的区别slot 是什么&#xff1f;有什么作用&#xff1f;原理是什么&#xff1f;\$nextTick 原理及作用Vue 单页应用与多页应用的区别Vue 中封装的数组方法有哪些&#xff0c;其如何实现页面更新Vue data 中某一个属性的值发…

商品管理系统【控制台+MySQL】(Java课设)

系统类型 控制台类型Mysql数据库存储数据 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 本系统源码地址&#xff1a;https://download.csdn.net/download/qq_50954361/87738976 更多系统资源库地…

辅助驾驶功能开发-功能规范篇(16)-2-领航辅助系统NAP-匝道跟车基础功能

书接上回 2.3.3匝道辅助驾驶 匝道辅助驾驶功能根据导航引导在ODD范围内辅助驾驶车辆进出匝道,主动变道并入或开出主路,并可根据导航路线引导车辆通过跨高速连接路。 前置条件: 1)驾驶员设置导航目的地及导航路线 2)开启辅助驾驶功能,系统进入NOA功能 2.3.3.1.上下匝道…

如何设计一个可扩展的优惠券功能

本文主要分享了如何设计一个可扩展的优惠券功能。 一、功能特性介绍 1.每个条件的代码独立&#xff0c;相当于单独的实现类实现接口&#xff0c;就能通过配置添加到优惠券条件校验当中&#xff0c;支持多种条件灵活组合 2.新增一种使用条件可以不修改核心流程代码&#xff0…

Angular 与PDF之二:打印预览的实现

如何在angular中实现打印和预览pdf的功能, 使用print.js这个包就可实现这个功能 Print.js介绍 Print.js可以打印pdf文件&#xff0c;html元素&#xff0c;图片。官网 https://printjs.crabbly.com/ Print.js使用 首先新建一个angular项目&#xff0c;在项目里下载print.js n…

[JS每M日N练] [格物] - 你所不知道的toString

文章目录 导读Object.prototype.toString常见类型转换结果Object.toString ! Object.prototype.toString对Object.prototype.toString.call(obj)的理解 .toString.toString TypeError误区tostring被改写了定义在原型链的什么位置上方法重写 文章小结参考资料 导读 开发过程中经…

同时使用注解和 xml 的方式引用 dubbo 服务产生的异常问题排查实战

文章目录 一、现象二、问题排查三、结论四、解决方案 一、现象 使用 nacos 作注册中心的线上 dubbo 消费端应用每隔 1 分钟就会抛出以下异常&#xff08;为使描述简单化&#xff0c;文章中使用本地 demo 来复现&#xff09;&#xff0c;该异常表示无法连接到 172.17.0.1:20881…

JavaWeb( 二 ) URL

1.4.URL统一资源定位符 URL代表Uniform Resource Locator 统一资源定位符&#xff0c;也叫 URL地址 。是用于标识和定位Web上资源的地址&#xff0c;通常用于在Web浏览器中访问网站和文件。 URL由若干部分组成&#xff0c;scheme:// host : port / path 例如&#xff1a; htt…

Contest3111 - 计科2101~2104算法设计与分析上机作业07

问题 A: 有重复元素的排列问题 题目描述 设R{ r 1 , r 2 , …, r n }是要进行排列的n个元素。其中元素r 1 , r 2 , …, r n 可能相同。试设计一个算法&#xff0c; 列出R的所有不同排列。给定n 以及待排列的n 个元素。计算出这n 个元素的所有不同排列。 输入 第1 行是元素个…