TQ2440-U-BOOT-1.1.6启动流程及常用功能浅析

关于uboot的启动流程,没有去整体的进行分析,只是选取了常用的功能进行学习

1. u-boot的启动流程

主要参考:http://www.cnblogs.com/heaad/archive/2010/07/17/1779829.html

总结一下:

uboot第一阶段:

start.s阶段,主要是完成一些cpu及ram相关操作,如中断向量表、watchdog、sdram控制器初始化、时钟设置、mmu、设置堆栈、将代码搬移到ram中运行(包括从nor和nand中搬移)、以及清空bss段、跳转到第二阶段入口,这些在网上都是有相当多的资料的,大概知道每个部分的意思吧!

uboot第二阶段:

入口是start_armboot函数,其主要做了以下几个部分:

2. 常见功能的实现

关于全局量gd_t

U-Boot使用了一个结构体gd_t来存储全局数据区的数据,如串口的波特率,环境变量的地址等,这个结构体在include/asm-arm/global_data.h中定义如下:

typedef	struct	global_data {
	bd_t		*bd;
	unsigned long	flags;
	unsigned long	baudrate;
	unsigned long	have_console;	/* serial_init() was called */
	unsigned long	reloc_off;	/* Relocation Offset */
	unsigned long	env_addr;	/* Address  of Environment struct */
	unsigned long	env_valid;	/* Checksum of Environment valid? */
	unsigned long	fb_base;	/* base address of frame buffer */
	void		**jt;		/* jump table */
} gd_t;
#define DECLARE_GLOBAL_DATA_PTR     register volatile gd_t *gd asm ("r8")

DECLARE_GLOBAL_DATA_PTR定义一个gd_t全局数据结构的指针,这个指针存放在指定的寄存器r8中。这个声明也避免编译器把r8分配给其它的变量。任何想要访问全局数据区的代码,只要代码开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,然后就可以使用gd指针来访问全局数据区了。

/* Pointer is writable since we allocated a register for it */
	gd = (gd_t*)(_armboot_start - CFG_MALLOC_LEN - sizeof(gd_t));
	/* compiler optimization barrier needed for GCC >= 3.4 */
	__asm__ __volatile__("": : :"memory");

start_armboot中的这句话应该解释了gd_t这个全局变量在RAM中存贮的位置.

go命令的实现

uboot提供有go命令,这条命令的用法是: go 0x30000000(go + address),此时程序会自动跑到相应的address地址处去执行,其实现代码很简单,如下:

rc = ((ulong (*)(int, char *[]))addr) (--argc, &argv[1]);

对于代码本身而言,无非是执行address地址处的函数,uboot实现的形式是:ulong func(int, char*)的函数,实际上也可以简单的写成:

((void (*)(void))(addr))();

直接跳到addr处去执行。下面是我在tq2440自带的裸机程序中添加的一条命令:

/* BEGIN: Added by forsakening, 2013/6/10   PN:arm_test */
static unsigned long hex_strtoul(char *string)
{
	char tmp = 0;
	int count = 0;
	unsigned long result = 0;

	tmp = string[count];
	while('\0' != tmp)
	{
		tmp = string[count];

		if (('0' <= tmp) && ('9' >= tmp)) 
			result = ( result * 16 + (tmp - '0') );
		else if(('a' <= tmp) && ('f' >= tmp))
			result = ( result * 16 + (tmp - 'a' + 10) );			

		count++;
		//Uart_Printf("Result is: 0x%x ...\n", result);
	}

	return result;
}

void bootfrom(void)
{
	unsigned long addr = 0;
	char string[16] = {0};

	Uart_Printf("\nPlease Input The Address(hex): ");
	Uart_GetString(string);
	addr = hex_strtoul(string);
	Uart_Printf("Start Programme At Address: 0x%x ...\n", addr);
		
	((void (*)(void))(addr))();
}
/* END:   Added by forsakening, 2013/6/10   PN:arm_test */

memset的简单实现

void * memset(void * s,int c,size_t count)
{
	char *xs = (char *) s;

	while (count--)
		*xs++ = c;

	return s;
}

#的作用

这里的#用于把宏参数变为一个字符串:http://wenku.baidu.com/view/d150b1c46137ee06eff91864.html

#define XMK_STR(x)	#x
#define MK_STR(x)	XMK_STR(x)

#ifdef	CONFIG_ETHADDR
	"ethaddr="	MK_STR(CONFIG_ETHADDR)		"\0"

已知#define CONFIG_ETHADDR   0a:1b:2c:3d:4e:5f,则展开为ethaddr=0a:1b:2c:3d:4e:5f,

^C(control c)的实现

int ctrlc (void)
{
	if (!ctrlc_disabled && gd->have_console) {
		if (tstc ()) {
			switch (getc ()) {
			case 0x03:		/* ^C - Control C */
				ctrlc_was_pressed = 1;
				return 1;
			default:
				break;
			}
		}
	}
	return 0;
}

检测是否有接收到字符0x03,收到说明按下了control C

strlen

size_t strlen(const char * s)
{
	const char *sc;

	for (sc = s; *sc != '\0'; ++sc)
		/* nothing */;
	return sc - s;
}

strcpy

char * strcpy(char * dest,const char *src)
{
	char *tmp = dest;

	while ((*dest++ = *src++) != '\0')
		/* nothing */;
	return tmp;
}

判断是否从NOR启动

int bBootFrmNORFlash(void)
{
	volatile unsigned int *pdw = (volatile unsigned int *)0;
	unsigned int dwVal;

	/*
	 * 无论是从NOR Flash还是从NAND Flash启动,
	 * 地址0处为指令"b	Reset", 机器码为0xEA00000B,
	 * 对于从NAND Flash启动的情况,其开始4KB的代码会复制到CPU内部4K内存中,
	 * 对于从NOR Flash启动的情况,NOR Flash的开始地址即为0。
	 * 对于NOR Flash,必须通过一定的命令序列才能写数据,
	 * 所以可以根据这点差别来分辨是从NAND Flash还是NOR Flash启动:
	 * 向地址0写入一个数据,然后读出来,如果没有改变的话就是NOR Flash
	 */

	dwVal = *pdw;       
	*pdw = 0x12345678;
	if (*pdw != 0x12345678)
	{
		return 1;
	}
	else
	{
		*pdw = dwVal;
		return 0;
	}
}

关于tftp下载方式

在选择下载方式的时候,tq2440自带的uboot提供了usb和tftp两种方式,而tftp方式,实际上在代码中调用的就是do_tftpb,继续跟踪的话,调用的是:

NetLoop(proto_t protocol);
protocol = TFTP;

最终呢,调用的是TftpStart();这个函数,其大概的流程为:

TftpSend() -> NetSendUDPPacket(NetServerEther, NetServerIP, TftpServerPort, TftpOurPort, len); ->eth_send(NetTxPacket, (pkt - NetTxPacket) + IP_HDR_SIZE + len);

其中的eth_send则是由以太网卡DM9000提供的接口函数, 用于发送真正的以太报文.

这是选择以tftp方式下载内核映像文件至nand中的命令:

case '3':
			{
				strcpy(cmd_buf, "tftp 0x30000000 zImage.bin; nand erase kernel; nand write.jffs2 0x30000000 kernel $(filesize)");
				run_command(cmd_buf, 0);
				break;
			}

可以看出一共分成3个命令:1)  tftp download; 2) nand 中的kernel分区格式化; 3) 从0x30000000处读取filesize长度至kernel分区;

关于ping命令

ping命令的实现和前述的tftp方式有点类似,都是发送协议报文而已,而ping命令发送的则是ICMP报文,其流程如下:

do_ping -> NetLoop(PING) -> PingStart() -> PingSend() -> ArpRequest() -> eth_send (NetTxPacket, (pkt - NetTxPacket) + ARP_HDR_SIZE)

这里面实际上涉及的是IP协议的实现,开发板是可以ping通主机,但是主机ping不通开发板在这里可以得到解释,那是因为,uboot是个单线程的,其根本没有实现接收ping请求报文的代码~

loadxyz命令

uboot提供了loady和loadz命令,这两种命令都是串口传输的一种协议,在uboot中得到了应用,这里mark一下。

