没什么好说的,直接上代码。(大部分代码都是参考百问网的,我添加了些注释,非常方便理解)
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启动内核流程的详细分析点击打开链接