在嵌入式Linux操作系统中,需要将三样东西(BootLoader
、内核kernel
、根文件系统
)传输到目标板中。一般而言,U-Boot烧写到SD卡中,而内核、根文件系统都采用TFTP的方式传输到目标板,然后通过U-Boot的命令进行启动。
那么U-Boot是如何烧写到SD卡中的呢?
为了检测U-Boot是否真正少烧写到了SD卡中,本文通过修改U-Boot下的U-boot/arch/arm/cpu/armv7/start.S
文件,在该文件中增加对GPIO的操作,来对LED进行点亮操作,这样如果U-Boot顺利烧写到SD卡中,开机运行后就会点亮LED。
S5PV210的启动机制如下所示:
S5PV210
的启动过程有三个步骤组成,其中,iROM是平台独立的,存储在片内内存
中,即芯片应该固化好的;First boot,也是平台独立的,但是它存储在外部内存
中(如nandflash\sd卡等),也就是说,这部分代码由用户去实现;second boot,是平台相关的,存储在外部内存
,是真正的boot loader代码。一般称iROM过程为BL0,称First Boot Loader为BL1,称second boot loader为BL2。
具体过程为:
S5PV210
上电复位后,将从iROM处执行已固化的启动代码——BL0;HeaderInfo
(包含CheckSum
),BL1检查HeaderInfo
后,继续运行,并拷贝 BL2到iRAM 中并对其校验,通过后转入BL2;本文通过修改start.S文件,在start.S文件中增加点亮LED的内容,然后对start.S进行编译,编译完成后,制作成16kB大小的内容,烧写到SD卡中。
在start.S文件末尾增加GPIO的初始化部分和点亮的部分:
gpio_out:
ldr r11, =0xE0200280 //获得寄存器地址
ldr r12, =0x00001111 //配置成输出状态
str r12, [r11] //将r12寄存器的值放回r11
ldr r11, =0xE0200284
ldr r12, =0xF
str r12, [r11]
mov pc, lr
.globl led1_on
led1_on:
ldr r11, =0xE0200284
ldr r12, [r11]
bic r12, r12, #1 //将r12的第一位清零,回写到r12
str r12, [r11]
mov pc, lr
然后在文件reset过程中调用这两个过程即可:
reset:
bl save_boot_params
/*
* set the cpu to SVC32 mode
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr,r0
/*
* Setup vector:
* (OMAP4 spl TEXT_BASE is not 32 byte aligned.
* Continue to use ROM code vector only in OMAP4 spl)
*/
#if !(defined(CONFIG_OMAP44XX) && defined(CONFIG_SPL_BUILD))
/* Set V=0 in CP15 SCTRL register - for VBAR to point to vector */
mrc p15, 0, r0, c1, c0, 0 @ Read CP15 SCTRL Register
bic r0, #CR_V @ V = 0
mcr p15, 0, r0, c1, c0, 0 @ Write CP15 SCTRL Register
/* Set vector address in CP15 VBAR register */
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
#endif
bl gpio_out //调用
bl led1_on //调用
/* the mask ROM code should have PLL and others stable */
#ifndef CONFIG_SKIP_LOWLEVEL_INIT
bl cpu_init_cp15
bl cpu_init_crit
#endif
修改成功后,make
一下,获得u-boot.bin
文件。make
文件是做什么的呢?是怎么写的呢?可以关注博主下一篇文章。
上文提到BL1过程的部分文件大小仅仅为16KB的大小,而u-boot.bin
的大小远远超过,而且在16KB的内容中包含一个叫做HeaderInfo
的东西,u-boot.bin
中显然没有。
本文的内容不涉及BL2的过程,仅仅需要BL1过程即可,即点亮LED等就好了。也就是说,本文只需要制作一个16KB大小的文件,将该文件烧写到SD卡中,到目标板中运行就行了。LED的程序肯定在前16KB的内容中。至于后面的LB2过程,以后的文章中再讲怎么操作。
那么HeaderInfo
的东西是怎么生成的呢?在文件mkv210_image.c
中详细介绍了整个过程:
/* 在BL0阶段,iRom内固化的代码需要读取nandflash或SD卡前16K的内容,
* 并比对前16字节中的校验和是否正确,正确则继续,错误则停止。
*/
#include
#include
#include
#define BUFSIZE (16*1024)
#define IMG_SIZE (16*1024)
#define SPL_HEADER_SIZE 16
#define SPL_HEADER "S5PC110 HEADER "
int main (int argc, char *argv[])
{
FILE *fp;
char *Buf, *a;
int BufLen;
int nbytes, fileLen;
unsigned int checksum, count;
int i;
// 1. 3个参数
if (argc != 3) //参数数目不对
{
printf("Usage: mkbl1 );
return -1;
}
// 2. 分配16K的buffer
BufLen = BUFSIZE;
Buf = (char *)malloc(BufLen);
if (!Buf) //如果分配空间不够,分配失败
{
printf("Alloc buffer failed!\n");
return -1;
}
memset(Buf, 0x00, BufLen); //这段内存区全部清零
// 3. 读源bin到buffer
// 3.1 打开源bin
fp = fopen(argv[1], "rb"); //打开文件
if( fp == NULL) //打开失败
{
printf("source file open error\n");
free(Buf);
return -1;
}
// 3.2 获取源bin长度
fseek(fp, 0L, SEEK_END); //指针移动到文件末尾
fileLen = ftell(fp); //获取文件长度
fseek(fp, 0L, SEEK_SET); //指针移动到文件开始
// 3.3 源bin长度不得超过16K-16byte
count = (fileLen < (IMG_SIZE - SPL_HEADER_SIZE))
? fileLen : (IMG_SIZE - SPL_HEADER_SIZE); //count等于16kB-16字节
// 3.4 buffer[0~15]存放"S5PC110 HEADER "
memcpy(&Buf[0], SPL_HEADER, SPL_HEADER_SIZE); //复制移动
// 3.5 读源bin到buffer[16]
nbytes = fread(Buf + SPL_HEADER_SIZE, 1, count, fp);
if ( nbytes != count )
{
printf("source file read error\n");
free(Buf);
fclose(fp);
return -1;
}
fclose(fp);
// 4. 计算校验和
// 4.1 从第16byte开始统计buffer中共有几个1
a = Buf + SPL_HEADER_SIZE;
for(i = 0, checksum = 0; i < IMG_SIZE - SPL_HEADER_SIZE; i++)
checksum += (0x000000FF) & *a++;
// 4.2 将校验和保存在buffer[8~15]
a = Buf + 8;
*( (unsigned int *)a ) = checksum;
// 5. 拷贝buffer中的内容到目的bin
// 5.1 打开目的bin
fp = fopen(argv[2], "wb");
if (fp == NULL)
{
printf("destination file open error\n");
free(Buf);
return -1;
}
// 5.2 将16k的buffer拷贝到目的bin中
a = Buf;
nbytes = fwrite( a, 1, BufLen, fp);
if ( nbytes != BufLen )
{
printf("destination file write error\n");
free(Buf);
fclose(fp);
return -1;
}
free(Buf);
fclose(fp);
return 0;
}
这里介绍一下main()的函数参数:
argc
= argument count :表示传入main函数的数组元素个数,为int类型;argv
= argument vector :表示传入main函数的指针数组,为char*[ ]类型。第一个数组元素argv[0]是程序名称,并且包含程序所在的完整路径。argc至少为1,即argv数组至少包含程序名。也就是说,当编译并运行main()函数的时候:
gcc mkv210_image.c -o mkv210 //编译文件
./mkv210 u-boot.bin u-boot.16k //运行文件,三个参数(第一个参数必须是本文件)
通过这段程序的运行,就可以将u-boot.bin
生成u-boot.16k
,该文件仅有16k的大小,并且包含HeaderInfo
。
当编译文件时,需要将.c
文件通过预编译变成.i
文件,再通过编译变成.s
文件,再通过汇编变成.o
文件,最后进行链接变成不带后缀名的文件。
但是不带后缀名的文件内不仅仅是二进制的内容,里面还包括许多的链接内容、注释内容等等,因此文件大小一般比较大,不能直接烧写到目标板中。而.bin
后缀的文件删除了这部分的内容,仅仅只有二进制的文件,可以烧写到目标板中。
因此,如果想要分析二进制文件采用不带后缀的文件,想要烧写到目标板中,采用.bin后缀的文件。
hexdump -C u-boot.16k | less //-C 输出规范的十六进制和ASCII码
运行结果如图所示:
查看uboot.bin
:
arm-linux-objdump -S u-boot | less
运行结果如图所示:
注意到,u-boot
里面第一句reset
的二进制表达是ea000014,而在u-boot.16k
中的表达是140000ea(第二行,去掉前16字节的HeaderInfo
)。
这就涉及到大小端的问题了,可以查看链接:大小端问题。
一般而言,处理器以小端模式为主,硬盘等存储设备以大选模式为主,网络通信都是大端模式(先传高位,再传低位)。
烧写到SD卡,一般采用的是dd
命令:
sudo dd iflag=dsync oflag=dsync if=u-boot.16k of=/dev/sdb seek=1
前两个选项iflag
和oflag
表示采取异步的方式,if
表示输入文件,of
表示输出设备,seek
表示从第几个扇区开始烧写。
dev
是设备(device)的英文缩写。/dev
这个目录对所有的用户都十分重要。因为在这个目录中包含了所有Linux系统中使用的外部设备。但是这里并不是放的外部设备的驱动程序,这一点和windows、dos操作系统不一样。它实际上是一个访问这些外部设备的端口。我们可以非常方便地去访问这些外部设备,和访问一个文件、一个目录没有任何区别。
插入SD卡到目标板,上电,LED就会按照程序执行。