1、關看門狗
2、設置棧,調用C函數進行其他初始化,但從定位代碼前的代碼,必須保證位置無關性。
3、初始化時鐘
4、初始化存儲控制器,以使用SDRAM
5、設置存儲控制器後,SDRAM可用了,重新設置棧指向SDRAM最高處。
6、初始化串口
7、初始化nand flash
8、重定位代碼
9、清楚bss段 //參考uboot源碼
//步驟123456789都可以參考之前寫過的裸板程序。沒什麼好說的。
10、跳轉到重定位後的main()函數中執行。//在boot.c文件中
10.1設置各種啓動參數
setup_start_tag();
setup_memory_tags();
setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0");
setup_end_tag();
10.2調用theKernel,跳轉到內核頭部執行
沒什麼好說的,直接上代碼。(大部分代碼都是參考百問網的,我添加了些註釋,非常方便理解)
start.S文件:
.text
.global _start
_start:
/* 1. 關看門狗 */
ldr r0, =0x53000000 @ WATCHDOG寄存器地址
mov r1, #0x0
str r1, [r0] @ 寫入0,禁止WATCHDOG,否則CPU會不斷重啓
ldr sp,=4096
bl clock_init @ 設置MPLL,改變FCLK、HCLK、PCLK
bl memsetup @ 設置存儲控制器以使用SDRAM
/* 4. 重定位 : 把bootloader本身的代碼從flash(nand 或者 nor flash)複製到它的鏈接地址去 */
ldr sp, =0x34000000
bl uart0_init /*sdram基地址0x30000000,64Mb 是0x34000000,指向最高 */
bl nand_init /* nand 啓動的話,要在前4K代碼裏面實現這個功能:把uboot全部從nand flash 複製到SDRAM */
/* 但是,nand啓動的話,這個uboot被加載在哪裏?被加載到nand的起始地址0,不然自動複製前4k就沒意義了 */
mov r0, #0 /* uboot 燒到nor flash 0地址 */
ldr r1, =_start /* 對nand flash,cpu發出的0地址是對nand flash的0地址讀寫數據,其相關寄存器纔對CPU統一編址 */
ldr r2, =__bss_start /* 這裏是__bss_start,不是__bss_end */
/* uboot 被鏈接在SDRAM很高的位置,最後512k,0x33f80000,0x30000000要放內核 */
sub r2, r2, r1 /* 讀到哪裏去?讀到對CPU來說的鏈接地址去 */
bl copy_code_to_sdram
bl clear_bss
/* 5. 執行main */
ldr lr, =halt
ldr pc, =main //main()函數在boot.c裏面
halt:
b halt
init.c文件:(主要提供一些初始化操作)
#include "serial.h"
/* NAND FLASH控制器 */
/* NAND Flash registers */
#define NFCONF (*(volatile unsigned int *)0x4e000000)
#define NFCMD (*(volatile unsigned char *)0x4e000004)
#define NFADDR (*(volatile unsigned char *)0x4e000008)
#define NFDATA (*(volatile unsigned char *)0x4e00000c)
#define NFSTAT (*(volatile unsigned char *)0x4e000010)
#define NFCONT (*((volatile unsigned long *)0x4E000004))
#define NFCMMD (*((volatile unsigned char *)0x4E000008))
void clock_init(void);
void memsetup(void);
void copy_steppingstone_to_sdram(void);
int isBootFromNorFlash(void);
void clear_bss(void);
void nand_read(unsigned int addr, unsigned char *buf, unsigned int len);
/*
* 對於MPLLCON寄存器,[19:12]爲MDIV,[9:4]爲PDIV,[1:0]爲SDIV
* 有如下計算公式:
* S3C2410: MPLL(FCLK) = (m * Fin)/(p * 2^s)
* S3C2440: MPLL(FCLK) = (2 * m * Fin)/(p * 2^s)
* 其中: m = MDIV + 8, p = PDIV + 2, s = SDIV
* 對於本開發板,Fin = 12MHz
* 設置CLKDIVN,令分頻比爲:FCLK:HCLK:PCLK=1:2:4,
* FCLK=200MHz,HCLK=100MHz,PCLK=50MHz
*/
void clock_init(void)
{
#define S3C2410_MPLL_200MHZ ((0x5c<<12)|(0x04<<4)|(0x00))
#define S3C2440_MPLL_200MHZ ((0x5c<<12)|(0x01<<4)|(0x02))
#define MPLLCON (*(volatile unsigned long *)0x4c000004)
#define CLKDIVN (*(volatile unsigned long *)0x4c000014)
#define GSTATUS1 (*(volatile unsigned long *)0x560000B0)
// LOCKTIME = 0x00ffffff; // 使用默認值即可
CLKDIVN = 0x03; // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1
/* 如果HDIVN非0,CPU的總線模式應該從“fast bus mode”變爲“asynchronous bus mode” */
__asm__(
"mrc p15, 0, r1, c1, c0, 0\n" /* 讀出控制寄存器 */
"orr r1, r1, #0xc0000000\n" /* 設置爲“asynchronous bus mode” */
"mcr p15, 0, r1, c1, c0, 0\n" /* 寫入控制寄存器 */
);
/* 判斷是S3C2410還是S3C2440 */
if ((GSTATUS1 == 0x32410000) || (GSTATUS1 == 0x32410002))
{
MPLLCON = S3C2410_MPLL_200MHZ; /* 現在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */
}
else
{
MPLLCON = S3C2440_MPLL_200MHZ; /* 現在,FCLK=200MHz,HCLK=100MHz,PCLK=50MHz */
}
}
/*
* 設置存儲控制器以使用SDRAM
*/
void memsetup(void)
{
#define MEM_CTL_BASE 0x48000000
volatile unsigned long *p = (volatile unsigned long *)MEM_CTL_BASE;
/* 這個函數之所以這樣賦值,而不是像前面的實驗(比如mmu實驗)那樣將配置值
* 寫在數組中,是因爲要生成”位置無關的代碼”,使得這個函數可以在被複制到
* SDRAM之前就可以在steppingstone中運行
*/
/* 存儲控制器13個寄存器的值 */
p[0] = 0x22011110; //BWSCON
p[1] = 0x00000700; //BANKCON0
p[2] = 0x00000700; //BANKCON1
p[3] = 0x00000700; //BANKCON2
p[4] = 0x00000700; //BANKCON3
p[5] = 0x00000700; //BANKCON4
p[6] = 0x00000700; //BANKCON5
p[7] = 0x00018005; //BANKCON6
p[8] = 0x00018005; //BANKCON7
/* REFRESH,
* HCLK=12MHz: 0x008C07A3,
* HCLK=100MHz: 0x008C04F4
*/
p[9] = 0x008C04F4;
p[10] = 0x000000B1; //BANKSIZE
p[11] = 0x00000030; //MRSRB6
p[12] = 0x00000030; //MRSRB7
}
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);
}
puts("copy code done!\n\r");
}
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);
puts("init nand done!\n\r");
}
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();
}
void clear_bss(void)
{
extern int __bss_start, __bss_end; /* c中用到這樣的地址要extern,彙編中不用extern */
int *p = &__bss_start;
for (; p < &__bss_end; p++)
*p = 0;
}
boot.c文件:(設置tags,調用theKernel())
#include "setup.h"
#include "serial.h"
extern void nand_read(unsigned int addr, unsigned char *buf, unsigned int len);
static struct tag *params;
void setup_start_tag(void)
{
params = (struct tag *)0x30000100; /* 內核如何知道我uboot把這些參數存放在0x30000100? */
/* thekernel 指向真正的uimage(不含頭部,頭部不被複制)0x30008000,何故不放在0x30000000? */
params->hdr.tag = ATAG_CORE; /* theKernel(0, 362, 0x30000100); 告訴內核參數位置 */
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; /* 各參數4字節對齊,包含字符串參數 */
/* 包含bootargs 的整一串字符串,而不是一個一個的存放 */
/* 這樣調用,各個命令一起:setup_commandline_tag("noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0"); */
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size = (sizeof (struct tag_header) + len + 3) >> 2; /* 加3再除4 即可取整 */
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);
/* theKernel(0, 362, 0x30000100); 相當於下面的彙編:
* mov r0, #0
* ldr r1, =362
* ldr r2, =0x30000100
* mov pc, #0x30008000
*/
puts("Error!\n\r");
/* 如果一切正常, 不會執行到這裏 */
return -1;
}
啓動時,事先要使用其他辦法把uImage燒寫到nand的60000中,能否掛載根文件系統是內核與bootloader設置的啓動參數的問題,這裏不作討論。
對啓動過程不理解,可以參考我這篇文章:U-boot啓動內核流程的詳細分析點擊打開鏈接