啥都吃的豆芽 · 3月26日 · 黑龙江

完全理解ARM启动流程:Uboot-Kernel(下)

接(上)

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的工作模式就无需设置了。

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

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

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

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

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);    




int do_bootm_linux(int flag, int argc, char * const argv[],
           bootm_headers_t *images)
{
    /* No need for those on ARM */
    if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE)
        return -1;

        // 当flag为BOOTM_STATE_OS_PREP,则说明只需要做准备动作boot_prep_linux
    if (flag & BOOTM_STATE_OS_PREP) {
        boot_prep_linux(images);
        return 0;
    }

        // 当flag为BOOTM_STATE_OS_GO ,则说明只需要做跳转动作 
        if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) {
        boot_jump_linux(images, flag);
        return 0;
    }

    boot_prep_linux(images); // 以全局变量bootm_headers_t images为参数传递给boot_prep_linux
    boot_jump_linux(images, flag);// 以全局变量bootm_headers_t images为参数传递给boot_jump_linux
    return 0;
}

boot_prep_linux用于实现跳转到linux前的准备动作。 而boot_jump_linux用于跳转到linux中。 都是以全局变量bootm_headers_t images为参数,这样就可以直接获取到前面步骤中得到的kernel镜像、ramdisk以及fdt的信息了。

  • boot_prep_linux 首先要说明一下LMB的概念。LMB是指logical memory blocks,主要是用于表示内存的保留区域,主要有fdt的区域,ramdisk的区域等等。 boot_prep_linux主要的目的是修正LMB,并把LMB填入到fdt中。

实现如下:

static void boot_prep_linux(bootm_headers_t *images)
{
    char *commandline = getenv("bootargs");

    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {
#ifdef CONFIG_OF_LIBFDT
        debug("using: FDT\n");
        if (image_setup_linux(images)) {
            printf("FDT creation failed! hanging...");
            hang();
        }
#endif
    }
  • boot_jump_linux 以arm为例: arch/arm/lib/bootm.c
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
    unsigned long machid = gd->bd->bi_arch_number; // 从bd中获取machine-id,machine-id在uboot启动流程的文章中有说明过了
    char *s;
    void (*kernel_entry)(int zero, int arch, uint params); // kernel入口函数,也就是kernel的入口地址,对应kernel的_start地址。
    unsigned long r2;
    int fake = (flag & BOOTM_STATE_OS_FAKE_GO); // 伪跳转,并不真正地跳转到kernel中

    kernel_entry = (void (*)(int, int, uint))images->ep; 
         // 将kernel_entry设置为images中的ep(kernel的入口地址),后面直接执行kernel_entry也就跳转到了kernel中了
        // 这里要注意这种跳转的方法

    debug("## Transferring control to Linux (at address %08lx)" \
        "...\n", (ulong) kernel_entry);
    bootstage_mark(BOOTSTAGE_ID_RUN_OS);
    announce_and_cleanup(fake);

        // 把images->ft_addr(fdt的地址)放在r2寄存器中
    if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len)
        r2 = (unsigned long)images->ft_addr;
    else
        r2 = gd->bd->bi_boot_params;

    if (!fake) {
            kernel_entry(0, machid, r2);
        // 这里通过调用kernel_entry,就跳转到了images->ep中了,也就是跳转到kernel中了,具体则是kernel的_start符号的地址。
        // 参数0则传入到r0寄存器中,参数machid传入到r1寄存器中,把images->ft_addr(fdt的地址)放在r2寄存器中
        // 满足了kernel启动的硬件要求
    }
}

Linux内核启动过程

Linux 的启动过程可以分为两部分:

第一阶段:引导阶段通常使用汇编语言编写:

  • 首先检查内核是否支持当前架构的处理器,
  • 然后检查是否支持当前开发板。通过检查后,就为调用下一阶段的start_kernel 函数作准备了。这主要分如下两个步骤。
  • 连接内核时使用的是虚拟地址,所以要设置页表、使能MMU
  • 做一些调用C函数 start_kernel之前的常规工作,包括复制数据段、清除BSS段、调用start_kernel函数。

第二阶段第二阶段的关键代码主要使用C语言编写。

它进行内核初始化的全部工作, 最后调用rest_init函数启动 init过程,创建系统第一个进程: init进程。

在第二阶段,仍有部分架构/开发板相关的代码。

image.png
image.png

在内核启动时执行自解压完成后,会跳转到解压后的地址处运行,在我的环境中就是地址0x00008000处,然后内核启动并执行初始化。

首先给出你内核启动的汇编部分的总流程如下:

image.png

内核启动程序的入口:参见arch/arm/kernel/vmlinux.lds(由arch/arm/kernel/vmlinux.lds.S生成)。

