• u-boot之start_armboot函数分析


    在分析start.S文件过程中提到过,最后从汇编跳到C函数执行的是start_armboot函数,位于lib_armoard.c文件下,它的执行流程图如下,截图来源于《嵌入式LINUX应用开发完全手册》。根据流程图,以下内容大致分几步写:

    1、gd全局变量初始化

    2、调用init_sequence函数指针数组里的初始化函数、nand初始化、环境变量初始化、USB初始化

    3、死循环main_loop()分析

    1、gd全局变量初始化

    gd是全局引用的变量,它的定义在Global_data.h (includeasm-arm)中,它利用的是CPU的寄存器r8。只有在文件中引用DECLARE_GLOBAL_DATA_PTR ,就可以使用gd这个变量

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

    它是一个指向gd_t结构体的指针,gd_t结构体如下所示

    typedef    struct    global_data {
        bd_t        *bd;                                                //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 */  //lcd的缓存地址
    #ifdef CONFIG_VFD
        unsigned char    vfd_type;    /* display type */
    #endif
    #if 0
        unsigned long    cpu_clk;    /* CPU clock in Hz!        */
        unsigned long    bus_clk;
        unsigned long    ram_size;    /* RAM size */
        unsigned long    reset_status;    /* reset status register at boot */
    #endif
        void        **jt;        /* jump table */
    } gd_t;

    其中env_addr、baudrate、bd等参数比较重要,bd也是一个结构体,在U-boot.h (includeasm-arm)里定义,定义如下所示,这里面bi_arch_number与bi_boot_params这两个参数是传给内核的,很重要。

    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 Address,网卡决定,不可配置
        struct environment_s           *bi_env;                  //指向环境变量的指针
        ulong            bi_arch_number;    /* unique id for this board *///CPU架构号码,传给内核
        ulong            bi_boot_params;    /* where this board expects params *///标记列表的开始地址,传给内核,告诉内核从这个地方取参数
        struct                /* RAM configuration */
        {
        ulong start;
        ulong size;
        }             bi_dram[CONFIG_NR_DRAM_BANKS];//sdram的起始地址与大小
    #ifdef CONFIG_HAS_ETH1
        /* second onboard ethernet port */
        unsigned char   bi_enet1addr[6];
    #endif
    } bd_t;

    environment_s结构体定义在Environment.h (include)中,如下所示。环境变量就是以这个格式存储在nand中的

    #define ENV_SIZE (CFG_ENV_SIZE - ENV_HEADER_SIZE)//0x20000-5,减去的5为crc校验与flags占用的
    
    typedef    struct environment_s {
        unsigned long    crc;        /* CRC32 over data bytes    *///crc校验
    #ifdef CFG_REDUNDAND_ENVIRONMENT
        unsigned char    flags;        /* active/obsolete flags    *///环境变量标志
    #endif
        unsigned char    data[ENV_SIZE]; /* Environment data        *///环境变量具体的数据。最大
    } env_t;

    start_armboot函数一开始先初始化gd变量。gd变量所指向的内容占用128字节存放在堆区后面,栈区前面。

        /* Pointer is writable since we allocated a register for it */
        gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));//gd地址向上增长
        /* compiler optimization barrier needed for GCC >= 3.4 */
        __asm__ __volatile__("": : :"memory");
    
        memset ((void*)gd, 0, sizeof (gd_t));//清0 gd段 
        gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));//求出bd段地址  
        memset (gd->bd, 0, sizeof (bd_t));//清0gd->bd段  
    
        monitor_flash_len = _bss_start - _armboot_start;//显示需要的flash长度

    2、调用init_sequence函数指针数组里的初始化函数、nand初始化、环境变量初始化、USB初始化

    start_armboot函数继续往下执行,执行初始化函数数组里的函数

    for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {//初始化各个函数  by andy
            if ((*init_fnc_ptr)() != 0) {//函数的返回值不为0,认为出错,打印出错信息 by andy
                hang ();//打印出错信息   by andy
            }
        }

    init_sequence数组的内容如下所示

        init_fnc_t *init_sequence[] = {
        cpu_init,        /* basic cpu dependent setup *///CPU的一些堆栈大小设置  by andy
        board_init,        /* basic board dependent setup *///设置芯片代码、设置与内核交互的地址 by andy
        interrupt_init,        /* set up exceptions *///10ms时钟定时器设置  by andy
        env_init,        /* initialize environment *///初始化环境变量,采用默认环境变量  by andy
        init_baudrate,        /* initialze baudrate settings *///初始化串口波特率为115200 by andy
        serial_init,        /* serial communications setup *///初始化串口在cpu/arm920t/s3c24x0中实现  by andy
        console_init_f,        /* stage 1 init of console *///设置控制台初始化标志 by andy
        display_banner,        /* say that we are here *///打印UBOOT版本信息 by andy
    #if defined(CONFIG_DISPLAY_CPUINFO)
        print_cpuinfo,        /* display cpu info (and speed) */
    #endif
    #if defined(CONFIG_DISPLAY_BOARDINFO)
        checkboard,        /* display board info */
    #endif
        dram_init,        /* configure available RAM banks *///内存起始地址以及大小设置 by andy
        display_dram_config,//打印出DRAM的大小  by andy
        NULL,
    };

    start_armboot函数继续往下执行,清0分配的堆区的内容

        /* armboot_start is defined in the board-specific linker script */
        mem_malloc_init (_armboot_start - CFG_MALLOC_LEN);//清0堆区的内容
    void mem_malloc_init (ulong dest_addr)
    {
        mem_malloc_start = dest_addr;                //堆区的首地址
        mem_malloc_end = dest_addr + CFG_MALLOC_LEN; //堆区的结束地址
        mem_malloc_brk = mem_malloc_start;
    
        memset ((void *) mem_malloc_start, 0,
                mem_malloc_end - mem_malloc_start);//清0堆区的内容
    }

    start_armboot函数继续往下执行,初始化nand,环境变量的内容存储在nand中

    puts ("NAND:  ");//打印出nand的大小  
        nand_init();        /* go init the NAND *///初始化nand flash

    start_armboot函数继续往下执行,重新检测环境变量,环境变量在初始化函数数组中已经初始化过,因为nand初始化后,所以再检测一般环境变量是否需要重新加载。env_relocate 函数大致的意思是检查nand中的存放环境变量位置的crc校验是否有效,如果无效则采用默认的环境变量,如果有效则采用nand中的环境变量

    /* initialize environment */
        env_relocate ();//初始化环境变量,crc有效的话从nand中读取存储的环境变量,否则采用默认的环境变量  by andy

    默认的环境变量的值如下

    uchar default_environment[] = {
    #ifdef    CONFIG_BOOTARGS
        "bootargs="    CONFIG_BOOTARGS            ""//"noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0",传给内核的参数
    #endif
    #ifdef    CONFIG_BOOTCOMMAND
        "bootcmd="    CONFIG_BOOTCOMMAND        ""//"nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0"。启动内核的命令
    #endif
    #ifdef    CONFIG_RAMBOOTCOMMAND
        "ramboot="    CONFIG_RAMBOOTCOMMAND        ""
    #endif
    #ifdef    CONFIG_NFSBOOTCOMMAND
        "nfsboot="    CONFIG_NFSBOOTCOMMAND        ""
    #endif
    #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
        "bootdelay="    MK_STR(CONFIG_BOOTDELAY)    ""//延时参数2S 
    #endif
    #if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
        "baudrate="    MK_STR(CONFIG_BAUDRATE)        ""//波特率115200
    #endif
    #ifdef    CONFIG_LOADS_ECHO
        "loads_echo="    MK_STR(CONFIG_LOADS_ECHO)    ""
    #endif
    #ifdef    CONFIG_ETHADDR
        "ethaddr="    MK_STR(CONFIG_ETHADDR)        ""//08:00:3e:26:0a:5b;MAC地址
    #endif
    #ifdef    CONFIG_ETH1ADDR
        "eth1addr="    MK_STR(CONFIG_ETH1ADDR)        ""
    #endif
    #ifdef    CONFIG_ETH2ADDR
        "eth2addr="    MK_STR(CONFIG_ETH2ADDR)        ""
    #endif
    #ifdef    CONFIG_ETH3ADDR
        "eth3addr="    MK_STR(CONFIG_ETH3ADDR)        ""
    #endif
    #ifdef    CONFIG_IPADDR
        "ipaddr="    MK_STR(CONFIG_IPADDR)        ""//板子IP192.168.7.17
    #endif
    #ifdef    CONFIG_SERVERIP
        "serverip="    MK_STR(CONFIG_SERVERIP)        ""//服务器IP192.168.7.11
    #endif
    #

    start_armboot函数继续往下执行,从环境变量中获取IP地址以及MAC地址

    gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");//从环境变量获得IP地址  by andy
        i = getenv_r ("ethaddr", tmp, sizeof (tmp));//从环境变量获得MAC地址
            s = (i > 0) ? tmp : NULL;
    
            for (reg = 0; reg < 6; ++reg) {//判断获得的MAC地址有效后存储在gd->bd->bi_enetaddr中
                gd->bd->bi_enetaddr[reg] = s ? simple_strtoul (s, &e, 16) : 0;
                if (s)
                    s = (*e) ? e + 1 : e;
            }

    start_armboot函数继续往下执行,控制台初始化,主要为设置输入输出与错误设备都为串口

    console_init_r ();    /* fully init console as a device *///控制台初始化

    start_armboot函数继续往下执行,USB与网络初始化,主要用来文件传输用的

    Port_Init();
        if (!PreLoadedONRAM) {
            /* enable exceptions */
            enable_interrupts ();
            /* add by www.100ask.net */
            usb_init();//USB初始化by andy
        }
        
        eth_initialize(gd->bd);//网络初始化

    3、死循环main_loop()分析

    for (;;) {//大循环
            main_loop ();//大循环  by andy
        }

    下面分析main_loop()函数,它位于Main.c (common)中。main_loop函数首先为nand flash实现分区

     extern int mtdparts_init(void);
        if (!getenv("mtdparts"))//如果mtdparts参数不存在  by andy
        {
            run_command("mtdparts default", 0);//执行分区命令 设置分区 by andy
        }
        else
        {
            mtdparts_init();
        }

    执行完run_command("mtdparts default", 0)后分区内容为:256k@0(bootloader),128k(params),2m(kernel),-(root)。意思是以bootloader占用256k为开头,初始地址为0。接着128k为参数,接着2m为内核,剩余的为文件。

    继续执行main_loop函数,从环境变量中取得一些必要的参数。在后面要用到

     s = getenv ("c");//从环境变量中取得boordelay参数
        bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;//将字符串转换成整形
        s = getenv ("bootcmd");//取得bootcmd环境变量为"nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0"

    继续执行main_loop函数,如果禁用bootdelay功能,则直接运行bootcmd环境变量。功能为从nand中取得内核到sdram的0x30007FC0 处。然后启动内核

        if (bootdelay >= 0 && s && !abortboot (bootdelay)) {//等待延时数倒计时,如果时间到了则运行bootcmd参数。如果在时间到之前按下了空格键,则继续网线执行
    # ifdef CONFIG_AUTOBOOT_KEYED
            int prev = disable_ctrlc(1);    /* disable Control C checking */
    # endif
    
    # ifndef CFG_HUSH_PARSER
            {
                printf("Booting Linux ...
    ");            
                run_command (s, 0);//执行bootcmd命令
            }
    # else
            parse_string_outer(s, FLAG_PARSE_SEMICOLON |
                        FLAG_EXIT_FROM_LOOP);
    # endif
    
    # ifdef CONFIG_AUTOBOOT_KEYED
            disable_ctrlc(prev);    /* restore Control C checking */
    # endif
        }

    继续执行main_loop函数,再来一个循环。若前面没有启动内核,那么进入死循环,等待控制台输入命令然后运行

    for (;;) {//若倒计时没到之前
    #ifdef CONFIG_BOOT_RETRY_TIME
            if (rc >= 0) {
                /* Saw enough of a valid command to
                 * restart the timeout.
                 */
                reset_cmd_timeout();
            }
    #endif
            len = readline (CFG_PROMPT);
    
            flag = 0;    /* assume no special flags for now */
            if (len > 0)
                strcpy (lastcommand, console_buffer);//取得控制台输入设备的数据
            else if (len == 0)
                flag |= CMD_FLAG_REPEAT;
    #ifdef CONFIG_BOOT_RETRY_TIME
            else if (len == -2) {
                /* -2 means timed out, retry autoboot
                 */
                puts ("
    Timed out waiting for command
    ");
    # ifdef CONFIG_RESET_TO_RETRY
                /* Reinit board to run initialization code again */
                do_reset (NULL, 0, 0, NULL);
    # else
                return;        /* retry autoboot */
    # endif
            }
    #endif
    
            if (len == -1)
                puts ("<INTERRUPT>
    ");
            else
                rc = run_command (lastcommand, flag);//运行从输入设备取得的数据 
    
            if (rc <= 0) {
                /* invalid command or not repeatable, forget it */
                lastcommand[0] = 0;
            }
        }
  • 相关阅读:
    STM32 IAP程序 源码 和测试代码 有详细的中文注释
    mysql读写分离配置,利用mybatis实现,解释为什么dynamicDataSource不行
    mysql主从复制的配置总结
    Chapter 2 Open Book——7
    leetcode415---字符串大数相加
    Chapter 2 Open Book——6
    leetcode83,删除有序链表中的重复元素
    Chapter 2 Open Book——5
    Chapter 2 Open Book——4
    leetcode24,交换链表相邻的节点
  • 原文地址:https://www.cnblogs.com/andyfly/p/9353315.html
Copyright © 2020-2023  润新知