文章主题
bootloader 是什么?如果你看到了这篇文章,肯定已经知道答案了,所以这里就不赘述了。这篇文章主要是根据韦东山老师的视频,从零开始写一个最简单的 bootloader,每一行代码都是手动输入。虽然直接看一遍视频,也能够理解其中的步骤或者原理,但是根据视频敲一遍之后,印象才是最深刻的。
内容导航
- 测试平台
- 文件目录结构
- 代码详解
- 一些操作指令和流程
测试平台
开发环境:电脑 MAC Pro,安装2个虚拟机 Windows7 和Ubuntu14.04。
- Win7: 用于代码编辑(SourceInsight)和烧录裸板程序(oflash)。需要安装驱动程序:OpenJTAG 驱动,PL2303 USB 转串口驱动。
- Ubuntu14.04:用于交叉编译,交叉编译工具链直接使用光盘里提供的 arm-linux-gcc 即可。
- 文件传送:Win7 与 Ubuntu 之间的文件复制使用 SecureCRT。
开发板:JZ2440-V3,这块板子上已经集成了 USB转串口芯片。
烧录工具:OpenJTAG,连接开发板的 JTAG 接口即可。
测试工具:串口,直接连接 MicroUSB 口到 PC 的 USB 口,串口工具 SecureCRT。
文件目录结构
start.S:第一个启动的程序,完成的功能是:
init.c:串口的初始化和发送数据,NandFlash 的初始化和读取数据,BSS 段初始化。
setup.h:主要是定义了向内核传递参数时所需要的 tag 结构体。
boot.c:main 函数,设置 bootloader 向内核传递的启动参数。
boot.lds:连接脚本。
Makefile:make 编译指令。
代码详解
我比较赞成“代码即注释”这样的编程风格,只要看代码中的注释,就能明白其中的逻辑。所以,这里只贴出代码,不再进行进一步解释说明了。
1、start.S
代码语言:javascript复制
#define S3C2440_MPLL_200MHZ ((0x5c<<12) | (0x01<<4) | (0x02))
#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 /* FLCK:HCLK:PCLK = 1:2:4, HDIVN=1, PDIVN=1*/
str r1,
[r0]
-
/*
如果 HDIVN 非0, CPU 的总线模式应该从
"fast bus mode"
改为
"asynchronous bus mod"*/
mrc p15,
0, r1, c1, c0,
0
/*
读出控制寄存器
*/
orr r1, r1,
#0xc0000000 /* 设置为 "asynchronous bus mod" */
mcr p15,
0, r1, c1, c0,
0
/*
写入控制寄存器
*/
-
/*
设置 MPLLCON */
ldr r0,
=0x4c000004
ldr r1,
=S3C2440_MPLL_200MHZ
str r1,
[r0]
-
/*
3.
初始化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
/* b表示向前跳转到标号1的地方*/
-
/*
4.
重定位:把bootloader本身的代码,从flash复制到它的链接地址上去
*/
ldr sp,
=0x34000000
/*
把堆栈设置为 SDRAM 的最顶端,因为是向下增长的
*/
bl nand_init /*
初始化 NAND Flash
*/
mov r0,
#0 /* 第一个参数:源地址 */
ldr r1,
=_start /*
第二个参数:目标地址,即链接地址*/
ldr r2,
=__bss_start
sub r2, r2, r1 /*
第三个参数:代码长度*/
bl copy_code_to_sdram /*
调用代码拷贝函数
*/
bl clean_bss /*
调用 bss 段清零函数
*/
-
/*
5.
执行 main */
ldr lr,
=halt
ldr pc,
=main
halt:
b halt
-
/* SDRAM 13
个控制器的值
*/
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
2、init.c
代码语言:javascript复制
/*
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 char *)0x56000070))
#define GPHUP (*((volatile unsigned char *)0x56000078))
/*
串口寄存器
*/
#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 PCLK 50000000 // init.c
#define UART_CLK PCLK // UART0 的时钟源设置为 PCLK
#define UART_BAUD_RATE 115200 // 波特率
#define UART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1)
#define TXD0READY (1<<2)
#define RXD0READY (1)
extern void nand_read(unsigned int addr, unsigned char*buf, unsigned int len);
/* UART0 初始化
*/
void uart0_init(void)
{
GPHCON |=
0xa0;
// GPH2,GPH3 用作 TXD0,RXD0
GPHUP =
0x0c;
// GPH2,GPH3内部上拉
ULCON0 =
0x03;
//
8N1
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;
}
unsigned char getc(void)
{
-
/*
等待,直到接受缓冲区有数据
*/
-
while
(!(UTRSTAT0 & RXD0READY));
-
/*
直接读取 URXH0 寄存器,即可获得接收到的数据
*/
-
return URXH0;
}
void puts(char *str)
{
int i =
0;
-
while(str[i])
-
{
putc(str[i]);
i ;
-
}
}
/*
*
Check
if boot from Nor
Flash
*
Return:
1-Yes,
0-No
*/
int isbootFromNorFlash(void)
{
volatile int *p =
(volatile int *)0;
int val;
val =
*p;
-
*p =
0x12345678;
-
if
(*p ==
0x12345678)
-
{
-
/* write data success, boot form Nand
Flash
*/
-
*p = val;
-
return
0;
-
}
-
else
-
{
-
/* write data failed, boot from Nor
Flash
*/
-
return
1;
-
}
}
/*
*
Init
Nand
Flash
Device
*
Return:
None
*/
void nand_init(void)
{
-
/*
NandFlash
的读写需要满足一定的时序,
-
首先查找 NAND FLASH 手册,确定每个信号需要持续的时间,以及两个信号之间需要等待的时间。
-
然后查找
2440
手册,确定这些时间要设置的是哪个寄存器的哪些bit位,然后通过公式计算出应该设置的值*/
-
#define TACLS 0
-
#define TWRPH0 3
-
#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)
{
unsigned int i;
NFCMMD = cmd;
-
for
(i =
0;i <
10; i );
}
void nand_addr(unsigned int addr)
{
volatile unsigned int i;
unsigned int col = addr %
2048;
unsigned int page = addr /
2048;
-
/*
发出列地址
*/
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));
}
char nand_data(void)
{
-
return NFDATA;
}
/*
*
Read data from Nand
Flash
*
Return:
None
*/
void nand_read(unsigned int addr, unsigned char*buf, unsigned int len)
{
unsigned int i =
0;
int col = addr 48;
-
/*
1.
选中
*/
nand_select();
-
while
(i < len)
-
{
-
/*
2.
发出读命令
0x00
*/
nand_cmd(0x00);
-
/*
3.
发出地址(分5步发出)
*/
nand_addr(addr);
-
/*
4.
发出读命令
0x30
*/
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();
}
/*
*
Copy code from NorFlash or NandFlash to SDRAM
*
Return:
None
*/
void copy_code_to_sdram(unsigned char *src, unsigned char *dest, unsigned int len)
{
unsigned int i =
0;
-
if
(isbootFromNorFlash())
-
{
-
while
(i < len)
-
{
dest[i]
= src[i];
i ;
-
}
-
}
-
else
-
{
nand_read((unsigned int)src, dest, len);
-
}
}
/*
*
Clear BSS Section
*
Return:
None
*/
void clean_bss(void)
{
extern int __bss_start, __bss_end;
int *p =
&__bss_start;
-
for
(; p <
&__bss_end; p )
-
*p =
0;
}
3、setup.h
主要是 tag 数据结构,这里就不贴全部代码了,如果需要可以直接从 uboot 的源码里找到这个文件。
代码语言:javascript复制
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;
};
4、boot.c
代码语言:javascript复制
#include "setup.h"
static struct tag *params;
void setup_start_tag(void)
{
-
/* uboot与kernel约定好:在地址
0x30000100
的地方开始存放参数
*/
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_tag(void)
{
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size(tag_mem32);
params->u.mem.start =
0x30000000;
// SDRAM 开始地址
params->u.mem.size =
64
*
1024
*
1024;
// SDRAM 容量
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 )
!=
'