在上一篇文章中介绍了zImage是怎么生成的,这对理解Linux 启动的第一部分汇编很有用。由于移植的Linux3.4.72源码是纯净的,所以可以从头学习如果移植Linux到samsung 2440 芯片。
首先需要更改Linux根目录中的Makefile 文件
#ARCH ?= $(SUBARCH)
ARCH ?= arm
#CROSS_COMPILE ?= $(CONFIG_CROSS_COMPILE:"%"=%)
CROSS_COMPILE ?= arm-linux-
为什么要更改文件的这个地方? 如果不更改这里,将ARCH 设置成arm,那么在下面执行 make s3c2410_defconfig就会出错。原因就是找不到s3c2410_defconfig.
原来的Makefile文件中ARCH值为:
SUBARCH := $(shell uname -m | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \
-e s/arm.*/arm/ -e s/sa110/arm/ \
-e s/s390x/s390/ -e s/parisc64/parisc/ \
-e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
-e s/sh[234].*/sh/ )
更改Makefile文件后,执行 $# make s3c2410_defconfig 结果就是将arch/arm/configs/s3c2410_defconfig拷贝到根目录下的.config文件。
接下来在linux的根目录下,直接执行make就可以了,最终就会生成zImage文件,位置arch/arm/boot/zImage。
将该zImage用TQ2440的u-boot烧写进nandflash中,然后启动,在串口中没有看到任何信息,说明串口还是有问题的。
为什么呢???
第一就是U-boot向内核提供的参数没有被内核找到或者解析不正确。我使用的是TQ提供好的u-boot,还好有源码。在u-boot源码中我知道,其将参数存放在了0x30000100的地址,并且是按照struct param_struct 这种结构体存储的。struct param_struct 是一种比较古老的u-boot传递给kernel的参数格式。 先说第一种情况,即位置Linux 3.4.70寻址的位置是否正确。 这里先说下结果,位置是正确的,所以是解析不正确造成的。
MACHINE_START(S3C2440, "SMDK2440")
/* Maintainer: Ben Dooks <ben-linux@fluff.org> */
.atag_offset = 0x100, //参数位移
.init_irq = s3c24xx_init_irq,
.map_io = smdk2440_map_io,
.init_machine = smdk2440_machine_init,
.timer = &s3c24xx_timer,
.restart = s3c244x_restart,
MACHINE_END
这部分代码时mach-s3c2440.c中的。 在这个结构体重,.atag_offset被设置成0x100,它在下面的地方被用到,调用的顺序是 start_kernel()-> setup_arch()->setup_machine_tags()
static struct machine_desc * __init setup_machine_tags(unsigned int nr)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc = NULL, *p;
char *from = default_command_line;
...................
for_each_machine_desc(p) // 查找 符合的machine_desc, 我这里的结果就是mach-s3c2440定义的
if (nr == p->nr) {
printk("Machine: %s\n", p->name);
mdesc = p;
break;
}
.................
if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->atag_offset)
tags = (void *)(PAGE_OFFSET + mdesc->atag_offset); // 由于mmu已经打开,所以这个PAGE_OFFSET 是0xc0000000.这个地址是虚拟地址,对应的是0x30000000,
// mdesc->atag_offset就是0x100,所以地址就是0x30000100,与u-boot的将参数存储的地址一样。
#if defined(CONFIG_DEPRECATED_PARAM_STRUCT) // 这里很重要,由于我先前没有打开这个宏,导致kernel无法识别 旧的struct param_struct 结构体。所以这里需要将宏 //打开,让kernel 帮助将 param_struch转换成 tags。
/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags); // 这里就是转换的函数
#endif
if (mdesc->fixup)
mdesc->fixup(tags, &from, &meminfo);
if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
save_atags(tags);
parse_tags(tags); // 这里面有一个tag的操作会调用到parse_tag_cmdline()函数,这个函数主要是给default_command_line数组赋值,这个数组就是form。 //至于如个调用到这个函数的,与调用arch_initcall宏标记过的函数是一样的,这里就不说了。
}
/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
early_print("boot_command_line =%s\n",boot_command_line); // 打印结果是"noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0" ,u-boot传输的参数中虽然带了console的名称,但是没有带波特率,所以我的kernel的默认是38400. 如果想让u-boot带上波特率,那么这样写 console=ttySAC0,115200n8r
//strlcpy(boot_command_line,"noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0,115200n8r",COMMAND_LINE_SIZE); // 这里是我原来修改的方法,挫死了
return mdesc;
}
至于这个console=ttySAC0,115200n8r最终是怎么被串口驱动用到的这里也不多讲了,那块用到东西比较多,虽然我是从串口的驱动程序开始的线索,读了3遍,依然不能很清楚的描述出来,比较做了很多得抽象工作在里面,不过我还是准备在读一遍。建议可以从printk的实现来看,找到strcut console console_console_drivers 相关的东西,然后在从串口的驱动注册函数看,最终的结果是他俩肯定是有关系的。
第二就是内核中的时钟源设置的不对,这个比较简单了,在上面的问题调整出来,串口上会打印乱码,而且无论怎么设置串口工具的波特率打印的依然是乱码,所以想到的就是时钟不对,顺藤摸瓜就可以了。我所用的开发板时钟源是12Mhz,而Linux中的源码设置是16934400hz。
更改的位置:
static void __init smdk2440_map_io(void)
{
s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));
// s3c24xx_init_clocks(16934400);
s3c24xx_init_clocks(12000000);
s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs));
}
这样我的开发板的串口就可以正常的工作了。 但是启动到文件系统的时候就停止了,以为还没弄到哪里。
上一遍中提到的问题,linux kernel 第一部分汇编做了 什么,其跟串口初始是否有关系。答案是没有关系的,至少在s3c2440的情况下没有。我在开始调试的时候,只能从汇编开始看起,还好汇编中提供了打印的方法,不至于我无路可走。 至于linux kernel 的汇编做了什么有机会再分析。
在printk正常工作前 依然可以依靠打印进行调试的,在汇编中调用 kputc 和 kphex, 在c文件中调用early_print函数,这些函数都是用宏控制的,默认是关闭的。注意:在汇编中的打印函数会调用r0 r1 r2 寄存器,所以一旦调用了打印函数,就会引起启动异常,所以在得到打印结果后,需要将打印函数去掉。