----------------------------------------------------------------------------------------------------------------------------
内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------
一、linux内核启动
我们回顾一下uboot引导linux内核启动过程, uboot通过执行bootcmd命令启动内核:
bootcmd="nand read 0x30000000 kernel; bootm 0x30000000"
uboot首先将内核镜像从NAND拷贝到内存地址0x30000000,然后执行bootm 0x30000000命令。
1.1 bootm命令
bootm这个命令用于启动一个操作系统映射,实际上调用的是do_bootm_linux函数,还函数位于arch/arm/lib/bootm.c文件:
/* Main Entry point for arm bootm implementation * * Modeled after the powerpc implementation * DIFFERENCE: Instead of calling prep and go at the end * they are called if subcommand is equal 0. */ int do_bootm_linux(int flag, int argc, char * const argv[], bootm_headers_t *images) { /* No need for those on ARM */ if (flag & BOOTM_STATE_OS_BD_T || flag & BOOTM_STATE_OS_CMDLINE) // 不执行 return -1; if (flag & BOOTM_STATE_OS_PREP) { // 执行 boot_prep_linux(images); return 0; } if (flag & (BOOTM_STATE_OS_GO | BOOTM_STATE_OS_FAKE_GO)) { // 执行 boot_jump_linux(images, flag); return 0; } boot_prep_linux(images); boot_jump_linux(images, flag); return 0; }
boot_prep_linux函数跟内核传递参数有关系,为内核设置启动参数,u-boot向内核传递参数就是在这里做的准备:
boot_jump_linux会将tags的首地址也就是gd->bd->bi_boot_params传给内核,并跳转到内核地址、启动内核,让内核解析这些tag。
1.2 boot_jump_linux
/* Subcommand: GO */ static void boot_jump_linux(bootm_headers_t *images, int flag) { unsigned long machid = gd->bd->bi_arch_number; // 获取机器码 char *s; void (*kernel_entry)(int zero, int arch, uint params); // 内核入口函数 unsigned long r2; int fake = (flag & BOOTM_STATE_OS_FAKE_GO); kernel_entry = (void (*)(int, int, uint))images->ep; // 指定为内核入口地址 s = getenv("machid"); if (s) { if (strict_strtoul(s, 16, &machid) < 0) { debug("strict_strtoul failed!\n"); return; } printf("Using machid 0x%lx from environment\n", machid); } debug("## Transferring control to Linux (at address %08lx)" \ "...\n", (ulong) kernel_entry); bootstage_mark(BOOTSTAGE_ID_RUN_OS); announce_and_cleanup(fake); if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) // 如果使能FDT,并且扁平设备树长度大于0 r2 = (unsigned long)images->ft_addr; else r2 = gd->bd->bi_boot_params; if (!fake) { kernel_entry(0, machid, r2); } }
1.2.1 matchid
由于我们没有使用设备树,需要uboo传递一个machid给linux 内核,uboot在不设置machid环境变量时,uboot会使用默认的机器id,我们在Mini2440之linux内核移植中将其修改为了168:
#define MACH_TYPE_SMDK2440 168 // 新增的
内核启动的时候会根据machid来比较内核machine_desc中的.nr,machine_desc定义在内核arch/arm/mach-s3c24xx/mach-smdk2440.c文件:
MACHINE_START(S3C2440, "SMDK2440") /* Maintainer: Ben Dooks <ben-linux@fluff.org> */ .atag_offset = 0x100, .init_irq = s3c2440_init_irq, .map_io = smdk2440_map_io, .init_machine = smdk2440_machine_init, .init_time = smdk2440_init_time, MACHINE_END
宏MACHINE_START定义在arch/arm/include/asm/mach/arch.h,如下:
/* * Set of macros to define architecture features. This is built into * a table by the linker. */ #define MACHINE_START(_type,_name) \ static const struct machine_desc __mach_desc_##_type \ __used \ __attribute__((__section__(".arch.info.init"))) = { \ .nr = MACH_TYPE_##_type, \ .name = _name, #define MACHINE_END \ };
这里attribute((section(“.arch.info.init”)))就是利用了编译器的特性,把machine_desc放到了.arch.info.init段。
.init.arch.info : { __arch_info_begin = .; *(.arch.info.init) __arch_info_end = .; }
回到前面的MACHINE_START(_type,_name)宏,_type是S3C2440。经过##连接.nr = MACH_TYPE_##_type就变为:.nr = MACH_TYPE_S3C2440。
MACH_TYPE_S3C2440定义在arch/arm/include/generated/asm/mach-types.h:
#define MACH_TYPE_S3C2440 168
现在使用设备树的话,这个参数就不需要设置了。
1.2.2 r2
如果我们使用了设备树,r2将会被设备为了:
r2 = (unsigned long)images->ft_addr;
二、支持FDT(扁平设备树)
2.1 支持FDT
我们如何修改uboot使其支持FDT呢,我们需要在include/configs/smdk2440.h中配置:
#define CONFIG_OF_LIBFDT
配置CONFIG_OF_LIBFDT之后,uboot编译时将会编译lib/libfdt文件夹:
lib/Makefile:54:obj-$(CONFIG_OF_LIBFDT) += libfdt/
2.1.1 编译uboot
2.1.2 下载uboot
2.2 启动命令
uboot支持FDT后,我们便可以将dtb下载到内存地址0x30001000中:
tftp 0x30001000 smdk2440.dtb
然后我们将内核镜像加载到内存0x30008000地址:
nand write 300008000 kernel
然后可以使用如下命令启动内核:
bootm 0x30008000 - 0x30000000 // 无设备树时,直接bootm 0x30008000 //bootm uImage地址 ramdisk地址 设备树镜像地址
其中:
- 第一个参数为内核映像在内存的地址;
- 第二个参数为initrd的地址,若不存在initrd,可以用“-”符号代替;
- 第三个参数dtb_address为.dtb文件在内存的地址;
此外对于dtb的地址0x30000000,我们需要遵循以下原则:
- 不要破坏uboot本身;
- 不要破坏内核本身: 内核本身的空间不能占用, 内核要用到的内存区域也不能占用;
由于Mini2440的内存区间为0x30000000~0x34000000,并且:
- 高地位为存储uboot代码,以及uboot使用的栈空间,大概250kb;
- 这里我们将内核加载地址修改为0x30008000地址,内核启动时一般会在它所处位置的下边放置页表,这块空间(一般是0x4000即16K字节)不能被占用;
2.2.1 smdk2440.dtb生成
2.3 FTD命令
uboot提供了fdt的相关命令:
"addr [-c] <addr> [<length>] - Set the [control] fdt location to <addr>\n" "fdt move <fdt> <newaddr> <length> - Copy the fdt to <addr> and make it active\n" "fdt resize [<extrasize>] - Resize fdt to size + padding to 4k addr + some optional <extrasize> if needed\n" "fdt print <path> [<prop>] - Recursive print starting at <path>\n" "fdt list <path> [<prop>] - Print one level starting at <path>\n" "fdt get value <var> <path> <prop> - Get <property> and store in <var>\n" "fdt get name <var> <path> <index> - Get name of node <index> and store in <var>\n" "fdt get addr <var> <path> <prop> - Get start address of <property> and store in <var>\n" "fdt get size <var> <path> [<prop>] - Get size of [<property>] or num nodes and store in <var>\n" "fdt set <path> <prop> [<val>] - Set <property> [to <val>]\n" "fdt mknode <path> <node> - Create a new node after <path>\n" "fdt rm <path> [<prop>] - Delete the node or <property>\n" "fdt header - Display header info\n" "fdt bootcpu <id> - Set boot cpuid\n" "fdt memory <addr> <size> - Add/Update memory node\n" "fdt rsvmem print - Show current mem reserves\n" "fdt rsvmem add <addr> <size> - Add a mem reserve\n" "fdt rsvmem delete <index> - Delete a mem reserves\n" "fdt chosen [<start> <end>] - Add/update the /chosen branch in the tree\n" " <start>/<end> - initrd start/end addr\n"
2.4 修改dtb
如果修改设备树中的设备节点信息,有两种办法;
- 修改dts文件,重新编译得到dtb并上传烧写
- 使用uboot提供的一些命令来修改dtb文件,修改后再把它保存到板子上,以后就使用这个修改后的dtb文件;
参考文章
[1]linux设备驱动(19)设备树详解3-u-boot传输dts