zoukankan      html  css  js  c++  java
  • UBoot 启动过程和源码分析(第二阶段)

    3. U-Boot启动第二阶段代码分析

    start_armboot函数在lib_arm/board.c中定义,是U-Boot第二阶段代码的入口。U-Boot启动第二阶段流程如下:

     

      在分析start_armboot函数前先来看看一些重要的数据结构:

    (1)gd_t结构体

      U-Boot使用了一个结构体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    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_VFD
        unsigned char    vfd_type;    /* display type */
    #endif
    #ifdef CONFIG_FSL_ESDHC
        unsigned long    sdhc_clk;
    #endif
    #if 0
        unsigned 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 */
    #endif
        void        **jt;        /* jump table */
    } gd_t;

      U-Boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址:

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

      DECLARE_GLOBAL_DATA_PTR定义一个gd_t全局数据结构的指针,这个指针存放在指定的寄存器r8中。

      这个声明也避免编译器把r8分配给其它的变量。

      任何想要访问全局数据区的代码,只要代码开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,然后就可以使用gd指针来访问全局数据区了。

      根据U-Boot内存使用图中可以计算gd的值:

      gd = TEXT_BASE -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)

    (2)bd_t结构体

           bd_t在include/asm/u-boot.h中定义如下:

    typedef struct bd_info {
        int                bi_baudrate;               /* 串口通讯波特率 */
        unsigned long     bi_ip_addr;          /* IP 地址*/
        struct environment_s        *bi_env;              /* 环境变量开始地址 */
        ulong            bi_arch_number;      /* 开发板的机器码 */
        ulong            bi_boot_params;       /* 内核参数的开始地址 */
        struct                         /* RAM配置信息 */
        {
                  ulong start;
                  ulong size;
        }bi_dram[CONFIG_NR_DRAM_BANKS]; 
    } bd_t;

      U-Boot启动内核时要给内核传递参数,这时就要使用gd_tbd_t结构体中的信息来设置标记列表。

      第一阶段调用start_armboot指向C语言执行代码区,首先它要从内存上的重定位数据获得不完全配置的全局数据表格和板级信息表格,即获得gd_tbd_t

      这两个类型变量记录了刚启动时的信息,并将要记录作为引导内核和文件系统的参数,如bootargs等等,并且将来还会在启动内核时,由uboot交由kernel时会有所用。

    (3)init_sequence数组

           U-Boot使用一个数组init_sequence来存储对于大多数开发板都要执行的初始化函数的函数指针。

      init_sequence数组中有较多的编译选项,去掉编译选项后init_sequence数组如下所示:

    typedef int (init_fnc_t) (void);
    
    int print_cpuinfo (void);
    
    init_fnc_t *init_sequence[] = {
    #if defined(CONFIG_ARCH_CPU_INIT)
        arch_cpu_init,        /* basic arch cpu dependent setup */
    #endif
        board_init,         /*开发板相关的配置--board/samsung/mini2440/mini2440.c */
    #if defined(CONFIG_USE_IRQ)
        interrupt_init,        /* set up exceptions */
    #endif
        timer_init,         /*开发板相关的配置--board/samsung/mini2440/mini2440.c */
    #ifdef CONFIG_FSL_ESDHC
        get_clocks,
    #endif
        env_init,        /*初始化环境变量--common/env_flash.c 或common/env_nand.c*/
        init_baudrate,         /*初始化波特率-- lib_arm/board.c */
        serial_init,         /* 串口初始化-- drivers/serial/serial_s3c24x0.c */
        console_init_f,        /* 控制通讯台初始化阶段1-- common/console.c */
        display_banner,        /*打印U-Boot版本、编译的时间-- gedit lib_arm/board.c */
    #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,         /*配置可用的RAM-- board/samsung/mini2440/mini2440.c */
    #if defined(CONFIG_CMD_PCI) || defined (CONFIG_PCI)
        arm_pci_init,
    #endif
        display_dram_config,  /* 显示RAM大小-- lib_arm/board.c */
        NULL,
    };

      其中的board_init函数在board/ti/ti8168_dvr/dvr.c中定义,该函数设置了软复位UART,以及一些GPIO寄存器的值,还设置了U-Boot机器码和内核启动参数地址 :

    /*
     * Basic board specific setup
     */
    int board_init(void)
    {
        u32 regVal;
    
        /* Get Timer and UART out of reset */
    
        /* UART 软复位 */
        regVal = __raw_readl(UART_SYSCFG);
        regVal |= 0x2;
        __raw_writel(regVal, UART_SYSCFG);
        while( (__raw_readl(UART_SYSSTS) & 0x1) != 0x1);
    
        /* 关闭smart idle */
        regVal = __raw_readl(UART_SYSCFG);
        regVal |= (1<<3);
        __raw_writel(regVal, UART_SYSCFG);
    
        /* 8168 dvr 开发板的机器码 */
        gd->bd->bi_arch_number = MACH_TYPE_TI8168EVM;
    
        /* 内核启动参数地址 */
        gd->bd->bi_boot_params = PHYS_DRAM_1 + 0x100;
    
        /*通用存储控制模块 初始化*/
        gpmc_init();
    
    /*gpio 初始化*/ gpio_clkctrl_enable(); gpio_dvr_init();
       /*初始化模拟spi的IO*/
    #if defined(CONFIG_3WIRE_EEPROM) dvr_netra_eeprom_init(); #endif //# RDK 支持8bit NAND gpmc_set_cs_buswidth(0, 0); return 0; }

    其中的dram_init函数在board/ti/ti8168_dvr/dvr.c中定义如下:

    /*
     * Configure DRAM banks
     *
     * Description: sets uboots idea of sdram size
     */
    int dram_init(void)
    {
        /* Fill up board info */
        gd->bd->bi_dram[0].start = PHYS_DRAM_1;
        gd->bd->bi_dram[0].size = PHYS_DRAM_1_SIZE;
    
        gd->bd->bi_dram[1].start = PHYS_DRAM_2;
        gd->bd->bi_dram[1].size = PHYS_DRAM_2_SIZE;
    
        return 0;
    }

       ti8168 dvr使用2片1GB的DRAM组成了2GB的内存,接在存储控制器的DDR3,地址空间是0x80000000~0xFFFFFFFFF。

           在include/configs/ti8168_dvr.h中PHYS_DRAM_1PHYS_DRAM_1_SIZE, 分别被定义为0x80000000和0xC0000000。

           分析完上述的数据结构,下面来分析start_armboot函数:

    void start_armboot (void)
    {
           init_fnc_t **init_fnc_ptr;
           char *s;
           … …
           /* 计算全局数据结构的地址gd */
           gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN - sizeof(gd_t));
           … …
           memset ((void*)gd, 0, sizeof (gd_t));
           gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));
           memset (gd->bd, 0, sizeof (bd_t));
           gd->flags |= GD_FLG_RELOC;
     
           monitor_flash_len = _bss_start - _armboot_start;
     
           /* 逐个调用init_sequence数组中的初始化函数  */
           for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
                  if ((*init_fnc_ptr)() != 0) {
                         hang ();
                  }
           }
     
           /* armboot_start 在cpu/arm920t/start.S 中被初始化为u-boot.lds连接脚本中的_start */
           mem_malloc_init (_armboot_start - CONFIG_SYS_MALLOC_LEN,
                         CONFIG_SYS_MALLOC_LEN);
     
           /* NOR Flash初始化 */
    #ifndef CONFIG_SYS_NO_FLASH
           /* configure available FLASH banks */
           display_flash_config (flash_init ());
    #endif /* CONFIG_SYS_NO_FLASH */
     
           … … 
           /* NAND Flash 初始化*/
    #if defined(CONFIG_CMD_NAND)
           puts ("NAND:  ");
           nand_init();         /* go init the NAND */
    #endif
           … …
           /*配置环境变量,重新定位 */
           env_relocate ();
           … …
           /* 从环境变量中获取IP地址 */
           gd->bd->bi_ip_addr = getenv_IPaddr ("ipaddr");
           stdio_init (); /* get the devices list going. */
           jumptable_init ();
           … …
           console_init_r (); /* fully init console as a device */
           … …
           /* enable exceptions */
           enable_interrupts ();
     
    #ifdef CONFIG_USB_DEVICE
           usb_init_slave();
    #endif
     
           /* 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
           … …
           /* 网卡初始化 */
    #if defined(CONFIG_CMD_NET)
    #if defined(CONFIG_NET_MULTI)
           puts ("Net:   ");
    #endif
           eth_initialize(gd->bd);
    … …
    #endif
     
           /* main_loop() can return to retry autoboot, if so just run it again. */
           for (;;) {
                  main_loop ();
           }
           /* NOTREACHED - no way out of command loop except booting */
    }

      main_loop函数在common/main.c中定义。

    void main_loop (void)
    {
    #ifndef CONFIG_SYS_HUSH_PARSER
        static char lastcommand[CONFIG_SYS_CBSIZE] = { 0, };
        int len;
        int rc = 1;
        int flag;
    #endif
    
    #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
        char *s;
        int bootdelay;
    #endif
    #ifdef CONFIG_PREBOOT
        char *p;
    #endif
    #ifdef CONFIG_BOOTCOUNT_LIMIT
        unsigned long bootcount = 0;
        unsigned long bootlimit = 0;
        char *bcs;
        char bcs_set[16];
    #endif /* CONFIG_BOOTCOUNT_LIMIT */
    
    #if defined(CONFIG_VFD) && defined(VFD_TEST_LOGO)
        ulong bmp = 0;        /* default bitmap */
        extern int trab_vfd (ulong bitmap);
    
    #ifdef CONFIG_MODEM_SUPPORT
        if (do_mdm_init)
            bmp = 1;    /* alternate bitmap */
    #endif
        trab_vfd (bmp);
    #endif    /* CONFIG_VFD && VFD_TEST_LOGO */
    
    #ifdef CONFIG_BOOTCOUNT_LIMIT
        bootcount = bootcount_load();
        bootcount++;
        bootcount_store (bootcount);
        sprintf (bcs_set, "%lu", bootcount);
        setenv ("bootcount", bcs_set);
        bcs = getenv ("bootlimit");
        bootlimit = bcs ? simple_strtoul (bcs, NULL, 10) : 0;
    #endif /* CONFIG_BOOTCOUNT_LIMIT */
    
    #ifdef CONFIG_MODEM_SUPPORT
        debug ("DEBUG: main_loop:   do_mdm_init=%d\n", do_mdm_init);
        if (do_mdm_init) {
            char *str = strdup(getenv("mdm_cmd"));
            setenv ("preboot", str);  /* set or delete definition */
            if (str != NULL)
                free (str);
            mdm_init(); /* wait for modem connection */
        }
    #endif  /* CONFIG_MODEM_SUPPORT */
    
    #ifdef CONFIG_VERSION_VARIABLE
        {
            extern char version_string[];
    
            setenv ("ver", version_string);  /* set version variable */
        }
    #endif /* CONFIG_VERSION_VARIABLE */
    
    #ifdef CONFIG_SYS_HUSH_PARSER
        u_boot_hush_start ();
    #endif
    
    #if defined(CONFIG_HUSH_INIT_VAR)
        hush_init_var ();
    #endif
    
    #ifdef CONFIG_AUTO_COMPLETE
        install_auto_complete(); //安装自动补全的函数,分析如下
    #endif
    
    #ifdef CONFIG_PREBOOT
        if ((p = getenv ("preboot")) != NULL) {
    # ifdef CONFIG_AUTOBOOT_KEYED
            int prev = disable_ctrlc(1);    /* disable Control C checking */
    # endif
    
    # ifndef CONFIG_SYS_HUSH_PARSER
            run_command (p, 0);
    # else
            parse_string_outer(p, FLAG_PARSE_SEMICOLON |
                        FLAG_EXIT_FROM_LOOP);
    # endif
    
    # ifdef CONFIG_AUTOBOOT_KEYED
            disable_ctrlc(prev);    /* restore Control C checking */
    # endif
        }
    #endif /* CONFIG_PREBOOT */
    
    #if defined(CONFIG_UPDATE_TFTP)
        update_tftp ();
    #endif /* CONFIG_UPDATE_TFTP */
    
    #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
        s = getenv ("bootdelay");
        bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;
    
        debug ("### main_loop entered: bootdelay=%d\n\n", bootdelay);
    
    # ifdef CONFIG_BOOT_RETRY_TIME
        init_cmd_timeout ();
    # endif    /* CONFIG_BOOT_RETRY_TIME */
    
    #ifdef CONFIG_POST
        if (gd->flags & GD_FLG_POSTFAIL) {
            s = getenv("failbootcmd");
        }
        else
    #endif /* CONFIG_POST */
    #ifdef CONFIG_BOOTCOUNT_LIMIT
        if (bootlimit && (bootcount > bootlimit)) {
            printf ("Warning: Bootlimit (%u) exceeded. Using altbootcmd.\n",
                    (unsigned)bootlimit);
            s = getenv ("altbootcmd");
        }
        else
    #endif /* CONFIG_BOOTCOUNT_LIMIT */
            s = getenv ("bootcmd"); //获取引导命令。分析见下面。
    
        printf ("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");
    
        if (bootdelay >= 0 && s && !abortboot (bootdelay)) {
                    //如果延时大于等于零,并且没有在延时过程中接收到按键,则引导内核。abortboot函数的分析见下面。 
    # ifdef CONFIG_AUTOBOOT_KEYED
            int prev = disable_ctrlc(1);    /* disable Control C checking */
    # endif
    
    # ifndef CONFIG_SYS_HUSH_PARSER
            run_command (s, 0);//运行引导内核的命令。这个命令是在配置头文件中定义的。run_command的分析在下面。
    # 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
        }
    
    # ifdef CONFIG_MENUKEY
        if (menukey == CONFIG_MENUKEY) {
            s = getenv("menucmd");
            if (s) {
    # ifndef CONFIG_SYS_HUSH_PARSER
            run_command (s, 0);
    # else
            parse_string_outer(s, FLAG_PARSE_SEMICOLON |
                        FLAG_EXIT_FROM_LOOP);
    # endif
            }
        }
    #endif /* CONFIG_MENUKEY */
    #endif    /* CONFIG_BOOTDELAY */
    
    #ifdef CONFIG_AMIGAONEG3SE
        {
            extern void video_banner(void);
            video_banner();
        }
    #endif
    
        /*
         * Main Loop for Monitor Command Processing
         */
    #ifdef CONFIG_SYS_HUSH_PARSER
        parse_file_outer();
        /* This point is never reached */
        for (;;);
    #else
        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 (CONFIG_SYS_PROMPT); //CONFIG_SYS_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 ("\nTimed out waiting for command\n");
    # 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>\n");
            else
                rc = run_command (lastcommand, flag); //执行命令 
    
            if (rc <= 0) {//执行失败,则清空记录 
                /* invalid command or not repeatable, forget it */
                lastcommand[0] = 0;
            }
        }
    #endif /*CONFIG_SYS_HUSH_PARSER*/
    }

    U-Boot启动Linux过程

      U-Boot使用标记列表(tagged list)的方式向Linux传递参数。标记的数据结构式是tag,在U-Boot源代码目录arch/arminclude/asm/setup.h中定义如下:

    struct tag_header {
           u32 size;       /* 表示tag数据结构的联合u实质存放的数据的大小*/
           u32 tag;        /* 表示标记的类型 */
    };
     
    struct tag {
           struct tag_header hdr;
           union {
                  struct tag_core           core;
                  struct tag_mem32      mem;
                  struct tag_videotext   videotext;
                  struct tag_ramdisk     ramdisk;
                  struct tag_initrd  initrd;
                  struct tag_serialnr       serialnr;
                  struct tag_revision      revision;
                  struct tag_videolfb     videolfb;
                  struct tag_cmdline     cmdline;
     
                  /*
                   * Acorn specific
                   */
                  struct tag_acorn  acorn;
                  /*
                   * DC21285 specific
                   */
                  struct tag_memclk      memclk;
           } u;
    };

      U-Boot使用命令bootm来启动已经加载到内存中的内核。而bootm命令实际上调用的是do_bootm函数。

      对于Linux内核,do_bootm函数会调用do_bootm_linux函数来设置标记列表和启动内核。

      do_bootm_linux函数在arch/arm/bootm.c 中定义如下:

    int do_bootm_linux(int flag, int argc, char *argv[], bootm_headers_t *images)
    {
           bd_t       *bd = gd->bd;
           char       *s;
           int   machid = bd->bi_arch_number;
           void       (*theKernel)(int zero, int arch, uint params);
      
    #ifdef CONFIG_CMDLINE_TAG
           char *commandline = getenv ("bootargs");   /* U-Boot环境变量bootargs */
    #endif
           … …
           theKernel = (void (*)(int, int, uint))images->ep; /* 获取内核入口地址 */
           … …
    #if   defined (CONFIG_SETUP_MEMORY_TAGS) || \
           defined (CONFIG_CMDLINE_TAG) || \
           defined (CONFIG_INITRD_TAG) || \
           defined (CONFIG_SERIAL_TAG) || \
           defined (CONFIG_REVISION_TAG) || \
           defined (CONFIG_LCD) || \
           defined (CONFIG_VFD)
           setup_start_tag (bd);                                     /* 设置ATAG_CORE标志 */
           … …
    #ifdef CONFIG_SETUP_MEMORY_TAGS
          setup_memory_tags (bd);                             /* 设置内存标记 */
    #endif
     #ifdef CONFIG_CMDLINE_TAG
          setup_commandline_tag (bd, commandline);      /* 设置命令行标记 */
    #endif
           … …
          setup_end_tag (bd);                               /* 设置ATAG_NONE标志 */          
    #endif
          /* we assume that the kernel is in place */
          printf ("\nStarting kernel ...\n\n");
           … …
          cleanup_before_linux ();          /* 启动内核前对CPU作最后的设置 */
     
          theKernel (0, machid, bd->bi_boot_params);      /* 调用内核 */
     
          /* does not return */
          return 1;
    }

      其中的setup_start_tag,setup_memory_tags,setup_end_tag函数在lib_arm/bootm.c中定义如下:

    (1)setup_start_tag函数

    static void setup_start_tag (bd_t *bd)
    {
           params = (struct tag *) bd->bi_boot_params;  /* 内核的参数的开始地址 */
     
           params->hdr.tag = ATAG_CORE;
           params->hdr.size = tag_size (tag_core);
     
           params->u.core.flags = 0;
           params->u.core.pagesize = 0;
           params->u.core.rootdev = 0;
     
           params = tag_next (params);
    }

      标记列表必须以ATAG_CORE开始,setup_start_tag函数在内核的参数的开始地址设置了一个ATAG_CORE标记。

    (2)setup_memory_tags函数

    static void setup_memory_tags (bd_t *bd)
    {
           int i;
           /*设置一个内存标记 */
           for (i = 0; i < CONFIG_NR_DRAM_BANKS; i++) {   
                  params->hdr.tag = ATAG_MEM;
                  params->hdr.size = tag_size (tag_mem32);
     
                  params->u.mem.start = bd->bi_dram[i].start;
                  params->u.mem.size = bd->bi_dram[i].size;
     
                  params = tag_next (params);
           }
    }

      setup_memory_tags函数设置了一个ATAG_MEM标记,该标记包含内存起始地址,内存大小这两个参数。

    (3)setup_end_tag函数

    static void setup_end_tag (bd_t *bd)
    {
           params->hdr.tag = ATAG_NONE;
           params->hdr.size = 0;
    }

      标记列表必须以标记ATAG_NONE结束,setup_end_tag函数设置了一个ATAG_NONE标记,表示标记列表的结束。

           U-Boot设置好标记列表后就要调用内核了。但调用内核前,CPU必须满足下面的条件:

      (1) CPU寄存器的设置

        Ø  r0=0

        Ø  r1=机器码

        Ø  r2=内核参数标记列表在RAM中的起始地址

      (2) CPU工作模式

        Ø  禁止IRQ与FIQ中断

        Ø  CPU为SVC模式

      (3) 使数据Cache与指令Cache失效

      do_bootm_linux中调用的cleanup_before_linux函数完成了禁止中断和使Cache失效的功能。

      cleanup_before_linux函数在cpu/arm920t/cpu.中定义:

    int cleanup_before_linux (void)
    {
           /*
            * this function is called just before we call linux
            * it prepares the processor for linux
            *
            * we turn off caches etc ...
            */
           disable_interrupts ();         /* 禁止FIQ/IRQ中断 */
     
           /* turn off I/D-cache */
           icache_disable();               /* 使指令Cache失效 */
           dcache_disable();              /* 使数据Cache失效 */
           /* flush I/D-cache */
           cache_flush();                    /* 刷新Cache */
     
           return 0;
    }

       由于U-Boot启动以来就一直工作在SVC模式,因此CPU的工作模式就无需设置了。

            do_bootm_linux中:

        void       (*theKernel)(int zero, int arch, uint params);
        … …
        theKernel = (void (*)(int, int, uint))images->ep;
        … …
        theKernel (0, machid, bd->bi_boot_params);    

      代码将内核的入口地址“images->ep”强制类型转换为函数指针。

      根据ATPCS规则,函数的参数个数不超过4个时,使用r0~r3这4个寄存器来传递参数。

      因此第128行的函数调用则会将0放入r0,机器码machid放入r1,内核参数地址bd->bi_boot_params放入r2,从而完成了寄存器的设置,最后转到内核的入口地址。

           到这里,U-Boot的工作就结束了,系统跳转到Linux内核代码执行。

    4. U-Boot添加命令的方法及U-Boot命令执行过程

      下面以添加menu命令(启动菜单)为例讲解U-Boot添加命令的方法。

    (1) 建立common/cmd_menu.c

           习惯上通用命令源代码放在common目录下,与开发板专有命令源代码则放在board/<board_dir>目录下,并且习惯以“cmd_<命令名>.c”为文件名。

    (2)定义“menu”命令

           在cmd_menu.c中使用如下的代码定义“menu”命令:

    _BOOT_CMD(
           menu,    3,    0,    do_menu,
           "menu - display a menu, to select the items to do something\n",
           " - display a menu, to select the items to do something"
    );

           其中U_BOOT_CMD命令格式如下:

    U_BOOT_CMD(name,maxargs,rep,cmd,usage,help)

      各个参数的意义如下:

           name:命令名,非字符串,但在U_BOOT_CMD中用“#”符号转化为字符串

           maxargs:命令的最大参数个数

           rep:是否自动重复(按Enter键是否会重复执行)

           cmd:该命令对应的响应函数

           usage:简短的使用说明(字符串)

           help:较详细的使用说明(字符串)

           在内存中保存命令的help字段会占用一定的内存,通过配置U-Boot可以选择是否保存help字段。

      若在include/configs/ti8168_dvr.h中定义了CONFIG_SYS_LONGHELP宏,则在U-Boot中使用help命令查看某个命令的帮助信息时将显示usage和help字段的内容,否则就只显示usage字段的内容。

           U_BOOT_CMD宏在include/command.h中定义:

    #define U_BOOT_CMD(name,maxargs,rep,cmd,usage,help) \
        cmd_tbl_t __u_boot_cmd_##name Struct_Section = {#name, maxargs, rep, cmd, usage, help}

           “##”与“#”都是预编译操作符,“##”有字符串连接的功能,“#”表示后面紧接着的是一个字符串。

           其中的cmd_tbl_t在include/command.h中定义如下:

    struct cmd_tbl_s {
           char       *name;          /* 命令名 */
           int          maxargs;       /* 最大参数个数 */
           int          repeatable;    /* 是否自动重复 */
           int          (*cmd)(struct cmd_tbl_s *, int, int, char *[]);  /*  响应函数 */
           char       *usage;         /* 简短的帮助信息 */
    #ifdef    CONFIG_SYS_LONGHELP
           char              *help;           /*  较详细的帮助信息 */
    #endif
    #ifdef CONFIG_AUTO_COMPLETE
           /* 自动补全参数 */
           int          (*complete)(int argc, char *argv[], char last_char, int maxv, char *cmdv[]);
    #endif
    };
    
    typedef struct cmd_tbl_s  cmd_tbl_t;

      一个cmd_tbl_t结构体变量包含了调用一条命令的所需要的信息。

           其中Struct_Section在include/command.h中定义如下:

     #define Struct_Section  __attribute__ ((unused,section (".u_boot_cmd")))

      凡是带有__attribute__ ((unused,section (".u_boot_cmd"))属性声明的变量都将被存放在".u_boot_cmd"段中,并且即使该变量没有在代码中显式的使用编译器也不产生警告信息。

           在U-Boot连接脚本u-boot.lds中定义了".u_boot_cmd"段:

    . = .;
    __u_boot_cmd_start = .;          /*将 __u_boot_cmd_start指定为当前地址 */
    .u_boot_cmd : { *(.u_boot_cmd) }
    __u_boot_cmd_end = .;           /*  将__u_boot_cmd_end指定为当前地址  */

       这表明带有“.u_boot_cmd”声明的函数或变量将存储在“u_boot_cmd”段。

      这样只要将U-Boot所有命令对应的cmd_tbl_t变量加上“.u_boot_cmd”声明,编译器就会自动将其放在“u_boot_cmd”段,查找cmd_tbl_t变量时只要在__u_boot_cmd_start与__u_boot_cmd_end之间查找就可以了。

           因此“menu”命令的定义经过宏展开后如下:

      cmd_tbl_t __u_boot_cmd_menu __attribute__ ((unused,section (".u_boot_cmd"))) = {menu, 3, 0, do_menu, "menu - display a menu, to select the items to do something\n", " - display a menu, to select the items to do something"}

      实质上就是用U_BOOT_CMD宏定义的信息构造了一个cmd_tbl_t类型的结构体。编译器将该结构体放在“u_boot_cmd”段,执行命令时就可以在“u_boot_cmd”段查找到对应的cmd_tbl_t类型结构体。

    (3)实现命令的函数

           在cmd_menu.c中添加“menu”命令的响应函数的实现。具体的实现代码略:

    int do_menu (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
    {
           /* 实现代码略 */
    }

    (4)将common/cmd_menu.c编译进u-boot.bin

       在common/Makefile中加入如下代码:

    COBJS-$(CONFIG_BOOT_MENU) += cmd_menu.o

      在include/configs/ti8168_dvr.h加入如代码:

    #define CONFIG_BOOT_MENU 1

           重新编译下载U-Boot就可以使用menu命令了

    (5)menu命令执行的过程

      在U-Boot中输入“menu”命令执行时,U-Boot接收输入的字符串“menu”,传递给run_command函数。

      run_command函数调用common/command.c中实现的find_cmd函数在__u_boot_cmd_start与__u_boot_cmd_end间查找命令,并返回menu命令的cmd_tbl_t结构。

      然后run_command函数使用返回的cmd_tbl_t结构中的函数指针调用menu命令的响应函数do_menu,从而完成了命令的执行。

    (6)例子:USB下载,命令很简单。

    #include <common.h>
    #include <command.h>
    extern char console_buffer[]; extern int readline (const char *const prompt); extern char awaitkey(unsigned long delay, int* error_p); extern void download_nkbin_to_flash(void); /** * Parses a string into a number. The number stored at ptr is * potentially suffixed with K (for kilobytes, or 1024 bytes), * M (for megabytes, or 1048576 bytes), or G (for gigabytes, or * 1073741824). If the number is suffixed with K, M, or G, then * the return value is the number multiplied by one kilobyte, one * megabyte, or one gigabyte, respectively. * * @param ptr where parse begins * @param retptr output pointer to next char after parse completes (output) * @return resulting unsigned int */ static unsigned long memsize_parse2 (const char *const ptr, const char **retptr) { unsigned long ret = simple_strtoul(ptr, (char **)retptr, 0); int sixteen = 1; switch (**retptr) { case 'G': case 'g': ret <<= 10; case 'M': case 'm': ret <<= 10; case 'K': case 'k': ret <<= 10; (*retptr)++; sixteen = 0; default: break; } if (sixteen) return simple_strtoul(ptr, NULL, 16); return ret; } void param_menu_usage() { printf("\r\n##### Parameter Menu #####\r\n"); printf("[v] View the parameters\r\n"); printf("[s] Set parameter \r\n"); printf("[d] Delete parameter \r\n"); printf("[w] Write the parameters to flash memeory \r\n"); printf("[q] Quit \r\n"); printf("Enter your selection: "); } void param_menu_shell(void) { char c; char cmd_buf[256]; char name_buf[20]; char val_buf[256]; while (1) { param_menu_usage(); c = awaitkey(-1, NULL); printf("%c\n", c); switch (c) { case 'v': { strcpy(cmd_buf, "printenv "); printf("Name(enter to view all paramters): "); readline(NULL); strcat(cmd_buf, console_buffer); run_command(cmd_buf, 0); break; } case 's': { sprintf(cmd_buf, "setenv "); printf("Name: "); readline(NULL); strcat(cmd_buf, console_buffer); printf("Value: "); readline(NULL); strcat(cmd_buf, " "); strcat(cmd_buf, console_buffer); run_command(cmd_buf, 0); break; } case 'd': { sprintf(cmd_buf, "setenv "); printf("Name: "); readline(NULL); strcat(cmd_buf, console_buffer); run_command(cmd_buf, 0); break; } case 'w': { sprintf(cmd_buf, "saveenv"); run_command(cmd_buf, 0); break; } case 'q': { return; break; } } } } void main_menu_usage(void) { printf("\r\n##### 100ask Bootloader for OpenJTAG #####\r\n"); printf("[n] Download u-boot to Nand Flash\r\n"); if (bBootFrmNORFlash()) printf("[o] Download u-boot to Nor Flash\r\n"); printf("[k] Download Linux kernel uImage\r\n"); printf("[j] Download root_jffs2 image\r\n"); // printf("[c] Download root_cramfs image\r\n"); printf("[y] Download root_yaffs image\r\n"); printf("[d] Download to SDRAM & Run\r\n"); printf("[z] Download zImage into RAM\r\n"); printf("[g] Boot linux from RAM\r\n"); printf("[f] Format the Nand Flash\r\n"); printf("[s] Set the boot parameters\r\n"); printf("[b] Boot the system\r\n"); printf("[r] Reboot u-boot\r\n"); printf("[q] Quit from menu\r\n"); printf("Enter your selection: "); } void menu_shell(void) { char c; char cmd_buf[200]; char *p = NULL; unsigned long size; unsigned long offset; struct mtd_info *mtd = &nand_info[nand_curr_device]; while (1) { main_menu_usage(); c = awaitkey(-1, NULL); printf("%c\n", c); switch (c) { case 'n': { strcpy(cmd_buf, "usbslave 1 0x30000000; nand erase bootloader; nand write.jffs2 0x30000000 bootloader $(filesize)"); run_command(cmd_buf, 0); break; } case 'o': { if (bBootFrmNORFlash()) { strcpy(cmd_buf, "usbslave 1 0x30000000; protect off all; erase 0 +$(filesize); cp.b 0x30000000 0 $(filesize)"); run_command(cmd_buf, 0); } break; } case 'k': { strcpy(cmd_buf, "usbslave 1 0x30000000; nand erase kernel; nand write.jffs2 0x30000000 kernel $(filesize)"); run_command(cmd_buf, 0); break; } case 'j': { strcpy(cmd_buf, "usbslave 1 0x30000000; nand erase root; nand write.jffs2 0x30000000 root $(filesize)"); run_command(cmd_buf, 0); break; } #if 0 case 'c': { strcpy(cmd_buf, "usbslave 1 0x30000000; nand erase root; nand write.jffs2 0x30000000 root $(filesize)"); run_command(cmd_buf, 0); break; } #endif case 'y': { strcpy(cmd_buf, "usbslave 1 0x30000000; nand erase root; nand write.yaffs 0x30000000 root $(filesize)"); run_command(cmd_buf, 0); break; } case 'd': { extern volatile U32 downloadAddress; extern int download_run; download_run = 1; strcpy(cmd_buf, "usbslave 1"); run_command(cmd_buf, 0); download_run = 0; sprintf(cmd_buf, "go %x", downloadAddress); run_command(cmd_buf, 0); break; } case 'z': { strcpy(cmd_buf, "usbslave 1 0x30008000"); run_command(cmd_buf, 0); break; } case 'g': { extern void do_bootm_rawLinux (ulong addr); do_bootm_rawLinux(0x30008000); } case 'b': { printf("Booting Linux ...\n"); strcpy(cmd_buf, "nand read.jffs2 0x30007FC0 kernel; bootm 0x30007FC0"); run_command(cmd_buf, 0); break; } case 'f': { strcpy(cmd_buf, "nand erase "); printf("Start address: "); readline(NULL); strcat(cmd_buf, console_buffer); printf("Size(eg. 4000000, 0x4000000, 64m and so on): "); readline(NULL); p = console_buffer; size = memsize_parse2(p, &p); sprintf(console_buffer, " %x", size); strcat(cmd_buf, console_buffer); run_command(cmd_buf, 0); break; } case 's': { param_menu_shell(); break; } case 'r': { strcpy(cmd_buf, "reset"); run_command(cmd_buf, 0); break; } case 'q': { return; break; } } } } int do_menu (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { menu_shell(); return 0; } U_BOOT_CMD(     menu, 3, 0, do_menu,     "menu - display a menu, to select the items to do something\n", " - display a menu, to select the items to do something" );

     TFTP下载

    #include <common.h>
    #include <command.h>
    
    /**功能:等待键盘输入***/
    static char awaitkey(unsigned long delay, int* error_p)
    {
         int i;
         char c;
    if (delay == -1) { while (1) { if (tstc()) /* we got a key press */ return getc(); } } else { for (i = 0; i < delay; i++) {     if (tstc()) /* we got a key press */       return getc(); udelay (10*1000); } }
    if (error_p) *error_p = -1;
    return 0; } /*****提示符,功能说明****/ void main_menu_usage(void) { printf("\r\n######## Hotips TFTP DownLoad for SMDK2440 ########\r\n"); printf("\r\n"); printf("[1] 下载 u-boot.bin 写入 Nand Flash\r\n"); printf("[2] 下载 Linux(uImage) 内核镜像写入 Nand Flash\r\n"); printf("[3] 下载 yaffs2(fs.yaffs) 文件系统镜像写入 Nand Flash\r\n"); printf("[4] 下载 Linux(uImage) 内核镜像到内存并运行\r\n"); printf("[5] 重启设备\r\n"); printf("[q] 退出菜单\r\n"); printf("\r\n"); printf("输入选择: "); } /***do_menu()的调用函数,命令的具体实现***/ void menu_shell(void) { char c; char cmd_buf[200]; while (1) { main_menu_usage(); c = awaitkey(-1, NULL); printf("%c\n", c); switch (c) { case '1': { strcpy(cmd_buf, "tftp 0x32000000 u-boot.bin; nand erase 0x0 0x60000; nand write 0x32000000 0x0 0x60000"); run_command(cmd_buf, 0); break; } case '2': { strcpy(cmd_buf, "tftp 0x32000000 uImage; nand erase 0x80000 0x200000; nand write 0x32000000 0x80000 0x200000"); run_command(cmd_buf, 0); break; } case '3': { strcpy(cmd_buf, "tftp 0x32000000 fs.yaffs; nand erase 0x280000; nand write.yaffs2 0x32000000 0x280000 $(filesize)"); run_command(cmd_buf, 0); break; } case '4': { strcpy(cmd_buf, "tftp 0x32000000 uImage; bootm 0x32000000"); run_command(cmd_buf, 0); break; } case '5': { strcpy(cmd_buf, "reset"); run_command(cmd_buf, 0); break; } case 'q': { return; break; } } } } int do_menu (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[]) { menu_shell(); return 0; } U_BOOT_CMD( menu, 1, 0, do_menu, "Download Menu", "U-boot Download Menu by Hotips\n" );

     对比两种下载方式我们清楚命令的添加和执行方式了

    嵌入式QQ交流群:127085086
  • 相关阅读:
    8、【C++基础】内存管理
    7、【C++基础】内联函数、友元函数
    5、【C++基础】强制类型转换
    4、【C++基础】引用和指针
    3、【C++基础】基本的输入输出
    2、【C++基础】命名空间
    1、【C++基础】bool数据类型
    13、【C语言基础】预处理器、头文件
    6、git常用命令总结
    5、git标签管理
  • 原文地址:https://www.cnblogs.com/cslunatic/p/2992717.html
Copyright © 2011-2022 走看看