- 具体问题
- 参考解决方案
- 解决思路
- 深入解决
1.具体问题:
在移植3.6.6的内核后,下载启动卡死,具体是串口打印信息停留在“Uncompressing Linux… done, booting the kernel.”
2. 参考解决方案:
依据网上的说法要确保如下情况:
- 2.1 内核的时钟频率正确
- 2.2 boot和kerel 配置一致的MACH_TYPE,即板子MACHINE ID
- 2.3 串口驱动配置正常
在内核配置device drivers->character devices->serial 中
<*> Samsung SoC serial support
[*] Support for console on Samsung SoC serial port
- 2.4. 启动参数设置正常
console 的ttySAC0 ,波特率115200对应正确
3.解决思路
检查了前面三项都是OK的,刚开始以为是内核无法启动,后来将显示驱动移植成功后(其它没变)下载到开发板,发现启动信息同样也无法通过串口打印到minicom或其它dnw窗口中,但在开发板的LCD上却打印出了内核的启动信息。由此我知道了内核移植是成功的,只是没有通过串口打印出来,那就是串口设置不对了,可检查发现bootloader的console这些设置都是对的,突然想起来内核里也可以设置启动参数,试了下果然成功了(在Boot options —> Default kernel command string 中,填上console=ttySAC0,115200等启动参数就行了)
4.深入解决
由于以前移植2.6.35版本的内核时,在改变启动参数时都是从bootloader里设置的而忽略了内核也可以设置启动参数,当时弄2.6.35以前的版本都不需要设置这里,而且我在更改NFS启动时内核设置的参数也不起作用,就以为此项没用,所以就没考虑到这,实际原因是2.6.35内核配置里要选中always use default…这个才行,因为飞凌的bootloader里固化了一些启动参数,也就是说无论你在bootloader里设没设,它实质上都设定了,所以覆盖了内核里默认的启动参数。但3.6.6的版本在这方面可能是有改进,在内核boot option配置中可以看到如下Kernel command line type:
(X) Use bootloader kernel arguments if available
( ) Extend bootloader kernel arguments
( ) Always use the default kernel command string
而在2.6.35的版本中只有一个Always use the default kernel command string
可选项,所以确实有改进
,因此从此处看也有可能是我的bootloader与内核在传递启动参数时有问题(bootloader启动参数的地址在内核中没有设置好)导致无法把bootloader里设置的参数传递给内核,造成内核找不到正确的串口从而无法打印内核信息,所以内核从bootloader中没有获取到可用参数就使用内核设置的启动参数了。经测试,无论我怎么改变bootloader里的启动参数都没效果,应该是内核中的bootloader传参地址设置有问题,如果无法从bootloader设置那每次更换都要编译内核,太麻烦,有没有解决方法呢?
首先了解下 kernel启动参数传递方式
了解内核启动参数可以参考
或自行百度
据此我明白了,kernel启动参数有两种,一种是通过内核设置固化到启动参数中,一种是从某段内存中读取进来取代内核启动参数(而这个设置是由bootloader来设置并保存在某段事先与内核协商好的内存地址中),在启动时如果在“Kernel command line type”选中的是第一项,就先去寻找这段内存看有没有参数,没有就直接使用内核启动参数,有就覆盖掉内核设置的。
那么内核是如何知道bootloader将启动参数保存在哪里的呢?
看了下2.6.35版里的/arch/arm/mach-s3c24xx/mach-smdk2440.c
在最后的MACHINE_START中有个boot_params
,好了猜测就是它了,但在3.6.6中加入会提示错误,那应该是改了名之类的,先寻找下MACHINE_START的定义在文件
arch/arm/include/asm/mach/arch.h中
struct machine_desc {
+--- 4 lines: Note! The first four elements are used--------------------------------------------------------
unsigned int nr; /* architecture number */
unsigned int phys_io; /* start of physical io */
unsigned int io_pg_offst; /* byte offset for io
* page tabe entry */
const char *name; /* architecture name */
unsigned long boot_params; /* tagged list */
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned int reserve_lp0 :1; /* never has lp0 */
unsigned int reserve_lp1 :1; /* never has lp1 */
unsigned int reserve_lp2 :1; /* never has lp2 */
unsigned int soft_reboot :1; /* soft reboot */
void (*fixup)(struct machine_desc *,
struct tag *, char **,
struct meminfo *);
void (*map_io)(void);/* IO mapping function */
void (*init_irq)(void);
struct sys_timer *timer; /* system tick timer */
void (*init_machine)(void);
};
可以看到boot_params; /* tagged list */
那我们到3.6.6版中去看看,果然更换了如下
struct machine_desc {
unsigned int nr; /* architecture number */
const char *name; /* architecture name */
unsigned long atag_offset; /* tagged list (relative) */
const char *const *dt_compat; /* array of device tree
* ‘compatible‘ strings */
unsigned int nr_irqs; /* number of IRQs */
#ifdef CONFIG_ZONE_DMA
unsigned long dma_zone_size; /* size of DMA-able area */
#endif
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned char reserve_lp0 :1; /* never has lp0 */
unsigned char reserve_lp1 :1; /* never has lp1 */
unsigned char reserve_lp2 :1; /* never has lp2 */
char restart_mode; /* default restart mode */
void (*fixup)(struct tag *, char **,
struct meminfo *);
void (*reserve)(void);/* reserve mem blocks */
void (*map_io)(void);/* IO mapping function */
void (*init_early)(void);
void (*init_irq)(void);
struct sys_timer *timer; /* system tick timer */
void (*init_machine)(void);
void (*init_late)(void);
#ifdef CONFIG_MULTI_IRQ_HANDLER
void (*handle_irq)(struct pt_regs *);
#endif
void (*restart)(char, const char *);
};
,好了,试着修改此参数为S3C2410_SDRAM_PA + 0x100(原值为0x100)编译下载后居然无法启动了,卡死在Uncompressing Linux… done, booting the kernel.这一步,修改回原来的值,寻找了下发现在arch/arm/kernel/setup.c的setup_machine_tags函数中有所改变
if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
else if (mdesc->atag_offset)
tags = (void *)(PAGE_OFFSET + mdesc->atag_offset);//实际是进入这一步,
跟2.6.35的不同,
if (__atags_pointer)
{
printk("using __atags_pointer to get tags.\n");
tags = phys_to_virt(__atags_pointer);
}
else if (mdesc->boot_params)
{
tags = phys_to_virt(mdesc->boot_params);//不同点
printk("using mdesc->boot_params to get tags.the tags is 0x%08x\n",tags);//anzyelay
printk("tags->hdr.tag = 0x%08x\n",tags->hdr.tag);
}
所以我直接打印输出下发现mdesc->boot_params结果居然不是0x30000100(S3C2410_SDRAM_PA + 0x100)而是
mdesc->boot_params=0xc0000100(为什么??不知道呀不知道 ,我看了下bootloader源码里也是0x30000100这个地址呀,懒得去跟踪了,反正旧版传过来是这个值。)
而3.6.6版本的从arch/arm/mach-s3c24xx/mach-smdk2440.c中传过来的0x0100加上PAGE_OFFSET(0xc000000正好是0xc0000100,所以你一改MACHINE_START里的.__atag_offset
还会出错。所以结论是地址是正确设置了的。
现在既然不是内核与bootloader传参地址错误这个原因那怎么办?我对比两个版本分析调试了下setup_machine_tags函数,发现原来是CONFIG_DEPRECATED_PARAM_STRUCT
这个宏没有定义的问题,导致后面内核读取到的参数地址tags是错误的(不在是0xc0000100)。
首先内核设置参数的路径是在main.c函数中, start_kernel–>setup_arch(&command_line)–>setup_command_line(command_line);
重点就在setup_arch函数中,此函数位于arch/arm/kernel/setup.c ,如下
void __init setup_arch(char **cmdline_p)
{
struct machine_desc *mdesc;
setup_processor();
mdesc = setup_machine_fdt(__atags_pointer);//因为__atags_pointer为空,直接跳出来了,
//这个参数是在R2寄存器给过来的吧,可以看head-common.s中的__mmap_switched
if (!mdesc)
mdesc = setup_machine_tags(machine_arch_type);//然后进入此处,
//可以对比下2.6.35,3.6.6这里优化了下。这次出错的原因也主要是在这个函数里
machine_desc = mdesc;
machine_name = mdesc->name;
setup_dma_zone(mdesc);
if (mdesc->restart_mode)
reboot_setup(&mdesc->restart_mode);
init_mm.start_code = (unsigned long) _text;
init_mm.end_code = (unsigned long) _etext;
init_mm.end_data = (unsigned long) _edata;
init_mm.brk = (unsigned long) _end;
/* populate cmd_line too for later use, preserving boot_command_line */
strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);
*cmdline_p = cmd_line;//获取到启动参数
parse_early_param();
sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL);
sanity_check_meminfo();
arm_memblock_init(&meminfo, mdesc);
paging_init(mdesc);
request_standard_resources(mdesc);
if (mdesc->restart)
arm_pm_restart = mdesc->restart;
unflatten_device_tree();
#ifdef CONFIG_SMP
if (is_smp())
smp_init_cpus();
#endif
reserve_crashkernel();
tcm_init();
#ifdef CONFIG_MULTI_IRQ_HANDLER
handle_arch_irq = mdesc->handle_irq;
#endif
#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
if (mdesc->init_early)
mdesc->init_early();
}
我们来看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;//编译内核时配置的Boot Options
init_tags.mem.start = PHYS_OFFSET;
/*
* locate machine in the list of supported machines.
* 这里是根据你在MACHINE_START(arg1,arg2)给的arg1=nr才找出
* 对应的设备描述结构p,所以,如果你没有设置正确arg1时,或者你的arg1
* 所对应的Machine ID(在文件arch/arm/tools/mach-types中)不对
* 时,那也会出现卡在Uncompres...这个错误。你开启kernel hacking中的
* early print时就会看到错误原因。(这就是所谓的机器码导致的卡死原因。)
*/
for_each_machine_desc(p)
if (nr == p->nr) {
printk("Machine: %s\n", p->name);
mdesc = p;
break;
}
if (!mdesc) {
early_print("\nError: unrecognized/unsupported machine ID"
" (r1 = 0x%08x).\n\n", nr);
dump_machine_table(); /* does not return */
}
if (__atags_pointer)
tags = phys_to_virt(__atags_pointer);
/*前面说过这个参数为空,直接路过了,跟参考文章2说的什么
* 设置了bootloader参数就直接获取过来有些不对,可能与我
* 用的飞凌的bootloader,如果用UBOOT估计从这里进吧,我没
* 有弄uboot,望知道的人告知下*/
else if (mdesc->atag_offset)
tags = (void *)(PAGE_OFFSET + mdesc->atag_offset);//我们是进入到这里,
//前面说了在配置文件中的atag_offset就是0x0100,不要改。tags=0xc0000100
/*2.6.35版的是没有下面这个宏的,由此直接跳过了convert_to_tag_list这一步
*导致后面的tags值改变,目前我的bootloader传来的参数就是old style,所以这一步
*不能省。这个在kernel配置里的kernel feature里选中Provide old way to
* pass kernel parameters 这项就行。*/
#if defined(CONFIG_DEPRECATED_PARAM_STRUCT)
/*
* 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 (tags->hdr.tag != ATAG_CORE) {
#if defined(CONFIG_OF)
/*
* If CONFIG_OF is set, then assume this is a reasonably
* modern system that should pass boot parameters
*/
early_print("Warning: Neither atags nor dtb found\n");
#endif
tags = (struct tag *)&init_tags;
}
//到这里tags=0xc0000100依旧,如果变了那肯定不对了(我就是在跟踪到此处不对才发现问题在上面这一步的)。
//同时tags->hdr.tag == ATAG_CORE(0x54410001)了,
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);
}
/* parse_early_param needs a boot_command_line */
strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);
return mdesc;
}
好了,依据上面的说法,make menuconfig 勾上kernel feature里的Provide old way to pass kernel parameters ,make zImage后,下载启动,可以正常通过 bootloader设置启动参数了。
另附加问题:我用arm-linux-gcc-4.9.4编译已经成功移植好的2.6.35内核也出现停留在此的现象,换回其它版本的编译器又能正常启动(只换个编译器,其它未做任何修改)。开始以为是4.9.4的编译器有问题,今天用来编译了下3.6.6的内核居然是可以的,所以应该是2.6.35内核移植还有问题不兼容4.9.4的gcc,具体不知原因!!!!