引导内核

 主要参考:

1) http://www.cnblogs.com/heaad/archive/2010/07/17/1779829.html U-Boot启动过程完全分析

2) http://wenku.baidu.com/view/cdee5ac20c22590102029df5.html uboot

其主要代码流程为:

int boot_zImage(ulong from, size_t size)
{
	int ret;
	ulong boot_mem_base;	/* base address of bootable memory */
	ulong to;
	ulong mach_type;

	boot_mem_base = 0x30000000;

	/* copy kerne image */
	to = boot_mem_base + LINUX_KERNEL_OFFSET;
	printf("Copy linux kernel from 0x%08lx to 0x%08lx, size = 0x%08lx ... ",
		from, to, size);
	/* 一: 从nandflash的"from"拷贝至内存地址"to"中 */
	ret = copy_kernel_img(to, (char *)from, size);
	if (ret) {
		printf("failed\n");
		return -1;
	} else {
		printf("Copy Kernel to SDRAM done,");
	}

	if (*(ulong *)(to + 9*4) != LINUX_ZIMAGE_MAGIC) {
		printf("Warning: this binary is not compressed linux kernel image\n");
		printf("zImage magic = 0x%08lx\n", *(ulong *)(to + 9*4));
	} else {
//		printf("zImage magic = 0x%08lx\n", *(ulong *)(to + 9*4));
		;
	}

	/* 二: 设置启动参数 */
	/* Setup linux parameters and linux command line */
	setup_linux_param(boot_mem_base + LINUX_PARAM_OFFSET);

	/* Get machine type */
	mach_type = MACH_TYPE_S3C2440;
//	printf("MACH_TYPE = %d\n", mach_type);

	/* 三: 启动内核 */
	/* Go Go Go */
	printf("NOW, Booting Linux......\n");	
	call_linux(0, mach_type, to);

	return 0;	
}

设置启动参数:

/*
启动参数是包装在数据结构里的,在linux kernel启动的时候,bootloader把这个数据结构拷贝到某个地址,
在改动PC跳向内核接口的同时,通过通用寄存器R2来传递这个地址的值,下面这句话就是uboot跳向linux kernel的代码(bootm命令):
theKernel(0,bd->bi_arch_number,bd->bi_boot_params);
thekernel其实不是个函数,而是指向内核入口地址的指针,把它强行转化为带三个参数的函数指针,会把三个
参数保存到通用寄存器中,实现了向kernel传递信息的功能,在这个例子里,会把R0赋值为0,R1赋值为机器号,
R2赋值为启动参数数据结构的首地址;
因此,要向内核传递参数很简单,只要把启动参数封装在linux预定好的数据结构里,拷贝到某个地址(一般约定俗成是内存首地址+100dex)
*/
/*
启动参数可保存在两种数据结构中,param_struct和tag,前者是2.4内核用的,后者是2.6以后的内核更期望用的;
但是,到目前为止,2.6的内核也可以兼容前一种结构,两种数据结构具体定义如下(arm cpu):
*/
/* 第一种方式:(tq2440自带的uboot采取这种方式) */
/* This is the old deprecated way to pass parameters to the kernel */
struct param_struct {
    union {
	struct {
	    unsigned long page_size;		/*  0 */
	    unsigned long nr_pages;		/*  4 */
	    unsigned long ramdisk_size;		/*  8 */
	    unsigned long flags;		/* 12 */
#define FLAG_READONLY	1
#define FLAG_RDLOAD	4
#define FLAG_RDPROMPT	8
	    unsigned long rootdev;		/* 16 */
	    unsigned long video_num_cols;	/* 20 */
	    unsigned long video_num_rows;	/* 24 */
	    unsigned long video_x;		/* 28 */
	    unsigned long video_y;		/* 32 */
	    unsigned long memc_control_reg;	/* 36 */
	    unsigned char sounddefault;		/* 40 */
	    unsigned char adfsdrives;		/* 41 */
	    unsigned char bytes_per_char_h;	/* 42 */
	    unsigned char bytes_per_char_v;	/* 43 */
	    unsigned long pages_in_bank[4];	/* 44 */
	    unsigned long pages_in_vram;	/* 60 */
	    unsigned long initrd_start;		/* 64 */
	    unsigned long initrd_size;		/* 68 */
	    unsigned long rd_start;		/* 72 */
	    unsigned long system_rev;		/* 76 */
	    unsigned long system_serial_low;	/* 80 */
	    unsigned long system_serial_high;	/* 84 */
	    unsigned long mem_fclk_21285;       /* 88 */
	} s;
	char unused[256];
    } u1;
    union {
	char paths[8][128];
	struct {
	    unsigned long magic;
	    char n[1024 - sizeof(unsigned long)];
	} s;
    } u2;
    char commandline[COMMAND_LINE_SIZE];
}
/* param_struct只需要设置cmmandline,u1.s.page_size,u1.s.nr_pages三个域 */
/*
 * pram_base: base address of linux paramter
 */
static void setup_linux_param(ulong param_base)
{
	struct param_struct *params = (struct param_struct *)param_base; 
	char *linux_cmd;
	
	memset(params, 0, sizeof(struct param_struct));

	params->u1.s.page_size = LINUX_PAGE_SIZE;
	params->u1.s.nr_pages = (DRAM_SIZE >> LINUX_PAGE_SHIFT);

	/* set linux command line */
	linux_cmd = getenv ("bootargs");
	if (linux_cmd == NULL) {
		printf("Wrong magic: could not found linux command line\n");
	} else {
		memcpy(params->commandline, linux_cmd, strlen(linux_cmd) + 1);
	}
}
/* 其中的bootargs在下面定义 */
#define CONFIG_BOOTARGS			"noinitrd root=/dev/mtdblock2 init=/linuxrc console=ttySAC0 mem=128M"

/* 第二种方式:tag方式 */
/*
对于tag来说,在实际使用中是一个struct?tag组成的列表,在tag->tag_header中,一项是u32 tag(重名,注意类型)
其值用宏ATAG_CORE,ATAG_MEM,ATAG_CMDLINE,ATAG_NONE等等来表示,此时下面union就会使用与之相关的数据结构
同时,规定tag列表中第一项必须是ATAG_CORE,最后一项必须是ATAG_NONE,比如在linux代码中,找到启动参数之后
首先看tag列表中的第一项的tag->hdr.tag是否为ATAG_CORE,如果不是,就会认为启动参数不是tag结构而是param_struct
结构,然后调用函数来转换.在tag->tag_header中,另一项是u32?size,表示tag的大小,tag组成列表的方式就是
指针+size,实际使用中用tag_next(params)
*/

启动内核:

/*
在调用内核之前,还需要保证:
1. CPU寄存器的设置: r0=0, r1=机器码, r2=内核参数标记列表在RAM中的起始地址
2. CPU工作模式: 禁止IRQ与FIQ中断, CPU为SVC模式
3. 使数据Cache与指令Cache失效
*/
call_linux(0, mach_type, to);

void  call_linux(long a0, long a1, long a2)
{
 	local_irq_disable();
	cache_clean_invalidate();
	tlb_invalidate();

__asm__(
	"mov	r0, %0\n"
	"mov	r1, %1\n"
	"mov	r2, %2\n"
	"mov	ip, #0\n"
	"mcr	p15, 0, ip, c13, c0, 0\n"	/* zero PID */
	"mcr	p15, 0, ip, c7, c7, 0\n"	/* invalidate I,D caches */
	"mcr	p15, 0, ip, c7, c10, 4\n"	/* drain write buffer */
	"mcr	p15, 0, ip, c8, c7, 0\n"	/* invalidate I,D TLBs */
	"mrc	p15, 0, ip, c1, c0, 0\n"	/* get control register */
	"bic	ip, ip, #0x0001\n"		/* disable MMU */
	"mcr	p15, 0, ip, c1, c0, 0\n"	/* write control register */
	"mov	pc, r2\n"
	"nop\n"
	"nop\n"
	: /* no outpus */
	: "r" (a0), "r" (a1), "r" (a2)
	: "r0","r1","r2","ip"
	);
}






 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值