Tiny4412 u-boot分析(3)u-boot 引导内核流程

在u-boot中,通过bootm命令启动内核。bootm命令的作用是将内核加载到指定的内存地址,然后通过R0、R1、R2寄存器传递启动参数之后启动内核。在启动内核之前需要对环境做一些初始化工作,主要有如下几个方面:

(1)、cpu 寄存器设置

* R0 = 0

* R1 = 板级 id

* R2 = 启动参数在内存中的起始地址

(2)、cpu 模式

* 禁止所有中断

* 必须为SVC(超级用户)模式

(3)、缓存、MMU

* 关闭 MMU

* 指令缓存可以开启或者关闭

* 数据缓存必须关闭并且不能包含任何脏数据

(4)、设备

* DMA 设备应当停止工作

(5)、boot loader 需要跳转到内核镜像的第一条指令处

这些需求都由 boot loader 实现,在常用的 uboot 中完成一系列的初始化后最后通过 bootm 命令加载 linux 内核。bootm 将内核镜像从各种媒介中读出,存放在指定的位置;然后设置标记列表给内核传递参数;最后跳到内核的入口点去执行。

在分析u-boot源码之前,我们首先来分析一下u-boot中的命令格式。u-boot中每个命令都是通过 U_BOOT_CMD 宏来定义的,格式如下:

U_BOOT_CMD(name,maxargs,repeatable,command,"usage","help")

各项参数的意义如下:

(1) -- name:命令的名字,注意,它不是一个字符串(不要用双引号括起来);

(2)-- maxargs:最大的参数个数;

(3)-- repeatable:命令是否可以重复,可重复是指运行一个命令后,下次敲回车即可再次运行;

(4)-- command:对应的函数指针,类型为(*cmd)(struct cmd_tbl_s *, int, int, char *[]);

(5) -- usage:简单的使用说明,这是个字符串;

(6)-- help:较详细的使用说明,这是个字符串。

下面就来具体分析一下bootm命令。bootm命令的源码路径为:u-boot源码路径/common/cmd_bootm.c

我们通过

U_BOOT_CMD(
    bootm,    CONFIG_SYS_MAXARGS,    1,    do_bootm, ...)

可以看出bootm命令的入口函数为d_bootm,下面我们就去看一下它的庐山真面目。

/*******************************************************************/
/* bootm - boot application image from image in memory */
/*******************************************************************/
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
#ifdef CONFIG_ZIMAGE_BOOT
#define LINUX_ZIMAGE_MAGIC    0x016f2818
    image_header_t    *hdr;
    ulong        addr;
   //找到内核镜像的地址
    /* find out kernel image address */
    if (argc < 2) {
        addr = load_addr;
        debug ("*  kernel: default image load address = 0x%08lx\n",
                load_addr);
    } else {
        addr = simple_strtoul(argv[1], NULL, 16);
    }
  //检查内核是否为zImage格式
    if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
        u32 val;
        printf("Boot with zImage\n");
    //将内核地址转换为物理地址
        //addr = virt_to_phys(addr);
        hdr = (image_header_t *)addr;
        hdr->ih_os = IH_OS_LINUX;
        hdr->ih_ep = ntohl(addr);
        //提取内核镜像的头信息
        memmove (&images.legacy_hdr_os_copy, hdr, sizeof(image_header_t));
    //保存头信息
        /* save pointer to image header */
        images.legacy_hdr_os = hdr;
        images.legacy_hdr_valid = 1;
        goto after_header_check;
    }
#endif
#ifdef CONFIG_NEEDS_MANUAL_RELOC
    static int relocated = 0;
    //重定位启动函数表
    /* relocate boot function table */
    if (!relocated) {
        int i;
        for (i = 0; i < ARRAY_SIZE(boot_os); i++)
            if (boot_os[i] != NULL)
                boot_os[i] += gd->reloc_off;
        relocated = 1;
    }
#endif
     //判断是否有子命令
    /* determine if we have a sub command */
    if (argc > 1) {
        char *endp;
        simple_strtoul(argv[1], &endp, 16);
        /* endp pointing to NULL means that argv[1] was just a
         * valid number, pass it along to the normal bootm processing
         *
         * If endp is ‘:‘ or ‘#‘ assume a FIT identifier so pass
         * along for normal processing.
         *
         * Right now we assume the first arg should never be ‘-‘
         */
        if ((*endp != 0) && (*endp != ‘:‘) && (*endp != ‘#‘))
            return do_bootm_subcommand(cmdtp, flag, argc, argv);
    }
   //获取内核相关信息
    if (bootm_start(cmdtp, flag, argc, argv))
        return 1;
    /*
     * 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();
#if defined(CONFIG_CMD_USB)
    /*
     * turn off USB to prevent the host controller from writing to the
     * SDRAM while Linux is booting. This could happen (at least for OHCI
     * controller), because the HCCA (Host Controller Communication Area)
     * lies within the SDRAM and the host controller writes continously to
     * this area (as busmaster!). The HccaFrameNumber is for example
     * updated every 1 ms within the HCCA structure in SDRAM! For more
     * details see the OpenHCI specification.
     */
     //关闭USB
    usb_stop();
#endif
  //加载内核
    ret = bootm_load_os(images.os, &load_end, 1);
    if (ret < 0) {
        if (ret == BOOTM_ERR_RESET)
            do_reset (cmdtp, flag, argc, argv);
        if (ret == BOOTM_ERR_OVERLAP) {
            if (images.legacy_hdr_valid) {
                if (image_get_type (&images.legacy_hdr_os_copy) == IH_TYPE_MULTI)
                    puts ("WARNING: legacy format multi component "
                        "image overwritten\n");
            } else {
                puts ("ERROR: new format image overwritten - "
                    "must RESET the board to recover\n");
                show_boot_progress (-113);
                do_reset (cmdtp, flag, argc, argv);
            }
        }
        if (ret == BOOTM_ERR_UNIMPLEMENTED) {
            if (iflag)
                enable_interrupts();
            show_boot_progress (-7);
            return 1;
        }
    }
    lmb_reserve(&images.lmb, images.os.load, (load_end - images.os.load));
    if (images.os.type == IH_TYPE_STANDALONE) {
        if (iflag)
            enable_interrupts();
        /* This may return when ‘autostart‘ is ‘no‘ */
        bootm_start_standalone(iflag, argc, argv);
        return 0;
    }
    show_boot_progress (8);
#if defined(CONFIG_ZIMAGE_BOOT)
after_header_check:
    images.os.os = hdr->ih_os;
    images.ep = image_get_ep (&images.legacy_hdr_os_copy);
#endif
#ifdef CONFIG_SILENT_CONSOLE
    if (images.os.os == IH_OS_LINUX)
        fixup_silent_linux();
#endif
  //获取内核启动参数
    boot_fn = boot_os[images.os.os];
    if (boot_fn == NULL) {
        if (iflag)
            enable_interrupts();
        printf ("ERROR: booting os ‘%s‘ (%d) is not supported\n",
            genimg_get_os_name(images.os.os), images.os.os);
        show_boot_progress (-8);
        return 1;
    }
  //内核启动前的准备
    arch_preboot_os();
  //启动内核,不返回
    boot_fn(0, argc, argv, &images);
    show_boot_progress (-9);
#ifdef DEBUG
    puts ("\n## Control returned to monitor - resetting...\n");
#endif
    do_reset (cmdtp, flag, argc, argv);
    return 1;
}

该函数主要的工作流程是,通过bootm_start来获取内核镜像文件的信息,然后通过bootm_load_os函数来加载内核,最后通过boot_fn来启动内核。

首先看一下bootm_start,该函数主要进行镜像的有效性判定、校验、计算入口地址等操作,大部分工作通过 boot_get_kernel -> image_get_kernel 完成。

static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    void        *os_hdr;
    int        ret;
    memset ((void *)&images, 0, sizeof (images));
    //读取环境变量,从环境变量中检查是否要对镜像的数据(不是镜像头)进行校验
    images.verify = getenv_yesno ("verify");
    //不做任何有意义的工作,除了定义# define lmb_reserve(lmb, base, size)
    bootm_start_lmb();
    //获取镜像头,加载地址,长度,返回指向内存中镜像头的指针
    /* get kernel image header, start address and length */
    os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,
            &images, &images.os.image_start, &images.os.image_len);
    if (images.os.image_len == 0) {
        puts ("ERROR: can‘t get kernel image!\n");
        return 1;
    }
    //根据镜像魔数获取镜像类型
    /* get image parameters */
    switch (genimg_get_format (os_hdr)) {
    case IMAGE_FORMAT_LEGACY:
        images.os.type = image_get_type (os_hdr);//镜像类型
        images.os.comp = image_get_comp (os_hdr);//压缩类型
        images.os.os = image_get_os (os_hdr);//操作系统类型
        images.os.end = image_get_image_end (os_hdr);//当前镜像的尾地址
        images.os.load = image_get_load (os_hdr);//镜像数据的载入地址
        break;
#if defined(CONFIG_FIT)
    case IMAGE_FORMAT_FIT:
        if (fit_image_get_type (images.fit_hdr_os,
                    images.fit_noffset_os, &images.os.type)) {
            puts ("Can‘t get image type!\n");
            show_boot_progress (-109);
            return 1;
        }
        if (fit_image_get_comp (images.fit_hdr_os,
                    images.fit_noffset_os, &images.os.comp)) {
            puts ("Can‘t get image compression!\n");
            show_boot_progress (-110);
            return 1;
        }
        if (fit_image_get_os (images.fit_hdr_os,
                    images.fit_noffset_os, &images.os.os)) {
            puts ("Can‘t get image OS!\n");
            show_boot_progress (-111);
            return 1;
        }
        images.os.end = fit_get_end (images.fit_hdr_os);
        if (fit_image_get_load (images.fit_hdr_os, images.fit_noffset_os,
                    &images.os.load)) {
            puts ("Can‘t get image load address!\n");
            show_boot_progress (-112);
            return 1;
        }
        break;
#endif
    default:
        puts ("ERROR: unknown image format type!\n");
        return 1;
    }
     //获取内核入口地址
    /* find kernel entry point */
    if (images.legacy_hdr_valid) {
        images.ep = image_get_ep (&images.legacy_hdr_os_copy);
#if defined(CONFIG_FIT)
    } else if (images.fit_uname_os) {
        ret = fit_image_get_entry (images.fit_hdr_os,
                images.fit_noffset_os, &images.ep);
        if (ret) {
            puts ("Can‘t get entry point property!\n");
            return 1;
        }
#endif
    } else {
        puts ("Could not find kernel entry point!\n");
        return 1;
    }
    if (((images.os.type == IH_TYPE_KERNEL) ||
         (images.os.type == IH_TYPE_MULTI)) &&
        (images.os.os == IH_OS_LINUX)) {
        //获取虚拟磁盘
        /* find ramdisk */
        ret = boot_get_ramdisk (argc, argv, &images, IH_INITRD_ARCH,
                &images.rd_start, &images.rd_end);
        if (ret) {
            puts ("Ramdisk image is corrupt or invalid\n");
            return 1;
        }

#if defined(CONFIG_OF_LIBFDT)
         //获取设备树,设备树是linux 3.XX版本特有的
        /* find flattened device tree */
        ret = boot_get_fdt (flag, argc, argv, &images,
                    &images.ft_addr, &images.ft_len);
        if (ret) {
            puts ("Could not find a valid device tree\n");
            return 1;
        }
        set_working_fdt_addr(images.ft_addr);
#endif
    }
    //将内核加载地址赋值给images.os.start
    images.os.start = (ulong)os_hdr;
    //更新镜像状态
    images.state = BOOTM_STATE_START;
    return 0;
}

接着看一下bootm_load_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;
typedef struct image_info {
        ulong           start, end;             /* start/end of blob */
        ulong           image_start, image_len; /* start of image within blob, len of image */
        ulong           load;                   /* load addr for the image */
        uint8_t         comp, type, os;         /* compression, type of image, os type */
} image_info_t;
static int bootm_start(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
    void        *os_hdr;
    int        ret;
    memset ((void *)&images, 0, sizeof (images));
    //读取环境变量,从环境变量中检查是否要对镜像的数据(不是镜像头)进行校验
    images.verify = getenv_yesno ("verify");
    //不做任何有意义的工作,除了定义# define lmb_reserve(lmb, base, size)
    bootm_start_lmb();
    //获取镜像头,加载地址,长度,返回指向内存中镜像头的指针
    /* get kernel image header, start address and length */
    os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,
            &images, &images.os.image_start, &images.os.image_len);
    if (images.os.image_len == 0) {
        puts ("ERROR: can‘t get kernel image!\n");
        return 1;
    }
    //根据镜像魔数获取镜像类型
    /* get image parameters */
    switch (genimg_get_format (os_hdr)) {
    case IMAGE_FORMAT_LEGACY:
        images.os.type = image_get_type (os_hdr);//镜像类型
        images.os.comp = image_get_comp (os_hdr);//压缩类型
        images.os.os = image_get_os (os_hdr);//操作系统类型
        images.os.end = image_get_image_end (os_hdr);//当前镜像的尾地址
        images.os.load = image_get_load (os_hdr);//镜像数据的载入地址
        break;
#if defined(CONFIG_FIT)
    case IMAGE_FORMAT_FIT:
        if (fit_image_get_type (images.fit_hdr_os,
                    images.fit_noffset_os, &images.os.type)) {
            puts ("Can‘t get image type!\n");
            show_boot_progress (-109);
            return 1;
        }
        if (fit_image_get_comp (images.fit_hdr_os,
                    images.fit_noffset_os, &images.os.comp)) {
            puts ("Can‘t get image compression!\n");
            show_boot_progress (-110);
            return 1;
        }
        if (fit_image_get_os (images.fit_hdr_os,
                    images.fit_noffset_os, &images.os.os)) {
            puts ("Can‘t get image OS!\n");
            show_boot_progress (-111);
            return 1;
        }
        images.os.end = fit_get_end (images.fit_hdr_os);
        if (fit_image_get_load (images.fit_hdr_os, images.fit_noffset_os,
                    &images.os.load)) {
            puts ("Can‘t get image load address!\n");
            show_boot_progress (-112);
            return 1;
        }
        break;
#endif
    default:
        puts ("ERROR: unknown image format type!\n");
        return 1;
    }
     //获取内核入口地址
    /* find kernel entry point */
    if (images.legacy_hdr_valid) {
        images.ep = image_get_ep (&images.legacy_hdr_os_copy);
#if defined(CONFIG_FIT)
    } else if (images.fit_uname_os) {
        ret = fit_image_get_entry (images.fit_hdr_os,
                images.fit_noffset_os, &images.ep);
        if (ret) {
            puts ("Can‘t get entry point property!\n");
            return 1;
        }
#endif
    } else {
        puts ("Could not find kernel entry point!\n");
        return 1;
    }
    if (((images.os.type == IH_TYPE_KERNEL) ||
         (images.os.type == IH_TYPE_MULTI)) &&
        (images.os.os == IH_OS_LINUX)) {
        //获取虚拟磁盘
        /* find ramdisk */
        ret = boot_get_ramdisk (argc, argv, &images, IH_INITRD_ARCH,
                &images.rd_start, &images.rd_end);
        if (ret) {
            puts ("Ramdisk image is corrupt or invalid\n");
            return 1;
        }

#if defined(CONFIG_OF_LIBFDT)
         //获取设备树,设备树是linux 3.XX版本特有的
        /* find flattened device tree */
        ret = boot_get_fdt (flag, argc, argv, &images,
                    &images.ft_addr, &images.ft_len);
        if (ret) {
            puts ("Could not find a valid device tree\n");
            return 1;
        }
        set_working_fdt_addr(images.ft_addr);
#endif
    }
    //将内核加载地址赋值给images.os.start
    images.os.start = (ulong)os_hdr;
    //更新镜像状态
    images.state = BOOTM_STATE_START;
    return 0;
}
#define BOOTM_ERR_RESET        -1
#define BOOTM_ERR_OVERLAP    -2
#define BOOTM_ERR_UNIMPLEMENTED    -3
static int bootm_load_os(image_info_t os, ulong *load_end, int boot_progress)
{
    uint8_t comp = os.comp;//压缩格式
    ulong load = os.load;//加载地址
    ulong blob_start = os.start;//系统起始地址
    ulong blob_end = os.end;//系统结束地址
    ulong image_start = os.image_start;//镜像起始地址
    ulong image_len = os.image_len;//镜像大小
    uint unc_len = CONFIG_SYS_BOOTM_LEN;//镜像最大长度
#if defined(CONFIG_LZMA) || defined(CONFIG_LZO)
    int ret;
#endif /* defined(CONFIG_LZMA) || defined(CONFIG_LZO) */
    //获取镜像类型
    const char *type_name = genimg_get_type_name (os.type);
    switch (comp) {
    case IH_COMP_NONE://镜像没有压缩过
        if (load == blob_start) {//判断是否需要移动镜像
            printf ("   XIP %s ... ", type_name);
        } else {
            printf ("   Loading %s ... ", type_name);
            memmove_wd ((void *)load, (void *)image_start,
                    image_len, CHUNKSZ);
        }
        *load_end = load + image_len;
        puts("OK\n");
        break;
#ifdef CONFIG_GZIP
    case IH_COMP_GZIP://镜像使用gzip压缩
        printf ("   Uncompressing %s ... ", type_name);
        //解压镜像文件
        if (gunzip ((void *)load, unc_len,
                    (uchar *)image_start, &image_len) != 0) {
            puts ("GUNZIP: uncompress, out-of-mem or overwrite error "
                "- must RESET board to recover\n");
            if (boot_progress)
                show_boot_progress (-6);
            return BOOTM_ERR_RESET;
        }
        *load_end = load + image_len;
        break;
#endif /* CONFIG_GZIP */
......
    return 0;
}

最后看一下boot_fn函数,boot_fn的定义为

boot_os_fn *boot_fn;

可以看出它是一个boot_os_fn类型的函数指针。它的定义为

//  common/cmd_bootm.c
typedef int boot_os_fn (int flag, int argc, char * const argv[],
                        bootm_headers_t *images); /* pointers to os/initrd/fdt */
#ifdef CONFIG_BOOTM_LINUX
extern boot_os_fn do_bootm_linux;
#endif
......

然后boot_fn在do_bootm函数中被赋值为

boot_fn = boot_os[images.os.os];

boot_os是一个函数指针数组

//  common/cmd_bootm.c
static boot_os_fn *boot_os[] = {
#ifdef CONFIG_BOOTM_LINUX
    [IH_OS_LINUX] = do_bootm_linux,
#endif
#ifdef CONFIG_BOOTM_NETBSD
    [IH_OS_NETBSD] = do_bootm_netbsd,
#endif
#ifdef CONFIG_LYNXKDI
    [IH_OS_LYNXOS] = do_bootm_lynxkdi,
#endif
#ifdef CONFIG_BOOTM_RTEMS
    [IH_OS_RTEMS] = do_bootm_rtems,
#endif
#if defined(CONFIG_BOOTM_OSE)
    [IH_OS_OSE] = do_bootm_ose,
#endif
#if defined(CONFIG_CMD_ELF)
    [IH_OS_VXWORKS] = do_bootm_vxworks,
    [IH_OS_QNX] = do_bootm_qnxelf,
#endif
#ifdef CONFIG_INTEGRITY
    [IH_OS_INTEGRITY] = do_bootm_integrity,
#endif
};

可以看出 boot_fn 函数指针最后指向的函数是位于 arch/arm/lib/bootm.c的 do_bootm_linux,这是内核启动前最后的一个函数,该函数主要完成启动参数的初始化,并将板子设定为满足内核启动的环境。

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    (*kernel_entry)(int zero, int arch, uint params);
    int    ret;
    //获取启动参数
#ifdef CONFIG_CMDLINE_TAG
    char *commandline = getenv ("bootargs");
#endif
    if ((flag != 0) && (flag != BOOTM_STATE_OS_GO))
        return 1;
    //从环境变量中获取机器码
    s = getenv ("machid");
    if (s) {
        machid = simple_strtoul (s, NULL, 16);
        printf ("Using machid 0x%x from environment\n", machid);
    }
    //获取ramdisk
    ret = boot_get_ramdisk(argc, argv, images, IH_ARCH_ARM,
            &(images->rd_start), &(images->rd_end));
    if(ret)
        printf("[err] boot_get_ramdisk\n");
    show_boot_progress (15);
#ifdef CONFIG_OF_LIBFDT
    if (images->ft_len)
        return bootm_linux_fdt(machid, images);
#endif
    kernel_entry = (void (*)(int, int, uint))images->ep;
    debug ("## Transferring control to Linux (at address %08lx) ...\n",
           (ulong) kernel_entry);
#if defined (CONFIG_SETUP_MEMORY_TAGS) || \
    defined (CONFIG_CMDLINE_TAG) ||     defined (CONFIG_INITRD_TAG) ||     defined (CONFIG_SERIAL_TAG) ||     defined (CONFIG_REVISION_TAG)
    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 (images->rd_start && images->rd_end)
        setup_initrd_tag (bd, images->rd_start, images->rd_end);
#endif
    setup_end_tag(bd);
#endif
    announce_and_cleanup();
#ifdef CONFIG_ENABLE_MMU
    theLastJump((void *)virt_to_phys(kernel_entry), machid, bd->bi_boot_params);
#else
    kernel_entry(0, machid, bd->bi_boot_params);
    /* does not return */
#endif
    return 1;
}

kernel_entry(0, machid, r2)

真正将控制权交给内核, 启动内核;

满足arm架构linux内核启动时的寄存器设置条件:第一个参数为0 ;第二个参数为板子id需与内核中的id匹配,第三个参数为启动参数地址bi_boot_params 。

(1)首先取出环境变量bootargs,这就是要传递给内核的参数。

(2)调用setup_XXX_tag

static void setup_start_tag (bd_t *bd)
{
       //将tags的首地址也就是bi_boot_params传给kernel
        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);
}

params是一个用来存储要传给kernel的参数的静态全局变量。

u-boot 是通过标记列表向内核传递参数,标记在源代码中定义为tag,是一个结构体,在 arch/arm/include/asm/setup.h 中定义。

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;

tag包括hdr和各种类型的tag_*,hdr来标志当前的tag是哪种类型的tag。setup_start_tag是初始化了第一个tag,是tag_core类型的tag。最后调用tag_next跳到第一个tag末尾,为下一个tag做准备。

tag_next是一个宏定义,被定义在arch/arm/include/asm/setup.h中

#define tag_next(t)     ((struct tag *)((u32 *)(t) + (t)->hdr.size))
struct tag_header {
        u32 size;
        u32 tag;
};

最后调用setup_end_tag,将末尾的tag设置为ATAG_NONE,标志tag列表结束。

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

u-boot将参数以tag数组的形式布局在内存的某一个地址,每个tag代表一种类型的参数,首尾tag标志开始和结束,首地址传给kernel供其解析

通过上面的分析,我们可以尝试自己写一个bootm来引导内核(代码与4412无关,是学6410时的笔记)

//atag.h
#define ATAG_CORE    0x54410001
#define ATAG_MEM    0x54410002
#define ATAG_CMDLINE    0x54410009
#define ATAG_NONE    0x00000000
struct tag_header {
    unsigned int size;
    unsigned int tag;
};
struct tag_core {
    unsigned int flags;
    unsigned int pagesize;
    unsigned int rootdev;
};
struct tag_mem32 {
    unsigned int    size;
    unsigned int    start;
};
struct tag_cmdline {
    char    cmdline[1];
};
struct tag {
    struct tag_header hdr;
    union {
        struct tag_core        core;
        struct tag_mem32    mem;
        struct tag_cmdline    cmdline;
    } u;
};
#define tag_size(type)    ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)
#define tag_next(t)    ((struct tag *)((unsigned int *)(t) + (t)->hdr.size))
//boot.c
#include "atag.h"
#include "string.h"
void (*theKernel)(int , int , unsigned int );
#define SDRAM_KERNEL_START 0x51000000
#define SDRAM_TAGS_START   0x50000100
#define SDRAM_ADDR_START   0x50000000
#define SDRAM_TOTAL_SIZE   0x16000000
struct tag *pCurTag;
const char *cmdline = "console=ttySAC0,115200 init=/init";
void setup_core_tag()
{
     pCurTag = (struct tag *)SDRAM_TAGS_START;

     pCurTag->hdr.tag = ATAG_CORE;
     pCurTag->hdr.size = tag_size(tag_core); 

     pCurTag->u.core.flags = 0;
     pCurTag->u.core.pagesize = 4096;
     pCurTag->u.core.rootdev = 0;

     pCurTag = tag_next(pCurTag);
}
void setup_mem_tag()
{
     pCurTag->hdr.tag = ATAG_MEM;
     pCurTag->hdr.size = tag_size(tag_mem32); 

     pCurTag->u.mem.start = SDRAM_ADDR_START;
     pCurTag->u.mem.size = SDRAM_TOTAL_SIZE;

     pCurTag = tag_next(pCurTag);
}
void setup_cmdline_tag()
{
     int linelen = strlen(cmdline);

     pCurTag->hdr.tag = ATAG_CMDLINE;
     pCurTag->hdr.size = (sizeof(struct tag_header)+linelen+1+4)>>2;

     strcpy(pCurTag->u.cmdline.cmdline,cmdline);

     pCurTag = tag_next(pCurTag);
}
void setup_end_tag()
{
    pCurTag->hdr.tag = ATAG_NONE;
    pCurTag->hdr.size = 0;
}
void boot_linux(){

    //1.获取Linux启动地址
    theKernel = (void (*)(int , int , unsigned int ))SDRAM_KERNEL_START;
    printf("huo qu linux qi dong di zhi");
    //2.设置启动参数
    //2.1.设置核心启动参数
    setup_core_tag();
    //2.2.设置内存参数
    setup_mem_tag();
    //2.3.设置命令行参数
    setup_cmdline_tag();
    //2.4.设置结束标志
    setup_end_tag();

    //4.启动Linux内核
    theKernel(0,1626,SDRAM_TAGS_START);
    printf("qi dong linux nei he");

    }
时间: 2024-10-13 17:32:45

Tiny4412 u-boot分析(3)u-boot 引导内核流程的相关文章

Legacy BIOS Boot 是如何启动或引导的

现在Windows 8 64位操作系统全面采用UEFI引导启动的方式,与过去的Legacy启动有什么区别呢?今天就让我们一起来了解下. Legacy BIOS UEFI Boot 是如何启动或引导的 当系统首次引导时,或系统被重置时,处理器会执行一个位于已知位置处的代码.这个位置在基本输入 / 输出系统(BIOS) 中.CPU 会调用这个重置 向量来启动一个位于闪存/ROM 中的已知地址处的程序.通常,它执行一个启动自测(POST)来检查机器.最后,它从引导驱动器上的主引导记录 (MBR)加载第

tiny4412 串口驱动分析 --- u-boot中的串口驱动

作者:彭东林 邮箱:[email protected] 开发板:tiny4412ADK+S700 4GB Flash 主机:Wind7 64位 虚拟机:Vmware+Ubuntu12_04 u-boot:U-Boot 2010.12 Linux内核版本:linux-3.0.31 Android版本:android-4.1.2 我们以tiny4412为例分析串口驱动,下面我们从u-boot开始分析,然后再分析到Linux. 串口初始化 关于这部分代码流程参考件:tiny4412 u-boot 启动

spring boot框架学习6-spring boot的web开发(2)

本章节主要内容: 通过前面的学习,我们了解并快速完成了spring boot第一个应用.spring boot企业级框架,那么spring boot怎么读取静态资源?如js文件夹,css文件以及png/jpg图片呢?怎么自定义消息转换器呢?怎么自定义spring mvc的配置呢?这些我们在公司都需要用的.这些怎么解决呢?在接下来的小节详细讲解这些.好了,现在开启spring boot的web开发第一节 本节主要: 1:InternalResourceViewResolver讲解 2:自动配置静态

spring boot框架学习7-spring boot的web开发(3)-自定义消息转换器

本章节主要内容: 通过前面的学习,我们了解并快速完成了spring boot第一个应用.spring boot企业级框架,那么spring boot怎么读取静态资源?如js文件夹,css文件以及png/jpg图片呢?怎么自定义消息转换器呢?怎么自定义spring mvc的配置呢?这些我们在公司都需要用的.这些怎么解决呢?在接下来的小节详细讲解这些.好了,现在开启spring boot的web开发第一节 本节主要: 1:自定义消息转换器 本文是<凯哥陪你学系列-框架学习之spring boot框架

《01.Spring Boot连载:Spring Boot入门介绍》

1 Spring Boot的概述 Spring Boot是开发者和Spring 本身框架的中间层,帮助开发者统筹管理应用的配置,提供基于实际开发中常见配置的默认处理(即习惯优于配置),简化应用的开发,简化应用的运维:总的来说,其目的Spring Boot就是为了对Java web 的开发进行"简化"和加"快"速度,简化开发过程中引入或启动相关Spring 功能的配置.这样带来的好处就是降低开发人员对于框架的关注点,可以把更多的精力放在自己的业务代码上. 同时随着微服

