从零开始uboot—uboot在引导linux可能遇到的问题(3)

1860阅读 0评论2016-08-01 L_502091250
分类:嵌入式

要解决的问题:
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已经运行了(图片都出来了),只是串口没有弄好而已。也算有进展。



参考文件:


上一篇:从零开始uboot—uboot在引导linux之前要做的事(2)
下一篇:Nand Flash,Nor Flash,CFI Flash,SPI Flash 之间的关系