挖坑网/填坑网 DebugDump Forum

别人挖坑默默填上,自己挖坑含泪填上。天下没有填不了的坑,只有不会填坑的人。来吧,加入我们,挖坑行,填坑你更行! 站长QQ: 516333132 点击这里给我发消息

您尚未登录。

#1 2018-06-08 16:52:29

xinxiaoci
会员
注册时间: 2018-04-18
累计发帖: 68

u-boot 源码分析 第二阶段源码分析

《单片机小白转嵌入式Linux学习记录,基于S3C2440----目录》


牢记 u-boot 的本质作用
1. 将内核从Flash读出内核到SDRAM
2. 启动内核
其它都是附加功能,分析第二阶段代码之前我们要明确目标,由于代码量比较庞大,不要花费很多精力去分析不是太重要的代码。

第二阶段代码从 start_armboot 开始

    gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t)); // gd 是一个结构体指针,指向内存 CFG_GBL_DATA_SIZE 128  字节(韦老师书本 P260) 全局变量,保存各种参数,比如 内存大小、内存起始地址 
   
    flash_init ();    // Flash 初始化
    nand_init();        /* go init the NAND */
   
    .......        /* 其它一系列的初始化 */
   
    for (;;) {//进入死循环
        main_loop ();
    }

分析 main_loop()

1. s = getenv ("bootdelay");    // 读取环境变量,bootdelay u-boot 启动延时时间
2. 倒计时 倒计时没有按下空格
    a. s = getenv ("bootcmd"); 读出环境变量 bootcmd = nand read.jffs2 0x30007FC0 kernel;bootm 0x30007FC0
    b. run_command (s, 0);//启动内核s = getenv ("bootcmd")
3. 如果倒计时按下空格
    a. len = readline (CFG_PROMPT);//读取串口的命令
    b. run_command (lastcommand, flag);
   
所以u-boot 的核心是 run_command() 各种命令的集合
关于怎么自定义命令,请看我的另一个帖子,这里不重要,可跳过。
------------------------------------------------------------------
自定义u-boot命令 及u-boot 链接脚本 .u_boot_cmd 段的理解
https://debugdump.com/t_1295.html
------------------------------------------------------------------

我们继续分析代码
怎么将内核拷贝到SDRAM
s = getenv ("bootcmd")//获取环境变量 bootcmd
run_command (s, 0) //执行环境变量对应的命令 nand read.jffs2 0x30007FC0 kernel;bootm 0x30007FC0

相当于执行了nand read.jffs2 0x30007FC0 kernel  和 bootm 0x30007FC0 两条命令

分析第一条命令前先分析一下什么是环境变量?

在u-boot 命令行下 输入 print 打印出来的就是一些常用的环境变量

pc 机每个硬盘上都有一个分区表 C 盘 D 盘 ...
嵌入式Linux 没有分区表,需要我们自己约定每个分区,一般NAND Flash 上的划分为:

bootloader||环境变量(参数)||kernel||文件系统(root)

由于没有分区表,所以这些分区在源码里写死的。在配置文件 100ask24x0.h 中

#define MTDPARTS_DEFAULT "mtdparts=nandflash0:256k@0(bootloader)," \    // 从0开始的256K是bootloader
                            "128k(params)," \    // 128K 是 params
                            "2m(kernel)," \        // 2M 是内核
                            "-(root)"            // 剩下的实际 root 文件系统
可以输入 u-boot 命令 mtd 查看
#: name                size        offset            mask_flags
0: boot_loader    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

在命令 nand read.jffs2 0x30007FC0 kernel 中 kernel也是一个环境变量  0x00200000  0x00060000
所以
nand read.jffs2 0x30007FC0 kernel
等价于
nand read.jffs2 0x30007FC0 0x00200000 0x00060000
意思是:从NAND Flash 的 0x00060000 位置读取 0x00200000(2M) 的数据到 SDRAM 的 0x30007FC0

名字kernel 不重要 重要的是他对应的地址
nand read.jffs2 0x30007FC0 kernel
nand read.jffs2 0x30007FC0 0x00200000 0x00060000
执行,是一样的效果。

由命令 nand read.jffs2 0x30007FC0 kernel 可以知道 是 nand 命令实现的烧写,所以我们分析 nand 命令对应的函数 do_nand

do_nand 函数里面的 !strcmp(s, ".jffs2") || !strcmp(s, ".e") 进而调用 ret = nand_read_opts(nand, &opts); 拷贝 内核到 SDRAM

jffs2 是一种文件格式,这里跟文件格式没有关系,只是用这个命令,对页对齐 块对齐没有限制


怎么调用内核? 
bootm 0x30007FC0
分析 do_bootm

先来说明一下内核的格式

uimage: 头 + 真正的内核

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;

bootm 启动内核分析内核当前地址是否等于加载地址,如果不等于加载地址,则把内核移动到加载地址,然后跳到内核入口去启动内核。
我们所用内核的加载地址是0x30008000
0x30008000 - 0x30007FC0 = 64 字节
64 字节刚好等于 内核头接结构 image_header_t 的长度,所以内核不用从新搬运,从而提高启动速度。

if(ntohl(hdr->ih_load) == data) ,位置相同,不搬运

继续讲怎么启动内核

1. uboot 通过特定数据格式(tag)告诉内核一些参数
     setup_serial_tag (&params);//起始标签
     setup_memory_tags (bd);//内存标签 告诉内核可用内存个数和大小
     setup_commandline_tag (bd, commandline);//命令标签
     setup_end_tag (bd);//结束标签
2. 跳转到内核入口地址
    void (*theKernel)(int zero, int arch, uint params);//定义函数指针
    theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);//获取内核入口
    theKernel (0, bd->bi_arch_number, bd->bi_boot_params);//传递参数 启动内核

一些分析过程和对应的文件
----------------------------------------------------------
lib_arm.c
调用 do_bootm_linux  (cmdtp, flag, argc, argv, addr, len_ptr, verify);

查看 Armlinux.c 下的 do_bootm_linux 函数
设置启动参数:
#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

启动内核

void (*theKernel)(int zero, int arch, uint params);//定义函数指针

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);//获取内核入口

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);//传递参数 启动内核


uboot 启动完内核就退出了,怎么传递参数呢?
1. 在某个地址(开发版:0x30000100)按照双方约定的某种格式tag保存数据。


setup_start_tag (bd);    //起始标签
setup_memory_tags (bd); // 内存的块数起始地址和大小
setup_commandline_tag (bd, commandline);    //传递命令行
setup_end_tag (bd);    //结束标签

setup_start_tag lib_arm.c
static void setup_start_tag (bd_t *bd)
{
    params = (struct tag *) bd->bi_boot_params; // 100ask24x0.c (board\100ask24x0):    gd->bd->bi_boot_params = 0x30000100;

    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);    // #define tag_next(t)    ((struct tag *)((u32 *)(t) + (t)->hdr.size))
}
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;
};


struct tag_core {
    u32 flags;        /* bit 0 = read-only */
    u32 pagesize;
    u32 rootdev;
};

struct tag_header {
    u32 size;
    u32 tag;
};

联合体与结构体的区别:
联合体内的成员共用同一个内存,占用内存空间为最大成员所占用的空间。同一时间联合体内部只能存放其中一个成员。
结构体每个成员都占用一定的内存空间。同一时间结构体内部可以存放多个成员。


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;

关于好多地方用到的gd 全局变量  搜索 gd_t *gd
#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

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;


setup_start_tag 在内存中的排列

U32        u.core.rootdev = 0                        0x30000110
U32        u.core.pagesize = 0                        0x3000010C
U32        u.core.flags=0                            0x30000108
U32        hdr.size = tag_size (tag_core) = 5        0x30000104                    #define tag_size(type)    ((sizeof(struct tag_header) + sizeof(struct type)) >> 2) // 右移2 等于 /4
U32        hdr.tag  = ATAG_CORE=(0x54410001)        0x30000100



setup_memory_tags (bd);
获取内存块数、起始地址、大小

setup_commandline_tag (bd, commandline)
char *commandline = getenv ("bootargs");// 来自于环境变量 bootargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0

----------------------------------------------------------

最近编辑记录 xinxiaoci (2018-06-12 19:34:43)

离线

快速回复

填写内容后点击按钮提交

页脚