手上的板子是友善之臂的mini2451,最近刚开始学BootLoader这块的内容,想具体了解下如何实现的,加上友善之臂的Superboot没有开源,所以想自己写一个,在此记录下遇到的问题。
按照我的理解BootLoader主要就是做了三件事
1.初始化一些简单的外设
2.从nand flash 复制 kernel
3.启动kernel(跳转到kernel)。
我现在用的板子是mini2451,cpu是S3C2451 ,S3C2451支持两种启动方式,一是 OneNAND 启动,二是 IROM 启动。 Mini2451 并没有 OneNAND,所以它使用的是第二种启动方式。 S3C2451 含有一个 64K 的 IROM 和 8K 的 SRAM。
1)关闭看门狗;
uboot源码一般都是大于8K的所以需要分为BootLoader1和BootLoader2,BootLoader1主要是初始化硬件,并且把完整的BootLoader代码复制到RAM。BootLoader2主要是一些指令的处理以及跳转到kernel。
我的代码也是参考uboot的启动步骤
1.设置svc32模式
2.关闭看门狗
3.关闭中断
4.关闭mmu
5.初始化SDRAM
6.设置栈
7.设置时钟 (C语言)
8.初始化NAND Falsh(C语言)
9.从NAND Falsh复制kernel 到 SDRAM
10.设置kernel启动参数
11.跳转到kernel启动地址
因为我这个只有一个启动kernel的功能,所以整体代码小于8KB,不需要像uboot那样分为两个BootLoader,直接使用2451芯片在IROM固化的代码就可以把整个BootLoader完整拷贝到SRAM。
我按照上面的步骤编写代码,首先编写了前4个步骤,并且增加了一个led的初始化,这样可以让我调试代码,知道运行到什么位置了。汇编代码如下
.text
.global _start
_start:
b reset
ldr pc,=_undefined_instructions
ldr pc,=_software_interrupt
ldr pc,=_prefetch_abort
ldr pc,=_data_abort
ldr pc,=_nop_opeartion
ldr pc,=_irq
ldr pc,=_fiq
_undefined_instructions: .word undefined_instructions
_software_interrupt: .word software_interrupt
_prefetch_abort: .word prefetch_abort
_data_abort: .word data_abort
_nop_opeartion: .word nop_opeartion
_irq: .word irq
_fiq: .word fiq
undefined_instructions:
nop
software_interrupt:
nop
prefetch_abort:
nop
data_abort:
nop
nop_opeartion:
nop
irq:
nop
fiq:
nop
reset:
bl set_svc_mode //设置svc32模式
bl disable_watchdog //关闭看门狗
bl disable_interrupt //关闭中断
bl disable_mmu //关闭mmu
bl init_led //初始化led
bl delay
led: bl bright_led
bl delay
bl dark_led
bl delay
b led
set_svc_mode:
mrs r0,cpsr
bic r0,r0,#0x1f
orr r0,r0,#0xdf
msr cpsr,r0
mov pc, lr
#define WTCON 0x53000000
disable_watchdog:
ldr r0,=WTCON
mov r1,#0x00
str r1,[r0]
mov pc, lr
#define INTMSK_g1 0x4a000008
#define INTMSK_g2 0x4a000048
disable_interrupt:
mvn r1,#0x00
ldr r0,=INTMSK_g1
str r1,[r0]
ldr r0,=INTMSK_g2
str r1,[r0]
mov pc, lr
disable_mmu:
mcr p15,0,r0,c7,c7,0 //失效 ID cache
mrc p15,0,r0,c1,c0,0 //读取CP15 register 1
bic r0, r0, #0x00000005 // 101 Dcache MMU
mcr p15,0,r0,c1,c0,0 //写入cp15 register1
mov pc, lr
#define GPCON 0x56000000
#define GPDAT 0x56000004
#define GPIO_OUT(GPIO) (0x01<<(GPIO))
#define LED_1_GPIO GPIO_OUT(25)
#define LED_2_GPIO GPIO_OUT(26)
init_led:
ldr r0,=GPCON
ldr r1,[r0]
bic r1,r1,#LED_1_GPIO
bic r1,r1,#LED_2_GPIO
str r1,[r0]
mov pc,lr
bright_led:
ldr r0,=GPDAT
ldr r1,[r0]
bic r1,r1,#LED_1_GPIO
bic r1,r1,#LED_2_GPIO
str r1,[r0]
mov pc,lr
dark_led:
ldr r0,=GPDAT
ldr r1,[r0]
orr r1,r1,#LED_1_GPIO
orr r1,r1,#LED_2_GPIO
str r1,[r0]
mov pc,lr
delay:
mov r0, #0x100000
delay_loop:
cmp r0, #0
sub r0, r0, #1
bne delay_loop
mov pc, lr
在linux下编译成功后下载到板子,发现led不会闪烁,把除了led初始化以及闪烁的代码之外都注释掉,编译下载后发现可以闪烁,定位错误应该在前面初始化有问题,于是一行行开始解注释,找到设置svc32模式后会出现程序跑飞问题,可以吧cpsr寄存器的数据读出来,只要一写进去
msr cpsr,r0
就会跑飞,这个问题暂时没找到是什么原因,所以后面的代码我是直接把这个注释掉的。有知道原因的可以告诉我下
注释掉 svc32模式 ,led可以闪烁,说明到这里的代码暂时是没问题的。
后面添加
初始化SDRAM
设置栈
设置时钟 (C语言)
初始化NAND Falsh(C语言)
并且添加了串口初始化调试代码。
把串口代码烧写到板子后,发现在电脑端收到的数据有问题,我初始化串口是115200波特率 8位数据位 1位停止位 无奇偶校验 ,串口助手也没配置错,但是数据都是错的,我以为是时钟设置有问题,检测了下代码是按照数据手册来配置的,时钟这块出问题应该不大,还是去检查串口初始化的代码
void uart_init(void)
{
// 配置引脚
GPHCON = (GPHCON & ~0xffff ) | 0xaaaa;
// 设置数据格式等
ULCON0 = 0x3; // 数据位:8, 无校验, 停止位: 1, 8n1
UCON0 = 0x5; // 时钟:PCLK,禁止中断,使能UART发送、接收
UFCON0 = 0x01; // FIFO ENABLE
UMCON0 = 0; // 无流控
// 设置波特率
// DIV_VAL = (PCLK / (bps x 16 ) ) - 1 = (66500000/(115200x16))-1 = 35.08
// DIV_VAL = 35.08 = UBRDIVn + (num of 1’s in UDIVSLOTn)/16
UBRDIV0 = 35;
UDIVSLOT0 = 0x1;
}
对比数据手册发现了一个可能的问题
按照串口的公式算出来
DIV_VAL = (PCLK / (bps x 16 ) ) - 1 = (66500000/(115200x16))-1 = 35.08
UBRDIV0 = 35;
UDIVSLOT0 = 0x1;
35.08
整数给UBRDIV0
小数按照手册数据对照后写入UDIVSLOTO
按照手册来看 0.08应该是0x01没错,但是这个是要求时钟是准确的66.5MHz,有偏差的话小数就有可能不是0.08,加上我以前写单片机程序时也有遇到这种问题, 于是我把UDIVSLOTO值修改为0x02,编译烧写后,串口数据打印正常。
到这里前面的初始化基本就完成了,剩下从NAND Falsh复制kernel到SDRAM中以及跳转到kernel。
从NAND Falsh中赋值kernel首先要知道kernel在NAND Falsh中的烧写地址,因为SuperBoot不是开源的所以我也不太清楚友善之臂把kernel下载在什么位置,幸好kernel启动过程中会打印一些调试信息,其中就有kernel在nand flash中的地址。
于是按照这里给出的nand falsh中的地址 以及kernel所占空间的大小,把整个kernel从nand falsh中复制到SDRAM中设置的地址,我设置的地址为0x30008000 ,SDRAM1的地址是从0x30000000到0x38000000 ,为什么不直接复制到0x30000000,是因为kernel的参数我要放在前面的空间,所以直接让kernel从0x30008000开始。
跳转到kernel需要传入三个参数
具体内容可以去看看这篇博客 http://blog.csdn.net/u013686019/article/details/22417165
第一个规定是0
第二参数需要传入和kernel约定的id,我在调试信息中找了半天终于找到一个疑似ID的东西
暂且先把这个作为ID传进去
第三个参数就是传给kernel的命令行,kernel的参数也是通过调试信息找到的
到这里代码基本是写完了,于是编译烧写到板子运行 ,提示我ID有错
我发现他提示这里也给了我一个16进制的ID 0x00000695,我把这个ID直接传入就可以了
ID错了有两种方式解决,可以百度下。
编译烧写,成功启动kernel
中间还遇到了一些其它问题,不过很多都是其它原因导致了,有时候代码下载进去,需要把整块板子断电才可以成功运行,不然一直会打印错误信息
关于链接器地址以及实际地址这块也纠结了一段时间,百度后大致知道是什么东西了,因为整个BootLoader除了最后跳转到kernel用的是绝对跳转,其它地方都是相对跳转,所以链接器地址在这里不用太多的关注。
代码已经上传到github,下面是地址
https://github.com/ypckyin/BootLoader_2451
git@github.com:ypckyin/BootLoader_2451.git