目录
1. uboot如何启动内核与创建单板
1.1 uboot如何启动内核
1.2 准备工作
1.2.1 环境
1.2.2 获取linux-3.4.2源码
1.3 创建单板
1.3.1 创建JZ2440相关单板文件夹
1.3.2 测试
1.3.3 分析为什么输出乱码
1.3.4 解决乱码
1.3.5 修改MTD分区
1.3.5.1 分析调用过程
1.3.5.2 修改分区
1.3.6 测试
1. uboot如何启动内核与创建单板
1.1 uboot如何启动内核
在移植u-boot-2012.04.01到JZ2440中我们已经实现了移植新uboot并启动内核,不过使用的是韦东山老师制作的内核,首先来分析uboot是如何启动内核的。
当uboot启动完成后如果设置了bootcmd环境变量的如下:
bootcmd=nand read 0x30000000 kernel; bootm 0x30000000 //从0x30000000处启动内核
uboot会执行这两条命令,第一条先从NAND Flash里的kernel分区读出内核到0x30000000地址;第二条bootm 0x30000000命令,下面具体分析该命令的执行过程。bootm命令定义在common/cmd_bootm.c文件中:
可以看到实际调用得是do_bootm()函数(common/cmd_bootm.c文件):
bootm_headers_t images; /* 指向 os/initrd/fdt类型的内核映像文件 */
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
boot_os_fn *boot_fn; //boot_fn是个数组函数
... ..
if (bootm_start(cmdtp, flag, argc, argv)) /* 得到内核头部,填充images结构体 */
return 1;
... ...
boot_fn = boot_os[images.os.os]; /* 通过头部标识从boot_os数组选择启动内核函数 */
... ...
boot_fn(0, argc, argv, &images); /* 调用启动内核函数 */
... ...
}
其中boot_os数组如下:
对于我们使用的uImage使用的是do_bootm_linux函数,该函数代码如下(common/bootm.c文件):
int do_bootm_linux(int flag, int argc, char *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) {
boot_jump_linux(images);
return 0;
}
/* 该函数会将各个tag参数保存在指定位置,比如:内存tag、bootargs环境变量tag、串口tag等 */
boot_prep_linux(images);
/* 该函数会跳转到内核起始地址 */
boot_jump_linux(images);
return 0;
}
最终调用boot_jump_linux()函数跳到内核起始地址运行内核,boot_jump_linux()函数代码如下(common/bootm.c文件):
static void boot_jump_linux(bootm_headers_t *images)
{
unsigned long machid = gd->bd->bi_arch_number; //获取机器ID
char *s;
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
kernel_entry = (void (*)(int, int, uint))images->ep; //设置kernel_entry()的地址为0x30000000
s = getenv("machid"); //判断环境变量machid是否设置,若设置则使用环境变量里的值
if (s) {
strict_strtoul(s, 16, &machid); //重新获取机器ID
printf("Using machid 0x%lx from environment\n", machid); //使用环境变量的machid
}
... ...
r2 = gd->bd->bi_boot_params; /* 获取tag参数地址, gd->bd->bi_boot_params在boot启动第二阶段board_init_r函数里调用board_init函数里被设置为0x30000100 */
kernel_entry(0, machid, r2); //跳转到0x30000000,r0=0,r1=机器ID,r2=tag参数地址
}
运行内核后工作内容如下:
1. 比较机器ID;
2. 解析uboot传入的启动参数;
3. 挂接根文件系统、执行第一个应用程序。
1.2 准备工作
1.2.1 环境
交叉编译工具:arm-linux-gcc-4.4.3
开发板:JZ2440V3
1.2.2 获取linux-3.4.2源码
从官方下载源码,地址:https://mirrors.edge.kernel.org/pub/linux/kernel/v3.x/,找到linux-3.4.2.tar.bz2并下载。
解压该文件,得到如下:
下面简单介绍一下这些文件夹的功能:
arch:体系结构相关代码,对于每个架构的CPU,arch目录下有一个对应的子目录,比如arch/arm;
block:块设备的通用函数;
crypto:常用加密和散列算法,还有一些压缩和CRC校验算法;
Documentation:内核文档;
drivers:所有的设备驱动程序,里面每一个子目录对应一类驱动程序,比如drivers/block/为块设备驱动程序,drivers/char/为字符设备驱动程序,drivers/mtd/为NOR Flash、NAND Flash等存储设备的驱动程序。
fs:Linux支持的文件系统的代码,每个字目录对应一种文件系统,比如fs/jffs2/、fs/ext2;
include:这 个目录包含linux源代码目录树中绝大部分头文件,每个体系架构都在该目录下对应一个子目录,该子目录中包含了给定体系结构所必需的宏定义和内联函数。
init:该目录中存放的是系统核心初始化代码,内核初始化入口函数start_kernel就是在该目录下的文件main.c内实现的。内核初始化入口函数start_kernel负责调用其它模块的初始化函数,完成系统的初始化工作。
ipc:用于实现System V的进程间通信(Inter Process Communication,IPC)模块。
kernel:用于存放特定体系结构特有信号量的实现代码和对称多处理器(Symmetric MultiProccessing,简称SMP)相关模块。
lib:内核用到的一些库函数代码,比如crc32.c、string.c,与处理器相关的库函数代码位于arch/*/lib目录下;
mm:内存管理代码,与处理器相关的内存管理代码位于arch/*/mm目录下;
net:网络支持代码,每个子目录对应于网络的一个方面;
samples:内核编程的例子;
scripts:该目录下没有内核代码,只包含了用来配置内核的脚本文件。当运行make menuconfig或者make xconfig之类的命令配置内核时,用户就是和位于这个目录下的脚本进行交互的。
security:这个目录包括了不同的Linux安全模型的代码,比如NSA Security-Enhanced Linux;
sound:音频设备的驱动程序
tools:与内核交互,以便在用户态时测试相关内核功能;
usr:目录下是initramfs相关的,和linux内核的启动有关.实现了用于打包和压缩的的cpio等。
1.3 创建单板
下面开始创建针对JZ2440开发板的相关单板文件。
1.3.1 创建JZ2440相关单板文件夹
1. 在服务器上解压linux-3.4.2.tar.bz2文件,执行如下命令:
tar xjf linux-3.4.2.tar.bz2
2. 进入根目录,vi Makefile修改顶层Makefile,修改如下内容:
ARCH ?= $(SUBARCH)
CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
改为:
ARCH ?= arm
CROSS_COMPILE ?= arm-linux-
3. 配置编译,执行如下命令:
make s3c2410_defconfig (使用相近单板配置文件进行配置,在arch/arm/configs下有许多配置文件选择)
make uImage
这样在arch/arm/boot目录下就会生成uImage。
1.3.2 测试
使用移植u-boot-2012.04.01到JZ2440移植的uboot下载uImage开发板上运行:
tftp 30000000 uImage (或者将uImage拷贝到/work/nfs_root/, nfs 30000000 192.168.1.13:/work/nfs_root/uImage)
bootm 30000000
可以看到输出一堆乱码。可能是波特率设置问题,所以重启开发板在uboot里输入如下命令添加设置波特率:
set bootargs console=ttySAC0,115200 root=/dev/mtdblock3
save
重新下载启动内核发现还是输出乱码,下面分析原因。
1.3.3 分析为什么输出乱码
执行bootm 0x30000000命令会调用到boot_jump_linux()函数,该函数里有如下代码:
移植u-boot-2012.04.01到JZ2440移植的uboot时,在 u-boot-2012.04.01/board/samsung/jz2440/jz2440.c中设置了默认ID:
宏MACH_TYPE_SMDK2410定义在u-boot-2012.04.01/arch/arm/include/asm/mach-types.h文件中,如下:
同时在board_f.c (common) 的board_init_f()函数中也有设置ID:
只不过uboot配置文件jz2440.h(include/configs)没有定义CONFIG_MACH_TYPE该宏,所以使用的是前面定义的MACH_TYPE_SMDK2410宏也就是193=0xc1。
在uboot任意设置一个机器ID值(set machid 33333),这样再次下载启动内核时,内核识别不出来,就会打印出当前的内核配置所支持的单板机器ID,如下(内核支持的所有机器ID定义在linux-3.4.2/include/generated/mach-types.h文件中):
由于之前配置(make s3c2410_defconfig),编译器就会将该类型的单板文件编译进内核,在arch/arm目录下输入"find -name "mach*.o"",如下:
启动内核时内核就会匹配uboot传入的机器ID,根据不同的ID使用不同mach-*.c文件,比如在1.3.2 测试中传入内核的ID是193,启动内核时查找内核里的机器ID单板文件,结果与如下定义匹配(arch/arm/mach-s3c24xx/mach-smdk2410.c文件中):
MACHINE_START定义如下:
用MACHINE_START宏替换后mach-smdk2410.c文件的.nr=MACH_TYPE_SMDK2410,查看该宏定义(include/generated/mach-types.h文件中)刚好就是193,所以调用的machine_desc结构体成员函数是使用arch/arm/mach-s3c24xx/mach-smdk2410.c文件里的machine_desc结构体成员map_io里设置了时钟,如下:
在该文件里设置晶振频率为0,本人使用的JZ2440开发板上用的是12MHZ晶振,所以1.3.2 测试中输出乱码。
1.3.4 解决乱码
我们不使用mach-smdk2410.c该单板文件,使用更相近的arch/arm/mach-s3c24xx/mach-smdk2440.c文件,所以应该设置机器ID为16a。设置ID可以修改UBOOT源码(按照1.3.3 分析为什么输出乱码中调用过程修改宏MACH_TYPE_SMDK2410或CONFIG_MACH_TYPE),也可以直接设置环境变量来修改机器ID。
如果设置机器ID为16a,那么使用的单板文件就是arch/arm/mach-s3c24xx/mach-smdk2440.c文件,该文件的时钟设置如下:
可以看出晶振频率设置也不对,将其中的晶振频率修改为12MHZ也就是12000000,如下:
然后重新编译内核(make uImage)。
重启开发板在uboot命令行输入如下命令:
set machid 16a (如果没有设置波特率还要设置:set bootargs console=ttySAC0,115200 root=/dev/mtdblock3)
save
重新下载启动内核:
tftp 30000000 uImage (或者将uImage拷贝到/work/nfs_root/, nfs 30000000 192.168.1.13:/work/nfs_root/uImage)
bootm 30000000
如下内核正确输出信息了:
1.3.5 修改MTD分区
上面启动内核后还有如下输出:
uboot传递的文件系统路径root=/dev/mtdblock3,所以内核便卡死在启动文件系统上。内核默认创建了8个分区,显然与我们之前在uboot中划分的分区不符,下面分析内核划分分区的过程。
1.3.5.1 分析调用过程
内核启动后,会调用如下arch_initcall标示的函数:
static int __init customize_machine(void)
{
/* customizes platform devices, or adds new ones */
if (machine_desc->init_machine)
machine_desc->init_machine();
return 0;
}
arch_initcall(customize_machine);
由于之前我们设置机器ID为对应arch/arm/mach-s3c24xx/mach-smdk2440.c文件,所以customize_machine()函数调用的init_machine()函数就是mach-smdk2440.c文件中如下代码:
smdk_machine_init()函数代码如下:
static struct platform_device __initdata *smdk_devs[] = {
&s3c_device_nand,
&smdk_led4,
&smdk_led5,
&smdk_led6,
&smdk_led7,
};
void __init smdk_machine_init(void)
{
... ...
s3c_nand_set_platdata(&smdk_nand_info); /* 设置platform数据 */
platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs)); /* 注册platform_device */
... ...
}
该函数会注册一些platform_device,其中s3c_device_nand结构体定义如下(arch/arm/plat-samsung/devs.c文件中):
而该name成员会被s3c244x_map_io函数调用s3c_nand_setname函数修改为"s3c2440-nand",如下(arch/arm/mach-s3c24xx/s3c244x.c文件中):
所以内核注册了名为"s3c2410-nand"的platform_device结构。我们之前使用的内核配置文件arch/arm/configs/s3c2410_defconfig中有如下代码:
而在drivers/mtd/nand/Makefile文件中有如下代码:
所以内核启动时会调用s3c2410.c驱动,在该驱动入口函数中会调用platform_driver_register()函数注册platform_driver结构,通过id_table匹配到之前注册的名为"s3c2440-nand"的platform_device结构,根据平台总线设备驱动模型,最终调用.probe函数也就是s3c24xx_nand_probe()函数,过程如下(drivers/mtd/nand/s3c2410.c文件中):
s3c24xx_nand_probe()函数最终会调用到add_mtd_partitions()函数添加分区(详细内容可以看十七、Linux驱动之nand flash驱动),使用的参数就是前面smdk_machine_init()函数调用的s3c_nand_set_platdata()函数里设置的smdk_nand_info结构体,如下(arch/arm/mach-s3c24xx/common-smdk.c文件中):
最终add_mtd_partitions()函数添加分区就是使用的smdk_default_nand_part数组里的参数,该数组定义如下(arch/arm/mach-s3c24xx/common-smdk.c文件中):
static struct mtd_partition smdk_default_nand_part[] = {
[0] = {
.name = "Boot Agent",
.size = SZ_16K,
.offset = 0,
},
[1] = {
.name = "S3C2410 flash partition 1",
.offset = 0,
.size = SZ_2M,
},
[2] = {
.name = "S3C2410 flash partition 2",
.offset = SZ_4M,
.size = SZ_4M,
},
[3] = {
.name = "S3C2410 flash partition 3",
.offset = SZ_8M,
.size = SZ_2M,
},
[4] = {
.name = "S3C2410 flash partition 4",
.offset = SZ_1M * 10,
.size = SZ_4M,
},
[5] = {
.name = "S3C2410 flash partition 5",
.offset = SZ_1M * 14,
.size = SZ_1M * 10,
},
[6] = {
.name = "S3C2410 flash partition 6",
.offset = SZ_1M * 24,
.size = SZ_1M * 24,
},
[7] = {
.name = "S3C2410 flash partition 7",
.offset = SZ_1M * 48,
.size = MTDPART_SIZ_FULL,
}
};
可以看到这里划分了8个分区,与我们启动内核打印出的信息一致。
1.3.5.2 修改分区
修改smdk_default_nand_part[]数组如下(arch/arm/mach-s3c24xx/common-smdk.c文件中):
static struct mtd_partition smdk_default_nand_part[] = {
[0] = {
.name = "bootloader",
.size = SZ_256K,
.offset = 0,
},
[1] = {
.name = "params",
.offset = MTDPART_OFS_APPEND,
.size = SZ_128K,
},
[2] = {
.name = "kernel",
.offset = MTDPART_OFS_APPEND,
.size = SZ_2M,
},
[3] = {
.name = "rootfs",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL,
}
};
1.3.6 测试
重新编译启动内核,有如下输出: