最近想自己做个摄像头采集图像,然后调用opencv库处理图像的小项目,由于担心uImage太大,超过之前uboot定义的内核烧录大小,所以想着修改下uboot,之后就索性从0开始把uboot从新移植一遍,然后自己写个ov7670的驱动,再写个应用代码,先从uboot移植开始;
uboot的运行环境是:编译系统是Ubuntu12.04版本,uboot是20120401版本,交叉编译工具链是4.3.2,linux内核是3.4.2版本的源码,开发板是买的jz2440;
分析官方提供的uboot源码:官方提供的是支持s3c2410的uboot,而且是只支持norflash启动的,为了支持nandflash启动,需要修改部分代码;
由于官方的uboot是在norflash内启动,所以链接脚本起始地址为0x00,但是实际代码是拷入sdram中运行,sdram起始地址为0x30000000,另外为了适应不同的移植情况编制的代码,所以有些变量的链接地址要再实际运行中才能确定链接地址,所以代码需要重定向才可以正确运行(在链接的时候加入-pie选项);
总结下官方uboot的启动过程:设置cpu为超级用户模式、关闭看门狗、屏蔽所有中断、设置时钟分频比例、初始化sdram、调用第一阶段初始化代码、然后重定位、再清除bss代码段、再调用第二阶段初始化代码.........
修改uboot新运行流畅如下:设置cpu为超级用户模式、关闭看门狗、屏蔽所有中断、设置系统主时钟、初始化存储管理器、简单初始化nandflash、转移代码到sdram、清除bss段、调用第一阶段初始化代码、调用第二阶段初始化代码.....
新代码简单分析:
1.start.s代码:
/* 2. 设置时钟 */ ldr r0, =0x4c000014 // mov r1, #0x03; // FCLK:HCLK:PCLK=1:2:4, HDIVN=1,PDIVN=1 mov r1, #0x05; // FCLK:HCLK:PCLK=1:4:8 str r1, [r0] /* 如果HDIVN非0,CPU的总线模式应该从“fast bus mode”变为“asynchronous bus mode” */ mrc p15, 0, r1, c1, c0, 0 /* 读出控制寄存器 */ orr r1, r1, #0xc0000000 /* 设置为“asynchronous bus mode” */ mcr p15, 0, r1, c1, c0, 0 /* 写入控制寄存器 */ #define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01)) /* MPLLCON = S3C2440_MPLL_200MHZ */ ldr r0, =0x4c000004 ldr r1, =S3C2440_MPLL_400MHZ str r1, [r0] /* 启动ICACHE */ mrc p15, 0, r0, c1, c0, 0 @ read control reg orr r0, r0, #(1<<12) mcr p15, 0, r0, c1, c0, 0 @ write it back #endif /* CONFIG_S3C24X0 */ /* * we do sys-critical inits only at reboot, * not when booting from ram! */ #ifndef CONFIG_SKIP_LOWLEVEL_INIT bl cpu_init_crit #endif ldr sp, =(CONFIG_SYS_INIT_SP_ADDR) /* sp = 30000f80 */ bic sp, sp, #7 /* 8-byte alignment for ABI compliance */ bl nand_init_ll mov r0, #0 // 源 ldr r1, _TEXT_BASE // 目的 ldr r2, _bss_start_ofs // 长度 bl copy_code_to_sdram bl clear_bss_ll ldr pc, =call_board_init_f call_board_init_f: ldr r0,=0x00000000 bl board_init_f /* unsigned int的值存在r0里, 正好给board_init_r */ ldr r1, _TEXT_BASE ldr sp, BASE_SP /* 重新设置栈 */ /* 调用第2阶段的代码 */ bl board_init_r
2.第一阶段初始化代码
bd_t *bd; init_fnc_t **init_fnc_ptr; gd_t *id; ulong addr, addr_sp; #ifdef CONFIG_PRAM ulong reg; #endif extern ulong BASE_SP; bootstage_mark_name ( BOOTSTAGE_ID_START_UBOOT_F, "board_init_f" ); /* Pointer is writable since we allocated a register for it */ gd = ( gd_t * ) ( ( CONFIG_SYS_INIT_SP_ADDR ) & ~0x07 ); // 初始化gd_t结构体存储起始地址,之后需要将这个位置的数据转移到新的gd_t结构体空间 /* compiler optimization barrier needed for GCC >= 3.4 */ __asm__ __volatile__ ( "": : :"memory" ); memset ( ( void * ) gd, 0, sizeof ( gd_t ) ); gd->mon_len = _bss_end_ofs; // 整个bin文件运行需要的内存空间(包括bss段的地址空间) #ifdef CONFIG_OF_EMBED /* Get a pointer to the FDT */ gd->fdt_blob = _binary_dt_dtb_start; #elif defined CONFIG_OF_SEPARATE /* FDT is at end of image */ gd->fdt_blob = ( void * ) ( _end_ofs + _TEXT_BASE ); #endif /* Allow the early environment to override the fdt address */ gd->fdt_blob = ( void * ) getenv_ulong ( "fdtcontroladdr", 16, ( uintptr_t ) gd->fdt_blob ); for ( init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr ) // 初始化函数列表 { if ( ( *init_fnc_ptr ) () != 0 ) { hang (); } } #ifdef CONFIG_OF_CONTROL /* For now, put this check after the console is ready */ if ( fdtdec_prepare_fdt() ) { panic ( "** CONFIG_OF_CONTROL defined but no FDT - please see " "doc/README.fdt-control" ); } #endif debug ( "monitor len: %08lX\n", gd->mon_len ); /* * Ram is setup, size stored in gd !! */ debug ( "ramsize: %08lX\n", gd->ram_size ); #if defined(CONFIG_SYS_MEM_TOP_HIDE) /* * Subtract specified amount of memory to hide so that it won't * get "touched" at all by U-Boot. By fixing up gd->ram_size * the Linux kernel should now get passed the now "corrected" * memory size and won't touch it either. This should work * for arch/ppc and arch/powerpc. Only Linux board ports in * arch/powerpc with bootwrapper support, that recalculate the * memory size from the SDRAM controller setup will have to * get fixed. */ gd->ram_size -= CONFIG_SYS_MEM_TOP_HIDE; #endif addr = CONFIG_SYS_SDRAM_BASE + gd->ram_size; // 设置addr初始值为sdram的最顶端。从最顶端开始分配内存空间 0x3400 0000 #ifdef CONFIG_LOGBUFFER #ifndef CONFIG_ALT_LB_ADDR /* reserve kernel log buffer */ addr -= ( LOGBUFF_RESERVE ); debug ( "Reserving %dk for kernel logbuffer at %08lx\n", LOGBUFF_LEN, addr ); #endif #endif #ifdef CONFIG_PRAM /* * reserve protected RAM */ reg = getenv_ulong ( "pram", 10, CONFIG_PRAM ); addr -= ( reg << 10 ); /* size is in kB */ debug ( "Reserving %ldk for protected RAM at %08lx\n", reg, addr ); #endif /* CONFIG_PRAM */ #if !(defined(CONFIG_SYS_ICACHE_OFF) && defined(CONFIG_SYS_DCACHE_OFF)) /* reserve TLB table */ addr -= ( 4096 * 4 ); // MMU管理模块 16K 存储页表信息 ,addr = 0x33ff c000 /* round down to next 64 kB limit */ addr &= ~ ( 0x10000 - 1 ); //再减去64K地址(包括前面的16K地址) addr = 0x33ff 0000 gd->tlb_addr = addr; debug ( "TLB table at: %08lx\n", addr ); #endif /* round down to next 4 kB limit */ addr &= ~ ( 4096 - 1 ); // 4K对齐 debug ( "Top of RAM usable for U-Boot at: %08lx\n", addr ); #ifdef CONFIG_LCD #ifdef CONFIG_FB_ADDR gd->fb_base = CONFIG_FB_ADDR; #else /* reserve memory for LCD display (always full pages) */ addr = lcd_setmem ( addr ); gd->fb_base = addr; #endif /* CONFIG_FB_ADDR */ #endif /* CONFIG_LCD */ /* * reserve memory for U-Boot code, data & bss * round down to next 4 kB limit */ // addr -= gd->mon_len; // 减去uboot运行需要的空间(text data bss段) // addr &= ~ ( 4096 - 1 ); // 4K对齐 addr = 0x33f80000; // 根据链接地址,设置uboot在内存中的地址 debug ( "Reserving %ldk for U-Boot at: %08lx\n", gd->mon_len >> 10, addr ); #ifndef CONFIG_SPL_BUILD /* * reserve memory for malloc() arena */ addr_sp = addr - TOTAL_MALLOC_LEN; // 再减去动态申请内存需要的空间 4*1024*1024 ,设置sp初始值,之后再计算sp的最终值 debug ( "Reserving %dk for malloc() at: %08lx\n", TOTAL_MALLOC_LEN >> 10, addr_sp ); /* * (permanently) allocate a Board Info struct * and a permanent copy of the "global" data */ addr_sp -= sizeof ( bd_t ); // 再减去存储单板信息结构体需要的空间 bd = ( bd_t * ) addr_sp; // bd指向存储单板信息结构体的起始地址 gd->bd = bd; // gb_t成员指向该地址 debug ( "Reserving %zu Bytes for Board Info at: %08lx\n", sizeof ( bd_t ), addr_sp ); #ifdef CONFIG_MACH_TYPE gd->bd->bi_arch_number = CONFIG_MACH_TYPE; /* board id for Linux */ #endif addr_sp -= sizeof ( gd_t ); // 再减去环境变量结构体需要的空间 id = ( gd_t * ) addr_sp; // id指向环境变了结构体起始地址 debug ( "Reserving %zu Bytes for Global Data at: %08lx\n", sizeof ( gd_t ), addr_sp ); /* setup stackpointer for exeptions */ gd->irq_sp = addr_sp; // 设置中断向量起始地址(中断向量处理堆栈向下生长,不会影响上面的gd_t结构体空间) #ifdef CONFIG_USE_IRQ addr_sp -= ( CONFIG_STACKSIZE_IRQ + CONFIG_STACKSIZE_FIQ ); debug ( "Reserving %zu Bytes for IRQ stack at: %08lx\n", CONFIG_STACKSIZE_IRQ + CONFIG_STACKSIZE_FIQ, addr_sp ); #endif /* leave 3 words for abort-stack */ addr_sp -= 12; /* 8-byte alignment for ABI compliance */ addr_sp &= ~0x07; #else /* 处理#ifndef CONFIG_SPL_BUILD */ addr_sp += 128; /* leave 32 words for abort-stack */ gd->irq_sp = addr_sp; #endif debug ( "New Stack Pointer is: %08lx\n", addr_sp ); #ifdef CONFIG_POST post_bootmode_init(); post_run ( NULL, POST_ROM | post_bootmode_get ( 0 ) ); #endif BASE_SP = addr_sp; // 读出最终堆栈指针作为uboot的堆栈指针 /* 设置环境变量 */ gd->bd->bi_baudrate = gd->baudrate; /* Ram ist board specific, so move it to board code ... */ dram_init_banksize(); display_dram_config(); /* and display it */ gd->relocaddr = addr; gd->start_addr_sp = addr_sp; gd->reloc_off = addr - _TEXT_BASE; debug ( "relocation Offset is: %08lx\n", gd->reloc_off ); memcpy ( id, ( void * ) gd, sizeof ( gd_t ) ); // 转移gd_t结构体到新的存储位置 return ( unsigned int ) id; // 返回gd_t结构体的起始地址
观察以上代码的中文解释,可以知道第一阶段主要是对sdram划分了不同的区域而已,然后把环境变量、设置好的uboot运行堆栈指针传递出来而已;
第二阶段就是针对单板的配置进行一些初始化:比如需要norflash启动、Nandflash启动、网络支持等等,均在第二阶段进行初始化;
总结:移植uboot只要清楚处理的流程,按照流程进行各模块的初始化,主要还是硬件的初始化工作。