【Linux】制作U-Boot烧写镜像到SD卡的过程(上篇)

在嵌入式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的启动机制如下所示:

【Linux】制作U-Boot烧写镜像到SD卡的过程(上篇)_第1张图片
【Linux】制作U-Boot烧写镜像到SD卡的过程(上篇)_第2张图片

S5PV210的启动过程有三个步骤组成,其中,iROM是平台独立的,存储在片内内存中,即芯片应该固化好的;First boot,也是平台独立的,但是它存储在外部内存中(如nandflash\sd卡等),也就是说,这部分代码由用户去实现;second boot,是平台相关的,存储在外部内存,是真正的boot loader代码。一般称iROM过程为BL0,称First Boot Loader为BL1,称second boot loader为BL2

具体过程为:

  1. S5PV210上电复位后,将从iROM处执行已固化的启动代码——BL0;
  2. 在BL0里初始化过程中对启动设备进行判断,并从启动设备拷贝BL1(16KB)到iRAM处,其中这16KB的内容需要包括16字节的HeaderInfo(包含CheckSum),BL1检查HeaderInfo后,继续运行,并拷贝 BL2到iRAM 中并对其校验,通过后转入BL2;
  3. BL2完成一些比较复杂的初始化,包括DARAM的初始化,完成后将OS代码拷贝DARAM 中,并跳到OS中执行并完成启动引导。

S5PV210的烧写过程

本文策略

本文通过修改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文件是做什么的呢?是怎么写的呢?可以关注博主下一篇文章。

HeaderInfo

上文提到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  \n");
		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

hexdump查看二进制文件

当编译文件时,需要将.c文件通过预编译变成.i文件,再通过编译变成.s文件,再通过汇编变成.o文件,最后进行链接变成不带后缀名的文件

但是不带后缀名的文件内不仅仅是二进制的内容,里面还包括许多的链接内容、注释内容等等,因此文件大小一般比较大,不能直接烧写到目标板中。而.bin后缀的文件删除了这部分的内容,仅仅只有二进制的文件,可以烧写到目标板中

因此,如果想要分析二进制文件采用不带后缀的文件,想要烧写到目标板中,采用.bin后缀的文件

hexdump -C u-boot.16k | less		//-C 输出规范的十六进制和ASCII码

运行结果如图所示:

【Linux】制作U-Boot烧写镜像到SD卡的过程(上篇)_第3张图片

查看uboot.bin

arm-linux-objdump -S u-boot | less

运行结果如图所示:

【Linux】制作U-Boot烧写镜像到SD卡的过程(上篇)_第4张图片

注意到,u-boot里面第一句reset的二进制表达是ea000014,而在u-boot.16k中的表达是140000ea(第二行,去掉前16字节的HeaderInfo)。

这就涉及到大小端的问题了,可以查看链接:大小端问题。

一般而言,处理器以小端模式为主,硬盘等存储设备以大选模式为主,网络通信都是大端模式(先传高位,再传低位)。

烧写到SD卡

烧写到SD卡,一般采用的是dd命令:

sudo dd iflag=dsync oflag=dsync if=u-boot.16k of=/dev/sdb seek=1

前两个选项iflagoflag表示采取异步的方式,if表示输入文件,of表示输出设备,seek表示从第几个扇区开始烧写。

dev是设备(device)的英文缩写。/dev这个目录对所有的用户都十分重要。因为在这个目录中包含了所有Linux系统中使用的外部设备。但是这里并不是放的外部设备的驱动程序,这一点和windows、dos操作系统不一样。它实际上是一个访问这些外部设备的端口。我们可以非常方便地去访问这些外部设备,和访问一个文件、一个目录没有任何区别。

检测结果

插入SD卡到目标板,上电,LED就会按照程序执行。

【Linux】制作U-Boot烧写镜像到SD卡的过程(上篇)_第5张图片

你可能感兴趣的:(《操作系统》Linux系统移植)