TQ2440-U-BOOT-1.1.6启动流程及常用功能浅析
2013-06-10 15:43
381 查看
关于uboot的启动流程,没有去整体的进行分析,只是选取了常用的功能进行学习
总结一下:
uboot第一阶段:
start.s阶段,主要是完成一些cpu及ram相关操作,如中断向量表、watchdog、sdram控制器初始化、时钟设置、mmu、设置堆栈、将代码搬移到ram中运行(包括从nor和nand中搬移)、以及清空bss段、跳转到第二阶段入口,这些在网上都是有相当多的资料的,大概知道每个部分的意思吧!
uboot第二阶段:
入口是start_armboot函数,其主要做了以下几个部分:
DECLARE_GLOBAL_DATA_PTR定义一个gd_t全局数据结构的指针,这个指针存放在指定的寄存器r8中。这个声明也避免编译器把r8分配给其它的变量。任何想要访问全局数据区的代码,只要代码开头加入“DECLARE_GLOBAL_DATA_PTR”一行代码,然后就可以使用gd指针来访问全局数据区了。
start_armboot中的这句话应该解释了gd_t这个全局变量在RAM中存贮的位置.
对于代码本身而言,无非是执行address地址处的函数,uboot实现的形式是:ulong func(int, char*)的函数,实际上也可以简单的写成:
直接跳到addr处去执行。下面是我在tq2440自带的裸机程序中添加的一条命令:
已知#define CONFIG_ETHADDR 0a:1b:2c:3d:4e:5f,则展开为ethaddr=0a:1b:2c:3d:4e:5f,
检测是否有接收到字符0x03,收到说明按下了control C
最终呢,调用的是TftpStart();这个函数,其大概的流程为:
其中的eth_send则是由以太网卡DM9000提供的接口函数, 用于发送真正的以太报文.
这是选择以tftp方式下载内核映像文件至nand中的命令:
可以看出一共分成3个命令:1) tftp download; 2) nand 中的kernel分区格式化; 3) 从0x30000000处读取filesize长度至kernel分区;
这里面实际上涉及的是IP协议的实现,开发板是可以ping通主机,但是主机ping不通开发板在这里可以得到解释,那是因为,uboot是个单线程的,其根本没有实现接收ping请求报文的代码~
1) http://www.cnblogs.com/heaad/archive/2010/07/17/1779829.html U-Boot启动过程完全分析
2) http://wenku.baidu.com/view/cdee5ac20c22590102029df5.html 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" ); }
相关文章推荐
- AM335x启动流程(BootRom->MLO->Uboot)
- u-boot-2012.10启动流程
- 嵌入式学习-uboot-lesson3-6410uboot启动流程分析
- 104. Spring Boot 启动流程分析第三篇【从零开始学Spring Boot】
- 14.3 U-Boot启动流程分析
- 【Spring Boot】SpringBoot-启动流程分析
- 2014.4新版uboot启动流程分析(建议看链接原文)
- android SystemUI浅析之SystemUI启动流程
- u-boot启动流程(一)
- lvm(逻辑卷管理器)的介绍和常用功能流程实验
- Spring Boot源码分析之启动流程
- U-BOOT内存布局及启动过程浅析
- u-boot关于启动流程
- 2014.4新版uboot启动流程分析
- 嵌入式Linux驱动学习之路(五)u-boot启动流程分析
- u-boot-2010.06在TQ2440上的移植<4>--支持nandflash启动
- U-BOOT启动流程分析
- u-boot 2011.06 启动流程分析
- 第五章——u-boot源码启动流程
- u-boot-1.1.6源码浅析(一)