新闻资讯
Group news
青岛广盛源肥业有限公司    您的位置: 首页  >  新闻资讯  >  正文

你了解u-boot与linux内核间的参数传递过程?

2019年10月11日 文章来源:网络整理 热度:50℃ 作者:刘英

U-boot会给Linux Kernel传递很多参数,如:串口,RAM,videofb、MAC地址等。而Linux kernel也会读取和处理这些参数。两者之间通过struct tag来传递参数。U-boot把要传递给kernel的东西保存在struct tag数据结构中,启动kernel时,把这个结构体的物理地址传给kernel;Linux kernel通过这个地址,用parse_tags分析出传递过来的参数。

本文主要以U-boot(1.1.6)传递RAM和Linux kernel读取RAM参数为例进行说明。

1、u-boot给kernel传RAM参数

在介绍该之前,我们需要看一看几个数据结构,这些是u-boot中几个重要的数据结构:
(1)gd_t结构体
U-Boot使用了一个结构体gd_t来存储全局数据区的数据,这个结构体在U-Boot的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  //我们一般没有配置这个,这个是frame buffer的首地址
    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;

/*
 * Global Data Flags
 */
#define    GD_FLG_RELOC    0x00001        /* Code was relocated to RAM        */
#define    GD_FLG_DEVINIT    0x00002        /* Devices have been iniTIalized    */
#define    GD_FLG_SILENT    0x00004        /* Silent mode                */

#define DECLARE_GLOBAL_DATA_PTR     register volaTIle gd_t *gd asm ("r8")
    在global_data.h中U-Boot使用了一个存储在寄存器中的指针gd来记录全局数据区的地址:
#define DECLARE_GLOBAL_DATA_PTR     register volaTIle gd_t *gd asm ("r8")
    DECLARE_GLOBAL_DATA_PTR定义一个gd_t全局数据结构的指针,这个指针存放在指定的寄存器r8中。这个声明也避免编译器把r8分配给其它的变量。任何想要访问全局数据区的代码,只要代码开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,然后就可以使用gd指针来访问全局数据区了。
    根据U-Boot内存使用图中可以计算gd的值:
gd = TEXT_BASE -CONFIG_SYS_MALLOC_LEN - sizeof(gd_t)

你了解u-boot与linux内核间的参数传递过程?

2)bd_t 保存与板子相关的配置参数
bd_t在U-Boot的include/asm-arm/u-boot.h中定义如下:
typedef struct bd_info {
    int            bi_baudrate;    /* 串口通讯波特率 */
    unsigned long    bi_ip_addr;    /* IP地址 */
    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配置信息 */
    {
    ulong start;
    ulong size;
    }     bi_dram[CONFIG_NR_DRAM_BANKS]; //在我的板子上DRAM配置是1个
#ifdef CONFIG_HAS_ETH1
    /* second onboard ethernet port */
    unsigned char   bi_enet1addr[6];
#endif
} bd_t;

#define bi_env_data bi_env->data
#define bi_env_crc  bi_env->crc
U-Boot启动内核时要给内核传递参数,这时就要使用gd_t,bd_t结构体中的信息来设置标记列表。
3)启动参数的数据结构
向内核传递启动参数可保存在两种数据结构中,param_struct和tag,前者是2.4内核用的,后者是2.6以后的内核更期望用的但是,到目前为止,2.6的内核也可以兼容前一种结构,内核参数通过一个静态的param_struct或tag链表在启动的时候传递到内核。需要注意的是,这两个数据结构在uboot中和linux中分别有定义,这个定义必须一致才能正常传递参数如果实际使用中不一致的话就不能正常传递,可以自行修改 两种数据结构具体定义如下(这里说的是内核源码中的定义): 
struct param_struct {
    union {
    struct {
        unsigned long page_size;        /*  0 */
        unsigned long nr_pages;        /*  4 */
        unsigned long ramdisk_size;        /*  8 */
        unsigned long flags;        /* 12 */
#define FLAG_READONLY    1
#define FLAG_RDLOAD    4
#define FLAG_RDPROMPT    8
        unsigned long rootdev;        /* 16 */
        unsigned long video_num_cols;    /* 20 */
        unsigned long video_num_rows;    /* 24 */
        unsigned long video_x;        /* 28 */
        unsigned long video_y;        /* 32 */
        unsigned long memc_control_reg;    /* 36 */
        unsigned char sounddefault;        /* 40 */
        unsigned char adfsdrives;        /* 41 */
        unsigned char bytes_per_char_h;    /* 42 */
        unsigned char bytes_per_char_v;    /* 43 */
        unsigned long pages_in_bank[4];    /* 44 */
        unsigned long pages_in_vram;    /* 60 */
        unsigned long initrd_start;        /* 64 */
        unsigned long initrd_size;        /* 68 */
        unsigned long rd_start;        /* 72 */
        unsigned long system_rev;        /* 76 */
        unsigned long system_serial_low;    /* 80 */
        unsigned long system_serial_high;    /* 84 */
        unsigned long mem_fclk_21285;       /* 88 */
    } s;
    char unused[256];
    } u1;
    union {
    char paths[8][128];
    struct {
        unsigned long magic;
        char n[1024 - sizeof(unsigned long)];
    } s;
    } u2;
    char commandline[COMMAND_LINE_SIZE];
};
param_struct只需要设置cmmandline,u1.s.page_size,u1.s.nr_pages三个域,下面是使用param_struct例子通过param_struct让uboot中的go命令可以传递参数
分析:go的代码在common/cmd_boot.c中,里面并没有拷贝启动参数的代码,转向内核的时候也没有传送
启动参数所在的地址,因此添加如下代码用于拷贝参数,可以看到,对于param_struct只需要设置cmmandline
u1.s.page_size,u1.s.nr_pages三个域 
        char *commandline = getenv("bootargs");
        struct param_struct *lxy_params=(struct param_struct *)0x80000100;
 
        printf("setup linux parameters at 0x80000100\n");
        memset(lxy_params,0,sizeof(struct param_struct));
        lxy_params->u1.s.page_size=(0x1>12; //64M 这个是必须有的,否则无法启动
        memcpy(lxy_params->commandline,commandline,strlen(commandline)+1);
        printf("linux command line is: \"%s\"\n",lxy_params->commandline);
然后还要向内核传递参数地址,将下面一行代码修改:
rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]);  //需要被修改的代码
rc = ((ulong(*)(int,int,uint))addr) (0, gd->bd->bi_arch_number,gd->bd->bi_boot_params);//修改之后的代码 
关于param_struct不是这里重点,下面主要分析tag

    对于tag来说,在实际使用中是一个struct tag组成的列表,在tag->tag_header中,一项是u32 tag(重名,注意类型)其值用宏ATAG_CORE,ATAG_MEM,ATAG_CMDLINE,ATAG_NONE等等来表示,此时下面union就会使用与之相关的数据结构同时,规定tag列表中第一项必须是ATAG_CORE,最后一项必须是ATAG_NONE,比如在linux代码中,找到启动参数之后首先看tag列表中的第一项的tag->hdr.tag是否为ATAG_CORE,如果不是,就会认为启动参数不是tag结构而是param_struct结构,然后调用函数来转换.在tag->tag_header中,另一项是u32 size,表示tag的大小,tag组成列表的方式就是指针+size
tag数据结构在arch/arm/include/asm/setup.h(U-Boot的在include/asm-arm/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_header为tag头,表明tag_xxx的类型和大小,之所以要标识tag_xxx的类型是因为不同的tag需要不同的处理函数
内核tag_header的结构(arch/arm/include/asm/setup.h)为
struct tag_header {
        __u32 size;
        __u32 tag;
};
U-Boot的在include/asm-arm/setup.h定义
struct tag_header {
    u32 size;
    u32 tag;
};
size表示tag的结构大小,tag为表示tag类型的常量。这个静态的链表必须以tag_header.tag = ATAG_CORE开始,并以tag_header.tag = ATAG_NONE结束。由于不同的tag所使用的格式可能不尽相同,所以内核又定义了一个结构tagtable来把tag和相应的操作函数关联起来
(arch/arm/include/asm/setup.h)
struct tagtable {
        __u32 tag;
        int (*parse)(const struct tag *);
};
其中tag为标识入ATAG_NONE,ATAG_CORE等。parse为处理函数。Linux内核将tagtable也组成了一个静态的链表放入.taglist.init节中,这是通过__tagtable宏来实现的 
#define __tag __used __attribute__((__secTIon__(".taglist.init")))
#define __tagtable(tag, fn) \
static struct tagtable __tagtable_##fn __tag = { tag, fn }
这个tagtable 列表 是怎么形成的?
如arch/arm/kernel/setup.c
556 static int __init parse_tag_mem32(const struct tag *tag)
557 {
558         return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
559 }
560 
561 __tagtable(ATAG_MEM, parse_tag_mem32);

607 __tagtable(ATAG_SERIAL, parse_tag_serialnr);
608 
609 static int __init parse_tag_revision(const struct tag *tag)
610 {
611         system_rev = tag->u.revision.rev;
612         return 0;
613 }
614 
615 __tagtable(ATAG_REVISION, parse_tag_revision);

618 static int __init parse_tag_cmdline(const struct tag *tag)
619 {
620         strlcpy(default_command_line, tag->u.cmdline.cmdline, COMMAND_LINE_SIZE);
621         return 0;
622 }
623 
624 __tagtable(ATAG_CMDLINE, parse_tag_cmdline);
根据前面相关宏定义,__tagtable(ATAG_CMDLINE, parse_tag_cmdline)展开后为
static struct tagtable __tagtable_parse_tag_cmdline __used __attribute__((__section__(".taglist.init"))) = { ATAG_CMDLINE, parse_tag_cmdline }
__tagtable将ATAG_CMDLINE和parse_tag_cmdline挂钩,
再参看arch/arm/kernel/vmlinux.lds.S文件
 34                 __proc_info_begin = .;
 35                         *(.proc.info.init)
 36                 __proc_info_end = .;
 37                 __arch_info_begin = .;
 38                         *(.arch.info.init)
 39                 __arch_info_end = .;
 40                 __tagtable_begin = .;
 41                         *(.taglist.init)
 42                 __tagtable_end = .;
tagtable 列表编译连接后被存放在.taglist.init中。


    现在再来看一下U-boot给Linux Kernel传递启动参数的传递过程
    启动参数是包装在struct tag数据结构里的,在linux kernel启动的时候,bootloader把这个数据结构拷贝到某个地址,在改动PC跳向内核接口的同时,通过通用寄存器R2来传递这个地址的值,在bootm执行的流程中,会调用do_bootm_linux()在执行Linux内核,内核的起始地址如下:
void (*theKernel)(int zero, int arch, uint params);
image_header_t *hdr = &header;
theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);
header是uImage的头部,通过头部,得到内核映像起始的执行地址,标识为theKernel。从中也可以看到,内核接受三个参数,第一个为0,第二个为系统的ID号,第三个是传入内核的参数。
在do_bootm_linux()的最后,会跳到内核去执行:
       theKernel (0, bd->bi_arch_number, bd->bi_boot_params);
thekernel其实不是个函数,而是指向内核入口地址的指针,把它强行转化为带三个参数的函数指针,会把三个
参数保存到通用寄存器中,实现了向kernel传递信息的功能,在这个例子里,把R0赋值为0,R1赋值为机器号bd->bi_arch_number, R2赋值为启动参数数据结构的首地址bd->bi_boot_params。最后两个参数在board/smdk2410/smdk2410.c的board_init()中被初始化。
    因此,要向内核传递参数很简单,只要把启动参数封装在linux预定好的数据结构里,拷贝到某个地址(一般
约定俗成是内存首地址+100dex,后面会见到)  p { margin-bottom: 0.21cm; }

U-boot向内核传递参数的具体实现过程

上一篇:需要了解的Linux模块编程框架


下一篇:Linux输入子系统:事件的编码

友情链接
Links