【Spring Boot】利用 Spring Boot Admin 进行项目监控管理

利用 Spring Boot Admin 进行项目监控管理 一.Spring Boot Admin 是什么 Spring Boot Admin (SBA) 是一个社区开源项目,用于管理和监视 Spring Boot 应用程序.应用程序通过 http 的方式注册到 Spring Boot 管理客户端,或者通过 Spring Cloud 的服务发现机制,然后针对 actuator 接口将数据通过 Vue.js 进行可视化管理. 对于我们来说,我们可以通过 Spring Boot Admin 浏览所有

黑马_13 Spring Boot:04.spring boot 配置文件

13 Spring Boot: 01.spring boot 介绍&&02.spring boot 入门 04.spring boot 配置文件 SpringBoot基础 四.SpringBoot的配置文件 SpringBoot配置文件类型和作用 SpringBoot是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用application.properties 或者 application.yml (application.yaml)进行配置. app

Spring Boot 2.X - Spring Boot整合AMQP之RabbitMQ

文章目录Spring Boot 2.X - Spring Boot整合AMQP之RabbitMQRabbitMQ简介引入依赖编写配置编写接口启用Rabbit注解消息监听消息测试Spring Boot 2.X - Spring Boot整合AMQP之RabbitMQSpring Boot 2 整合RabbitMQ案例. RabbitMQ简介简介RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现.核心概念Message消息,

使用centos引导内核错误:kernel: pnp 00:0b: can&#39;t evaluate _CRS: 8

CentOS系统在开机过程中,一直遇到黑屏提示:“kernel: pnp 00:0b: can't evaluate _CRS: 8”,不理会它仍能启动系统并正常工作,未知何故. 经查,这是内核引导的错误,需要修改内核参数,才能避免开机出现这个错误: pnpacpi=off 禁用ACPI的即插即用功能,转而使用古董的PNPBIOS来代替. 具体操作为: [[email protected] ~]# sudo vi /boot/grub/grub.conf # 在内核引导参数的最后,加上这个参数(