uboot详解

news2024/11/26 4:37:54

uboot入口

在PC机上引导程序一般由BIOS开始执行,然后读取硬盘中位于MBR(Main Boot Record,主引导记录)中的Bootloader(例如LILO或GRUB),并进一步引导操作系统的启动。然而在嵌入式系统中通常没有像BIOS那样的固件程序,因此整个系统的加载启动就完全由bootloader来完成。它主要的功能是加载与引导内核映像 。
 

主引导记录(MBR,Master Boot Record)是计算机开机以后访问硬盘时所必须要读取的第一个扇区,位于硬盘的第0柱面第0磁道第一扇区,大小为512字节。

        主引导程序的任务就是查找并且加载处在硬盘分区上的次引导程序。通过分区表查找活动分区,并将处在活动分区的次引导加载程序读取到内存里面运行。

        MBR的BootLoader中安装有GRUB程序的一部分,因为BootLoader只有446Bytes,容量太小,所以只安装了GRUB中一个小的程序在里面,用于加载剩余的GRUB程序。
 

uboot的入口函数

同大多数的Bootloader一样,uboot的启动过程也分为BL1、BL2两个阶段,分别对应着SPL和Uboot。

  • SPL(BL1阶段):负责开发板的基础配置和设备初始化,并且搬运Uboot到内存中,由汇编代码和少量的C语言实现
  • Uboot(BL2阶段):主要负责初始化外部设备,引导Kernel启动,由纯C语言实现。
  1. u-boot.lds: uboot的连接脚本,决定uboot的入口,由uboot中的makefile编译生成,可以在编译(make)后,根据编译信息找到其生成的具体位置;
  2. 连接脚本的宏定义在linkage.h,位于include\linux中;
  3. 根据1中的程序入口找到程序入口,根据u-boot.lds可知(arch/arm/cpu/armv7/start.o(,text*)),即该目录侠的start.s文件;
  4. 进入main,位于arch/arm/lib中俄crt0.s,在crt0.s中可以看到调用DeC函数board_init_f和board_init_r接口。
  • U-Boot启动完成后,最终进入到main_loop()循环中。若在bootdelay倒计时为0以前,U-Boot控制台有输入,则进入命令解析-执行的循环;若控制台无输入,U-Boot将启动内核。
  • U-Boot启动内核可归结为如下四个步骤:  

                           1)将内核搬移至DDR中;数据结构

                           2)校验内核格式、CRC;ide

                           3)准备传参;函数                         

                         4)跳转执行内核。

我们熟悉的u-boot启动的时候执行的一段程序,这段程序一般存放在Nand flash中或Nor flash中。我们所说的Nand flash启动或Nor flash启动主要是涉及到一段搬移代码。这段搬移代码的功能是u-boot自己把自己搬移到内存中执行。如下是Nor flash启动中的这段搬移代码(这里以s3c2410为例)

  relocate:                        /* relocate U-Boot to RAM */

  adr r0, _start        /* r0 <- current position of code */

  ldr r1, _TEXT_BASE        /* test if we run from flash or RAM */

  cmp r0, r1                        /* don't reloc during debug */

  beq stack_setup

  ldr r2, _armboot_start

  ldr r3, _bss_start

  sub r2, r3, r2                /* r2 <- size of armboot */

  add r2, r0, r2                /* r2 <- source end address */

  copy_loop:

  ldmia r0!, {r3-r10}                /* copy from source address [r0] */

  stmia r1!, {r3-r10}                /* copy to target address [r1] */

  cmp r0, r2                /* until source end addreee [r2] */

  ble copy_loop

  #endif        /* CONFIG_SKIP_RELOCATE_UBOOT */

  #endif

上面这段代码就是把u-boot搬移到内存。而不同的启动方式区别也就在这段代码上,如果我们这里是Nand flash启动的话我们也需要写相同功能的代码,不同的是对于Nand的操作和Nor的操作是完全不同的,选择Nor flash启动是将Nor flash映射到片选0上也就是0x0地址而选择Nand flash启动则是将CPU的片内RAM(4K)映射到0地址,通过Nand flash控制器操作Nand flash。我们这里讨论如何实现Nand  Nor双启动。下面我们看看这两种启动的映射关系。

uboot启动linux的过程 

linux内核镜像常见到的有两种形式,zImage和uImage。这两种文件的格式稍有差别,所以启动这两种格式的内核镜像也会有所不同。目前,uboot只支持启动uImage类型的镜像,对zImage还不支持(但是可以移植,TQ2440就是这样做的)。

uImage和zImage

 zImage是用命令“#make zImage”生成的,我截取了生成信息最后部分的内容如下:

  OBJCOPY arch/arm/boot/Image
  Kernel: arch/arm/boot/Image is ready
  GZIP    arch/arm/boot/compressed/piggy.gz
  AS      arch/arm/boot/compressed/piggy.o
  LD      arch/arm/boot/compressed/vmlinux
  OBJCOPY arch/arm/boot/zImage
  Kernel: arch/arm/boot/zImage is ready

从中可以看到,zImage是经过gzip压缩过的,所以在内核启动过程(不属于u-boot控制范围,在内核镜像的头部嵌有解压函数)中必然会对应一个解压过程。

uImage是u-boot专用的内核镜像,可用命令“#make uImage”生成。生成信息最后部分的内容如下:

  Kernel: arch/arm/boot/Image is ready
  Kernel: arch/arm/boot/zImage is ready
  UIMAGE  arch/arm/boot/uImage
Image Name:   Linux-2.6.30.4-EmbedSky
Created:      Thu Mar 20 19:53:32 2014
Image Type:   ARM Linux Kernel Image (uncompressed)
Data Size:    2314736 Bytes = 2260.48 kB = 2.21 MB
Load Address: 0x30008000
Entry Point:  0x30008000
  Image arch/arm/boot/uImage is ready

 事实上,uImage是调用mkimage(uboot制作的工具)这个工具生成的。

root@daneiqi:/opt/EmbedSky#  mkimage -n 'linux-2.6.30' -A arm -O linux -T kernel -C none -a 0x30008000 -e 0x30008000 -d zImage uImage
Image Name:   linux-2.6.30
Created:      Thu Mar 20 19:59:36 2014
Image Type:   ARM Linux Kernel Image (uncompressed)
Data Size:    2314736 Bytes = 2260.48 kB = 2.21 MB
Load Address: 0x30008000
Entry Point:  0x30008000

在原来的可执行映象文件zImage的前面加上一个0x40字节的头, 记录参数所指定的信息,这样uboot才能识别这个映象是针对哪个CPU体系结构的,哪个OS的, 哪种类型,加载内存中的哪个位置,入口点在内存的那个位置以及映象名是什么。 

头部的结构是在include/image.h中定义的,如下所示:

typedef struct image_header {
       uint32_t  ih_magic;       /* Image Header Magic Number   */
       uint32_t  ih_hcrc;   /* Image Header CRC Checksum  */
       uint32_t  ih_time;  /* Image Creation Timestamp       */
       uint32_t  ih_size;   /* Image Data Size        */
       uint32_t  ih_load;   /* Data    Load  Address            */
       uint32_t  ih_ep;            /* Entry Point Address          */
       uint32_t  ih_dcrc;   /* Image Data CRC Checksum      */
       uint8_t           ih_os;             /* Operating System             */
       uint8_t           ih_arch;   /* CPU architecture              */
       uint8_t           ih_type;   /* Image Type               */
       uint8_t           ih_comp; /* Compression Type            */
       uint8_t           ih_name[IH_NMLEN];  /* Image Name             */
} image_header_t;

 打开上边生成的uImage文件,可以看到对应的数据。 

(1)ih_magic    0x27051956  magic值,我觉得是uImage的头部开始值,根据这个值,判断是否是uImage

(2)ih_crc    0x19dbf9c6    头部校验

(3)ih_time   0x74295319   创建时间

(4)ih_size   0x002351f0     镜像大小为2260.48KB

(5)ih_load  0x30008000 内核加载地址

(6)ih_ep        0x30008000 内核运行地址,“theKernel”指向该地址,说明这里藏着进入第一个函数--解压

(7)ih_dcrc      0x38fc654e    内核校验

(8)ih_os        0x05       #define IH_OS_LINUX  5 /* Linux */

(9)ih_arch     0x02     #define IH_CPU_ARM  2 /* ARM  */

(10)ih_type   0x02         #define IH_TYPE_KERNEL  2 /* OS Kernel Image  */

(11)ih_comp  0x00        #define IH_COMP_NONE  0 /*  No  Compression Used */

(12)ih_name         Linux_2.6.30.4-EmbedSky

u-boot内核启动流程概述

 前文已经说明u-boot只支持uImage,步骤三、四都是针对uImage的。

另外声明一点,步骤三四的测试uboot代码是韦东山视频提供的。

1、从NandFlash中读取内核到RAM中

2、在RAM中,给内核进行重定位

3、给内核传递参数

4、启动内核

u-boot启动内核细节分析

启动命令

从环境变量中查看启动命令:

从NandFlash中读取内核到RAM中 

nand read.jffs2 0x30007FC0 kernel

此命令会激活(common/cmd_nand.c)中的do_nand函数,从而将nandflash上的kernel分区加载到0x30007fc0位置处。

OpenJTAG> mtd

device nand0 <nandflash0>, # parts = 4
 #: name                        size            offset          mask_flags
 0: bootloader          0x00040000      0x00000000      0
 1: params              0x00020000      0x00040000      0
 2: kernel              0x00200000      0x00060000      0
 3: root                0x0fda0000      0x00260000      0

active partition: nand0,0 - (bootloader) 0x00040000 @ 0x00000000

defaults:
mtdids  : nand0=nandflash0
mtdparts: mtdparts=nandflash0:256k@0(bootloader),128k(params),2m(kernel),-(root)

从分区表中,可以看出kernel分区的起始地址是0x60000,大小是0x200000(2M),这条命令实际上等效于

nand read.jffs2 0x30007FC0 0x60000 0x200000

也可以使用命令

nand read 0x30007FC0 0x60000 0x200000

nand read.jffs2可以自动页对齐,所以大小可以是非页整的;如果使用nand read的大小必须是页对齐的。

读取uImage头部 

bootm 0x30007fc0

此命令会激活(common/cmd_bootm.c)中的do_bootm函数,从而开始执行

2、在RAM中,给内核进行重定位
3、给内核传递参数
4、启动内核

image_header_t header;  定义一个全局变量header,是读取头部的缓冲区

addr = simple_strtoul(argv[1], NULL, 16);  定位头部地址,将字符串“0x30007fc0”转化为整型

printf ("## Booting image at %08lx ...\n", addr); 显示从哪儿启动

memmove (&header, (char *)addr, sizeof(image_header_t)); 读取头部到header变量中

判断当前的内存区是否是uImage的开始位置

 if (ntohl(hdr->ih_magic) != IH_MAGIC) {
      {
  puts ("Bad Magic Number\n");
  SHOW_BOOT_PROGRESS (-1);
  return 1;
     }
 }

注意到:

#define IH_MAGIC 0x27051956 /* Image Magic Number  */(include/image.h)

校验头部

    data = (ulong)&header;
    len  = sizeof(image_header_t);

    checksum = ntohl(hdr->ih_hcrc);
    hdr->ih_hcrc = 0;

    if (crc32 (0, (uchar *)data, len) != checksum) {
        puts ("Bad Header Checksum\n");
        SHOW_BOOT_PROGRESS (-2);
        return 1;
    }

 打印头部信息

    /* for multi-file images we need the data part, too */
    print_image_hdr ((image_header_t *)addr);

核查内核数据

    data = addr + sizeof(image_header_t);
    len  = ntohl(hdr->ih_size);

    if (verify) {
        puts ("   Verifying Checksum ... ");
        if (crc32 (0, (uchar *)data, len) != ntohl(hdr->ih_dcrc)) {
            printf ("Bad Data CRC\n");
            SHOW_BOOT_PROGRESS (-3);
            return 1;
        }
        puts ("OK\n");
    }
    SHOW_BOOT_PROGRESS (4);

注意到data已经跳过了uImage的头部,指向了真正的内核首部,也即0x30008000。

核查架构、内核类型、压缩类型等信息,其中会涉及到重定位 

    len_ptr = (ulong *)data;

#if defined(__PPC__)
    if (hdr->ih_arch != IH_CPU_PPC)
#elif defined(__ARM__)
    if (hdr->ih_arch != IH_CPU_ARM)
#elif defined(__I386__)
    if (hdr->ih_arch != IH_CPU_I386)
#elif defined(__mips__)
    if (hdr->ih_arch != IH_CPU_MIPS)
#elif defined(__nios__)
    if (hdr->ih_arch != IH_CPU_NIOS)
#elif defined(__M68K__)
    if (hdr->ih_arch != IH_CPU_M68K)
#elif defined(__microblaze__)
    if (hdr->ih_arch != IH_CPU_MICROBLAZE)
#elif defined(__nios2__)
    if (hdr->ih_arch != IH_CPU_NIOS2)
#elif defined(__blackfin__)
    if (hdr->ih_arch != IH_CPU_BLACKFIN)
#elif defined(__avr32__)
    if (hdr->ih_arch != IH_CPU_AVR32)
#else
# error Unknown CPU type
#endif
    {
        printf ("Unsupported Architecture 0x%x\n", hdr->ih_arch);
        SHOW_BOOT_PROGRESS (-4);
        return 1;
    }
    SHOW_BOOT_PROGRESS (5);

    switch (hdr->ih_type) {
    case IH_TYPE_STANDALONE:
        name = "Standalone Application";
        /* A second argument overwrites the load address */
        if (argc > 2) {
            hdr->ih_load = htonl(simple_strtoul(argv[2], NULL, 16));
        }
        break;
    case IH_TYPE_KERNEL:
        name = "Kernel Image";
        break;
    case IH_TYPE_MULTI:
        name = "Multi-File Image";
        len  = ntohl(len_ptr[0]);
        /* OS kernel is always the first image */
        data += 8; /* kernel_len + terminator */
        for (i=1; len_ptr[i]; ++i)
            data += 4;
        break;
    default: printf ("Wrong Image Type for %s command\n", cmdtp->name);
        SHOW_BOOT_PROGRESS (-5);
        return 1;
    }
    SHOW_BOOT_PROGRESS (6);

    /*
     * We have reached the point of no return: we are going to
     * overwrite all exception vector code, so we cannot easily
     * recover from any failures any more...
     */

    iflag = disable_interrupts();

#ifdef CONFIG_AMIGAONEG3SE 
    /*
     * We've possible left the caches enabled during
     * bios emulation, so turn them off again
     */
    icache_disable();  
    invalidate_l1_instruction_cache();
    flush_data_cache();
    dcache_disable();
#endif

    switch (hdr->ih_comp) {
    case IH_COMP_NONE:
        if(ntohl(hdr->ih_load) == data) {
            printf ("   XIP %s ... ", name);
        } else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
            size_t l = len;
            void *to = (void *)ntohl(hdr->ih_load);
            void *from = (void *)data;

            printf ("   Loading %s ... ", name);

            while (l > 0) {
                size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
                WATCHDOG_RESET();
                memmove (to, from, tail);
                to += tail;
                from += tail;
                l -= tail;
            }
#else    /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
            memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif    /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
        }
        break;
    case IH_COMP_GZIP:
        printf ("   Uncompressing %s ... ", name);
        if (gunzip ((void *)ntohl(hdr->ih_load), unc_len,
                (uchar *)data, &len) != 0) {
            puts ("GUNZIP ERROR - must RESET board to recover\n");
            SHOW_BOOT_PROGRESS (-6);
            do_reset (cmdtp, flag, argc, argv);
        }
        break;
#ifdef CONFIG_BZIP2
    case IH_COMP_BZIP2:
        printf ("   Uncompressing %s ... ", name);
        /*
         * If we've got less than 4 MB of malloc() space,
         * use slower decompression algorithm which requires
         * at most 2300 KB of memory.
         */
        i = BZ2_bzBuffToBuffDecompress ((char*)ntohl(hdr->ih_load),
                        &unc_len, (char *)data, len,
                        CFG_MALLOC_LEN < (4096 * 1024), 0);
        if (i != BZ_OK) {
            printf ("BUNZIP2 ERROR %d - must RESET board to recover\n", i);
            SHOW_BOOT_PROGRESS (-6);
            udelay(100000);
            do_reset (cmdtp, flag, argc, argv);
        }
        break;
#endif /* CONFIG_BZIP2 */
    default:
        if (iflag)
            enable_interrupts();
        printf ("Unimplemented compression type %d\n", hdr->ih_comp);
        SHOW_BOOT_PROGRESS (-7);
        return 1;
    }
    puts ("OK\n");
    SHOW_BOOT_PROGRESS (7);

    switch (hdr->ih_type) {
    case IH_TYPE_STANDALONE:
        if (iflag)
            enable_interrupts();

        /* load (and uncompress), but don't start if "autostart"
         * is set to "no"
         */
        if (((s = getenv("autostart")) != NULL) && (strcmp(s,"no") == 0)) {
            char buf[32];
            sprintf(buf, "%lX", len);
            setenv("filesize", buf);
            return 0;
        }
        appl = (int (*)(int, char *[]))ntohl(hdr->ih_ep);
        (*appl)(argc-1, &argv[1]);
        return 0;
    case IH_TYPE_KERNEL:
    case IH_TYPE_MULTI:
        /* handled below */
        break;
    default:
        if (iflag)
            enable_interrupts();
        printf ("Can't boot image type %d\n", hdr->ih_type);
        SHOW_BOOT_PROGRESS (-8);
        return 1;
    }
    SHOW_BOOT_PROGRESS (8);

 在这部分代码中,有这么一部分关于压缩类型的:

    switch (hdr->ih_comp) {
    case IH_COMP_NONE:
        if(ntohl(hdr->ih_load) == data) {
            printf ("   XIP %s ... ", name);
        } else {
#if defined(CONFIG_HW_WATCHDOG) || defined(CONFIG_WATCHDOG)
            size_t l = len;
            void *to = (void *)ntohl(hdr->ih_load);
            void *from = (void *)data;

            printf ("   Loading %s ... ", name);

            while (l > 0) {
                size_t tail = (l > CHUNKSZ) ? CHUNKSZ : l;
                WATCHDOG_RESET();
                memmove (to, from, tail);
                to += tail;
                from += tail;
                l -= tail;
            }
#else    /* !(CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG) */
            memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
#endif    /* CONFIG_HW_WATCHDOG || CONFIG_WATCHDOG */
        }
        break;

可以看到,u-boot会判断当前去除uImage头部内核代码所处的位置(7步骤已经说明地址是data)是否与编译时安排的重定位位置(hdr->ih_load)一致。

  如果一致,就打印一句话。

  如果不一致,则需要调用 memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);进行内核的重定位,要知道它有2M多的大小,会花费一些时间。尽量使读取内核的时候,就读取到hdr->ih_load-64的位置上,这样就不必再搬运一次。

 根据操作系统类型,启动对应的操作系统

复制代码
    switch (hdr->ih_os) {
    default:            /* handled by (original) Linux case */
    case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
        fixup_silent_linux();
#endif
        do_bootm_linux  (cmdtp, flag, argc, argv,
                 addr, len_ptr, verify);
        break;
    case IH_OS_NETBSD:   

执行do_bootm_linux,继续启动linux系统

此函数在lib_arm/armlinux.c中

    void (*theKernel)(int zero, int arch, uint params);
    image_header_t *hdr = &header;
    bd_t *bd = gd->bd;

#ifdef CONFIG_CMDLINE_TAG
    char *commandline = getenv ("bootargs");
#endif

    theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

可见,已经将内核运行的首地址赋给了theKernel函数指针变量,将来可以利用这个变量调用进入内核的函数。

另外,在进入内核之前,要给内核传递参数。方法是将参数以一定的结构放在内存指定的位置上,将来内核从该地址读取数据即可。

命令行的启动参数存储在以bootargs命名的对象里,值为

bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0

告诉内核,启动后的根文件系统位于mtd的哪个区,初始进程,以及控制台.

判断是否是一个ramdisk或者multi镜像

   /*
     * Check if there is an initrd image
     */
    if (argc >= 3) {
        SHOW_BOOT_PROGRESS (9);

        addr = simple_strtoul (argv[2], NULL, 16);

        printf ("## Loading Ramdisk Image at %08lx ...\n", addr);

        /* Copy header so we can blank CRC field for re-calculation */
#ifdef CONFIG_HAS_DATAFLASH
        if (addr_dataflash (addr)) {
            read_dataflash (addr, sizeof (image_header_t),
                    (char *) &header);
        } else
#endif
            memcpy (&header, (char *) addr,
                sizeof (image_header_t));

        if (ntohl (hdr->ih_magic) != IH_MAGIC) {
            printf ("Bad Magic Number\n");
            SHOW_BOOT_PROGRESS (-10);
            do_reset (cmdtp, flag, argc, argv);
        }

        data = (ulong) & header;
        len = sizeof (image_header_t);

        checksum = ntohl (hdr->ih_hcrc);
        hdr->ih_hcrc = 0;

        if (crc32 (0, (unsigned char *) data, len) != checksum) {
            printf ("Bad Header Checksum\n");
            SHOW_BOOT_PROGRESS (-11);
            do_reset (cmdtp, flag, argc, argv);
        }

        SHOW_BOOT_PROGRESS (10);

        print_image_hdr (hdr);

        data = addr + sizeof (image_header_t);
        len = ntohl (hdr->ih_size);

#ifdef CONFIG_HAS_DATAFLASH
        if (addr_dataflash (addr)) {
            read_dataflash (data, len, (char *) CFG_LOAD_ADDR);
            data = CFG_LOAD_ADDR;
        }
#endif

        if (verify) {
            ulong csum = 0;

            printf ("   Verifying Checksum ... ");
            csum = crc32 (0, (unsigned char *) data, len);
            if (csum != ntohl (hdr->ih_dcrc)) {
                printf ("Bad Data CRC\n");
                SHOW_BOOT_PROGRESS (-12);
                do_reset (cmdtp, flag, argc, argv);
            }
            printf ("OK\n");
        }

        SHOW_BOOT_PROGRESS (11);

        if ((hdr->ih_os != IH_OS_LINUX) ||
            (hdr->ih_arch != IH_CPU_ARM) ||
            (hdr->ih_type != IH_TYPE_RAMDISK)) {
            printf ("No Linux ARM Ramdisk Image\n");
            SHOW_BOOT_PROGRESS (-13);
            do_reset (cmdtp, flag, argc, argv);
        }

#if defined(CONFIG_B2) || defined(CONFIG_EVB4510) || defined(CONFIG_ARMADILLO)
        /*
         *we need to copy the ramdisk to SRAM to let Linux boot
         */
        memmove ((void *) ntohl(hdr->ih_load), (uchar *)data, len);
        data = ntohl(hdr->ih_load);
#endif /* CONFIG_B2 || CONFIG_EVB4510 */

        /*
         * Now check if we have a multifile image
         */
    } else if ((hdr->ih_type == IH_TYPE_MULTI) && (len_ptr[1])) {
        ulong tail = ntohl (len_ptr[0]) % 4;
        int i;

        SHOW_BOOT_PROGRESS (13);

        /* skip kernel length and terminator */
        data = (ulong) (&len_ptr[2]);
        /* skip any additional image length fields */
        for (i = 1; len_ptr[i]; ++i)
            data += 4;
        /* add kernel length, and align */
        data += ntohl (len_ptr[0]);
        if (tail) {
            data += 4 - tail;
        }

        len = ntohl (len_ptr[1]);

    } else {
        /*
         * no initrd image
         */
        SHOW_BOOT_PROGRESS (14);

        len = data = 0;
    }

#ifdef    DEBUG
    if (!data) {
        printf ("No initrd\n");
    }
#endif

给内核传递参数

#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);
#ifdef CONFIG_SERIAL_TAG
    setup_serial_tag (&params);
#endif
#ifdef CONFIG_REVISION_TAG
    setup_revision_tag (&params);
#endif
#ifdef CONFIG_SETUP_MEMORY_TAGS
    setup_memory_tags (bd);
#endif
#ifdef CONFIG_CMDLINE_TAG
    setup_commandline_tag (bd, commandline);
#endif
#ifdef CONFIG_INITRD_TAG
    if (initrd_start && initrd_end)
        setup_initrd_tag (bd, initrd_start, initrd_end);
#endif
#if defined (CONFIG_VFD) || defined (CONFIG_LCD)
    setup_videolfb_tag ((gd_t *) gd);
#endif
    setup_end_tag (bd);
#endif

 比较重要的函数有:

   setup_start_tag (bd);

  setup_memory_tags (bd);

  setup_commandline_tag (bd, commandline);

  setup_end_tag (bd);

  其中 bd->bi_boot_params(参考uboot全局变量),bi_boot_params=>>0x30000100,启动参数存放的位置。

启动内核

    printf ("\nStarting kernel ...\n\n");
  theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

把机器码以及启动参数存放的位置都告诉给内核。

启动过程展示

u-boot启动zImage

直接启动zImage

既然,zImage是uImage去除头部的部分,那么可以从0x30008000直接启动zImage,我们用go命令去执行。

可见,内核的第一个函数果然是解压函数。但是程序卡到图片最后的位置,不能继续执行。

原因是由于没有给内核传递启动参数,也就是说在执行函数theKernel之前,没有做好准备

void (*theKernel)(int zero, int arch, uint params);

移植u-boot支持启动zImage

 再来看一下启动大纲:

1、从NandFlash中读取内核到RAM中

2、在RAM中,给内核进行重定位

3、给内核传递参数

4、启动内核

可以直接从nandflash中将内核zImage读取到内存0x30008000位置处,然后在0x30000100位置处传递参数,也就是调用函数:

setup_start_tag (bd);
setup_memory_tags (bd);
setup_commandline_tag (bd, commandline);
setup_end_tag (bd);

最后,调用theKernel函数启动内核。

uboot全局变量

global_data(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? */
    unsigned long    fb_base;    /* base address of frame buffer */
#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;

include/asm-arm/global_data.h,定义了全局变量指针r8,可以说全局变量区不像普通变量可以用变量名访问,它只能用指针访问

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

 r8在start_armboot(lib_arm/board.c)函数的开始处赋值 

/* Pointer is writable since we allocated a register for it */
    gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));

 通过计算便能得到r8的值,不同的开发板配置可能不一样,但是算法是一样的,这里算得的结果是0x33f4ffdc。从内存中查看到底存的是什么东东。

1、bd =>>0x33f4ffb8,bd指针

2、flag =>>0x02

3、 baudrate =>>0x0001c200=115200,串口波特率

4、have_console =>>0x01

5、reloc_off =>>0x0

6、env_addr =>>0x33f5a164,环境变量的分配内存地址,这个值在堆区

7、 env_valid =>>0x01,环境变量已经设置完成

8、fb_base =>>0x0,显示器缓冲区

9、jt =>>0x33f61bc0,跳转函数表指针

 总共:36 Bytes

bd_info (include/asm-arm/u-boot.h)

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

1、 bi_baudrate=>>0x0001c200,串口波特率

2、 bi_ip_addr=>>0x6e00000a,ip地址

3、 bi_enetaddr=>>0x00000000 、0x00000000

    注意:它虽然需要6个字节,但编译器对齐分配的时候取了8个字节

4、bi_env=>>0x0

5、 bi_arch_number=>>0xc1,机器码,对应着MACH_TYPE_S3C2410

6、bi_boot_params=>>0x30000100,启动参数

7、 bi_dram[CONFIG_NR_DRAM_BANKS],DRAM信息

     smdk2410中定义CONFIG_NR_DRAM_BANKS为1

    1> start=>>0x30000000,DRAM起始地址

    2> size=>>0x04000000,DRAM大小

总共:36 Bytes

environment_s(include/environment.h)

typedef    struct environment_s {
    unsigned long    crc;        /* CRC32 over data bytes    */
#ifdef CFG_REDUNDAND_ENVIRONMENT
    unsigned char    flags;        /* active/obsolete flags    */
#endif
    unsigned char    data[ENV_SIZE]; /* Environment data        */
} env_t;

crc

crc校验值如图所示为0x48ec48fd

 环境变量打印效果

所有的环境变量打印的效果,打印的顺序是依据其在内存中排放的先后顺序

65532为分配的环境变量区大小,127是当前使用大小

 环境变量完整内存

环境变量对应的二进制内存值

 结论:

1>一个环境变量以“空”结束

2>每个环境变量等号前边的是其名字,等号后边的是其值(值也是以字符串存储在内存中,计算机真正使用

    还需要转换)

3>所有环境变量的最后边以“空”结束

参考文档:https://www.cnblogs.com/amanlikethis/p/3614594.html

uboot全局变量 - amanlikethis - 博客园 (cnblogs.com)

uboot分析:uboot启动内核 - 爱悠闲 (aiuxian.com)

原来Uboot是这样启动的! - 知乎 (zhihu.com)

ubootu3 添加nand flash支持-robinfit01-ChinaUnix博客

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

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

相关文章

mybatis打印sql语句出现多余的limit关键字

1、事情起因 在项目中使用了PageHelper分页插件&#xff0c;由于需求特殊&#xff0c;需要自定义分页&#xff0c;代码编写完成后&#xff0c;事故出现了。 前端传参: {pageNum: 1,pageSize: 10, }已知表中数据10条&#xff0c;但是每次分页查询只有10条数据&#xff0c;排查…

第17章-Spring AOP经典应用场景

文章目录 一、日志处理二、事务控制三、参数校验四、自定义注解五、AOP 方法失效问题1. ApplicationContext2. AopContext3. 注入自身 六、附录1. 示例代码 AOP 提供了一种面向切面操作的扩展机制&#xff0c;通常这些操作是与业务无关的&#xff0c;在实际应用中&#xff0c;可…

5. C++类的封装

一、类的封装 封装的含义&#xff1a; 所有属性都是私有的&#xff0c;外部不能直接访问提供与属性相关的成员函数&#xff0c;间接访问属性 模板代码&#xff1a; #include <iostream> using namespace std;class Clock { private:int hour, minute, second; public…

《UNIX 传奇:历史与回忆》读后感

《UNIX 传奇&#xff1a;历史与回忆》 是 bwk&#xff08;Brian W. Kernighan&#xff09;2019 年的新作&#xff0c;回忆了 UNIX 在大半个世纪的风雨历程&#xff0c;是一本引人入胜的书籍。通过对 UNIX 操作系统的历史和发展进行详细的叙述和回顾&#xff0c;让我对这个操作系…

浅析基于安防监控EasyCVR视频汇聚融合技术的运输管理系统

一、项目背景 近年来&#xff0c;随着物流行业迅速发展&#xff0c;物流运输费用高、运输过程不透明、货损货差率高、供应链协同能力差等问题不断涌现&#xff0c;严重影响了物流作业效率&#xff0c;市场对于运输管理数字化需求愈发迫切。当前运输行业存在的难题如下&#xf…

Java volatile关键字分析

每个线程创建时&#xff0c;JVM会为其创建一份私有的工作内存&#xff08;栈空间&#xff09;&#xff0c;不同线程的工作内存之间不能直接互相访问 JMM规定所有的变量都存在主内存&#xff0c;主内存是共享内存区域&#xff0c;所有线程都可以访问 线程对变量进行读写&#xf…

计算机组成原理-笔记-第三章

目录 计算机组成原理——第三章——存储系统 1、基本概念 &#xff08;1&#xff09;分类 &#xff08;2&#xff09;总结 2、主存储器&#xff08;基本原理&#xff09; &#xff08;1&#xff09;译码器、控制电路、存储单元 &#xff08;容量计算&#xff09; &…

匈牙利算法详解

匈牙利算法(Hungarian Algorithm)是一种组合优化算法(combinatorial optimization algorithm)&#xff0c;用于求解指派问题(assignment problem)&#xff0c;算法时间复杂度为O(N^3)。Harold Kuhn发表于1955年&#xff0c;由于该算法基于两位匈牙利数学家的早期研究成果&#…

数字IC流片经历有多重要?怎样才能有流片机会?

都说拥有流片经验可以显示你在实际项目中的实践能力和对整个设计流程的了解程度&#xff0c;流片经历的重要性不言而喻。 什么是芯片流片 像流水线一样通过一系列工艺步骤制造芯片&#xff0c;这就是流片。在芯片制造过程中一般有两段时间可以叫作流片。 流片&#xff1a;英…