arch/arm/kernel/vmlinux.lds:

ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
......
. = 0xC0000000 + 0x00008000;
.head.text : {
_text = .;
*(.head.text)
}
.text : { /* Real text segment */
_stext = .; /* Text and read-only data */

此处的TEXT_OFFSET表示内核起始地址相对于RAM地址的偏移值,定义在arch/arm/Makefile中,值为0x00008000:

textofs-y := 0x00008000
......
# The byte offset of the kernel image in RAM from the start of RAM.
TEXT_OFFSET := $(textofs-y)

PAGE_OFFSET表示内核虚拟地址空间的起始地址,定义在arch/arm/include/asm/memory.h中:

#ifdef CONFIG_MMU
 
/*
* PAGE_OFFSET - the virtual address of the start of the kernel image
* TASK_SIZE - the maximum size of a user space task.
* TASK_UNMAPPED_BASE - the lower boundary of the mmap VM area
*/
#define PAGE_OFFSET UL(CONFIG_PAGE_OFFSET)

CONFIG_PAGE_OFFSET定义在arch/arm/Kconfig中,采用默认值0xC0000000。

config PAGE_OFFSET
hex
default 0x40000000 if VMSPLIT_1G
default 0x80000000 if VMSPLIT_2G
default 0xC0000000

所以,可以看出内核的链接地址采用的是虚拟地址,地址值为0xC0008000。

内核启动程序的入口在linux/arch/arm/kernel/head.S中,head.S中定义了几个比较重要的变量,在看分析程序前先来看一下:

/*
* swapper_pg_dir is the virtual address of the initial page table.
* We place the page tables 16K below KERNEL_RAM_VADDR. Therefore, we must
* make sure that KERNEL_RAM_VADDR is correctly set. Currently, we expect
* the least significant 16 bits to be 0x8000, but we could probably
* relax this restriction to KERNEL_RAM_VADDR >= PAGE_OFFSET + 0x4000.
*/
#define KERNEL_RAM_VADDR (PAGE_OFFSET + TEXT_OFFSET)
#if (KERNEL_RAM_VADDR & 0xffff) != 0x8000
#error KERNEL_RAM_VADDR must start at 0xXXXX8000
#endif
 
#ifdef CONFIG_ARM_LPAE
/* LPAE requires an additional page for the PGD */
#define PG_DIR_SIZE 0x5000
#define PMD_ORDER 3
#else
#define PG_DIR_SIZE 0x4000
#define PMD_ORDER 2
#endif
 
.globl swapper_pg_dir
.equ swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE
 
.macro pgtbl, rd, phys
add \rd, \phys, #TEXT_OFFSET - PG_DIR_SIZE
.endm

其中KERNEL_RAM_VADDR表示内核启动地址的虚拟地址,即前面看到的链接地址0xC0008000,同时内核要求这个地址的第16位必须是0x8000。

然后由于没有配置ARM LPAE,则采用一级映射结构,页表的大小为16KB,页大小为1MB。

最后swapper_pg_dir表示初始页表的起始地址,这个值等于内核起始虚拟地址-页表大小=0xC0004000(内核起始地址下16KB空间存放页表)。虚拟地址空间如下图:

image.png

需要说明一下:在我的环境中,内核在自解压阶段被解压到了0x00008000地址处,由于内核入口链接地址采用的是虚拟地址0xC0008000,这两个地址并不相同;并且此时MMU并没有被使能,所以无法进行虚拟地址到物理地址的转换,程序开始执行后在打开MMU前的将使用位置无关码。

在知道了内核的入口位置后,来看一下此时的设备和寄存器的状态:

/*
* Kernel startup entry point.
* ---------------------------
*
* This is normally called from the decompressor code. The requirements
* are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
* r1 = machine nr, r2 = atags or dtb pointer.
*
* This code is mostly position independent, so if you link the kernel at
* 0xc0008000, you call this at __pa(0xc0008000).
*
* See linux/arch/arm/tools/mach-types for the complete list of machine
* numbers for r1.
*
* We're trying to keep crap to a minimum; DO NOT add any machine specific
* crap here - that's what the boot loader (or in extreme, well justified
* circumstances, zImage) is for.
*/
.arm
 
__HEAD
ENTRY(stext)

注释中说明了,此时的MMU关闭、D-cache关闭、r0 = 0、r1 = 机器码、r2 = 启动参数atags或dtb的地址(我的环境中使用的是atags),同时内核支持的机器码被定义在了linux/arch/arm/tools/mach-types中。

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"
);

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

参考资料

作者:Hcoco
文章来源:TrustZone

推荐阅读

欢迎大家点赞留言,更多Arm技术文章动态请关注极术社区Arm技术博客专栏欢迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向。
推荐阅读
关注数
23583
内容数
1033
Arm相关的技术博客,提供最新Arm技术干货,欢迎关注
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息