前些日子,完成u-boot的移植,随后所作的事情就是进行linux内核的移植,在移植的过程中遭遇了一些莫名其妙的错误,但无论如何,已经实现了最终 的移植工作,在移植的过程中学会了一些内核的debug方法,依据此方法对内核启动过程进行了跟踪,相关的内容也将在本章节内进行阐述。
移植硬件平台 友善之臂 s3c2410 ,内核版本 linux 2.6.20.3 ,交叉编译器gcc4.0.2 。
交叉编译器的建立文章参见ARM-Linux交叉编译工具链制作攻略 。
linux内核移植过程:
(1)解压内核并进入内核代码树工作目录。
tar xjvf linux-2.6.20.3.tar.bz2 cd linux-2.6.20.3 |
(2)修改Makefile文件。
vi Makefile 找到如下变量,并按照下述修改 ARCH =arm CROSS_COMPILE =arm-linux- |
(3)在arch/arm/mach-s3c2410/common-smdk.c文件内修改flash相关信息。
a.分区信息设置 static struct mtd_partition smdk_default_nand_part[] = { [0] = {//建立bootloader 分区 .name = "U-Boot-1.2.0", .size = SZ_128K, .offset = 0, }, [1] = {//建立u-boot参数分区 .name = "U-Boot Parameter", .offset = SZ_128K, .size = SZ_64K, }, [2] = {//建立linux内核分区 .name = "Linux2.6.20.3", .offset = SZ_128K+SZ_64K, .size = SZ_4M+(SZ_1M-SZ_128K-SZ_64K), }, [3] = {//建立根文件分区(实际用nfs,该部分用作debug内核) .name = "Rootfs", .offset = SZ_1M * 5, .size = SZ_1M * 5, }, [4] = {//用户自定义分区 .name = "Userfs", .offset = SZ_1M * 10, .size = SZ_1M * 55, } }; b.设置flash硬件特性 static struct s3c2410_platform_nand smdk_nand_info = { .tacls = 0, .twrph0 = 30, .twrph1 = 0, .nr_sets = ARRAY_SIZE(smdk_nand_sets), .sets = smdk_nand_sets, }; |
(4)修改drivers/mtd/nand/s3c2410.c文件中的ECC校验信息
chip->ecc.mode = NAND_ECC_NONE; |
(5)配置内核(略:网上有很多类似文章例如:移植Linux2.6.22.2到博创2410-S(s3c2410A)系列 )
(6)编译内核
make zImage |
以上的工作给出一个patch,将此patch打上后,编译出的内核就是可以直接运行于友善之臂上。
|
随后的工作就是要将内核运行到卡发板上,运行的方式是tftp的方式。相关参数在u-boot中进行设置。
setenv bootcmd tftp 0x30008000 zImage.img;bootm setenv bootargs console=ttySAC0,115200 mem=64M saveenv |
由于一个弱智的错误,导致我的移植 工作移植停留在linux解压后暂停的过程中,如下:
Uncompressing Linux............................................................. |
错误的根源如下:
我使用的是rhel5的linux的操作系统,该系统的firefox浏览器在浏览网页时有时出现字体的覆盖,上个窗体中的mem=64M在我的浏览器中是如下的样子: mem64M 。由于我整个移植过程的资料全都是依赖于网络上过来人的移植信息,因此我非常绝对的相信了这个 mem64M。
错误虽然已经犯下,但也算是因或得福,在调整这个错误的过程中,我跟踪了这个linux2.6.20.3版本的内核启动方式,并采用了汇编以及led并用的调试方式,最终判定错误的出现原因,因此也在此进行整理。
调试技巧:
(1) 字符终端输出方式。 在内核中arch/arm/kernel/head.S中存在一个debug函数叫做printascii,该函数的汇编使用方法如下:
adr r0, str_p1 bl printascii str_p1:.asciz"/nError: unrecognized/unsupported processor variant./n" |
使用该调试函数可以实现低级调试信息在终端上输出。
(2) led点灯方式。
ldr r1, =0x56000010 ldr r2, =0x154000 str r2, [r1] ldr r1, =0x56000018 mov r2, #0x00000000 str r2, [r1] ldr r1, =0x56000014 mov r2, #0x00000000 str r2, [r1] b . /*因为改变了寄存器值会影响系统随后的启动,因此在此进入死循环*/ |
运行该代码后,板子上的led全部都会被打开。
调试跟踪过程
因为我的错误起源于解压之后,因此,我的调试工作也从解压后的第一个执行的程序开始,该程序存在于arch/arm/kernel/head.S文件。
引导过程中的主工作流程代码如下:
__INIT .type stext, %function ENTRY(stext) msr cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode @ and irqs disabled mrc p15, 0, r9, c0, c0 @ get processor id bl __lookup_processor_type @ r5=procinfo r9=cpuid movs r10, r5 @ invalid processor (r5=0)? beq __error_p @ yes, error 'p' bl __lookup_machine_type @ r5=machinfo movs r8, r5 @ invalid machine (r5=0)? beq __error_a @ yes, error 'a' bl __create_page_tables ldr r13, __switch_data @ address to jump to after @ mmu has been enabled adr lr, __enable_mmu @ return (PIC) address add pc, r10, #PROCINFO_INITFUNC .type __enable_mmu , %function __enable_mmu: #ifdef CONFIG_ALIGNMENT_TRAP orr r0, r0, #CR_A #else bic r0, r0, #CR_A #endif #ifdef CONFIG_CPU_DCACHE_DISABLE bic r0, r0, #CR_C #endif #ifdef CONFIG_CPU_BPREDICT_DISABLE bic r0, r0, #CR_Z #endif #ifdef CONFIG_CPU_ICACHE_DISABLE bic r0, r0, #CR_I #endif mov r5, #(domain_val(DOMAIN_USER, DOMAIN_MANAGER) | / domain_val(DOMAIN_KERNEL, DOMAIN_MANAGER) | / domain_val(DOMAIN_TABLE, DOMAIN_MANAGER) | / domain_val(DOMAIN_IO, DOMAIN_CLIENT)) mcr p15, 0, r5, c3, c0, 0 @ load domain access register mcr p15, 0, r4, c2, c0, 0 @ load page table pointer b __turn_mmu_on .align 5 .type __turn_mmu_on, %function __turn_mmu_on: mov r0, r0 mcr p15, 0, r0, c1, c0, 0 @ write control reg mrc p15, 0, r3, c0, c0, 0 @ read id reg mov r3, r3 mov r3, r3 mov pc, r13 |
.type __create_page_tables, %function __create_page_tables: 代码略. |
代码执行的流程描述如下:
(1) 首先进入SVC模式,关闭中断,并检测cpu类型。
(2) 创建页表格 bl __create_page_tables
(3) ldr r13, __switch_data 这是一个非常重要的步骤,他的作用将在随后的步骤中体现
(4) adr lr, __enable_mmu 也是一个非常重要的步骤,作用在随后步骤中解释。
(5) add pc, r10, #PROCINFO_INITFUNC
#PROCINFO_INITFUNC宏定义在include/asm/asm-offset.h中,这个宏的定义如下
#define PROCINFO_INITFUNC 16 /* offsetof(struct proc_info_list, __cpu_flush) @ */ |
那么现在我们就不得不提出问题, r10内的内容是什么? 这个16又是如何使用的呢?
事实上,r10内存放的是一个结构体的首地址,该结构体在__lookup_processor_type后由r5传来,而r5则指向一个定义的结构体,该结构体声明于include/arm-asm/procinfo.h文件中,结构体如下:
struct proc_info_list { unsigned int cpu_val; unsigned int cpu_mask; unsigned long __cpu_mm_mmu_flags;/*used by head.S */ unsigned long __cpu_io_mmu_flags;/*used by head.S */ unsigned long __cpu_flush; /* used by head.S */ const char *arch_name; const char *elf_name; unsigned int elf_hwcap; const char *cpu_name; struct processor *proc; struct cpu_tlb_fns *tlb; struct cpu_user_fns *user; struct cpu_cache_fns *cache; }; |
其具体的值的实现在arch/arm/mm/proc-arm9tdmi.S中,具体如下:
__arm9tdmi_proc_info: .long 0x41009900 .long 0xfff8ff00 .long 0 .long 0 b __arm9tdmi_setup .long cpu_arch_name .long cpu_elf_name .long HWCAP_SWP | HWCAP_THUMB | HWCAP_26BIT .long cpu_arm9tdmi_name .long arm9tdmi_processor_functions .long 0 .long 0 .long v4_cache_fns .size __arm9tdmi_proc_info, . - __arm9dmi_proc_info |
应此add pc, r10, #PROCINFO_INITFUNC实际上就是调用语句 b __arm9tdmi_setup
在 __arm9tdmi_setup中只有一个语句就是 mov pc, lr。因为之前的步骤(4)中使用了 adr lr, __enable_mmu 这个语句将__enable_mmu放入到lr中,因此,这此跳转直接转到__enable_mmu函数内。
(5)调用__enable_mmu函数,该函数内会调用__turn_mmu_on函数,打开对mmu的支持。
在这部分,我遭遇到了另一个问题,这个问题就是led程序在打开mmu之后就不再好用了,为此我查到了一个相关的介绍资料ARM Linux Mailing Lists - FAQ 在该FAQ中特别指明了这个问题的普遍性(言辞有点激烈,有点打击人),具体部分的摘录如下:
|
其实主要的说明就是这会mmu已经开启了,系统中的物理地址都变成虚拟地址了,因此原来基于物理地址的调试方案将都会失败,但可以使用 printascii继续调试,该调试功能同时支持物理地址与虚拟地址,并且提供了一个解决方案就是将printascii加入到printk的 vsprintf()之后。(注:通过这种方案也可以发现是否是因为u-boot于linux的频率不匹配而导致的终端内容无显示)
它说到的这个调试方案的修改方法(我加在vprintk内):修改该文件 kernel/printk.c
extern void printascii(const char*);//kernel/printk.c 516行 printascii(printk_buf);// //kernel/printk.c 539行 |
(6)__turn_mmu_on:最后一句是 mov pc, r13 上述重要步骤之第(3)步恰好就是将 __switch_data传入到r13中。因此pc直接指向的是__switch_data代码部分。
__switch_data定义在arch/arm/kernel/head-common.S中,具体实现代码如下:
.type __switch_data, %object __switch_data: .long __mmap_switched .long __data_loc @ r4 .long __data_start @ r5 .long __bss_start @ r6 .long _end @ r7 .long processor_id @ r4 .long __machine_arch_type @ r5 .long cr_alignment @ r6 .long init_thread_union + THREAD_START_SP @ sp |
由此可以看出,实际上是执行__mmap_switched函数。
(7) 在__mmap_switched 的最后一句就是b start_kernel,到此位置,我们就跳入到内核初始化函数中。
实际在调试的过程中,我遭遇的问题就是在b start_kernel之前printascii都是好用的,可是到start_kernel之后,这个函数就不好用了,太奇怪了。琢磨2天没有希望,决定换一个交叉编译器,选择了codesourcery 公司的 ARM 2007q3 Release 的 交叉编译器,该编译器建立在linux2.6.22的内核的基础之上制作。我选择了一款linux2.6.22的内核按上述步骤进行编译,得到的内核 burn到板子里,printascii竟然好用了!但是仍然是跑到一半,就会挂起。真是让人挠头。我觉得可能是编译器有问题。于是就着根据 linux2.6.22部分的start_kernel进行调试(调试步骤比较简单,就不多说了),最终发现挂起在内存page初始化的部分!不由的让我 想到我传入到内核的参数,是否是因为“mem64M”的问题呢?到网上再此搜索,终于在一个站点上看到,竟然是mem=64M,我晕的头都大了。改变 bootargs重启板子,引导启动信息终于显示出来了,换回到linux2.26.20.3并换回编译器,引导信息仍然可显示出来。到此为止,我算是成 功的搞定了这个破问题。
希望我的这次经历能给看到这篇文章的同僚们一些启示,那也就不妄我这几天的郁闷了。