自己寫 bootloader,或者移植 uboot ,需要知道的一些信息
1 內核在 nandflash 中的地址,是 uImage 還是 zImage ,我這里用的是 uImage ,在 nandflash 里的 0x60000處,uImage 是用來支持 uboot 啟動的,不過我們自己寫的話用 uImage 也沒關系,因為 uImage 就是在 zImage 的基礎上加了一個 64 字節的頭部,用來告訴 uboot 內核的信息,比如版本號、加載地址、鏈接地址、編譯時間等。uboot 會獲取到這個頭部,干一些事情。我們自己寫的話,直接跳到 uImage 啟示地址 + 64 的地方去執行就OK 。
2 內核的鏈接地址:0x30008000 ,這個連接地址實際上應該說是“真正”內核的鏈接地址,如果是 uImage ,不應該包含它的頭部。知道什么意思了吧,使用 uImage 時,我們應當將整個 uImage 拷貝到 0x30008000 - 64 的地方去,也就是 0x30007fc0 。
3 bootloader 的連接地址:uboot的鏈接地址一般為 0x3ff80000 處,我們自己寫的也用這個地址好了。說到這里,必須得提一下,對于2440來說,不管是 nand 啟動,還是 nor 啟動,剛上電的時候我們的 bootloader 并不是運行在鏈接地址處,那么這時候跑在非鏈接地址處的這些指令就得有特殊要求——位置無關碼。我個人做法,在將自身代碼拷貝到鏈接地址處之前,能用匯編寫的盡量不用C寫,因為用匯編我們自己可以分得清哪些是位置無關碼,迫不得已的情況下用C寫,寫完看反匯編,看看那些跳轉指令是否依賴于當前PC值。
4 標記 tag 的地址,2440 常用的做法是放在 0x30000100 處,我們同樣也放在這里。其實放那都行,地址會作為參數傳遞給內核的。tag的放置方法,可以參考 uboot ,必須以 ATAG_CORE 類型的開頭,以 ATAG_NONE 類型的結尾。
5 啟動內核時傳遞給內核的參數,3個,第一個默認0,第三個是前邊提到的 tag 的地址,第二個是機器ID。內核中所有支持的“機器”或者稱作開發板、單板都用一個 MACHINE_START 宏來定義,這個宏的作用就是填充一個 machine_desc 類型的結構體,填充的過程中會對它的 .nr 成員賦值,這個 nr 就是所謂的機器ID。
6 內核剛啟動時會打印一些信息,但是那時候內核自己并不會初始化串口,也為了方便自己調試,bootloader里需要初始化串口。
熟悉以上6條,自己寫一個 bootloader 就十分簡單了
第一階段:
- 硬件初始化
1 關看門狗
2 設置時鐘
3 初始化SDRAM
4 重定位:bootloader可能大于4K,把flash中的bootloader拷貝到SDRAM中。
5 跳轉到main
第二階段:
- 軟件初始化
6 初始化串口,內核啟動打印參數
7 設置啟動參數,供內核啟動時解析使用
8 跳轉啟動內核
實現第一階段
- 初始化硬件操作
關看門狗
設置時鐘
設置SDRAM
初始化NAND FLASH
硬件初始化 start.S
#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. 關看門狗 */
ldr r0, =0x53000000
mov r1, #0
str r1, [r0]
/* 2. 設置時鐘 */
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
str r1, [r0]
/* 如果HDIVN非0,CPU的總線模式應該從“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_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. 初始化SDRAM */
ldr r0, =MEM_CTL_BASE
adr r1, sdram_config /* sdram_config的當前地址 */
add r3, r0, #(13*4)
1:
ldr r2, [r1], #4
str r2, [r0], #4
cmp r0, r3
bne 1b
/* 4. 重定位 : 把bootloader本身的代碼從flash復制到它的鏈接地址去 */
ldr sp, =0x34000000
bl nand_init
mov r0, #0
ldr r1, =_start
ldr r2, =__bss_start
sub r2, r2, r1
bl copy_code_to_sdram
bl clear_bss
/* 5. 執行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
Rom初始化
/* NAND FLASH控制器 */
#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 NFSTAT (*((volatile unsigned char *)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);
int isBootFromNorFlash(void)
{
volatile int *p = (volatile int *)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)
{
int i = 0;
/* 如果是NOR啟動 */
if (isBootFromNorFlash())
{
while (i < len)
{
dest[i] = src[i];
i++;
}
}
else
{
//nand_init();
nand_read((unsigned int)src, dest, len);
}
}
void clear_bss(void)
{
extern int __bss_start, __bss_end;
int *p = &__bss_start;
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控制器, 初始化ECC, 禁止片選 */
NFCONT = (1<<4)|(1<<1)|(1<<0);
}
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;
volatile 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;
}
void nand_read(unsigned int addr, unsigned char *buf, unsigned int len)
{
int col = addr % 2048;
int i = 0;
/* 1. 選中 */
nand_select();
while (i < len)
{
/* 2. 發出讀命令00h */
nand_cmd(0x00);
/* 3. 發出地址(分5步發出) */
nand_addr(addr);
/* 4. 發出讀命令30h */
nand_cmd(0x30);
/* 5. 判斷狀態 */
nand_wait_ready();
/* 6. 讀數據 */
for (; (col < 2048) && (i < len); col++)
{
buf[i] = nand_data();
i++;
addr++;
}
col = 0;
}
/* 7. 取消選中 */
nand_deselect();
}
#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;
int 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);
}
}
鏈接文檔
SECTIONS {
. = 0x33f80000;
.text : { *(.text) }
. = ALIGN(4);
.rodata : {*(.rodata*)}
. = ALIGN(4);
.data : { *(.data) }
. = ALIGN(4);
__bss_start = .;
.bss : { *(.bss) *(COMMON) }
__bss_end = .;
}
軟件初始化
- 從NAND FLASH里把內核讀入內存 0x60000+64(64K的頭部)
內核地址0x30008000(內核中配置好的地址)nand_read的實現內核和UBOOT約定好在某個地址存放啟動參數,比如此處是0x30000100開始存放啟動參數,uboot將參數存放到0x30000100開始的地方,內核啟動時會從這里取出參數。實現代碼如下:
下面幾個函數,都向地址內寫入了
1 參數代號
2 本參數所占內存大小
3 參數內容
4 每個參數設置最后都指向了下一參數設置地址
5 設置啟動參數
#include "setup.h"
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);
static struct tag *params;
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;
params->u.mem.size = 64*1024*1024;
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;
}
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(0x1234ABCD);
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;
}