要解决的问题: 1. 虚拟地址怎么改? 2. bootloader默认把他下载到了那里? 3. 参数tag的地址怎么改 ,什么时候使用这个地址的? 4. 自解压的地址怎么改? 5. 从启动到linux开始运行,整个ram的布局的变换? 6. 怎么把kernel-2.6.13用uboot引导? 1. 虚拟地址怎么改? 生成vmlinux的命令如下: /usr/local/arm/3.4.1/bin/arm-linux-ld -EL -p --no-undefined -X -o vmlinux -T arch/arm/kernel/vmlinux.lds arch/arm/kernel/head.o arch/arm/kernel/init_task.o init/built-in.o --start-group usr/built-in.o arch/arm/kernel/built-in.o arch/arm/mm/built-in.o arch/arm/common/built-in.o arch/arm/mach-s3c2410/built-in.o arch/arm/nwfpe/built-in.o kernel/built-in.o mm/built-in.o fs/built-in.o ipc/built-in.o security/built-in.o crypto/built-in.o lib/lib.a arch/arm/lib/lib.a lib/built-in.o arch/arm/lib/built-in.o drivers/built-in.o sound/built-in.o net/built-in.o --end-group .tmp_kallsyms2.o 可见连接脚本在这里。 -T arch/arm/kernel/vmlinux.lds 他的内容如下: SECTIONS { . = 0xC0008000; 虚拟地址就是这里了。 .init : { /* Init code and data */ _stext = .; _sinittext = .; *(.init.text) _einittext = .; ...... 但是这个文件是本目录下vmlinux.lds.S文件生成的,vmlinux.lds.S内容如下 SECTIONS { . = TEXTADDR; .init : { /* Init code and data */ _stext = .; _sinittext = .; *(.init.text) _einittext = .; ...... 看看 TEXTADDR 这个车票是怎么来的,在/arch/arm/makefile中 textaddr-y := 0xC0008000 TEXTADDR := $(textaddr-y) 可见就是从这里来的。 改成我想要的试试 textaddr-y := 0xC0009000 编译出错,出错信息如下: arch/arm/kernel/head.S:48:2: #error TEXTADDR must start at 0xXXXX8000 去head.S看看,有如下内容 /* * We place the page tables 16K below TEXTADDR. Therefore, we must make sure * that TEXTADDR is correctly set. Currently, we expect the least significant * 16 bits to be 0x8000, but we could probably relax this restriction to * TEXTADDR >= PAGE_OFFSET + 0x4000 * * Note that swapper_pg_dir is the virtual address of the page tables, and * pgtbl gives us a position-independent reference to these tables. We can * do this because stext == TEXTADDR */ #if (TEXTADDR & 0xffff) != 0x8000 #error TEXTADDR must start at 0xXXXX8000 #endif 就是说必须是0xXXXX8000 这样的虚拟地址。 改成下面的试试 textaddr-y := 0xa0008000 ok SECTIONS { . = 0xa0008000; .init : { /* Init code and data */ _stext = .; _sinittext = .; *(.init.text) ...... 上面是新生成的vmlinux.lds,就是我想要的了,一直到最后都没有错误,0xc0000000-0xffffffff的1g分配给内核,这符合道理,呵呵。 lzd@lzd-laptop:~/Desktop/kernel-2.6.13$ cat System.map |head -20 a0004000 A swapper_pg_dir a0008000 T __init_begin a0008000 T _sinittext a0008000 T stext a0008000 T _stext a000802c t __switch_data a0008050 t __mmap_switched a0008094 t __enable_mmu a00080c0 t __turn_mmu_on a00080d8 t __create_page_tables a0008148 t __error a0008148 t __error_a a0008148 t __error_p a0008150 t __lookup_processor_type a000818c T lookup_processor_type a00081b0 t __lookup_machine_type a00081e4 T lookup_machine_type a0008200 t nosmp a0008224 t maxcpus a0008254 t debug_kernel 导出的符号表是个证明。 3. 参数tag的地址怎么改? 在uboot中的board/smdk2410/smdk2410.c中(其他的板子会有变化) /* adress of boot parameters */ gd->bd->bi_boot_params = 0x30000100; 这个0x30000100就是内核参数的地址,当然,这么作只是uboot单方的,如果内核不知道,也没有用。 在arch/arm/s3c2410/mach-smdk2410.c中 MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch * to SMDK2410 */ /* Maintainer: Jonas Dietsche */ .phys_ram = S3C2410_SDRAM_PA, .phys_io = S3C2410_PA_UART, .io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc, .boot_params = S3C2410_SDRAM_PA + 0x100, 就是这里了。 .map_io = smdk2410_map_io, .init_irq = smdk2410_init_irq, .timer = &s3c24xx_timer, MACHINE_END 在include/asm-arm/arch-s3c2410/map.h中 #define S3C2410_CS6 (0x30000000) #define S3C2410_SDRAM_PA (S3C2410_CS6) 可见 .boot_params = 0x30000100 正好与uboot约定的地址吻合。 至于内核什么时候使用这个参数,以后在分析。??? 在board/smdk2410/config.mk中只有下面的定义 TEXT_BASE = 0x33F80000 这个是用来确定,uboot要把自家加载到内核的什么地址的地址。为什么要这么靠后呢?为了给uimage留下足够的空间,避免冲突。 2.bootloader默认把他(内核)下载到了那里? 最后生成的内核镜象有两种zImage以及uImage。其中zImage下载到目标板中后,可以直接用uboot的命令go来进行直接跳转。 这时候内核直接解压启动。但是无法挂载文件系统,因为go命令没有将内核需要的相关的启动参数传递给内核。传递启动参数我 们必须使用命令bootm来进行跳转。Bootm命令跳转只处理uImage的镜象(什么时候读读bootm,看看是怎么传递tag list的)。 #define CONFIG_BOOTDELAY 3 /*#define CONFIG_BOOTARGS "root=ramfs devfs=mount console=ttySA0,9600" */ /*#define CONFIG_BOOTCOMMAND "tftp; bootm" */ 通常,上面要自己设置,因为每个人下载uimage的地点不同。比如 #define CONFIG_BOOTCOMMAND "nboot 0x32000000 0 0 ;bootm 0x32000000" 先把nandflash中的内核下载到0x32000000,然后传递启动参数(只是传递启动参数吗?),跳过去。 这样uboot kernel taglist在内存的不同位置,不冲突,也就是说0x32000000不是随便设置的,首先他不能覆盖taglist的位置 也不能覆盖uboot,更重要的是要给解压缩后内核的地址留出空间来。内核开始解压启动。到了这里,uboot已经可以从内存中消失了, 而内存中只有参数列表和正在解压缩的内核。 4. 自解压的地址怎么改? 首先看看uimage的解压缩地址。 在制作uimage的时候已经指定了,他的地址是0x30008000,有如下的命令: mkimage -A arm -O linux -T kernel -C gzip -a 0x30008000 -e 0x30008000 -n "linux kernel image" -d linux.bin.gz uImage 在来看看zImage的解压缩地址。 在arch/arm/boot/makefile 中 ifneq ($(MACHINE),) include $(srctree)/$(MACHINE)/Makefile.boot endif # Note: the following conditions must always be true: # ZRELADDR == virt_to_phys(TEXTADDR) # PARAMS_PHYS must be within 4MB of ZRELADDR # INITRD_PHYS must be in RAM ZRELADDR := $(zreladdr-y) PARAMS_PHYS := $(params_phys-y) INITRD_PHYS := $(initrd_phys-y) 而在arch/arm/mach-s3c2410/makefile.boot中 zreladdr-y := 0x30008000 params_phys-y := 0x30000100 也就是说,在这里可以设置内核的解压缩地址,和taglist的地址。 4. 从启动到linux开始运行,整个ram的布局的变换? 经过上面的分析,已经很清晰了。 6. 怎么把kernel-2.6.13用uboot引导? 生成uboot格式的内核压缩镜像 arm-linux-objcopy -O binary -R .comment -S ../vmlinux linux.bin &&gzip -9 linux.bin&&./mkimage -A arm -O linux -T kernel -C gzip -a 0x30008000 -e 0x30008000 -n "linux kernel image" -d linux.bin.gz uimage gzip -9 linux.bin ./mkimage -A arm -O linux -T kernel -C gzip -a 0x30008000 -e 0x30008000 -n "linux kernel image" -d linux.bin.gz uimage lzd> nfs 0x32000000 /home/lzd/nfs/uimage lzd> bootm 0x32000000 ## Booting image at 32000000 ... Image Name: linux kernel image Created: 2009-09-30 10:57:54 UTC Image Type: ARM Linux Kernel Image (gzip compressed) Data Size: 1534476 Bytes = 1.5 MB Load Address: 30008000 Entry Point: 30008000 Verifying Checksum ... OK Uncompressing Kernel Image ... OK Starting kernel ... 会死在这里,为什么呢? 我用的vmlinux是源代码根目录下的vmlinux,用了arch/arm/boot/compress/vmlinux就会这样了 Starting kernel ... Uncompressing Linux............................................................. 不论哪种情况, 在跳到 Linux 内核执行之前 CPU 的寄存器必须满足以下条 件:r0=0,r1=处理器类型,r2=标记列表在 RAM 中的地址。 调试如下 lzd> bootm 0x32000000 ## Booting image at 32000000 ... Image Name: Linux Kernel Image Created: 2009-10-02 2:49:30 UTC Image Type: ARM Linux Kernel Image (gzip compressed) Data Size: 1534476 Bytes = 1.5 MB Load Address: 30008000 Entry Point: 30008000 Verifying Checksum ... OK Uncompressing Kernel Image ... OK argc = 2 No initrd ## Transferring control to Linux (at address 30008000) ... Starting kernel ... theKernel (0, bd->bi_arch_number, bd->bi_boot_params); theKernel:30008000,0,16a,30000100 theKernel的地址是0x30008000 r0=0 r1=0x16a r2=0x30000100 跟上面的描述是一致的,为什么启动不起来呢? 现在就跳到了head.S文件,在没有用r0 r1 r2 之前,不能破坏他们的数据。 在lds里有 __arch_info_begin = .; *(.arch.info) __arch_info_end = .; 作了一个点灯代码,在uboot里go了一下,闪烁的挺快的 把他加到linux的head.S文件后,在运行这个uimage,发现速度超级慢,不知道原因何在? 但是这说明了,在解压缩内核后,他确实被解压缩到了0x30008000的位置,并且传递的r0 r1 r2都是正确的,只是速度下来了不少。 如果把linux.bin文件直接下载到0x30008000位置,然后go的话,速度没问题。 linux.bin文件就是经过objcopy的二进制文件,他不是gzip压缩的。 go的关键代码如下: rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]); 哦,找到原因了: cleanup_before_linux (); debug("theKernel (0, bd->bi_arch_number, bd->bi_boot_params);\n"); debug("theKernel:%x,0,%x,%x\n",theKernel,bd->bi_arch_number,bd->bi_boot_params); theKernel (0, bd->bi_arch_number, bd->bi_boot_params); int cleanup_before_linux (void) { /* * this function is called just before we call linux * it prepares the processor for linux * * we turn off caches etc ... */ unsigned long i; disable_interrupts (); /* turn off I/D-cache */ asm ("mrc p15, 0, %0, c1, c0, 0":"=r" (i)); i &= ~(C1_DC | C1_IC); asm ("mcr p15, 0, %0, c1, c0, 0": :"r" (i)); /* flush I/D-cache */ i = 0; asm ("mcr p15, 0, %0, c7, c7, 0": :"r" (i)); return (0); } 呵呵,go和bootm的区别很大阿, go只是简单的跳过去,bootm作了很多事,这里他/* turn off I/D-cache */并且 /* flush I/D-cache */,以前不太了解i/d cache对速度的提高有多大影响,现在明白了,原来影响这么大。 这说明uboot 解压缩正常,跳到linux的head.S的代码都正常,主要原因还是没出来,但是排除了 有降低cpu速度的原因。 现在可以开始调试linux这个大家伙了。 bl __lookup_processor_type @ r5=procinfo r9=cpuid movs r10, r5 @ invalid processor (r5=0)? beq __error_p @ yes, error 'p' 把点灯代码放在这里,ok, bl __lookup_machine_type @ r5=machinfo movs r8, r5 @ invalid machine (r5=0)? beq __error_a 但是放在这里就死掉了,呵呵,找到原因了。 /* * Look in include/asm-arm/procinfo.h and arch/arm/kernel/arch.[ch] for * more information about the __proc_info and __arch_info structures. */ .long __proc_info_begin .long __proc_info_end 3: .long . .long __arch_info_begin .long __arch_info_end /* * Lookup machine architecture in the linker-build list of architectures. * Note that we can't use the absolute addresses for the __arch_info * lists since we aren't running with the MMU on (and therefore, we are * not in the correct address space). We have to calculate the offset. * * r1 = machine architecture number * Returns: * r3, r4, r6 corrupted * r5 = mach_info pointer in physical address space */ .type __lookup_machine_type, %function __lookup_machine_type: adr r3, 3b ldmia r3, {r4, r5, r6} sub r3, r3, r4 @ get offset between virt&phys add r5, r5, r3 @ convert virt addresses to add r6, r6, r3 @ physical address space 1: ldr r3, [r5, #MACHINFO_TYPE] @ get machine type MACHINFO_TYPE是偏移量,为0 teq r3, r1 @ matches loader number? beq 2f @ found add r5, r5, #SIZEOF_MACHINE_DESC @ next machine_desc cmp r5, r6 blo 1b mov r5, #0 @ unknown machine 2: mov pc, lr 分析: 现看看__arch_info_begin在那个位置。 lzd@lzd-laptop:~/Desktop/kernel-2.6.13$ cat System.map |grep arch_info_begin a001d794 T __arch_info_begin 在虚拟地址 0xa001d794 处,后面的注释很清晰,r5放的是 __arch_info_begin 的物理地址 r6放的是 __arch_info_end 的物理地址,就是实实在在可以访问到该数据的地址,大概在0x30008000以上的某个地儿。 在前面我把虚拟地址改了,现在改回来0xc0008000可能是这个地址引起的。后面证明不是 下面是点灯代码: LDR R0,=0x56000010 MOV R1,#0x00000400 STR R1,[R0] MAIN_LOOP: LDR R0,=0x56000014 MOV R1,#0x00000000 STR R1,[R0] mov r5, #0x100000 1: subs r5, r5, #1 bne 1b MOV R1,#0xffffffff STR R1,[R0] mov r5, #0x100000 1: subs r5, r5, #1 bne 1b B MAIN_LOOP 在/include/asm-arm/match/arch.h中有 machine_desc 的定义 struct machine_desc { /* * Note! The first five elements are used * by assembler code in head-armv.S */ unsigned int nr; /* architecture number */ unsigned int phys_ram; /* start of physical ram */ 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); }; arm-linux-objdump -d vmlinux > dump 从dump文件中找__arch_info_begin,得到如下的信息。 对应上面的数据结构,分析。 c001d754 : c001d754: 0000030e 30e对应着nr,在include/asm/match-types.h中#define MACH_TYPE_QQ2440 782(0x30e) c001d758: 30000000 andcc r0, r0, r0 c001d75c: 50000000 andpl r0, r0, r0 c001d760: 00003c20 andeq r3, r0, r0, lsr #24 c001d764: c026cb70 eorgt ip, r6, r0, ror fp c001d768: 30000100 andcc r0, r0, r0, lsl #2 ... c001d77c: c001107c andgt r1, r1, ip, ror r0 c001d780: c00110d8 ldrgtd r1, [r1], -r8 c001d784: c02a98b4 strgth r9, [sl], -r4 c001d788: c00110ec andgt r1, r1, ip, ror #1 c001d78c : 78c - 754 = 56(dec) 而 machine_desc 也正好是56个字节,14个机器字(4byte) 现在发现原因了,怪不得不匹配,要传递给它的应该是0x30e才ok,而我的uboot传递的是 0x16a(bd->bi_arch_number) 现在要么改掉uboot的 bd->bi_arcmachine_arch_type 要么改掉linux的0x30e # undef machine_arch_type # define machine_arch_type __machine_arch_type # else # define machine_arch_type MACH_TYPE_QQ2440 # endif # define machine_is_qq2440() (machine_arch_type == MACH_TYPE_QQ2440) #else # define machine_is_qq2440() (0) #endif 哦,现在明白了,在config_n35配置文件中,有这样的定义 CONFIG_SOUND_QQ2440=y #ifdef CONFIG_ARCH_QQ2440 # ifdef machine_arch_type # undef machine_arch_type # define machine_arch_type __machine_arch_type # else # define machine_arch_type MACH_TYPE_QQ2440 # endif # define machine_is_qq2440() (machine_arch_type == MACH_TYPE_QQ2440) #else # define machine_is_qq2440() (0) #endif 就是说在config_n35里面可以定义这个板子的类型,如果和uboot传递进来的0x16a匹配,就ok了。 我不想改uboot传进来的0x16a,那就去改config_n35的配置去。 #define MACH_TYPE_S3C2440 362(0x16a) 所以要在config_n35里改成 MACH_TYPE_S3C2440对应的配置。 在include/asm/match-types.h中 #ifdef CONFIG_ARCH_S3C2440 # ifdef machine_arch_type # undef machine_arch_type # define machine_arch_type __machine_arch_type # else # define machine_arch_type MACH_TYPE_S3C2440 # endif # define machine_is_s3c2440() (machine_arch_type == MACH_TYPE_S3C2440) #else # define machine_is_s3c2440() (0) #endif 也就是说要定义CONFIG_ARCH_S3C2440 为y。 # CONFIG_ARCH_S3C2440 is not set # CONFIG_ARCH_AESOP2440 is not set CONFIG_ARCH_QQ2440=y 就是说要关掉CONFIG_ARCH_QQ2440,打开CONFIG_ARCH_S3C2440 ok lzd@lzd-laptop:~/Desktop/kernel-2.6.13$ make menuconfig 在arch/arm/match-s3c2410/Kconfig中 config ARCH_S3C2440 bool "SMDK2440" select CPU_S3C2440 help Say Y here if you are using the SMDK2440. config ARCH_QQ2440 bool "QQ2440/mini2440" select CPU_S3C2440 help Say Y here if you are using the FriendlyARM QQ2440 or mini2440. 在同样目录的 makefile中 obj-$(CONFIG_ARCH_BAST) += mach-bast.o usb-simtec.o obj-$(CONFIG_ARCH_H1940) += mach-h1940.o obj-$(CONFIG_MACH_N30) += mach-n30.o obj-$(CONFIG_ARCH_SMDK2410) += mach-smdk2410.o obj-$(CONFIG_ARCH_S3C2440) += mach-smdk2440.o # ghcstop add obj-$(CONFIG_ARCH_AESOP2440) += mach-aesop2440.o obj-$(CONFIG_ARCH_QQ2440) += mach-qq2440.o obj-$(CONFIG_MACH_VR1000) += mach-vr1000.o usb-simtec.o obj-$(CONFIG_MACH_RX3715) += mach-rx3715.o obj-$(CONFIG_MACH_OTOM) += mach-otom.o obj-$(CONFIG_MACH_NEXCODER_2440) += mach-nexcoder.o 就是说可以定义多个板子的支持,在mach-xxx.c中定义了该板子相关的一些内容,比如machine_desc。 也就是说要打开 CONFIG_ARCH_S3C2440 就要选上这个条目了,同时还要关掉ARCH_QQ2440这个条目。 好了,去make喽。 ok nfs bootm 有了新的问题,输出来的东西是乱码,但是有输出,说明linux已经运行了(图片都出来了),只是串口没有弄好而已。也算有进展。 |
参考文件: