Tuesday, September 8, 2015

Bootloader

Bootloader概念
關於S3C2440的啟動方式有兩種Nor Flash 啟動和Nand Flash 啟動
Nor Flash Nand Flash 都是非易失性記憶體,Nor Flash 的特點是晶片內執行和不能直接寫操作,程式可以直接在其中運行,而不必將程式讀取到RAM 中運行。Nor Flash 雖然具有這個優點,但是它的性價比遠低於Nand Flash,因而很多系統採用Nand Flash 啟動。Nand Flash 的特點是採用非線性存儲模式,程式無法在其中運行,它只能作為程式或資料的存儲載體,存儲在其中的程式只能先拷貝到RAM 中才能運行。

Nor Flash 啟動時,與nGCS0 相連的Nor Flash 就被映射到nGCS0 片選的空間,其位址被映射為0x00000000;從Nand Flash 啟動時,S3C2440 晶片內部自帶的一塊容量為4K 的被稱為“Steppingstone”BootSRAM 被映射到nGCS0 片選的空間,其位址被映射為0x00000000。當系統上電或重定時,程式會從位址處開始執行,因此我們編寫的啟動代碼要確保存儲在位址處。

當啟動方式為Nor Flash 啟動時,沒有額外需要考慮的問題,因為這種情況下程式在系統啟動前就存儲在Nor Flash 中,我們只要保證將啟動代碼保存在Nor Flash 開始的位置即可,系統上電或重定時,位址處的啟動代碼就會被執行。

在啟動方式為Nand Flash 啟動的情況下,系統啟動前所有的程式存儲在Nand Flash 中,系統的啟動過程稍微有點複雜:系統上電或重定時,位址處為S3C2440 內部自帶的BootSRAM,啟動前裡面沒有任何存儲內容,啟動後S3C2440 先通過硬體機制Nand Flash 4K 的內容拷貝至其中,然後再運行裡面的程式(從位址處)。這種情況下我們需要保證將啟動代碼保存在Nand Flash 開始的位置,並且啟動代碼的大小要小於4K。這就是我們的裸機程式為什麼在nandflash能跑的原因。


啟動流程

系統上電之後,需要一段程式來進行初始化。比如:關閉WATCHDOG、改變系統時鐘(system clock controller)、初始化記憶體控制器(memory controller)、將更多的代碼複製到記憶體中(SDRAM)等。如果它能將作業系統內核覆制到記憶體中運行,無論從本地(比如Flash)還是從遠端(比如通過網路),就稱這段程式為Bootloader。簡單地說,Bootloader就是那麼一小段程式,它在系統上電時開始執行,,初始化硬體設備、準備好軟體環境,最後調用作業系統內核。

啟動過程: 系統上電  --> Bootloader --> Kernel --> Filesystem --> Application

Bootloader 可視作裸機程式集合,在系統上電時,需做硬體初始化,使得作業系統能夠在系統上運行,其主要重點如下
  1. 對硬體的操作
  2. ARM處理器的瞭解
  3. 程式的基本概念:重定位、棧、程式碼片段、資料段、BSS
自己寫 Bootloader 程式流程:
  1. close watchdog
  2. set system clock
  3. initail sdram
  4. relocate location, 把bootloader本身的代碼從flash複製到它的連結位址去
  5. run main
  6. 從NAND FLASH 把內核讀入內存
  7. 設置參數(放置Kernel所需要的參數)
  8. 跳轉執行
params

====================================
start.c


#define S3C2440_MPLL_200MHZ     ((0x5c<<12)|(0x01<<4)|(0x02))

#define S3C2440_MPLL_400MHZ     ((0x5c<<12)|(0x01<<4)|(0x01))

#define MEM_CTL_BASE    0x48000000



.text

.global _start

_start:




//1 ● close watchdog
ldr r0, =0x5300000
mov r1, #0
str r1, [r0]
//2 ● set system clock
ldr r0, =0x4c000014
// mov r1, #0x03 // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1
mov r1, #0x05 // FCLK:HCLK:PCLK=1:4:8, HDIVN=2,PDIVN=1
str r1, [r0]

// s3c2440 規定
// 如果HDIVN0CPU的匯流排模式應該從“fast bus mode”變為“asynchronous bus mode”
mrc p15, 0, r1, c1, c0, 0 /* 讀出控制暫存器 */ 
orr r1, r1, #0xc0000000 /* 設置為“asynchronous bus mode” */
mcr p15, 0, r1, c1, c0, 0 /* 寫入控制暫存器*/

// MPLLCON = S3C2440_MPLL_200MHZ
ldr r0, =0x4c000004
// ldr r1, = S3C2440_MPLL_200MHZ
ldr r1, = S3C2440_MPLL_400MHZ
str r1, [r0]

// 啟動ICACHE
mrc p15, 0, r0, c1, c0, 0 @ read control reg
orr r0, r0, #(1<<12)
mcr p15, 0, r0, c1, c0, 0   @ write it back

//3 ● initail sdram
ldr r0, =MEM_CTL_BASE
adr r1, sdram_config
add r3, r0, #(13*4)
1:
ldr r2, [r1], #4
str r2, [r0], #4
cmp r0, r3
bne 1b @f:往前跳
//4 ● relocate location, 把bootloader本身的代碼從flash複製到它的連結位址去
ldr sp, =0x34000000
bl copy_code_to_sdram

mov r0, #0
ldr r1, =_start
ldr r2, =__bss_start
sub r2, r2, r1

bl copy_code_to_sdram
bl clear_bss



//5 ● run main
ldr lr, =halt
ldr pc, =main
halt:
b halt


sdram_config:
.long 0x22011110 //BWSCON
.long 0x00000700 //BANKCON0
.long 0x00000700 //BANKCON1
.long 0x00000700 //BANKCON2
.long 0x00000700 //BANKCON3  
.long 0x00000700 //BANKCON4
.long 0x00000700 //BANKCON5
.long 0x00018005 //BANKCON6
.long 0x00018005 //BANKCON7
.long 0x008C04F4 //REFRESH
.long 0x000000B1 //BANKSIZE
.long 0x00000030 //MRSRB6
.long 0x00000030 //MRSRB7
====================================
boot.c

#include "setup.h"

void setup_start_tag(void);
void setup_memory_tags(void);
void setup_commandline_tag(char *cmdline);
void setup_end_tag(void);

extern void uart0_init(void);
extern void nand_read(unsigned int addr, unsigned char *buf, unsigned int len);
extern void puts(char *str);
extern void puthex(unsigned int val);

int main(void)
{
void (*theKernel)(int zero, int arch, unsigned int params);
volatile unsigned int *p = (volatile unsigned int *)0x30008000;


// 0. 幫內核設置串口: 內核啟動的開始部分會從串口列印一些資訊,
 //    但是內核一開始沒有初始化串
//    若不初始化串口,程式會變成死循環
uart0_init();


// 1. 從NAND FLASH 把內核讀入內存
puts("Copy kernel from nand\n\r");
nand_read(0x60000 + 64, (unsigned char *)0x30008000, 0x200000);
puthex(0x12345ABCD);
puts("\n\r");
puthex(*p);
puts("\n\r");

// 2.設置參數
puts("Set boot params\n\r");
setup_start_tag();
setup_memory_tags();
setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");
setup_end_tag();

// 3. 跳轉執行
puts("Boot kernel\n\r");
theKernel = (void (*)(int, int, unsigned int))0x30008000;
theKernel(0, 362, 0x30000100);
/*
* mov r0, #0
* ldr r1, =362
* ldr r2, =0x30000100
* mov pc, #0x30008000
*/

// 如果一切正常, 不會執行到這裡
puts("Error!\n\r");
return -1;
}

static struct tag *params; // 設置kernel所需參數
void setup_start_tag(void)
{
params = (struct tag*)0x30000100;

params -> hdr.tag = ATAG_CORE;
params -> hdr.size = tag_size(tag_core);

params -> u.core.flags = 0;
params -> u.core.pagesize = 0;
params -> u.core.rootdev = 0;

params = tag_next (params);
}


void setup_memory_tags(void)
{
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size (tag_mem32);

params->u.mem.start = 0x30000000;    // bd->bi_dram[i].start;
params->u.mem.size = 64*1024*1024;   // (64*1024*1024 = 0x4000000) 
                                      // bd->bi_dram[i].size;

params = tag_next (params); 
}


int strlen(char *str)
{
int i = 0;
while(str[i])
{
i++;
}
return i;
}


void strcpy(char *dest, char *src)
{
while((*dest++ = *src++) != '\0');
}


void setup_commandline_tag(char *cmdline)
{
int len = strlen(cmdline) + 1;

params->hdr.tag = ATAG_CMDLINE;
params->hdr.size = (sizeof (struct tag_header) + len + 3) >> 2;

strcpy (params->u.cmdline.cmdline, cmdline);

params = tag_next (params);
}


void setup_end_tag(void)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}
====================================
init.c

// NAND FLASH Controller
#define NFCONF (*(volatile unsigned long *)0x4E000000)
#define NFCONT (*(volatile unsigned long *)0x4E000004)
#define NFCMMD (*(volatile unsigned long *)0x4E000008)
#define NFADDR (*(volatile unsigned long *)0x4E00000C)
#define NFDATA (*(volatile unsigned long *)0x4E000010)
#define NFSTAT (*(volatile unsigned long *)0x4E000020)

// GPIO
#define GPHCON              (*(volatile unsigned long *)0x56000070)
#define GPHUP               (*(volatile unsigned long *)0x56000078)

// UART registers
#define ULCON0              (*(volatile unsigned long *)0x50000000)
#define UCON0               (*(volatile unsigned long *)0x50000004)
#define UFCON0              (*(volatile unsigned long *)0x50000008)
#define UMCON0              (*(volatile unsigned long *)0x5000000c)
#define UTRSTAT0            (*(volatile unsigned long *)0x50000010)
#define UTXH0               (*(volatile unsigned char *)0x50000020)
#define URXH0               (*(volatile unsigned char *)0x50000024)
#define UBRDIV0             (*(volatile unsigned long *)0x50000028)

#define TXD0READY   (1<<2)


void nand_read(unsigned int addr, unsigned char *buf, unsigned int len);
void nand_select(void);
void nand_deselect(void);
void nand_cmd(unsigned char cmd);
void nand_addr(unsigned int addr);
void nand_wait_ready(void);

unsigned char nand_data(void);

int isBootFromNorFlash(void)
{
volatile int *p = (volatile *)0;
int val;

val = *p;
*p = 0x12345678;
if(*p == 0x12345678)
{
// 寫成功,nand啟動
*p = val;
return 0; 
}
else
{
// NOR不能像內存一樣寫
return 1;
}
}


void copy_code_to_sdram(unsigned char *src, unsigned char *dest, unsigned int len)
{
// NOR Start
if(isBootFromNorFlash())
{
while(i < len)
{
dest[i] = src[i];
i++;
}
}
else
{
// nand_init(); 放到start.s, 因為不管是nor啟動或是nand啟動,同樣需要將
// 內核從nand複製到sdram
nand_read(src, dest, len);
}
}

void clear_bss()
{
extern  int __bss_state, __bss_end;
int *p = &__bss_state;

for(;p < &__bss_end; p++)
{
*p = 0;
}
}

void nand_init(void)
{
#define TACLS 0
#define TWRPH0 1
#define TWRPH1 0

// 設置時序
NFCONF = (TACLS << 12) | (TWRPH0 << 8) | (TWRPH1 << 4);
// 始能NAND FLASH Controller, 初始化ECC, 禁止片選
NFCONT = (1 << 4) | (1 << 1) | (1 << 0);
}

void nand_read(unsigned int addr, unsigned char *buffer, unsigned int len)
{
int col = addr % 2048; // 可能從中間page開始讀
int i = 0;

// 1. 選中
nand_select();

while(i < len)
{

// 2. 發出read命令 (00h)
nand_cmd(0x00);

// 3. 發出地址(分五次發出)
nand_addr(addr);

// 4. 發出read命命 (0x3h)
nand_cmd(0x03);

// 5. 判斷狀態
nand_wait_ready();

// 6. read 數據
for(; (col < 2048) && (i < len); col++)
{
buf[i] = nand_data();
i++;
addr++;
}
col = 0; // 讀完一頁, col 歸零
}
// 7. 取消選中
nand_deselect();
}

void nand_select(void)
{
NFCONT &= ~(1<<1);
}

void nand_deselect(void)
{
NFCONT |= (1<<1);
}

void nand_cmd(unsigned char cmd)
{
volatile int i;
NFCMMD = cmd;
for (i = 0; i < 10; i++);
}

void nand_addr(unsigned int addr)
{
unsigned int col = addr % 2048;
unsigned int page = addr / 2048;
unsigned int i;

NFADDR = col & 0xff;
for (i = 0; i < 10; i++);
NFADDR = (col >> 8)  && 0xff;
for (i = 0; i < 10; i++);

NFADDR = page & 0xff;
for (i = 0; i < 10; i++);
NFADDR = (page >> 8) & 0xff;
for (i = 0; i < 10; i++);
NFADDR = (page >> 16) & 0xff;
for (i = 0; i < 10; i++);
}

void nand_wait_ready(void)
{
while(!(NFSTAT & 1));
}

unsigned char nand_data(void)
{
return NFDATA;
}






#define PCLK            50000000    // init.c中的clock_init函數設置PCLK為50MHz
#define UART_CLK        PCLK        //  UART0的時鐘源設為PCLK
#define UART_BAUD_RATE  115200      // 串列傳輸速率
#define UART_BRD        ((UART_CLK  / (UART_BAUD_RATE * 16)) - 1)

/*
 * 初始化UART0
 * 115200,8N1,無流控
 */
void uart0_init(void)
{
    GPHCON  |= 0xa0;    // GPH2,GPH3用作TXD0,RXD0
    GPHUP   = 0x0c;     // GPH2,GPH3內部上拉

    ULCON0  = 0x03;     // 8N1(8個資料位元,無較驗,1個停止位)
    UCON0   = 0x05;     // 查詢方式,UART時鐘源為PCLK
    UFCON0  = 0x00;     // 不使用FIFO
    UMCON0  = 0x00;     // 不使用流控
    UBRDIV0 = UART_BRD; // 串列傳輸速率為115200
}


// 發送一個字元
void putc(unsigned char c)
{
    // 等待,直到發送緩衝區中的資料已經全部發送出去
    while (!(UTRSTAT0 & TXD0READY));
    
    // 向UTXH0寄存器中寫入資料,UART即自動將它發送出去
    UTXH0 = c;
}


// 打印除錯訊息
void puts(char *str)
{
int i = 0;

while(str[i])
{
putc(str[i]);
i++;
}
}

void puthex(unsigned int val)
{
// 0x1234abcd
int i,j;


puts("0x");
for(i=0; i<8; i++)
{
j = (val >> ((7-i)*4)) & 0xf;
if((j >= 0) && (j <= 9))
putc('0' + j);
else
putc('A' + j - 0xa);
}
}
====================================
/*
 *  linux/include/asm/setup.h
 *
 *  Copyright (C) 1997-1999 Russell King
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 *  Structure passed to kernel to tell it about the
 *  hardware it's running on.  See linux/Documentation/arm/Setup
 *  for more info.
 *
 * NOTE:
 *  This file contains two ways to pass information from the boot
 *  loader to the kernel. The old struct param_struct is deprecated,
 *  but it will be kept in the kernel for 5 years from now
 *  (2001). This will allow boot loaders to convert to the new struct
 *  tag way.
 */