分布式测试插件 pytest-xdist 使用详解

目录 使用背景&#xff1a; 使用前提&#xff1a; 使用快速入门&#xff1a; 使用小结&#xff1a; 使用背景&#xff1a; 大型测试套件&#xff1a;当你的测试套件非常庞大&#xff0c;包含了大量的测试用例时&#xff0c;pytest-xdist可以通过并行执行来加速整体的测试过…

Unity数字可视化学校_昼夜(二)

1、时间设置&#xff1a; 2、新建夜晚 3、新建侧置球&#xff08;BOX&#xff09;,测试灯光强度 降低亮度 色调&#xff1a;冷色调 4、自发光 新建shader 灯光控制 道路线&#xff1a; 建筑&#xff1a; 夜晚加灯光&#xff1a; 玻璃&#xff1a; 加大灯光数量&#xff1a; 边缘…

深入理解负载均衡原理及算法

1. 前言 在互联网早期,网络还不是很发达,上网用户少,流量相对较小,系统架构以单体架构为主。但如今在互联网发达的今天,流量请求动辄百亿、甚至上千亿,单台服务器或者实例已完全不能满足需求,这就有了集群。不论是为了实现高可用还是高性能,都需要用到多台机器来扩展服…

校对软件助力司法公正:确保法律文书准确无误

校对软件在司法系统中的应用可以助力司法公正&#xff0c;确保法律文书的准确性和无误性。以下是校对软件如何发挥作用&#xff1a; 1.确保准确性&#xff1a;校对软件可以自动检查法律文书中的语法、拼写和标点等方面的错误。通过及时发现和修正这些错误&#xff0c;可以确保文…

自监督去噪:Recorrupted-to-Recorrupted原理分析与总结

文章目录 1. 方法原理1.1 相关研究1.2 研究思路1.3 小结 2. 实验结果3. 总结 文章地址: https://ieeexplore.ieee.org/document/9577798/footnotes#footnotes 参考博客: https://github.com/PangTongyao/Recorrupted-to-Recorrupted-Unsupervised-Deep-Learning-for-Image-Den…

C++如何改变文字的颜色(不同字显示不同颜色)

许多同学们在制作c游戏的时候只有黑白两种颜色。就像si人了一样 非常影响视觉效果&#xff0c;显得十分不好看&#xff0c;因此&#xff0c;我决定发一个改变文字颜色的文章&#xff01; 下面介绍方法&#xff1a; 在了解程序之前&#xff0c;首先好了解光的三原色已经三原色…

数据结构刷题训练——链表篇(三)

目录 文章目录 前言 1. 题目一&#xff1a;环形链表Ⅱ 1.1 思路 1.2 分析 1.3 题解 1.4 方法二 2. 题目二&#xff1a;复制带随机指针的链表 2.1 思路 2.2 分析 2.3 题解 总结 前言 在这个专栏博客中&#xff0c;我们将提供丰富的题目资源和解题思路&#xff0c;帮助读者逐步提…

浅谈智能配电房在海岛中的应用

安科瑞 华楠 摘要&#xff1a;以海陵岛旅游区海岛智能配电房设计方案为研究对象,从功能设计、逻辑设计和拓扑设计三方面进行 分析,得出契合海陵岛实际和海岛特点的智能配电房方案设计。通过对设计方案研究,总结提炼了海岛智 能配电房组成要素,为进一步提炼统一规范的智能配电房…

【深度学习】【风格迁移】Zero-shot Image-to-Image Translation

论文&#xff1a;https://arxiv.org/abs/2302.03027 代码&#xff1a;https://github.com/pix2pixzero/pix2pix-zero/tree/main 文章目录 Abstract1. Introduction相关工作3. Method Abstract 大规模文本到图像生成模型展示了它们合成多样且高质量图像的显著能力。然而&#x…

【网络编程】实现一个简单多线程版本TCP服务器(附源码)

TCP多线程 &#x1f335;预备知识&#x1f384; Accept函数&#x1f332;字节序转换函数&#x1f333;listen函数 &#x1f334;代码&#x1f331;Log.hpp&#x1f33f;Makefile☘️TCPClient.cc&#x1f340;TCPServer.cc&#x1f38d; util.hpp &#x1f335;预备知识 &…

未来C#上位机软件发展趋势

C#上位机软件迎来新的发展机遇。随着工业自动化的快速发展&#xff0c;C#作为一种流行的编程语言在上位机软件领域发挥着重要作用。未来&#xff0c;C#上位机软件可能会朝着以下几个方向发展&#xff1a; 1.智能化&#xff1a;随着人工智能技术的不断演进&#xff0c;C#上位机…