网上关于Linux的BOOTLOADER**不少了,但是大都是vivi,blob等比较庞大的程序,读起来不太方便,编译出的文件也比较大,而且更多的是面向开发用的引导代码,做成产品时还要裁减,这一定程度影响了开发速度,对初学者学习开销也比较大,在此分析一种简单的BOOTLOADER,是在三星公司提供的2410 BOOTLOADER上稍微修改后的结果,编译出来的文件大小不超过4k,希望对大家有所帮助.<br /><br />1.几个重要的概念<br /><br />COMPRESSED KERNEL and DECOMPRESSED KERNEL<br /><br />压缩后的KERNEL,按照文档资料,现在不提倡使用DECOMPRESSED KERNEL,而要使用COMPRESSED KERNEL,它包括了解压器.因此要在ram分配时给压缩和解压的KERNEL提供足够空间,这样它们不会相互覆盖.<br /><br />当执行指令跳转到COMPRESSED KERNEL后,解压器就开始工作,如果解压器探测到解压的代码会覆盖掉COMPRESSEDKERNEL,那它会直接跳到COMPRESSED KERNEL后存放数据,并且重新定位KERNEL,所以如果没有足够空间,就会出错.<br /><br />Jffs2 File System<br /><br />可以使armlinux应用中产生的数据保存在FLASH上,我的板子还没用到这个.<br /><br />RAMDISK<br /><br />使用RAMDISK可以使ROOT FILESYSTEM在没有其他设备的情况下启动.一般有两种加载方式,我就介绍最常用的吧,把COMPRESSED RAMDISKIMAGE放到指定地址,然后由BOOTLOADER把这个地址通过启动参数的方式ATAG_INITRD2传递给KERNEL.具体看代码分析.<br /><br />启动参数(摘自IBM developer)<br /><br />在调用内核之前,应该作一步准备工作,即:设置 Linux 内核的启动参数。Linux 2.4.x 以后的内核都期望以标记列表(taggedlist)的形式来传递启动参数。启动参数标记列表以标记 ATAG_CORE 开始,以标记 ATAG_NONE 结束。每个标记由标识被传递参数的tag_header 结构以及随后的参数值数据结构来组成。数据结构 tag 和 tag_header 定义在 Linux内核源码的include/asm/setup.h 头文件中.<br /><br />在嵌入式 Linux 系统中,通常需要由 BOOTLOADER 设置的常见启动参数有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。<br /><br />(注)参数也可以用COMMANDLINE来设定,在我的BOOTLOADER里,我两种都用了.<br /><br />2.开发环境和开发板配置:<br /><br />CPU:S3C2410,BANK6上有64M的SDRAM(两块),BANK0上有32M NOR FLASH,串口当然是逃不掉的.这样,按照数据手册,地址分配如下:<br /><br />0x4000_0000开始是4k的片内DRAM.<br /><br />0x0000_0000开始是32M FLASH 16bit宽度<br /><br />0x3000_0000开始是64M SDRAM 32bit宽度<br /><br />注意:控制寄存器中的BANK6和BANK7部分必须相同.<br /><br />0x4000_0000(片内DRAM)存放4k以内的BOOTLOADER IMAGE<br /><br />0x3000_0100开始存放启动参数<br /><br />0x3120_0000 存放COMPRESSED KERNEL IMAGE<br /><br />0x3200_0000 存放COMPRESSED RAMDISK<br /><br />0x3000_8000 指定为DECOMPRESSED KERNEL IMAGE ADDRESS<br /><br />0x3040_0000 指定为DECOMPRESSED RAMDISK IMAGE ADDRESS<br /><br />开发环境:Redhat Linux,armgcc toolchain, armlinux KERNEL<br /><br />如何建立armgcc的编译环境:建议使用toolchain,而不要自己去编译armgcc,偶试过好多次,都以失败告终.<br /><br />先下载arm-gcc 3.3.2 toolchain<br /><br />将arm-linux-gcc-3.3.2.tar.bz2 解压到 /toolchain<br /><br /># tar jxvf arm-linux-gcc-3.3.2.tar.bz2<br /><br /># mv /usr/local/arm/3.3.2 /toolchain<br /><br />在makefile 中在把arch=arm CROSS_COMPILE设置成toolchain的路径<br /><br />还有就是INCLUDE = -I ../include -I /root/my/usr/local/arm/3.3.2/include.,否则库函数就不能用了<br /><br />3.启动方式:<br /><br />可以放在FLASH里启动,或者用Jtag仿真器.由于使用NOR FLASH,根据2410的手册,片内的4KDRAM在不需要设置便可以直接使用,而其他存储器必须先初始化,比如告诉memorycontroller,BANK6里有两块SDRAM,数据宽度是32bit,= =.否则memorycontrol会按照复位后的默认值来处理存储器.这样读写就会产生错误.<br /><br />所以第一步,通过仿真器把执行代码放到0x4000_0000,(在编译的时候,设定TEXT_BAS<br /><br />E=0x40000000)<br /><br />第二步,通过 AxD把linux KERNEL IMAGE放到目标地址(SDRAM)中,等待调用<br /><br />第三步,执行BOOTLOADER代码,从串口得到调试数据,引导armlinux<br /><br />4.代码分析<br /><br />讲了那么多执行的步骤,是想让大家对启动有个大概印象,接着就是BOOTLOADER内部的代码分析了,BOOTLOADER**内容网上很多,我这里精简了下,删除了不必要的功能.<br /><br />BOOTLOADER一般分为2部分,汇编部分和c语言部分,汇编部分执行简单的硬件初始化,C部分负责复制数据,设置启动参数,串口通信等功能.<br /><br />BOOTLOADER的生命周期:<br /><br />1. 初始化硬件,比如设置UART(至少设置一个),检测存储器= =.<br /><br />2. 设置启动参数,这是为了告诉内核硬件的信息,比如用哪个启动界面,波特率 = =.<br /><br />3. 跳转到Linux KERNEL的首地址.<br /><br />4. 消亡<br /><br /><br /><br />当然,在引导阶段,象vivi等,都用虚地址,如果你嫌烦的话,就用实地址,都一样.<br /><br />我们来看代码:<br /><br />2410init.s<br /><br />.global _start//开始执行处<br /><br />_start:<br /><br />//下面是中断向量<br /><br />b reset @ Supervisor Mode//重新启动后的跳转<br /><br />……<br /><br />……<br /><br />reset:<br /><br />ldr r0,=WTCON /WTCON地址为53000000,watchdog的控制寄存器 */<br /><br />ldr r1,=0x0 /*关watchdog*/<br /><br />str r1,[r0]<br /><br /><br /><br />ldr r0,=INTMSK<br /><br />ldr r1,=0xffffffff /*屏蔽所有中断*/<br /><br />str r1,[r0]<br /><br /><br /><br />ldr r0,=INTSUBMSK<br /><br />ldr r1,=0x3ff /*子中断也一样*/<br /><br />str r1,[r0]<br /><br />/*Initialize Ports...for display LED.*/<br /><br />ldr r0, =GPFCON<br /><br />ldr r1, =0x55aa<br /><br />str r1, [r0]<br /><br />ldr r0, =GPFUP<br /><br />ldr r1, =0xff<br /><br />str r1, [r0]<br /><br />ldr r0,=GPFDAT<br /><br />ldr r1,=POWEROFFLED1<br /><br />str r1,[r0]<br /><br />/* Setup clock Divider control register<br /><br />* you must configure CLKDIVN before LOCKTIME or MPLL UPLL<br /><br />* because default CLKDIVN 1,1,1 set the SDMRAM Timing Conflict<br /><br />nop<br /><br />* FCLK:HCLKCLK = 1:2:4 in this case<br /><br />*/<br /><br />ldr r0,=CLKDIVN<br /><br />ldr r1,=0x3<br /><br />str r1,[r0]<br /><br /><br /><br />/*To reduce PLL lock time, adjust the LOCKTIME register. */<br /><br />ldr r0,=LOCKTIME<br /><br />ldr r1,=0xffffff<br /><br />str r1,[r0]<br /><br />/*Configure MPLL */<br /><br />ldr r0,=MPLLCON<br /><br />ldr r1,=((M_MDIV<<12)+(M_PDIV<<4)+M_SDIV) //Fin=12MHz,Fout=203MHz<br /><br />str r1,[r0]<br /><br />ldr r1,=GSTATUS2<br /><br />ldr r10,[r1]<br /><br />tst r10,#OFFRST<br /><br />bne 1000f<br /><br />//以上这段,我没动,就用三星写的了,下面是主要要改的地方<br /><br />/* MEMORY C0NTROLLER(MC)设置*/<br /><br />add r0,pc,#MCDATA - (.+8)// r0指向MCDATA地址,那里存放着MC初始化要用到的数据<br /><br />ldr r1,=BWSCON // r1指向MC控制器寄存器的首地址<br /><br />add r2,r0,#52 // 复制次数,偏移52字<br /><br /><br /><br />1: //按照偏移量进行循环复制<br /><br />ldr r3,[r0],#4<br /><br />str r3,[r1],#4<br /><br />cmp r2,r0<br /><br />bne 1b<br /><br />.align 2<br /><br /><br /><br />MCDATA:<br /><br />.word(0+(B1_BWSCON<<4)+(B2_BWSCON<<8)+(B3_BWSCON<<12)+(B4_BWSCON<<16)+(B5_BWSCON<<20)+(B6_BWSCON<<24)+(B7_BWSCON<<28))<br /><br />上面这行就是BWSCON的数据,具体参数意义如下:<br /><br /><br /><br />需要更改设置DW6 和DW7都设置成10,即32bit,DW0 设置成01,即16bit<br /><br />下面都是每个BANK的控制器数据,大都是时钟相关,可以用默认值,设置完MC后,就跳到调用main函数的部分<br /><br />.word((B0_Tacs<<13)+(B0_Tcos<<11)+(B0_Tacc<<8)+(B0_Tcoh<<6)+(B0_Tah<<4)+(B0_Tacp<<2)+(B0_PMC))<br /><br />.word((B1_Tacs<<13)+(B1_Tcos<<11)+(B1_Tacc<<8)+(B1_Tcoh<<6)+(B1_Tah<<4)+(B1_Tacp<<2)+(B1_PMC))<br /><br />.word((B2_Tacs<<13)+(B2_Tcos<<11)+(B2_Tacc<<8)+(B2_Tcoh<<6)+(B2_Tah<<4)+(B2_Tacp<<2)+(B2_PMC))<br /><br />.word((B3_Tacs<<13)+(B3_Tcos<<11)+(B3_Tacc<<8)+(B3_Tcoh<<6)+(B3_Tah<<4)+(B3_Tacp<<2)+(B3_PMC))<br /><br />.word((B4_Tacs<<13)+(B4_Tcos<<11)+(B4_Tacc<<8)+(B4_Tcoh<<6)+(B4_Tah<<4)+(B4_Tacp<<2)+(B4_PMC))<br /><br />.word((B5_Tacs<<13)+(B5_Tcos<<11)+(B5_Tacc<<8)+(B5_Tcoh<<6)+(B5_Tah<<4)+(B5_Tacp<<2)+(B5_PMC))<br /><br />.word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN))<br /><br />.word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN))<br /><br />.word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT)<br /><br />.word 0xB2 /* REFRESH Control Register */<br /><br />.word 0x30 /* BANKSIZE Register : Burst Mode */<br /><br />.word 0x30 /* SDRAM Mode Register */<br /><br /><br /><br />.align 2<br /><br />.global call_main //调用main函数,函数参数都为0<br /><br />call_main:<br /><br />ldr sp,STACK_START<br /><br />mov fp,#0 /* no previous frame, so fp=0*/<br /><br />mov a1, #0 /* set argc to 0*/<br /><br />mov a2, #0 /* set argv to NUL*/<br /><br />bl main /* call main*/<br /><br />STACK_START:<br /><br />.word STACK_BASE<br /><br />undefined_instruction:<br /><br />software_interrupt:<br /><br />prefetch_abort:<br /><br />data_abort:<br /><br />not_used:<br /><br />irq:<br /><br />fiq:<br /><br />/*以上是主要的汇编部分,实现了时钟设置,串口设置watchdog关闭,中断关闭功能(如果有需要还可以降频使用),然后转入main*/<br /><br />2410init.c file<br /><br />int main(int argc,char **argv)<br /><br />{<br /><br />u32 test = 0;<br /><br />void(*theKERNEL)(int zero, int arch, unsigned long params_addr) = (void(*)(int, int, unsigned long))RAM_COMPRESSED_KERNEL_BASE; //压缩后的IMAGE地址<br /><br />int i,k=0;<br /><br />// downPt=(RAM_COMPRESSED_KERNEL_BASE);<br /><br />chkBs=(_RAM_STARTADDRESS);//SDRAM开始的地方<br /><br />// fromPt=(FLASH_LINUXKERNEL);<br /><br />MMU_EnableICache();<br /><br />ChangeClockDivider(1,1); // 1:2:4<br /><br />ChangeMPllValue(M_MDIV,M_PDIV,M_SDIV); //Fin=12MHz FCLK=200MHz<br /><br />Port_Init();//设置I/O端口,在使用com口前,必须调用这个函数,否则通信芯片根本得不到数据<br /><br />Uart_Init(PCLK, 115200);//PCLK使用默认的200000,拨特率115200<br /><br />/*******************(检查ram空间)*******************/<br /><br />Uart_SendString('\\n\\tLinux S3C2410 Nor BOOTLOADER\\n');<br /><br />Uart_SendString('\\n\\tChecking SDRAM 2410loader.c...\\n');<br /><br />for(;chkBs<0x33FA0140;chkBs=chkBs+0x4,test++)//<br /><br /><br /><br />//根据我的经验,最好以一个字节为递增,我们的板子,在256byte递增检测的时候是没问题的,但是<br /><br />//以1byte递增就出错了,第13跟数据线随几的会冒”1”,检测出来是硬件问题,现象如下<br /><br />//用仿真器下代码测试SDRAM,开始没贴28F128A3JFLASH片子,测试结果很好,但在上了FLASH片子//之后,测试数据(data)为0x00000400连续成批写入读出时,操作大约1k左右内存空间就会出错,//而且随机。那个出错数据总是变为0x00002400,数据总线10位和13位又没短路发生。用其他数据//测试比如0x00000200;0x00000800没这问题。dx帮忙。<br /><br />//至今没有解决,所以我用不了Flash.<br /><br />{<br /><br />chkPt1 = chkBs;<br /><br />*(u32 *)chkPt1 = test;//写数据<br /><br />if(*(u32 *)chkPt1==1024))//读数据和写入的是否一样?<br /><br />{<br /><br />chkPt1 += 4;<br /><br />Led_Display(1);<br /><br />Led_Display(2);<br /><br />Led_Display(3);<br /><br />Led_Display(4);<br /><br />}<br /><br />else<br /><br />goto error;<br /><br />}<br /><br />Uart_SendString('\\n\\tSDRAM Check Successful!\\n\\tMemory Maping...');<br /><br />get_memory_map();<br /><br />//获得可用memory 信息,做成列表,后面会作为启动参数传给KERNEL<br /><br />//所谓内存映射就是指在4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。<br /><br />Uart_SendString('\\n\\tMemory Map Successful!\\n');<br /><br />//我用仿真器把KERNEL,RAMDISK直接放在SDRAM上,所以下面这段是不需要的,但是如果KERNEL,RAMDISK在FLASH里,那就需要.<br /><br />/*******************(copy linux KERNEL)*******************/<br /><br />Uart_SendString('\\tLoading KERNEL IMAGE from FLASH... \\n');<br /><br />Uart_SendString('\\tand copy KERNEL IMAGE to SDRAM at 0x31000000\\n');<br /><br />Uart_SendString('\\t\\tby LEIJUN DONG dongleijun4000@hotmail.com \\n');<br /><br />for(k = 0;k< 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M<br /><br />* (u32 *)downPt = * (u32 *)fromPt;<br /><br />/*******************(load RAMDISK)*******************/<br /><br />Uart_SendString('\\t\\tloading COMPRESSED RAMDISK...\\n');<br /><br />downPt=(RAM_COMPRESSED_RAMDISK_BASE);<br /><br />fromPt=(FLASH_RAMDISK_BASE);<br /><br />for(k = 0;k< 196608;k++,downPt += 1,fromPt += 1)//3*1024*1024/32linux KERNEL des,src,length=3M<br /><br />* (u32 *)downPt = * (u32 *)fromPt;<br /><br />/******jffs2文件系统,在开发中如果用不到FLASH,这段也可以不要********/<br /><br />Uart_SendString('\\t\\tloading jffs2...\\n');<br /><br />downPt=(RAM_JFFS2);<br /><br />fromPt=(FLASH_JFFS2);<br /><br />for(k = 0;k< (1024*1024/32);k++,downPt += 1,fromPt += 1)<br /><br />* (u32 *)downPt = * (u32 *)fromPt;<br /><br />Uart_SendString('Load Success...Run...\\n');<br /><br />/*******************(setup param)*******************/<br /><br />setup_start_tag();//开始设置启动参数<br /><br />setup_memory_tags();//内存印象<br /><br />setup_commandline_tag('console=ttyS0,115200n8');//启动命令行<br /><br />setup_initrd2_tag();//root device<br /><br />setup_RAMDISK_tag();//ramdisk image<br /><br />setup_end_tag();<br /><br />/*关I-cache */<br /><br />asm ('mrc p15, 0, %0, c1, c0, 0':'=r' (i));<br /><br />i&= ~0x1000;<br /><br />asm ('mcr p15, 0, %0, c1, c0, 0': :'r' (i));<br /><br />/* flush I-cache */<br /><br />asm ('mcr p15, 0, %0, c7, c5, 0': :'r' (i));<br /><br />//下面这行就跳到了COMPRESSED KERNEL的首地址<br /><br />theKERNEL(0, ARCH_NUMBER, (unsigned long *)(RAM_BOOT_PARAMS));<br /><br />//启动kernel时候,I-cache可以开也可以关,r0必须是0,r1必须是CPU型号<br /><br />(可以从linux/arch/arm/tools/mach-types中找到),r2必须是参数的物理开始地址<br /><br />/*******************END*******************/<br /><br />error:<br /><br />Uart_SendString('\\n\\nPanic SDRAM check error!\\n');<br /><br />return 0;<br /><br />}<br /><br />static void setup_start_tag(void)<br /><br />{<br /><br />params = (struct tag *)RAM_BOOT_PARAMS;//启动参数开始的地址<br /><br />params->hdr.tag = ATAG_CORE;<br /><br />params->hdr.size = tag_size(tag_core);<br /><br />params->u.core.flags = 0;<br /><br />params->u.core.pagesize = 0;<br /><br />params->u.core.rootdev = 0;<br /><br />params = tag_next(params);<br /><br />}<br /><br /><br /><br /><br /><br />static void setup_memory_tags(void)<br /><br />{<br /><br />int i;<br /><br /><br /><br />for(i = 0; i< NUM_MEM_AREAS; i++) {<br /><br />if(memory_map.used) {<br /><br />params->hdr.tag = ATAG_MEM;<br /><br />params->hdr.size = tag_size(tag_mem32);<br /><br />params->u.mem.start = memory_map.start;<br /><br />params->u.mem.size = memory_map.len;<br /><br />params = tag_next(params);<br /><br />}<br /><br />}<br /><br />}<br /><br /><br /><br /><br /><br />static void setup_commandline_tag(char *commandline)<br /><br />{<br /><br />int i = 0;<br /><br />/* skip non-existent command lines so the kernel will still<br /><br />* use its default command line.<br /><br />*/<br /><br />params->hdr.tag = ATAG_CMDLINE;<br /><br />params->hdr.size = 8;<br /><br />//console=ttyS0,115200n8<br /><br />strcpy(params->u.cmdline.cmdline, p);<br /><br />params = tag_next(params);<br /><br />}<br /><br /><br /><br /><br /><br />static void setup_initrd2_tag(void)<br /><br />{<br /><br />/* an ATAG_INITRD node tells the kernel where the compressed<br /><br />* ramdisk can be found. ATAG_RDIMG is a better name, actually.<br /><br />*/<br /><br />params->hdr.tag = ATAG_INITRD2;<br /><br />params->hdr.size = tag_size(tag_initrd);<br /><br />params->u.initrd.start = RAM_COMPRESSED_RAMDISK_BASE;<br /><br />params->u.initrd.size = 2047;//k byte<br /><br />params = tag_next(params);<br /><br />}<br /><br /><br /><br /><br /><br />static void setup_ramdisk_tag(void)<br /><br />{<br /><br />/* an ATAG_RAMDISK node tells the kernel how large the<br /><br />* decompressed ramdisk will become.<br /><br />*/<br /><br />params->hdr.tag = ATAG_RAMDISK;<br /><br />params->hdr.size = tag_size(tag_ramdisk);<br /><br />params->u.ramdisk.start = RAM_DECOMPRESSED_RAMDISK_BASE;<br /><br />params->u.ramdisk.size = 7.8*1024; //k byte<br /><br />params->u.ramdisk.flags = 1; // automatically load ramdisk<br /><br />params = tag_next(params);<br /><br />}<br /><br /><br /><br /><br /><br />static void setup_end_tag(void)<br /><br />{<br /><br />params->hdr.tag = ATAG_NONE;<br /><br />params->hdr.size = 0;<br /><br />} void Uart_Init(int pclk,int baud)//串口是很重要的<br /><br />{<br /><br />int i;<br /><br />if(pclk == 0)<br /><br />pclk = PCLK;<br /><br />rUFCON0 = 0x0; //UART channel 0 FIFO control register, FIFO disable<br /><br />rUMCON0 = 0x0; //UART chaneel 0 MODEM control register, AFC disable<br /><br /><br /><br />//UART0<br /><br />rULCON0 = 0x3; //Line control register : Normal,No parity,1 stop,8 bits<br /><br />下面这段samsung好象写的不太对,但是我按照Normal,No parity,1 stop,8 bits算出来的确是0x245<br /><br /><br /><br />// [10] [9] [8] [7] [6] [5] [4] [3:2] [1:0]<br /><br />// Clock Sel, Tx Int, Rx Int, Rx Time Out, Rx err, Loop-back, Send break, Transmit Mode, Receive Mode<br /><br />// 0 1 0 , 0 1 0 0 , 01 01<br /><br />// PCLK Level Pulse Disable Generate Normal Normal Interrupt or Polling<br /><br />rUCON0 = 0x245; // Control register<br /><br />rUBRDIV0=( (int)(PCLK/16./ baud) -1 ); //Baud rate divisior register 0<br /><br />delay(10);<br /><br />}<br /><br />经过以上的折腾,接下来就是kernel的活了.能不能启动kernel,得看你编译kernel的水平了.<br /><br />这个BOOTLOADER不象blob那样需要交互信息,使用虚拟地址,总的来说非常简洁明了.<br /> |
|