#ifndef __ASMARM_SETUP_H
#define __ASMARM_SETUP_H

#define u8  unsigned char
#define u16 unsigned short
#define u32 unsigned long

/*
 * Usage:
 *  - do not go blindly adding fields, add them at the end
 *  - when adding fields, don't rely on the address until
 *    a patch from me has been released
 *  - unused fields should be zero (for future expansion)
 *  - this structure is relatively short-lived - only
 *    guaranteed to contain useful data in setup_arch()
 */
#define COMMAND_LINE_SIZE 1024

/* This is the old deprecated way to pass parameters to the kernel */
struct param_struct {
    union {
struct {
   unsigned long page_size; /*  0 */
   unsigned long nr_pages; /*  4 */
   unsigned long ramdisk_size; /*  8 */
   unsigned long flags; /* 12 */
#define FLAG_READONLY 1
#define FLAG_RDLOAD 4
#define FLAG_RDPROMPT 8
   unsigned long rootdev; /* 16 */
   unsigned long video_num_cols; /* 20 */
   unsigned long video_num_rows; /* 24 */
   unsigned long video_x; /* 28 */
   unsigned long video_y; /* 32 */
   unsigned long memc_control_reg; /* 36 */
   unsigned char sounddefault; /* 40 */
   unsigned char adfsdrives; /* 41 */
   unsigned char bytes_per_char_h; /* 42 */
   unsigned char bytes_per_char_v; /* 43 */
   unsigned long pages_in_bank[4]; /* 44 */
   unsigned long pages_in_vram; /* 60 */
   unsigned long initrd_start; /* 64 */
   unsigned long initrd_size; /* 68 */
   unsigned long rd_start; /* 72 */
   unsigned long system_rev; /* 76 */
   unsigned long system_serial_low; /* 80 */
   unsigned long system_serial_high; /* 84 */
   unsigned long mem_fclk_21285;       /* 88 */
} s;
char unused[256];
    } u1;
    union {
char paths[8][128];
struct {
   unsigned long magic;
   char n[1024 - sizeof(unsigned long)];
} s;
    } u2;
    char commandline[COMMAND_LINE_SIZE];
};


/*
 * The new way of passing information: a list of tagged entries
 */

/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE 0x00000000

struct tag_header {
u32 size;
u32 tag;
};

/* The list must start with an ATAG_CORE node */
#define ATAG_CORE 0x54410001

struct tag_core {
u32 flags; /* bit 0 = read-only */
u32 pagesize;
u32 rootdev;
};

/* it is allowed to have multiple ATAG_MEM nodes */
#define ATAG_MEM 0x54410002

struct tag_mem32 {
u32 size;
u32 start; /* physical start address */
};

/* VGA text type displays */
#define ATAG_VIDEOTEXT 0x54410003

struct tag_videotext {
u8 x;
u8 y;
u16 video_page;
u8 video_mode;
u8 video_cols;
u16 video_ega_bx;
u8 video_lines;
u8 video_isvga;
u16 video_points;
};

/* describes how the ramdisk will be used in kernel */
#define ATAG_RAMDISK 0x54410004

struct tag_ramdisk {
u32 flags; /* bit 0 = load, bit 1 = prompt */
u32 size; /* decompressed ramdisk size in _kilo_ bytes */
u32 start; /* starting block of floppy-based RAM disk image */
};

/* describes where the compressed ramdisk image lives (virtual address) */
/*
 * this one accidentally used virtual addresses - as such,
 * its depreciated.
 */
#define ATAG_INITRD 0x54410005

/* describes where the compressed ramdisk image lives (physical address) */
#define ATAG_INITRD2 0x54420005

struct tag_initrd {
u32 start; /* physical start address */
u32 size; /* size of compressed ramdisk image in bytes */
};

/* board serial number. "64 bits should be enough for everybody" */
#define ATAG_SERIAL 0x54410006

struct tag_serialnr {
u32 low;
u32 high;
};

/* board revision */
#define ATAG_REVISION 0x54410007

struct tag_revision {
u32 rev;
};

/* initial values for vesafb-type framebuffers. see struct screen_info
 * in include/linux/tty.h
 */
#define ATAG_VIDEOLFB 0x54410008

struct tag_videolfb {
u16 lfb_width;
u16 lfb_height;
u16 lfb_depth;
u16 lfb_linelength;
u32 lfb_base;
u32 lfb_size;
u8 red_size;
u8 red_pos;
u8 green_size;
u8 green_pos;
u8 blue_size;
u8 blue_pos;
u8 rsvd_size;
u8 rsvd_pos;
};

/* command line: \0 terminated string */
#define ATAG_CMDLINE 0x54410009

struct tag_cmdline {
char cmdline[1]; /* this is the minimum size */
};

/* acorn RiscPC specific information */
#define ATAG_ACORN 0x41000101

struct tag_acorn {
u32 memc_control_reg;
u32 vram_pages;
u8 sounddefault;
u8 adfsdrives;
};

/* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */
#define ATAG_MEMCLK 0x41000402

struct tag_memclk {
u32 fmemclk;
};

struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;

/*
* Acorn specific
*/
struct tag_acorn acorn;

/*
* DC21285 specific
*/
struct tag_memclk memclk;
} u;
};

struct tagtable {
u32 tag;
int (*parse)(const struct tag *);
};


#define tag_member_present(tag,member) \
((unsigned long)(&((struct tag *)0L)->member + 1) \
<= (tag)->hdr.size * 4)

#define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size))
#define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2)

#define for_each_tag(t,base) \
for (t = base; t->hdr.size; t = tag_next(t))

/*
 * Memory map description
 */
#define NR_BANKS 8

struct meminfo {
int nr_banks;
unsigned long end;
struct {
unsigned long start;
unsigned long size;
int           node;
} bank[NR_BANKS];
};

extern struct meminfo meminfo;

#endif
====================================
Makefile

CC      = arm-linux-gcc
LD      = arm-linux-ld
AR      = arm-linux-ar
OBJCOPY = arm-linux-objcopy
OBJDUMP = arm-linux-objdump

CFLAGS := -Wall -O2
CPPFLAGS   := -nostdinc -nostdlib -fno-builtin

objs := start.o init.o boot.o

boot.bin: $(objs)
${LD} -Tboot.lds -o boot.elf $^
${OBJCOPY} -O binary -S boot.elf $@
${OBJDUMP} -D -m arm boot.elf > boot.dis
%.o:%.c
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<

%.o:%.S
${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $<

clean:
rm -f *.o *.bin *.elf *.dis
====================================
boot.lds


SECTIONS {
    . = 0x33f80000;
    .text : { *(.text) }
    
    . = ALIGN(4);
    .rodata : {*(.rodata*)} 
    
    . = ALIGN(4);
    .data : { *(.data) }
    
    . = ALIGN(4);
    __bss_start = .;
    .bss : { *(.bss)  *(COMMON) }
    __bss_end = .;
}

====================================

參考資料:
  1. 嵌入式Linux應用開發完全手冊
  2. S3C2440手冊
  3. http://6xudonghai.blog.163.com/blog/static/336406292008724103317304/ (arm9的CP15 && MRC && MCR)
  4. http://blog.csdn.net/tommy_wxie/article/details/8847247 (Uboot分析)
  5. http://blog.csdn.net/tommy_wxie/article/details/8760770 (Boot Loader啟動過程分析)

No comments:

Post a Comment