Chinaunix首页 | 论坛 | 博客
  • 博客访问: 196108
  • 博文数量: 71
  • 博客积分: 3000
  • 博客等级: 中校
  • 技术积分: 450
  • 用 户 组: 普通用户
  • 注册时间: 2007-11-29 21:28
个人简介

思考人生、专注技术

文章分类

全部博文(71)

文章存档

2015年(1)

2009年(2)

2008年(11)

2007年(57)

我的朋友

分类: C/C++

2007-12-04 16:41:22

最近由于在做u-boot-1.1.4到深圳优龙的ST2410开发板的移植,想通过修改U-Boot的源代码来让它支持从NAND Flash的启动。现在移植已经做得有点眉目了,还存在一些问题。在这个过程中,最近遇到的问题是启动ARM Linux的必备条件和U-Boot的go命令和bootm命令的一些区别,有所体会,在此记录一下。

先来引用一下这篇介绍“ARM Linux内核启动要求”的文章ARM Linux Kernel Boot Requirements,是ARM Linux内核的维护者Russell King写的。
  • CPU register settings
    • r0 = 0.
    • r1 = machine type number.
    • r2 = physical address of tagged list in system RAM.
  • CPU mode
    • All forms of interrupts must be disabled (IRQs and FIQs.)
    • The CPU must be in SVC mode. (A special exception exists for Angel.)
  • Caches, MMUs
    • The MMU must be off.
    • Instruction cache may be on or off.
    • Data cache must be off and must not contain any stale data.
  • Devices
    • DMA to/from devices should be quiesced.
  • The boot loader is expected to call the kernel image by jumping directly to the first instruction of the kernel image.
大致就是以上条件了,请特别关注一下第一条,这个基本上就是U-Boot的go命令和bootm命令之间的本质区别所在了。先来看看bootm命令的实现,在common/cmd_bootm.c的第119行开始有:

#ifdef CONFIG_PPC
static boot_os_Fcn do_bootm_linux;
#else
extern boot_os_Fcn do_bootm_linux;
#endif

这里的预编译宏说明了,非PPC体系结构的CPU的do_bootm_linux()函数都不是在这个文件内实现的(extern)。可想而知,这个函数的实现应该是和体系结构相关的,具体到arm体系结构的实现就是在lib_arm/armlinux.c这个文件当中。可以看到从lib_arm/armlinux.c中的第77行开始就是do_bootm_linux()函数的实现。
其中第85行声明了这样一个函数指针theKernel:

void (*theKernel)(int zero, int arch, uint params);

看看它的名字和参数的命名我们也可以猜到这个其实就是内核的入口函数的指针了。几个参数的命名也说明了上文提到的ARM Linux内核启动要求的第一条,因为根据ACPS(ARM/Thumb Procedure Call Standard)的规定,这三个参数就是依次使用r0,r1和r2来传递的。
接下来第93行就是给这个函数指针赋值:

theKernel = (void (*)(int, int, uint))ntohl(hdr->ih_ep);

可以看到theKernel被赋值为hdr->ih_ep,这个hdr是指使用tools/mkimage工具程序制作uImage时加在linux.bin.gz前面的一个头部,而ih_ep结构体成员保存的就是使用mkimage时指定的-e参数的值,即内核的入口点(Entry Point)。 知道了hdr->ih_ep的意义之后,给theKernel赋这个值也就是理所当然的了。
最后是对内核入口函数的调用,发生在第270行:

theKernel (0, bd->bi_arch_number, bd->bi_boot_params);

调用的时候对参数进行赋值,r0=0,r1=bd->bi_arch_number,r2=bd->bi_boot_params,一个都不少。至此U-Boot的使命完成,开始进入ARM Linux的美丽新世界。
本文还是以u-boot-1.1.4为例,以make smdk2410_config命令配置源代码之后进行分析。
对于C语言部分代码的调用出现在cpu/arm920t/start.S的第223行:
[code]
        ldr pc, _start_armboot
_start_armboot: .word start_armboot
[/code]
这里的start_armboot就是lib_arm/board.c中第207行的start_armboot()函数,由此U-Boot开始执行C语言部分的代码。
start_armboot()函数一开始首先是一个宏调用:
[code]
DECLARE_GLOBAL_DATA_PTR;
[/code]
这个宏的定义在include/asm-arm/global_data.h文件的第64行:
[code]
#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")
[/code]
大概的意思是声明一个指向gd_t结构体变量的指针gd,并固定使用寄存器r8来存放该指针。而对gd_t结构体的定义从上面的第36行开始:
[code]
typedef struct global_data {
    bd_t  *bd;
    unsigned long flags;
    unsigned long baudrate;
    unsigned long have_console; /* serial_init() was called */
    unsigned long reloc_off; /* Relocation Offset */
    unsigned long env_addr; /* Address  of Environment struct */
    unsigned long env_valid; /* Checksum of Environment valid? */
    unsigned long fb_base; /* base address of frame buffer */
#ifdef CONFIG_VFD
    unsigned char vfd_type; /* display type */
#endif
#if 0
    unsigned long cpu_clk; /* CPU clock in Hz!  */
    unsigned long bus_clk;
    unsigned long ram_size; /* RAM size */
    unsigned long reset_status; /* reset status register at boot */
#endif
    void  **jt;  /* jump table */
} gd_t;
[/code]
在lib_arm/board.c文件中之所以能够直接使用这个宏和gd_t结构体是因为包含了include/common.h头文件,在common.h的第115行包含了include/asm-arm/global_data.h头文件。
继续来看start_armboot()函数的执行。lib_arm/board.c第229行开始的一个for循环:
[code]
for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {
    if ((*init_fnc_ptr)() != 0) {
        hang ();
    }
}
[/code]
这里的init_sequence是定义在上面第190行处的一个指针数组:
[code]
init_fnc_t *init_sequence[] = {
    cpu_init,  /* basic cpu dependent setup */
    board_init,  /* basic board dependent setup */
    interrupt_init,  /* set up exceptions */
    env_init,  /* initialize environment */
    init_baudrate,  /* initialze baudrate settings */
    serial_init,  /* serial communications setup */
    console_init_f,  /* stage 1 init of console */
    display_banner,  /* say that we are here */
    dram_init,  /* configure available RAM banks */
    display_dram_config,
#if defined(CONFIG_VCMA9) || defined (CONFIG_CMC_PU2)
    checkboard,
#endif
    NULL,
};
[/code]
数组类型init_fnc_t则是一个定义在第188行的函数指针类型:
[code]
typedef int (init_fnc_t) (void);
[/code]
由此可知,init_sequence[]数组当中的所有元素都是函数指针了,而这个for循环的作用就是遍历这个数组的所有元素,然后用“(*init_fnc_ptr)()”就依次调用了这些函数来进行初始化的工作。
书接上回,我们依次来看看init_sequence[]数组当中的各个元素。
首先是cpu_init()函数,定义于lib_arm/arm920t/cpu.c第88行。
接下来的board_init()函数定义于board/smdk2410/smdk2410.c第68行:

int board_init (void)
{
    DECLARE_GLOBAL_DATA_PTR;
    S3C24X0_CLOCK_POWER * const clk_power = S3C24X0_GetBase_CLOCK_POWER();
    S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();

    /* to reduce PLL lock time, adjust the LOCKTIME register */
    clk_power->LOCKTIME = 0xFFFFFF;

    /* configure MPLL */
    clk_power->MPLLCON = ((M_MDIV << 12) + (M_PDIV << 4) + M_SDIV);

    /* some delay between MPLL and UPLL */
    delay (4000);

    /* configure UPLL */
    clk_power->UPLLCON = ((U_M_MDIV << 12) + (U_M_PDIV << 4) + U_M_SDIV);

    /* some delay between MPLL and UPLL */
    delay (8000);

    /* set up the I/O ports */
    gpio->GPACON = 0x007FFFFF;
    gpio->GPBCON = 0x00044555;
    gpio->GPBUP = 0x000007FF;
    gpio->GPCCON = 0xAAAAAAAA;
    gpio->GPCUP = 0x0000FFFF;
    gpio->GPDCON = 0xAAAAAAAA;
    gpio->GPDUP = 0x0000FFFF;
    gpio->GPECON = 0xAAAAAAAA;
    gpio->GPEUP = 0x0000FFFF;
    gpio->GPFCON = 0x000055AA;
    gpio->GPFUP = 0x000000FF;
    gpio->GPGCON = 0xFF95FFBA;
    gpio->GPGUP = 0x0000FFFF;
    gpio->GPHCON = 0x002AFAAA;
    gpio->GPHUP = 0x000007FF;

    /* arch number of SMDK2410-Board */
    gd->bd->bi_arch_number = MACH_TYPE_SMDK2410;

    /* adress of boot parameters */
    gd->bd->bi_boot_params = 0x30000100;

    icache_enable();
    dcache_enable();

    return 0;
}

interrupt_init()函数定义于cpu/arm920t/s3c24x0/interrupts.c第55行:

int interrupt_init (void)
{
    S3C24X0_TIMERS * const timers = S3C24X0_GetBase_TIMERS();

    /* use PWM Timer 4 because it has no output */
    /* prescaler for Timer 4 is 16 */
    timers->TCFG0 = 0x0f00;
    if (timer_load_val == 0)
    {
        
/*
         * for 10 ms clock period @ PCLK with 4 bit divider = 1/2
         * (default) and prescaler = 16. Should be 10390
         * @33.25MHz and 15625 @ 50 MHz
         */

        timer_load_val = get_PCLK()/(2 * 16 * 100);
    }
    /* load value for 10 ms timeout */
    lastdec = timers->TCNTB4 = timer_load_val;
    /* auto load, manual update of Timer 4 */
    timers->TCON = (timers->TCON & ~0x0700000) | 0x600000;
    /* auto load, start Timer 4 */
    timers->TCON = (timers->TCON & ~0x0700000) | 0x500000;
    timestamp = 0;

    return (0);
}

阅读(771) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~