2.6. uboot向kernel的传参机制–bootm与tags
u-boot在启动内核时,会向内核传递一些参数,可以通过两种方式传递参数给内核,一种是通过参数链表(ttagged list)方式,一种是通过设备树方式。这些参数主要包括 系统的根设备标志,页面大小,内存的起始地址和大小等参数。
2.6.1. struct tag传递参数
u-boot把要传递给kernel的参数保存在struct tag数据结构中,启动内核时,把这个结构体的物理地址传递给kernel,kernel会通过这个地址分析出u-boot传递的参数
2.6.1.1. 主要数据结构介绍
首先看一下两个比较重要的数据结构 struct global_data
和 struct tag
struct global_data
该数据结构定义在 include/asm-generic/global_data.h 中,其中包含了体系相关的数据结构 struct arch_global_data arch
typedef struct global_data {
bd_t *bd;
unsigned long flags;
unsigned int baudrate;
unsigned long cpu_clk; /* CPU clock in Hz! */
unsigned long bus_clk;
/* We cannot bracket this with CONFIG_PCI due to mpc5xxx */
unsigned long pci_clk;
unsigned long mem_clk;
#if defined(CONFIG_LCD) || defined(CONFIG_VIDEO)
unsigned long fb_base; /* Base address of framebuffer mem */
#endif
#if defined(CONFIG_POST)
unsigned long post_log_word; /* Record POST activities */
unsigned long post_log_res; /* success of POST test */
unsigned long post_init_f_time; /* When post_init_f started */
#endif
#ifdef CONFIG_BOARD_TYPES
unsigned long board_type;
#endif
unsigned long have_console; /* serial_init() was called */
#if CONFIG_IS_ENABLED(PRE_CONSOLE_BUFFER)
unsigned long precon_buf_idx; /* Pre-Console buffer index */
#endif
unsigned long env_addr; /* Address of Environment struct */
unsigned long env_valid; /* Environment valid? enum env_valid */
unsigned long env_has_init; /* Bitmask of boolean of struct env_location offsets */
int env_load_prio; /* Priority of the loaded environment */
unsigned long ram_base; /* Base address of RAM used by U-Boot */
unsigned long ram_top; /* Top address of RAM used by U-Boot */
unsigned long relocaddr; /* Start address of U-Boot in RAM */
phys_size_t ram_size; /* RAM size */
unsigned long mon_len; /* monitor len */
unsigned long irq_sp; /* irq stack pointer */
unsigned long start_addr_sp; /* start_addr_stackpointer */
unsigned long reloc_off;
struct global_data *new_gd; /* relocated global data */
#ifdef CONFIG_DM
struct udevice *dm_root; /* Root instance for Driver Model */
struct udevice *dm_root_f; /* Pre-relocation root instance */
struct list_head uclass_root; /* Head of core tree */
#endif
#ifdef CONFIG_TIMER
struct udevice *timer; /* Timer instance for Driver Model */
#endif
const void *fdt_blob; /* Our device tree, NULL if none */
void *new_fdt; /* Relocated FDT */
unsigned long fdt_size; /* Space reserved for relocated FDT */
#ifdef CONFIG_OF_LIVE
struct device_node *of_root;
#endif
struct jt_funcs *jt; /* jump table */
char env_buf[32]; /* buffer for env_get() before reloc. */
#ifdef CONFIG_TRACE
void *trace_buff; /* The trace buffer */
#endif
#if defined(CONFIG_SYS_I2C)
int cur_i2c_bus; /* current used i2c bus */
#endif
#ifdef CONFIG_SYS_I2C_MXC
void *srdata[10];
#endif
unsigned int timebase_h;
unsigned int timebase_l;
#if CONFIG_VAL(SYS_MALLOC_F_LEN)
unsigned long malloc_base; /* base address of early malloc() */
unsigned long malloc_limit; /* limit address */
unsigned long malloc_ptr; /* current address */
#endif
#ifdef CONFIG_PCI
struct pci_controller *hose; /* PCI hose for early use */
phys_addr_t pci_ram_top; /* top of region accessible to PCI */
#endif
#ifdef CONFIG_PCI_BOOTDELAY
int pcidelay_done;
#endif
struct udevice *cur_serial_dev; /* current serial device */
struct arch_global_data arch; /* architecture-specific data */
#ifdef CONFIG_CONSOLE_RECORD
struct membuff console_out; /* console output */
struct membuff console_in; /* console input */
#endif
#ifdef CONFIG_DM_VIDEO
ulong video_top; /* Top of video frame buffer area */
ulong video_bottom; /* Bottom of video frame buffer area */
#endif
#ifdef CONFIG_BOOTSTAGE
struct bootstage_data *bootstage; /* Bootstage information */
struct bootstage_data *new_bootstage; /* Relocated bootstage info */
#endif
#ifdef CONFIG_LOG
int log_drop_count; /* Number of dropped log messages */
int default_log_level; /* For devices with no filters */
struct list_head log_head; /* List of struct log_device */
int log_fmt; /* Mask containing log format info */
#endif
} gd_t;
在 arch/arm/include/asm/global_data.h
#ifdef CONFIG_ARM64
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("x18")
#else
#define DECLARE_GLOBAL_DATA_PTR register volatile gd_t *gd asm ("r9")
#endif
#endif
在需要使用gd指针的时候,只需要加入DECLARE_GLOBAL_DATA_PTR这句话就可以了,可以知道gd指针始终是放在r9或者x18中的
其中的第一个变量,bd_t结构体,定义在 include/asm-generic/u-boot.h 中
typedef struct bd_info {
unsigned long bi_memstart; /* start of DRAM memory */
phys_size_t bi_memsize; /* size of DRAM memory in bytes */
unsigned long bi_flashstart; /* start of FLASH memory */
unsigned long bi_flashsize; /* size of FLASH memory */
unsigned long bi_flashoffset; /* reserved area for startup monitor */
unsigned long bi_sramstart; /* start of SRAM memory */
unsigned long bi_sramsize; /* size of SRAM memory */
#ifdef CONFIG_ARM
unsigned long bi_arm_freq; /* arm frequency */
unsigned long bi_dsp_freq; /* dsp core frequency */
unsigned long bi_ddr_freq; /* ddr frequency */
#endif
#if defined(CONFIG_MPC8xx) || defined(CONFIG_E500) || defined(CONFIG_MPC86xx)
unsigned long bi_immr_base; /* base of IMMR register */
#endif
#if defined(CONFIG_M68K)
unsigned long bi_mbar_base; /* base of internal registers */
#endif
#if defined(CONFIG_MPC83xx)
unsigned long bi_immrbar;
#endif
unsigned long bi_bootflags; /* boot / reboot flag (Unused) */
unsigned long bi_ip_addr; /* IP Address */
unsigned char bi_enetaddr[6]; /* OLD: see README.enetaddr */
unsigned short bi_ethspeed; /* Ethernet speed in Mbps */
unsigned long bi_intfreq; /* Internal Freq, in MHz */
unsigned long bi_busfreq; /* Bus Freq, in MHz */
#if defined(CONFIG_CPM2)
unsigned long bi_cpmfreq; /* CPM_CLK Freq, in MHz */
unsigned long bi_brgfreq; /* BRG_CLK Freq, in MHz */
unsigned long bi_sccfreq; /* SCC_CLK Freq, in MHz */
unsigned long bi_vco; /* VCO Out from PLL, in MHz */
#endif
#if defined(CONFIG_M68K)
unsigned long bi_ipbfreq; /* IPB Bus Freq, in MHz */
unsigned long bi_pcifreq; /* PCI Bus Freq, in MHz */
#endif
#if defined(CONFIG_EXTRA_CLOCK)
unsigned long bi_inpfreq; /* input Freq in MHz */
unsigned long bi_vcofreq; /* vco Freq in MHz */
unsigned long bi_flbfreq; /* Flexbus Freq in MHz */
#endif
#ifdef CONFIG_HAS_ETH1
unsigned char bi_enet1addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH2
unsigned char bi_enet2addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH3
unsigned char bi_enet3addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH4
unsigned char bi_enet4addr[6]; /* OLD: see README.enetaddr */
#endif
#ifdef CONFIG_HAS_ETH5
unsigned char bi_enet5addr[6]; /* OLD: see README.enetaddr */
#endif
ulong bi_arch_number; /* unique id for this board */
ulong bi_boot_params; /* where this board expects params */
#ifdef CONFIG_NR_DRAM_BANKS
struct { /* RAM configuration */
phys_addr_t start;
phys_size_t size;
} bi_dram[CONFIG_NR_DRAM_BANKS];
#endif /* CONFIG_NR_DRAM_BANKS */
} bd_t;
bd_t中的变量 bi_boot_params 表示传递给内核的参数的位置,u-boot中命令bdinfo也是查看该结构体的值
2.6.1.2. global data介绍及背后的思考
要理解global data的意义,需要理解以下的事实:
u-boot是一个bootloader,有些情况下他可能位于系统的只读存储器(rom或者flash)中,并从那里开始执行。因此,这种情况下,在uboot执行的前期(在将自己copy到可读写的存储器之前)它所在 的存储空间是不可写的,这会有两个问题:
堆栈无法使用,无法执行函数调用,也就是说C环境不可用
没有data段(或正确初始化的data段)可用,不同函数或者代码之间,无法通过全局变量的形式共享数据
针对问题一,通常的解决方案是:
u-boot运行起来之后,在那些不需要执行任何初始化动作即可使用的、可读写的存储区域开辟一段堆栈(stack)空间。一般来说,大部分的平台都有自己的sram可用作堆栈空间,如果实在不行,也可借用 CPU的data cache方法
针对问题二,解决方案要稍微复杂一些
首先对于开发者而言,在u-boot被拷贝到可读写的ram(这个动作被称作relacation)之前,永远不要使用全局变量,其次在relocation之前不同模块之间确实有通过全局变量传递数据的需求怎么办,这就是 global data需要解决的事情。。
为了在relocation之前通过全局变量的形式传递数据,u-boot设计了一个巧妙的方法:在堆栈配置好之后,在堆栈开始的位置,为struct global_data预留空间,并将开始地址(就是一个struct global_data指针)保存在 一个寄存器中,后续的传递,都是通过保存在寄存器中的指针实现。对于arm64平台而言,该指针保存在了X18寄存器中
在 arch/arm/lib/crt0_64.S 中有如下实现
bl board_init_f_alloc_reserve //board_init_f_alloc_reserve的返回值(x0)就是global data的指针
mov sp,x0
/* set up gd here, outside any C code */
mv x18,x0
bl board_init_init_reserve
内核参数链表的格式和说明可以从内核源码目录 arch/arm/include/asm/setup.h 中找到,链表必须以ATAG_CORE开始,以ATAG_NONE结束
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_ramdisk {
u32 flags; /* bit 0 = load, bit 1 = prompt */
u32 size; /* decompressed ramdisk size in _kilo_ bytes */
u32 start; /* starting block of floppy-based RAM disk image */
};
struct tag_cmdline {
char cmdline[1]; /* this is the minimum size */
};
...
参数结构包括两个部分,一个是tag_header, 一个是u联合体
tag_header结构体定义如下,
struct tag_header {
u32 size; //表示整个tag结构体的大小,用字的个数表示而不是字节,等于tag_header加上u联合体的大小
u32 tag; //整个tag结构体的标记,如ATAG_CORE
};
现在来分析boot_prep_linux函数
/* Subcommand: PREP */
static void boot_prep_linux(bootm_headers_t *images)
{
char *commandline = env_get("bootargs"); //获取环境变量bootargs,这就是要传递给kernel的参数
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) {
#ifdef CONFIG_OF_LIBFDT
debug("using: FDT\n");
if (image_setup_linux(images)) {
printf("FDT creation failed! hanging...");
hang();
}
#endif
} else if (BOOTM_ENABLE_TAGS) {
debug("using: ATAGS\n");
setup_start_tag(gd->bd); //通过bd结构体中的参数在内存中的存放位置gd->bd->bi_boot_params来构建初始化tag结构,
//表明参数结构的开始
if (BOOTM_ENABLE_SERIAL_TAG)
setup_serial_tag(¶ms); //构建串口参数的tag结构
if (BOOTM_ENABLE_CMDLINE_TAG)
setup_commandline_tag(gd->bd, commandline); //构建命令行参数的tag结构
if (BOOTM_ENABLE_REVISION_TAG)
setup_revision_tag(¶ms);
if (BOOTM_ENABLE_MEMORY_TAGS)
setup_memory_tags(gd->bd);
if (BOOTM_ENABLE_INITRD_TAG) {
/*
* In boot_ramdisk_high(), it may relocate ramdisk to
* a specified location. And set images->initrd_start &
* images->initrd_end to relocated ramdisk's start/end
* addresses. So use them instead of images->rd_start &
* images->rd_end when possible.
*/
if (images->initrd_start && images->initrd_end) {
setup_initrd_tag(gd->bd, images->initrd_start,
images->initrd_end);
} else if (images->rd_start && images->rd_end) {
setup_initrd_tag(gd->bd, images->rd_start,
images->rd_end);
}
}
setup_board_tags(¶ms);
setup_end_tag(gd->bd);
} else {
printf("FDT and ATAGS support not compiled in - hanging\n");
hang();
}
}
setup_start_tag
static void setup_start_tag (bd_t *bd)
{
params = (struct tag *)bd->bi_boot_params; //获取struct tag指针
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))
setup_commandline_tag
static void setup_commandline_tag(bd_t *bd, char *commandline)
{
char *p;
printf("set cmd line ...\n");
if (!commandline)
return;
/* eat leading white space */
for (p = commandline; *p == ' '; p++);
/* skip non-existent command lines so the kernel will still
* use its default command line.
*/
if (*p == '\0')
return;
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size =
(sizeof (struct tag_header) + strlen (p) + 1 + 4) >> 2;
strcpy (params->u.cmdline.cmdline, p);
params = tag_next (params);
}
2.6.2. 设备树传参
新版本的linux内核都采用设备树的方式进行板级设备信息描述,uboot在启动内核的时候需要将设备树的地址传递给kernel。跟以前的方式不同,采用设备树时 不再使用struct tag进行参数传递,设备数中本身有对需要传递参数的描述,uboot中将设备树进行解析并设置对应参数。kernel直接解析uboot传递的设备树并从中 找到要传递参数
boot_prep_linux函数中有两个主分支,定义设备树时运行分支一,没有定义时采用struct tag进行参数传递
int image_setup_linux(bootm_headers_t *images)
{
ulong of_size = images->ft_len;
char **of_flat_tree = &images->ft_addr;
struct lmb *lmb = &images->lmb;
int ret;
if (IMAGE_ENABLE_OF_LIBFDT)
boot_fdt_add_mem_rsv_regions(lmb, *of_flat_tree);
if (IMAGE_BOOT_GET_CMDLINE) {
ret = boot_get_cmdline(lmb, &images->cmdline_start,
&images->cmdline_end);
if (ret) {
puts("ERROR with allocation of cmdline\n");
return ret;
}
}
if (IMAGE_ENABLE_OF_LIBFDT) {
ret = boot_relocate_fdt(lmb, of_flat_tree, &of_size);
if (ret)
return ret;
}
if (IMAGE_ENABLE_OF_LIBFDT && of_size) {
ret = image_setup_libfdt(images, *of_flat_tree, of_size, lmb); //此函数中会调用fdt_chosen进行参数传递
if (ret)
return ret;
}
return 0;
}
int fdt_chosen(void *fdt)
{
int nodeoffset;
int err;
char *str; /* used to set string properties */
err = fdt_check_header(fdt);
if (err < 0) {
printf("fdt_chosen: %s\n", fdt_strerror(err));
return err;
}
/* find or create "/chosen" node. */
nodeoffset = fdt_find_or_add_subnode(fdt, 0, "chosen");
if (nodeoffset < 0)
return nodeoffset;
str = env_get("bootargs");
if (str) {
err = fdt_setprop(fdt, nodeoffset, "bootargs", str,
strlen(str) + 1);
if (err < 0) {
printf("WARNING: could not set bootargs %s.\n",
fdt_strerror(err));
return err;
}
}
return fdt_fixup_stdout(fdt, nodeoffset);
}