目录
1. uboot如何启动内核与创建单板
1.1 uboot如何启动内核
1.2 准备工作
1.2.1 环境
1.2.2 获取linux-4.12源码
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-2016.11到JZ2440中我们已经实现了移植新
uboot并启动内核,首先来分析该版本
uboot是如何启动内核的。
当
uboot启动完成后如果设置了
bootcmd环境变量的如下:
bootcmd=nand read 0x30000000 kernel; bootm 0x30000000 //从0x30000000处启动内核
uboot会执行这两条命令,第一条先从
NAND Flash里的
kernel分区读出内核到
0x30000000地址;第二条
bootm 0x30000000命令,下面具体分析该命令的执行过程。
bootm命令定义在
cmd/bootm.c文件中:
可以看到实际调用得是
do_bootm()函数
(cmd/bootm.c文件):
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
argc--; argv++;
if (argc > 0) {
char *endp;
simple_strtoul(argv[0], &endp, 16); /* 将参数转换成字符串 */
if ((*endp != 0) && (*endp != ':') && (*endp != '#'))
return do_bootm_subcommand(cmdtp, flag, argc, argv);
}
... ...
}
simple_strtoul函数将输入的命令转换成字符串,存入
endp指针,由于我们的命令中没有
‘:’、‘#’字符,所以该函数返回的是
do_bootm_subcommand函数。代码如下
(cmd/bootm.c文件):
static int do_bootm_subcommand(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
int ret = 0;
long state;
cmd_tbl_t *c;
c = find_cmd_tbl(argv[0], &cmd_bootm_sub[0], ARRAY_SIZE(cmd_bootm_sub)); /* 查找命令 */
argc--; argv++;
if (c) {
state = (long)c->cmd;
if (state == BOOTM_STATE_START)
state |= BOOTM_STATE_FINDOS | BOOTM_STATE_FINDOTHER;
} else {
/* Unrecognized command */
return CMD_RET_USAGE;
}
... ...
ret = do_bootm_states(cmdtp, flag, argc, argv, state, &images, 0); /* 启动内核 */
return ret;
}
do_bootm_states函数代码如下
(cmd/bootm.c文件):
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
int states, bootm_headers_t *images, int boot_progress)
{
boot_os_fn *boot_fn;
ulong iflag = 0;
int ret = 0, need_boot_fn;
images->state |= states;
if (states & BOOTM_STATE_START)
ret = bootm_start(cmdtp, flag, argc, argv); /* 得到内核头部,填充images结构体 */
... ...
boot_fn = bootm_os_get_boot_func(images->os.os); /* 通过头部标识从boot_os数组选择启动内核函数 */
/* Now run the OS! We hope this doesn't return */
if (!ret && (states & BOOTM_STATE_OS_GO))
ret = boot_selected_os(argc, argv, BOOTM_STATE_OS_GO,
images, boot_fn); /* 启动os内核 */
/* Deal with any fallout */
err:
... ...
}
bootm_os_get_boot_func函数里会通过头部标识从
boot_os数组选择启动内核的函数,
boot_os数组如下
(common/bootm_os.c文件中):
对于我们使用的
uImage使用的是
do_bootm_linux函数,该函数代码如下
(arch/arm/lib/bootm.c文件):
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;
}
/* 该函数会将各个tag参数保存在指定位置,比如:内存tag、bootargs环境变量tag、串口tag等 */
boot_prep_linux(images);
/* 该函数会跳转到内核起始地址 */
boot_jump_linux(images, flag);
return 0;
}
最终调用
boot_jump_linux()函数跳到内核起始地址运行内核,
boot_jump_linux()函数代码如下
(common/bootm.c文件):
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
#ifdef CONFIG_ARM64 /* 未定义CONFIG_ARM64,执行else分支 */
... ...
#else
/* gd->bd->bi_arch_number在boot启动第二阶段init_sequence_r数组里的board_init函数里设置 */
unsigned long machid = gd->bd->bi_arch_number; /* 设置默认机器ID */
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; /* 设置kernel_entry()的地址为0x30000000 */
s = getenv("machid"); /* 判断环境变量machid是否设置,若设置则使用环境变量里的值 */
if (s) {
/* 重新获取机器ID,使用环境变量的machid */
if (strict_strtoul(s, 16, &machid) < 0) {
debug("strict_strtoul failed!
");
return;
}
printf("Using machid 0x%lx from environment
", machid);
}
debug("## Transferring control to Linux (at address %08lx)"
"...
", (ulong) kernel_entry);
bootstage_mark(BOOTSTAGE_ID_RUN_OS);
announce_and_cleanup(fake);
if (IMAGE_ENABLE_OF_LIBFDT && images->ft_len) /* 执行else分支 */
r2 = (unsigned long)images->ft_addr;
else
/* 获取tag参数地址, gd->bd->bi_boot_params在boot启动第二阶段init_sequence_r
数组里的board_init函数里被设置为0x30000100 */
r2 = gd->bd->bi_boot_params;
if (!fake) {
kernel_entry(0, machid, r2); /* 跳转到0x30000000,r0=0,r1=机器ID,r2=tag参数地址0x30000100 */
}
#endif
}
运行内核后工作内容如下:
1. 比较机器ID;
2. 解析uboot传入的启动参数;
3. 挂接根文件系统、执行第一个应用程序。
1.2 准备工作
1.2.1 环境
交叉编译工具:arm-linux-gcc-4.4.3
开发板:JZ2440V3
1.2.2 获取linux-4.12源码
从官方下载源码,地址:
https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/,找到
linux-4.12.tar.gz并下载。
解压该文件,得到如下:
下面简单介绍一下这些文件夹的功能:
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-4.12.tar.gz文件,执行如下命令:
tar xzf linux-4.12.tar.gz
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-2016.11到JZ2440移植的
uboot下载
uImage开发板上运行:
tftp 30000000 uImage (或者将uImage拷贝到/work/nfs_root/, nfs 30000000 192.168.1.13:/work/nfs_root/uImage)
bootm 30000000
可以看到
uboot去启动内核后就没有任何输出了,猜测可能是波特率设置问题,所以重启开发板在
uboot里输入如下命令添加设置波特率:
set bootargs console=ttySAC0,115200 root=/dev/mtdblock3
save
重新下载启动内核发现还是无输出,下面分析原因。
1.3.3 分析启动内核为什么无输出
执行
bootm 0x30000000命令会调用到
boot_jump_linux()函数,该函数里有如下代码
(u-boot-2016.11/arch/arm/lib/bootm.c文件中):
移植u-boot-2016.11到JZ2440移植的
uboot里,在
u-boot-2016.11/board/samsung/jz2440/jz2440.c中设置了默认
ID:
宏
MACH_TYPE_SMDK2410定义在
u-boot-2016.11/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文件里的
,这样显然不对,我们应该使用
arch/arm/mach-s3c24xx/mach-smdk2440.c文件里的函数
。
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(本人使用的
JZ2440开发板上用的是
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
* On DT based machines, we fall back to populating the
* machine from the device tree, if no callback is provided,
* otherwise we would always need an init_machine callback.
*/
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文件中如下代码:
smdk2440_machine_init()函数又调用
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 测试
重新编译启动内核,有如下输出: