本文主要探讨210的uboot启动内核过程。
嵌入式系统状态启动
未上电时bootloader、kernel、rootfs以镜像形式存储在启动介质中(X210为iNand/SD卡),运行时搬运到DDR中
未上电时u-boot.bin,zImage,rootfs在SD卡中各自对应的分区中,启动时去对应分区寻找(分区表一致)
动态启动为从SD卡到DDR内存,并且运行启动代码进行硬/软件初始化
uboo在第一阶段重定位时将第二阶段(整个uboot镜像)加载到DDR的0xc3e00000地址处(uboot链接地址)
uboot启动内核时从SD卡读取内核到DDR地址是0x30008000(内核链接地址)
uboot启动内核:将内核搬移到DDR中,校验内核格式、CRC等,准备传参,跳转执行内核
启动内核主要函数是:do_boom,do_bootm_linux
uboot能启动的内核格式:zImage,uImage,fdt
image_header_t
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;
image_header_t *hdr;
uboot启动内核使用的数据结构(镜像头部信息)
内核启动
内核不能自主开机,uboot帮助内核重定位到链接地址(SD卡到DDR)并且给内核传递启动参数
210的iNand版本uboot使用movi命令完成内核重定位(movi read kernel 30008000),kernel为uboot中kernel分区
do_bootm(cmd_bootm.c)
int do_bootm (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
/* 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);
debug ("* kernel: cmdline image address = 0x%08lx\n", img_addr);
}
ulong load_addr = CFG_LOAD_ADDR; /* Default Load Address */
#define CFG_LOAD_ADDR MEMORY_BASE_ADDRESS /* default load address */
#define MEMORY_BASE_ADDRESS 0x30000000
启动命令:bootm 0x30008000,故do_boom的argc=2,argv[0]=bootm,argv[1]=0x30008000
启动命令:bootm,从CFG_LOAD_ADDR地址启动(x210_sd.h)
vmlinuz,zImage,uImage
uboot编译生成u-boot(elf格式),由u-boot用arm-linux-objcopy工具加工得到u-boot.bin(镜像)用来烧录
linux内核编译生成vmlinux或vmlinuz(几十M)(elf格式),用objcopy工具加工成烧录Image镜像(几M),对Image压缩,在image压缩后的文件前端附加解压缩代码构成zImage
uImage是由zImage加工得到,mkimage工具由zImage加工生成uImage给uboot启动,在zImage前加64字节头信息即可得到
zImage启动
#ifdef CONFIG_ZIMAGE_BOOT
#define LINUX_ZIMAGE_MAGIC 0x016f2818
/* 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);
debug ("* kernel: cmdline image address = 0x%08lx\n", img_addr);
}
if (*(ulong *)(addr + 9*4) == LINUX_ZIMAGE_MAGIC) {
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
CONFIG_ZIMAGE_BOOT(x210_sd.h)用宏控制编译支持zImage格式的内核启动
LINUX_ZIMAGE_MAGIC是魔数(0x016f2818)来表示zImage,即zImage格式镜像在头部存放该数作为标记辨别它是否是zImage(是否等于LINUX_ZIMAGE_MAGIC),若为在Image则对头部信息改造,用头信息初始化images,完成了校验
after_header_check
#if defined(CONFIG_ZIMAGE_BOOT)
after_header_check:
os = hdr->ih_os;
#endif
switch (os) {
default: /* handled by (original) Linux case */
case IH_OS_LINUX:
#ifdef CONFIG_SILENT_CONSOLE
fixup_silent_linux();
#endif
do_bootm_linux (cmdtp, flag, argc, argv, &images);
break;
case IH_OS_NETBSD:
do_bootm_netbsd (cmdtp, flag, argc, argv, &images);
break;
#ifdef CONFIG_LYNXKDI
case IH_OS_LYNXOS:
do_bootm_lynxkdi (cmdtp, flag, argc, argv, &images);
break;
#endif
case IH_OS_RTEMS:
do_bootm_rtems (cmdtp, flag, argc, argv, &images);
break;
#if defined(CONFIG_CMD_ELF)
case IH_OS_VXWORKS:
do_bootm_vxworks (cmdtp, flag, argc, argv, &images);
break;
case IH_OS_QNX:
do_bootm_qnxelf (cmdtp, flag, argc, argv, &images);
break;
#endif
#ifdef CONFIG_ARTOS
case IH_OS_ARTOS:
do_bootm_artos (cmdtp, flag, argc, argv, &images);
break;
#endif
}
show_boot_progress (-9);
#ifdef DEBUG
puts ("\n## Control returned to monitor - resetting...\n");
do_reset (cmdtp, flag, argc, argv);
#endif
if (iflag)
enable_interrupts();
return 1;
}
确定image类型,依据类型进行头信息校验,校验通过则准备启动内核
uImage启动和设备数启动
/* get kernel image header, start address and length */
os_hdr = boot_get_kernel (cmdtp, flag, argc, argv,
&images, &os_data, &os_len);
if (os_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:
type = image_get_type (os_hdr);
comp = image_get_comp (os_hdr);
os = image_get_os (os_hdr);
image_end = image_get_image_end (os_hdr);
load_start = 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, &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, &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, &os)) {
puts ("Can't get image OS!\n");
show_boot_progress (-111);
return 1;
}
break;
#endif
default:
puts ("ERROR: unknown image format type!\n");
return 1;
}
image_start = (ulong)os_hdr;
load_end = 0;
type_name = genimg_get_type_name (type);
static void *boot_get_kernel (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
bootm_headers_t *images, ulong *os_data, ulong *os_len)
{
image_header_t *hdr;
ulong img_addr;
#if defined(CONFIG_FIT)
void *fit_hdr;
const char *fit_uname_config = NULL;
const char *fit_uname_kernel = NULL;
const void *data;
size_t len;
int cfg_noffset;
int os_noffset;
#endif
/* find out kernel image address */
if (argc < 2) {
img_addr = load_addr;
debug ("* kernel: default image load address = 0x%08lx\n",
load_addr);
#if defined(CONFIG_FIT)
} else if (fit_parse_conf (argv[1], load_addr, &img_addr,
&fit_uname_config)) {
debug ("* kernel: config '%s' from image at 0x%08lx\n",
fit_uname_config, img_addr);
} else if (fit_parse_subimage (argv[1], load_addr, &img_addr,
&fit_uname_kernel)) {
debug ("* kernel: subimage '%s' from image at 0x%08lx\n",
fit_uname_kernel, img_addr);
#endif
} else {
img_addr = simple_strtoul(argv[1], NULL, 16);
debug ("* kernel: cmdline image address = 0x%08lx\n", img_addr);
}
show_boot_progress (1);
/* copy from dataflash if needed */
img_addr = genimg_get_image (img_addr);
/* check image type, for FIT images get FIT kernel node */
*os_data = *os_len = 0;
switch (genimg_get_format ((void *)img_addr)) {
case IMAGE_FORMAT_LEGACY:
printf ("## Booting kernel from Legacy Image at %08lx ...\n",
img_addr);
hdr = image_get_kernel (img_addr, images->verify);
if (!hdr)
return NULL;
show_boot_progress (5);
/* get os_data and os_len */
switch (image_get_type (hdr)) {
case IH_TYPE_KERNEL:
*os_data = image_get_data (hdr);
*os_len = image_get_data_size (hdr);
break;
case IH_TYPE_MULTI:
image_multi_getimg (hdr, 0, os_data, os_len);
break;
default:
printf ("Wrong Image Type for %s command\n", cmdtp->name);
show_boot_progress (-5);
return NULL;
}
/*
* copy image header to allow for image overwrites during kernel
* decompression.
*/
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;
show_boot_progress (6);
break;
#if defined(CONFIG_FIT)
case IMAGE_FORMAT_FIT:
fit_hdr = (void *)img_addr;
printf ("## Booting kernel from FIT Image at %08lx ...\n",
img_addr);
if (!fit_check_format (fit_hdr)) {
puts ("Bad FIT kernel image format!\n");
show_boot_progress (-100);
return NULL;
}
show_boot_progress (100);
if (!fit_uname_kernel) {
/*
* no kernel image node unit name, try to get config
* node first. If config unit node name is NULL
* fit_conf_get_node() will try to find default config node
*/
show_boot_progress (101);
cfg_noffset = fit_conf_get_node (fit_hdr, fit_uname_config);
if (cfg_noffset < 0) {
show_boot_progress (-101);
return NULL;
}
/* save configuration uname provided in the first
* bootm argument
*/
images->fit_uname_cfg = fdt_get_name (fit_hdr, cfg_noffset, NULL);
printf (" Using '%s' configuration\n", images->fit_uname_cfg);
show_boot_progress (103);
os_noffset = fit_conf_get_kernel_node (fit_hdr, cfg_noffset);
fit_uname_kernel = fit_get_name (fit_hdr, os_noffset, NULL);
} else {
/* get kernel component image node offset */
show_boot_progress (102);
os_noffset = fit_image_get_node (fit_hdr, fit_uname_kernel);
}
if (os_noffset < 0) {
show_boot_progress (-103);
return NULL;
}
printf (" Trying '%s' kernel subimage\n", fit_uname_kernel);
show_boot_progress (104);
if (!fit_check_kernel (fit_hdr, os_noffset, images->verify))
return NULL;
/* get kernel image data address and length */
if (fit_image_get_data (fit_hdr, os_noffset, &data, &len)) {
puts ("Could not find kernel subimage data!\n");
show_boot_progress (-107);
return NULL;
}
show_boot_progress (108);
*os_len = len;
*os_data = (ulong)data;
images->fit_hdr_os = fit_hdr;
images->fit_uname_os = fit_uname_kernel;
images->fit_noffset_os = os_noffset;
break;
#endif
default:
printf ("Wrong Image Format for %s command\n", cmdtp->name);
show_boot_progress (-108);
return NULL;
}
debug (" kernel data at 0x%08lx, len = 0x%08lx (%ld)\n",
*os_data, *os_len, *os_len);
return (void *)img_addr;
}
IMAGE_FORMAT_LEGACY为uImage启动方式,启动校验在boot_get_kernel函数:校验uImage头信息,得kernel起始位置去启动
CONFIG_FIT:设备树方式启动
do_bootm_linux(zImage)
void do_bootm_linux (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[],
bootm_headers_t *images)
{
ulong initrd_start, initrd_end;
ulong ep = 0;
bd_t *bd = gd->bd;
char *s;
int machid = bd->bi_arch_number;
void (*theKernel)(int zero, int arch, uint params);
int ret;
#ifdef CONFIG_CMDLINE_TAG
char *commandline = getenv ("bootargs");
#endif
/* find kernel entry point */
if (images->legacy_hdr_valid) {
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, &ep);
if (ret) {
puts ("Can't get entry point property!\n");
goto error;
}
#endif
} else {
puts ("Could not find kernel entry point!\n");
goto error;
}
theKernel = (void (*)(int, int, uint))ep;
s = getenv ("machid");
if (s) {
machid = simple_strtoul (s, NULL, 16);
printf ("Using machid 0x%x from environment\n", machid);
}
ret = boot_get_ramdisk (argc, argv, images, IH_ARCH_ARM,
&initrd_start, &initrd_end);
if (ret)
goto error;
show_boot_progress (15);
debug ("## Transferring control to Linux (at address %08lx) ...\n",
(ulong) theKernel);
#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) || \
defined (CONFIG_MTDPARTITION)
setup_start_tag (bd);
#ifdef CONFIG_SERIAL_TAG
setup_serial_tag (¶ms);
#endif
#ifdef CONFIG_REVISION_TAG
setup_revision_tag (¶ms);
#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
#ifdef CONFIG_MTDPARTITION
setup_mtdpartition_tag();
#endif
setup_end_tag (bd);
#endif
/* we assume that the kernel is in place */
printf ("\nStarting kernel ...\n\n");
#ifdef CONFIG_USB_DEVICE
{
extern void udc_disconnect (void);
udc_disconnect ();
}
#endif
cleanup_before_linux ();
theKernel (0, machid, bd->bi_boot_params);
/* does not return */
return;
error:
do_reset (cmdtp, flag, argc, argv);
return;
}
ulong ep = 0;ep(entrypoint)是程序入口,镜像文件起始执行部分不在镜像开头,在镜像开头某个字节处(有偏移量)
运行镜像过程:读取头信息MAGIC_NUM来确定镜像种类,镜像校验,读取头信息获取镜像信息(镜像长度,种类,入口地址),去entrypoint处运行镜像
theKernel = (void (*)(int, int, uint))ep;将ep赋值给theKernel,函数指向OS镜像真正入口地址(操作系统运行首代码)。
int machid = bd->bi_arch_number;uboot启动内核时,机器码传给内核,uboot通过全局变量gd->bd->bi_arch_num将x210_sd.h定义的机器码传给uboot
函数(110-144)是uboot给linux内核传递参数处理
Starting kernel标志uboot加载内核镜像,校验通过,获得内核入口地址
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;
struct tag_mtdpart mtdpart_info;
} u;
};
struct tag_header {
u32 size;
u32 tag;
};
static struct tag *params;
tag_header中有tag的size和类型编码
tag函数参宏
ATAG_NONE tag_header tag结束
ATAG_CORE tag_core
ATAG_MEM tag_mem32 内存配置信息
ATAG_VIDEOTEXT tag_videotext
ATAG_RAMDISK tag_ramdisk
ATAG_INITRD tag_initrd
ATAG_SERIAL tag_serialnr
ATAG_REVISION tag_revision
ATAG_VIDEOLFB tag_videolfb
ATAG_CMDLINE tag_cmdline 启动命令参数(的bootargs)
ATAG_ACORN tag_acorn tag起始
ATAG_MEMCLK tag_memclk
ATAG_MTDPART tag_mtdpart iNand/SD卡分区表
/* The list must start with an ATAG_CORE node */
#define ATAG_CORE 0x54410001
struct tag_core {
u32 flags; /* bit 0 = read-only */
u32 pagesize;
u32 rootdev;
};
theKernel (0, machid, bd->bi_boot_params);
/* does not return */
uboot调用theKernel函数来运行linux内核,uboot调用时传递3个参数:(0,机器码,tag首地址)
demo:
编译uImage
ubuntu:
root@kaxi-virtual-machine:~/qt_x210v3s/kernel# cat mk
#!/bin/sh
#QT_KERNEL_CONFIG=x210_jffs_defconfig
QT_KERNEL_CONFIG=x210ii_qt_defconfig
CPU_NUM=$(cat /proc/cpuinfo |grep processor|wc -l)
CPU_NUM=$((CPU_NUM+1))
make distclean
make clean
make ${QT_KERNEL_CONFIG}
make -j${CPU_NUM}
cd qt_x210v3s/kernel
vim Makefile
CROSS_COMPILE ?= /root/arm-2009q3/bin/arm-none-linux-gnueabi-
make clean &> /dev/null && make distclean &> /dev/null
cp ../uboot/tools/mkimage /usr/local/bin/
vim mk
QT_KERNEL_CONFIG=x210ii_qt_defconfig
生成zImage
./mk &>/dev/null
make uImage &>/dev/null
cp arch/arm/boot/uImage /root/tftp
uboot:
tftp 30008000 uImage
movi read kernel 30008000
bootm 30008000
结果示例: