Mini440之uboot移植之实践NAND启动(四)


在之前的章节我们已经介绍了u-boot如何支持我们的NOR FLASH,以及DM9000网卡的支持。

这一节我们将会在Young / s3c2440_project[u-boot-2016.05-nor-flash】代码的基础上新建u-boot-2016.05-nand-flash项目,使得我们项目支持NAND,由于u-boot默认采用的NOR启动,这里我们需要修改u-boot实现NAND启动。

如果想支持NAND启动,我们需要考虑以下问题,S3C2440在NAND启动时S3C2440的NAND 控制器会自动把NAND FLASH中的前4K代码数据搬到内部SRAM中(在S3C2440片内有一块被称为SteppingStone的片内SRAM,它的大小为4K),片内SRAM被映射到nGCS0片选的空间(nGCS0片选的空间,即0x00000000),CPU从0x00000000位置开始运行程序。

然而我们的uboot程序是远远大于4kb的,因此程序大于4kb的时候需要从 NAND FLASH中拷贝到 SDRAM中去运行。自然可以想到烧到 NAND FLASH中的程序前面一部分代码应该是初始化SDRAM(程序最终需要拷贝到SDRAM中去运行)和 将NAND FLASH中的剩余的程序拷贝到SDRAM中去(全考过去也行,方便点),然后跳转到SDRAM中执行。

既然我们已经有了思路,那么接下来就是如何去实现。

一、NAND支持

在分析Mini440之uboot移植之源码分析start.S(一)中我们介绍到CPU上点首先执行的是start.S文件,在内核级配置和初始化过程中会调用cpu_init_crit函数。

cpu_init_crit:
    /*
     * flush v4 I/D caches      [1]. 清除cache
     */
    mov r0, #0
    mcr p15, 0, r0, c7, c7, 0   /* flush v3/v4 cache */
    mcr p15, 0, r0, c8, c7, 0   /* flush v4 TLB */

    /*
     * disable MMU stuff and caches  [2]. 禁止MMU
     */
    mrc p15, 0, r0, c1, c0, 0
    bic r0, r0, #0x00002300 @ clear bits 13, 9:8 (--V- --RS)
    bic r0, r0, #0x00000087 @ clear bits 7, 2:0 (B--- -CAM)
    orr r0, r0, #0x00000002 @ set bit 1 (A) Align
    orr r0, r0, #0x00001000 @ set bit 12 (I) I-Cache
    mcr p15, 0, r0, c1, c0, 0/*
     * before relocating, we have to setup RAM timing
     * because memory timing is board-dependend, you will
     * find a lowlevel_init.S in your board directory.
     */
    mov ip, lr

    bl  lowlevel_init          //[3]. 进入board/samsung/smdk2440/lowlevel_init.S执行,
                               //     设置内存控制寄存器时序参数
    mov lr, ip
    mov pc, lr

我们可以在cpu_init_crit函数执行完之后进行适当扩充,添加以下功能:

  • NAND初始化;
  • 代码重定位,将uboot拷贝到SDRAM,并进行跳转运行;

1.1 新建init.c文件

新建init.c文件并将文件放在board/samsung/smdk2440路径下,并修改当前路径下的Makefile:

obj-y    := smdk2440.o init.o
obj-y    += lowlevel_init.o

init.c代码如下:

/**************************************************************************
 *
 *  FileName  : init.c
 *  Function  : 一些初始化相关的函数
 *  Author    : zy
 *
 *************************************************************************/
#include 


/**************************************************************************
 *
 *  Function    :  nand flash读写相关函数
 *
 *************************************************************************/

/* 等待NAND Flash就绪 */
void _nand_wait_idle(void)
{
    int i;
    while(!(NFSTAT & BUSY));
    for(i=0; i<10; i++);
}

/* 发出片选信号 */
void _nand_select_chip(void)
{
    int i;
    NFCONT &= ~(1<<1);                   /* set CE=0 */
    for(i=0; i<10; i++);    
}

/* 取消片选信号 */
void _nand_deselect_chip(void)
{
    NFCONT |= (1<<1);                   /* set CE=1 */
}

/* 发出命令 */
void _nand_write_cmd(unsigned char cmd)
{
    int i;
    NFCMMD = cmd;
    for(i=0; i<10; i++);
}

/* 发出地址 */
void _nand_write_addr_byte(unsigned char addr)
{
    volatile int i;
    NFADDR = addr;
    for(i=0; i<10; i++);
}

/* 发出地址,5个周期来发送,前2个周期为col地址,后三个周期为row(page)地址 */
void _nand_write_addr(unsigned int addr)
{
    int col, page;

    col = addr & NAND_PAGE_MASK_SIZE;
    page = addr / NAND_PAGE_SIZE;     /* 块号  */

    _nand_write_addr_byte(col & 0xff);            /* Column Address A0~A7 */
    _nand_write_addr_byte((col >> 8) & 0x0f);     /* Column Address A8~A11 */
    _nand_write_addr_byte(page & 0xff);            /* Row Address A12~A19 */
    _nand_write_addr_byte((page >> 8) & 0xff);    /* Row Address A20~A27 */
    _nand_write_addr_byte((page >> 16) & 0x01);    /* Row Address A28 */
}

/* 复位 */
void _nand_reset(void)
{
    _nand_select_chip();
    _nand_write_cmd(0xff);          // 复位命令
    _nand_wait_idle();
    _nand_deselect_chip();
}

/* 读取数据 */
unsigned char _nand_read_data(void)
{
    return NFDATA;
}

/* 写入数据 */
void _nand_write_data(unsigned char data)
{
    int i;
    NFDATA = data;
    for(i=0; i<10; i++);
}


/* 初始化and Flash */
void nand_init_ll(void)
{
    #define TACLS   0
    #define TWRPH0  1
    #define TWRPH1  0

    /* 设置时序 */
    NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4);
    /* 使能NAND Flash控制器, 初始化ECC, 禁止片选, 使能NAND控制器 */
    NFCONT = (1<<4)|(1<<1)|(1<<0);

    /* 复位NAND Flash */
    _nand_reset();
}


/* 发送读取数据命令,将nand start_addr处数据读取到buf中,读取长度为size, start_addr参数必须按页对齐   success:0 fail:-1  */
int nand_read_data(unsigned char *buf, unsigned int start_addr, unsigned int size)
{
    int i = 0;
    int j = 0;

    if (start_addr & NAND_PAGE_MASK_SIZE ) {
        return -1;    /* 地址或长度不对齐 */
    }

    /* 选中芯片 */
    _nand_select_chip();

    /* 按页读取 */
    while (i < size) {
      /* 发出READ命令 */
      _nand_write_cmd(0);

      /* write address */
      _nand_write_addr(start_addr+i);

      /* write cmd */
      _nand_write_cmd(0x30);

      /* wait */
      _nand_wait_idle();

      /* 读取一页数据 */
      for(; (j < NAND_PAGE_SIZE) &&  (i < size); j++)
      {
          buf[i++] = _nand_read_data();
      }
      j = 0;
    }

    /* 取消片选信号 */
    _nand_deselect_chip();
    
    return 0;
}



/**************************************************************************
 *
 *  Function    :  自动区分是nand启动还是nor启动
 *                 NAND启动,此时内部4k SRAM映射到0x00处,才可以访问该内存
 *                 NOR启动,此时内部2M NOR FLASH映射到地址0x00处,此时无法写入内存,片内SRAM映射到0x40000000地址处
 *
 *************************************************************************/
int is_boot_from_nor_flash(void)
{
    volatile unsigned int *p = (volatile unsigned int *)0x00;

    unsigned int val = *p;          /* get value from address 0x00 */
    *p = 0x12345678;                /* write value to address 0x00 */

    /* 写成功, 对应nand启动 */
    if(*p == 0x12345678){
        *p = val;    
        return 0;
    }

    return 1;
}

/**************************************************************************
 *
 *  Function    :  清除bss段
 *
 *************************************************************************/
void clean_bss(void)
{
    /* 要从lds文件中获得 __bss_start, __bss_end
     */
    extern int __bss_end, __bss_start;

    int *p = &__bss_start;
    for (; p < &__bss_end; p++)
        *p = 0;

}



/**************************************************************************
 *
 *  Function    :  将代码从nand/nor falsh复制到sdram
 *
 *************************************************************************/
void copy_code_to_sdram(void)
{
    /* 要从lds文件中获得 __image_copy_start, __image_copy_end 然后从0地址把数据复制到__image_copy_start */
    extern int __image_copy_start, __image_copy_end;
    volatile unsigned int *dest = (volatile unsigned int *)&__image_copy_start;
    volatile unsigned int *end = (volatile unsigned int *)&__image_copy_end;
    volatile unsigned int *src = (volatile unsigned int *)0;
    unsigned int len = (unsigned int)(&__image_copy_end) - (unsigned int)(&__image_copy_start);

    /* nor falsh boot:  nor flash address 0x00 */
    if (is_boot_from_nor_flash())
    {
        /* 把nor flash的内容全部copy到sdram */
         while (dest < end)
         {
             *dest++ = *src++;
          }
    }
    else 
    {
        // 将nand flash内容复制到SDRAM
        nand_read_data(dest, (unsigned int)src, len);
    }
}

在include目录下新建init.h头文件,代码如下:

#ifndef __S3C2440_INIT__
#define __S3C2440_INIT__

#define GSTATUS1        (*(volatile unsigned long *)0x560000B0)
#define BUSY            1

/* page size 2048byte */
#define NAND_PAGE_SIZE           2048
#define NAND_PAGE_MASK_SIZE      (NAND_PAGE_SIZE - 1)
#define NAND_BLOCK_SIZE          (64*NAND_PAGE_SIZE)
#define NAND_BLOCK_MASK_SIZE     (NAND_BLOCK_SIZE-1)

 /* NAND FLASH (see S3C2440 manual chapter 6, www.100ask.net) */
#define     NFCONF        (*(volatile unsigned long *)0x4e000000)        // 配置寄存器
#define     NFCONT       (*(volatile unsigned long *)0x4e000004)        // 控制寄存器
#define     NFCMMD       (*(volatile unsigned char *)0x4e000008)        // 命令寄存器
#define     NFADDR       (*(volatile unsigned char *)0x4e00000C)        // 地址寄存器
#define     NFDATA       (*(volatile unsigned char *)0x4e000010)        // 数据寄存器
#define     NFMECCD0     (*(volatile unsigned long *)0x4e000014)
#define     NFMECCD      (*(volatile unsigned long *)0x4e000018)
#define     NFSECCD      (*(volatile unsigned long *)0x4e00001C)
#define     NFSTAT       (*(volatile unsigned long *)0x4e000020)
#define     NFESTAT0     (*(volatile unsigned long *)0x4e000024)
#define     NFESTAT1     (*(volatile unsigned long *)0x4e000028)
#define     NFMECC0      (*(volatile unsigned long *)0x4e00002C)
#define     NFMECC1      (*(volatile unsigned long *)0x4e000030)
#define     NFSECC       (*(volatile unsigned long *)0x4e000034)
#define     NFSBLK       (*(volatile unsigned long *)0x4e000038)
#define     NFEBLK       (*(volatile unsigned long *)0x4e00003C)

/* 函数声明 */
extern void nand_init_ll(void);
extern void copy_code_to_sdram();
extern void clean_bss(void);


#endif

修改include/configs/smdk2440.h文件,将CONFIG_SYS_TEXT_BASE宏改为0x33f00000,也就是uboot重定位后的位置, 这里没有设置成0x33f80000,而是留了1M空间供给uboot重定位,主要是因为我们的u-boot可能大于512kb。

具体可以看考Mini440之uboot移植之源码分析u-boot重定位(三)

1.2 start.S修改

lowlevel_init函数执行完之后就是执行bl _main(arch/arm/lib/crt0.S),因此我们可以在cpu_init_crit执行之后,并加入NAND初始化,以及重定位相关代码。修改之后的start.S代码:

#ifndef CONFIG_SKIP_LOWLEVEL_INIT
    bl    cpu_init_crit
#endif

    ldr sp, =(CONFIG_SYS_INIT_SP_ADDR)      @1. 设置栈    0x30000f50
    bic sp, sp, #7  /* 8-byte alignment for ABI compliance */
   
    bl nand_init_ll                          @ 2. nand 初始化
    bl copy_code_to_sdram                  @3.代码重定位  
    bl bss_init                            @4. 清除bss段(参考自制uboot章节)
    ldr pc,=_main                          @5. 绝对跳转,跳转到SDRAM运行 


/*  bss段初始化,该段内存清零  */
bss_init:
    ldr r0,=__bss_start          @ 获取.bss段初始地址    在链接文件中定义
    ldr r1,=__bss_end            @ 获取.bss段的结束地址  在链接文件中定义
    cmp r0,r1                  
    moveq  pc,lr                 @ 相等则返回
    
clear:
    mov r2,#0x00
    str r2,[r0],#0x04         @ [r0] = 0x00   r0=r0+4        
    cmp r0,r1
    bne clear                 @不相等跳转
    mov pc,lr                 @ 返回

最后我们使用ldr进行了绝对跳转,跳转到SDRAM中运行。

1.3 屏蔽u-boot重定位代码

因为我们已经将代码从NAND FLASH拷贝到了SDRAM中,因此需要屏蔽掉u-boot的重定位、以及BSS清除相关的代码。屏蔽掉_main(arch/arm/lib/crt0.S)中如下代码:

    ldr    r0, [r9, #GD_RELOCADDR]        /* r0 = gd->relocaddr */
    b    relocate_code

修改reserve_uboot(common/board_f.c):

static int reserve_uboot(void)
{
    /*
     * reserve memory for U-Boot code, data & bss
     * round down to next 4 kB limit
     */
    gd->relocaddr = CONFIG_SYS_TEXT_BASE;


    debug("Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10,
          gd->relocaddr);

    gd->start_addr_sp = gd->relocaddr;

    return 0;
}

1.4 修改链接脚本

start.S, init.c(实现重定位), lowlevel.S(实现初始化SDRAM)等文件放在内存空间最前面。修改arch/arm/cpu/u-boot.lds部分信息如下:

    .text :
    {
        *(.__image_copy_start)
        *(.vectors)
        CPUDIR/start.o (.text*)
        board/samsung/smdk2440/built-in.o (.text*)  
        *(.text*)
    }

build-in.o是将smdk2440单板目录下的所有*.c,*S文件编译后,连接成一个库文件。

1.5、去掉 "-pie"选项

在u-boot源码分析重定位中,我们说过在arm-linux-ld时加了"-pie"选项, 使得u-boot.bin里多了"*(.rel*)", "*(.dynsym)",从而程序非常大,不利于从NAND启动,所以我们修改代码,并取消"-pie"选项。运行:

 grep "\-pie"  *  -nR

我们修改arch/arm/config.mk,屏蔽掉下面代码:

LDFLAGS_u-boot += -pie

此外,我们还需要屏蔽掉顶层Makefile文件中的重定位规则检验的代码,否则编译汇报如下错误:

 我们定位到1387行,修改为如下:

checkarmreloc: u-boot
    @RELOC="`$(CROSS_COMPILE)readelf -r -W $< | cut -d ' ' -f 4 | \
        grep R_A | sort -u`"; \
#    if test "$$RELOC" != "R_ARM_RELATIVE" -a \
#         "$$RELOC" != "R_AARCH64_RELATIVE"; then \
#        echo "$< contains unexpected relocations: $$RELOC"; \
#        false; \
#    fi

修改arch/arm/cpu/u-boot.lds,将如下内容:

    .bss_start __rel_dyn_start (OVERLAY) : {
        KEEP(*(.__bss_start));
        __bss_base = .;
    }

    .bss __bss_base (OVERLAY) : {
        *(.bss*)
         . = ALIGN(4);
         __bss_limit = .;
    }

    .bss_end __bss_limit (OVERLAY) : {
        KEEP(*(.__bss_end));
    }

修改为:

. = ALIGN(4);
/* 定义变量,保存.bss段起始地址,可以在汇编代码中之间使用 */
__bss_start = .;

/* 全局的未初始化变量存在于.bss段中 */
.bss :
{
    *(.bss*)
}

 __bss_end = .;

为什么修改这个呢,主要是因为源代码配置的bss和rel.dyn使用的同一个段空间,而我们编译时取消了-pie选项,会导致bss段起始地址变成0x00。具体可以参考u-boot中bss段的使用。

二、 编译下载运行

2.1 配置编译

make distclean
make smdk2440_config
make ARCH=arm CROSS_COMPILE=arm-linux- V=1

2.2 查看内存分布

直接反汇编u-boot文件,可以得到反汇编代码:

arm-linux-objdump -D u-boot > u-boot.dis

查看u-boot.dis,可以看到代码的链接起始地址为0x33f00000:

继续查看u-boot.dis,找到NAND初始化代码:

 可以看到NAND相关代码均没有超过4kb,执行完代码重定位后,执行了代码跳转,跳转到SDRAM中运行__main函数:

2.3 下载到NAND FALSH

我们将代码下载到NAND FLASH中,并从NAND启动,串口输出信息如下:

参考文章

[1]S3C2440移植uboot之支持NAND启动

[2]

[3]移植u-boot-2016.11到JZ2440(三:修改源码之实现NOR启动与NAND启动)