Exynos4412 Uboot 移植(四)—— Uboot引导内核过程分析

发布者:PeacefulWarrior最新更新时间:2021-12-14 来源: eefocus关键字:Exynos4412  Uboot  移植  引导内核 手机看文章 扫描二维码
随时随地手机看文章

bootloader 要想启动内核,可以直接跳到内核的第一个指令处,即内核的起始地址,这样便可以完成内核的启动工作了。但是要想启动内核还需要满足一些条件,如下所示:


1、cpu 寄存器设置


    * R0 = 0

    * R1 = 机器类型 id

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


2、cpu 模式


    * 禁止所有中断

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


3、Cache、MMU


    * 关闭 MMU

    * 指令Cache可以开启或者关闭

    * 数据Cache必须关闭


4、设备


    * DMA 设备应当停止工作


5、PC为内核的起始地址

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


Uboot版本:u-boot-2013.01


一、bootm命令用法介绍如下:


在 common/cmd_bootm.c 中可以看到bootm 的定义:

可以看到 bootm 命令使调用了do_bootm 函数。


do_bootm 函数


在cmd_bootm.c 第586行可以看到do_bootm函数的定义(为方便阅读,对其中一些代码进行了删减,完整代码请阅读uboot源码):


/*******************************************************************/

/* bootm - boot application image from image in memory */

/*******************************************************************/

 

int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])

{

ulong iflag;

ulong load_end = 0;

int ret;

boot_os_fn *boot_fn;

 

if (bootm_start(cmdtp, flag, argc, argv))// 获取镜像信息

return 1;

 

iflag = disable_interrupts(); // 关闭中断

 

usb_stop();// 关闭usb设备

 

ret = bootm_load_os(images.os, &load_end, 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;

}

 

boot_fn = boot_os[images.os.os];//获取启动参数

 

arch_preboot_os();//启动前准备

 

boot_fn(0, argc, argv, &images);//启动,不再返回

 

#ifdef DEBUG

puts("n## Control returned to monitor - resetting...n");

#endif

do_reset(cmdtp, flag, argc, argv);

 

return 1;

}

 该函数的实现分为 3 个部分:

a -- 首先通过 bootm_start 函数分析镜像的信息;


b -- 如果满足判定条件则进入 bootm_load_os 函数进行加载;


c -- 加载完成后就可以调用 boot_fn 开始启动。


1、bootm_start


在cmd_bootm.c 第193行可以看到bootm_start函数的定义, 主要作用是填充内核相关信息

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");//获取环境变量

 

boot_start_lmb(&images);

 

bootstage_mark_name(BOOTSTAGE_ID_BOOTM_START, "bootm_start");

 

/*获取镜像头,加载地址,长度 */

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;

}

 

/*获取镜像参数*/

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 (images.legacy_hdr_valid) {

images.ep = image_get_ep(&images.legacy_hdr_os_copy);

 

} else {

puts("Could not find kernel entry point!n");

return 1;

}

 

if (images.os.type == IH_TYPE_KERNEL_NOLOAD) {

images.os.load = images.os.image_start;

images.ep += images.os.load;

}

 

if (((images.os.type == IH_TYPE_KERNEL) ||

     (images.os.type == IH_TYPE_KERNEL_NOLOAD) ||

     (images.os.type == IH_TYPE_MULTI)) &&

    (images.os.os == IH_OS_LINUX)) {

/* 查询是否存在虚拟磁盘 */

ret = boot_get_ramdisk(argc, argv, &images, IH_INITRD_ARCH,

&images.rd_start, &images.rd_end);

if (ret) {

puts("Ramdisk image is corrupt or invalidn");

return 1;

}

 

#if defined(CONFIG_OF_LIBFDT)

/* 找到设备树,设备树是linux 3.XX版本特有的 */

ret = boot_get_fdt(flag, argc, argv, &images,

   &images.ft_addr, &images.ft_len);

if (ret) {

puts("Could not find a valid device treen");

return 1;

}

 

set_working_fdt_addr(images.ft_addr);

#endif

}

 

images.os.start = (ulong)os_hdr;//赋值加载地址

images.state = BOOTM_STATE_START;//更新状态

 

return 0;

}

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


2、bootm_load_os


在cmd_bootm.c 第317行可以看到bootm_load_os函数的定义, 这个函数主要判断镜像是否需要解压,并且将镜像移动到加载地址:


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;   /* 镜像最大长度 */  

  

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

  

            if (load != image_start) {  

                memmove_wd ((void *)load, (void *)image_start, image_len, CHUNKSZ);  

            }  

        }  

        *load_end = load + image_len;  

        puts("OKn");  

        break;  

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

            return BOOTM_ERR_RESET;  

        }  

  

        *load_end = load + image_len;  

        break;  

    ...  

    default:  

        printf ("Unimplemented compression type %dn", comp);  

        return BOOTM_ERR_UNIMPLEMENTED;  

    }  

    puts ("OKn");  

    debug ("   kernel loaded at 0x%08lx, end = 0x%08lxn", load, *load_end);  

  

    if ((load < blob_end) && (*load_end > blob_start)) {  

        debug ("images.os.start = 0x%lX, images.os.end = 0x%lxn", blob_start, blob_end);  

        debug ("images.os.load = 0x%lx, load_end = 0x%lxn", load, *load_end);  

        return BOOTM_ERR_OVERLAP;  

    }  

  

    return 0;  

}  


3、do_bootm_linux

在bootm_load_os 执行结束后,回到do_bootm 函数,调用boot_fn 运行linux 内核;

boot_os 为函数指针数组,在cmd_bootm.c 136行有定义

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

可以看到 do_bootm_linux 实际调用的是 boot_jump_linux 函数。


4、boot_jump_linux 


在arch/arm/lib/bootm.c 下第326行有定义


/* Subcommand: GO */

static void boot_jump_linux(bootm_headers_t *images)

{

unsigned long machid = gd->bd->bi_arch_number;//获取机器码

char *s;

void (*kernel_entry)(int zero, int arch, uint params);//内核入口函数

unsigned long r2;

 

kernel_entry = (void (*)(int, int, uint))images->ep;

 

s = getenv("machid");//从环境变量中获取机器码

if (s) {

strict_strtoul(s, 16, &machid);

printf("Using machid 0x%lx from environmentn", machid);

}

 

debug("## Transferring control to Linux (at address %08lx)"

"...n", (ulong) kernel_entry);

bootstage_mark(BOOTSTAGE_ID_RUN_OS);

announce_and_cleanup();

 

#ifdef CONFIG_OF_LIBFDT

if (images->ft_len)

r2 = (unsigned long)images->ft_addr;

else

#endif

r2 = gd->bd->bi_boot_params;//将启动参数地址赋给 r2 

 

kernel_entry(0, machid, r2);

}


kernel_entry(0, machid, r2) 

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


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


二、为内核设置启动参数

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

tag_header 结构体定义如下:

在一些内存标记、命令行标记的示例代码就是取自Uboot 中的 setup_memory_tags、setup_commandline_tag函数,他们都是在 arch/arm/lib/bootm.c中定义。


#if defined(CONFIG_SETUP_MEMORY_TAGS) ||

defined(CONFIG_CMDLINE_TAG) ||

defined(CONFIG_INITRD_TAG) ||

defined(CONFIG_SERIAL_TAG) ||

defined(CONFIG_REVISION_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);

}

#endif

 

#ifdef CONFIG_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;//物理内存起始地址

[1] [2]
关键字:Exynos4412  Uboot  移植  引导内核 引用地址:Exynos4412 Uboot 移植(四)—— Uboot引导内核过程分析

上一篇:Exynos4412 内核移植(五)—— 驱动的移植
下一篇:Exynos4412 内核移植(二)—— 内核编译过程分析

小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved