项目步骤:html
第一阶段:node
一、关看门狗;linux
二、时钟初始化;c++
三、内存初始化;缓存
四、NandFlash初始化;架构
五、代码重定位(将flash中的代码复制到指定的内存地址处,也即代码段连接地址);异步
六、跳转到main函数;async
第二阶段:ide
七、写main函数,在函数中设置要传给内核的参数;svg
八、跳转到内核入口,启动内核
九、制做连接脚本
第三阶段:
十、编写Makefile文件;
十一、编译、下载、运行
本文件须要完成的目标:
1.关看门狗
2.设置时钟
3.开启指令缓存,初始化SDRAM
4.重定位(把bootloader自己的代码从flash复制到它的连接地址(c函数编写),而后清空bss段(c函数编写))
5.跳转到main函数。
#define CLKDIVN 0X4C000014 /*设置FCLK:HCLK:PCLK的比例*/ #define MPLLCON 0x4C000004 /*设置FCLK频率*/ #define S3C2440_MPLL_200MHZ ((0x5c<<12)|(0x01<<4)|(0x02)) #define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01)) #define WTCON 0x53000000 /*看门狗寄存器*/ #define BWSCON 0X48000000 /*BANK寄存器*/ .text /*设置代码段*/ .global _start /*定义全局变量,要被连接脚本用到*/ _start: /*_start跳转到这里实现硬件初始化*/ /* 1.关看门狗*/ ldr r0, =WTCON mov r1, #0 str r1, [r0] /* 2.设置时钟(必须设为异步总线模式) */ ldr r0, =CLKDIVN mov r1, #5 /* FCLK:HCLK:PCLK=1:4:8 */ str r1, [r0] mrc p15, 0, r1, c1, c0, 0 /*设置为asynchronous bus mode*/ orr r1, r1, #0xc0000000 mcr p15, 0, r1, c1, c0, 0 ldr r0, =MPLLCON /* 设置时钟频率FCLK为400MHZ */ ldr r1, =S3C2440_MPLL_400MHZ str r1, [r0] /* 3.使能ICache,加快指令访问速度;由于目前没有开启MMU,因此还不能开启数据缓存DCache */ 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 */ /* 4.初始化SDRAM */ ldr r0, =BWSCON adr r1, sdram_config /* 使用adr跳转,由于SDRAM未初始化 */ add r3, r0, #(13*4) 1: ldr r2, [r1], #4 str r2, [r0], #4 cmp r0, r3 bne 1b /*back to 1 when no equal*/ /* 4.重定位 */ ldr sp, =0x34000000 /*由于SDRAM大小为64MB,因此堆栈指针设在最高地址处*/ bl nand_init mov r0, #0 /* src = r0 */ ldr r1, =_start /* dest = r1,_start为代码段的起始和连接地址0x30000000 */ ldr r2, =__bss_start sub r2, r2, r1 /* len = r2,代码段长度*/ bl copy_code_to_sdram /* 调用C函数copy_code_to_sdram(src, dest, len)*/ bl clear_bss /* 清除bss段*/ /* 5.执行main */ ldr lr, =halt ldr pc, =main mov pc,lr /* 若main函数跳出后,使PC等于lr连接寄存器,避免程序跑飞 */ halt: b halt /* 死循环,避免跑飞 */ sdram_config: .long 0x22011110 //BWSCON .long 0x00000700 //BANKCON0 .long 0x00000700 //BANKCON1 .long 0x00000700 //BANKCON2 .long 0x00000700 //BANKCON3 .long 0x00000700 //BANKCON4 .long 0x00000700 //BANKCON5 .long 0x00018005 //BANKCON6 .long 0x00018005 //BANKCON7 .long 0x008C04F4 // REFRESH .long 0x000000B1 //BANKSIZE .long 0x00000030 //MRSRB6 .long 0x00000030 //MRSRB7
关于时钟频率的设置解释:
经过查看数据手册,得知当FCLK取400MHZ时,设置MDIV为0X5C,PDIV为0x1,SDIV为0x1。
关于Cache的设置解释:
经过高速缓存存储器能够加快对内存的数据访问,在CAHE中有ICAHE(指令缓存)和DCAHE(数据缓存):
ICAHE: 指令缓存,用来存放执行这些数据的指令;
DCAHE: 用来存放数据,须要开启MMU才能开启DCAHE。
在没开启ICAHE以前,CPU读取SDRAM地址数据时,每次都须要先访问一次地址值,在读数据.
当开了ICAHE后,第一次读取SDRAM地址数据时,ICAHE发现缓存里没有这个地址数据,而后将SDRAM中须要读取的那部分一大块内存数据都复制在缓存中,后面陆续读取数据就不会再访问SDRAM了,直到CPU没有找到地址数据后ICAHE再从SDRAM中从新复制
经过CP15协处理器来开启ICAHE:ICAHE控制位在CP15的寄存器C1中位12(以下图), 而后经过MRS和MSR向该位12置1,开启ICAHE.因此代码以下(放在SDRAM初始化以前):
mrc p15, 0, r0, c1, c0, 0 //将 CP15 的寄存器 C1 的值读到 r0 中 orr r0, r0, #(1<<12) //将r0中位12置1 mcr p15,0, r0,c1,c0,0 //开启ICAHE
准备知识:
我使用的NandFlash型号为K9F2G08U0M,经过查阅芯片手册获知该flash大小=2048块Block=128KPages=256MB=2Gb 。且其构成为:
1个设备=2048(Block)
1块Block=64(Pages)
1页=(2K+64)(Byte) 由于每一个地址里都存放了一个字节,因此用Byte表示,其中64B是存放ECC的OOB地址,(ECC:存放判断位反转的校验码)
肯定通讯时序:
tCS:等待芯片使能CE的时间, tCS=20nS
tCLS和tALS:等待WE(写信号)结束的时间, tCLS=tALS=15nS
tWP:WE(写信号)维持时间, tWP=15nS
tALH:等待命令写入成功的时间, tALH=5nS
tCLH:等待地址写入成功的时间, tCLH=5nS
TACLS:属于等待WE(写信号)就绪的时间,对比图2得出TACLS= tCLS- tWP=0nS
TWRPH0:属于WE(写信号)的时间, 对比图2得出TWRPH0= tWP=15nS
TWRPH1:属于等待命令写入成功的时间,对比图2得出TWRPH1=tALH=tCLH=5nS
TACLS[13:12] :表示Duration(持续时间)=HCLK*TACLS,因为Duration=0nS,因此TACLS=0
TWRPH0 [10:8] :表示Duration(持续时间)=HCLK*( TWRPH0+1),因为Duration=15nS,HCLK=10nS(100Mhz),因此TWRPH0 =1.
TWRPH1 [6:4] :表示Duration(持续时间)= HCLK*( TWRPH1 +1),因为Duration=5nS,HCLK=10nS(100Mhz),因此TWRPH1 =0
/* nand flash 时序 */#define TACLS 0 #define TWRPH0 1 #define TWRPH1 0 /* nand flash 寄存器 */ #define NFCONF *((volatile unsigend long *)0X4E000000); //配置寄存器(用来设置时序) #define NFCONT *((volatile unsigend long *)0X4E000000); //控制寄存器(用来使能nandflash控制器以及ECC编码器,还有控制芯片使能CE脚) #define NFCMMD *((volatile unsigend char *)0X4E000000);//发送命令寄存器(命令只有8位) #define NFADDR *((volatile unsigend char *)0X4E000000);//发送地址寄存器(地址只有8位) #define NFDATA *((volatile unsigend int *)0X4E000000);//读/写数据寄存器(数据只有8位) #define NFSTAT *((volatile unsigend int *)0X4E000000);//运行状态寄存器(用于判断RnB脚) /*由于Nand Flash只有8位I/O脚,因此NFCMMD/ NFADDR/ NFDATA三个寄存器值都是unsigend char型 */ void nand_init(void) { /* 设置时序 */ NFCONF = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4); /* bit4=1:初始化ECC, bit1=1:禁止片选 bit0=1:启动nandflash控制器*/ NFCONT = (1<<4)|(1<<1)|(1<<0); }
在实现nand_read()函数前,还须要实现如下几个子函数:nand_select()、nand_deselect()、nand_cmd()、nand_waite_idle()、nand_read_data();
void nand_select(void) //使能片选 { int i; NFCONT&=~(1<<1); // NFCONT控制器位1置0 for(i=0;i<10;i++); //等待芯片使能成功 }
void nand_deselect(void) //取消片选 { int i; NFCONT&=~(1<<1); // NFCONT控制器位1置0 for(i=0;i<10;i++); //等待芯片使能成功 }
void nand_cmd(unsigned char cmd) { volatile int i; NFCMMD = cmd; for (i = 0; i < 10; i++); }
void nand_wait_ready(void) { while (!(NFSTAT & 1)); }
unsigned char nand_data(void) { return NFDATA; }
首先Nand Flash引脚只有8位,然而地址共有2048(块)64(页)2KB,为了读出多个地址,以下图,因此须要分5个周期来实现发送地址:
如上图,其中 A10\~A0对应页大小(列),因为nandflash每页2048B,因此只用到A10~A0;
A28~A11对应页目录(行),表示共有2048块*64(每块有64页)个目录
例如,4097 地址就是:
A10~A0=4097%2048= 1(A0=1,其他为0)
A28~A11=4097/2048=2(A13=1,其他为0)
void nand_addr(unsigned int addr) { unsigned int col = addr % 2048; unsigned int page = addr / 2048; volatile int i; NFADDR = col & 0xff; /* A7~A0,第1周期 */ for (i = 0; i < 10; i++); NFADDR = (col >> 8) & 0xff; /* A10~A8,第2周期 */ for (i = 0; i < 10; i++); NFADDR = page & 0xff; /* A18~A11,第3周期 */ for (i = 0; i < 10; i++); NFADDR = (page >> 8) & 0xff; /* A26~A19,第4周期 */ for (i = 0; i < 10; i++); NFADDR = (page >> 16) & 0xff; /* A27~A28,第5周期 */ for (i = 0; i < 10; i++); }
如上图,例如:当要reset复位nand flash时:
1) 使能片选nand_select();
2) 发送0XFF复位命令nand_cmd(0xFF);
3) 等待RnB状态是否就绪 nand_wait_idle();
4) 取消片选 nand_deselect();
nand flash 读数据分为了如下几个步骤:
(1) 使能片选CE,将CLE置1,等待发送命令
(2) 将WE置低,将IO置为0X00,而后拉高WE,触发一次上升沿,则将把0x00写入flash中
(3) 将CLE置0,表示发送地址(分为5个周期)
(4) 发送读命令0X30
(5) 等待RnB信号为高电平
(6) 读数据(在同一页里,数据能够连续读,读下一页时,须要从新发送新的地址才行例如:读1000地址到2050地址时,
1.发出1000地址,到达页0的1000地址上,而后再连续读(2048-1000)次,直到读到页0的2047处.
2.再发出2048地址,到达页1的0地址上,而后连续读(2051-2048)次,直到读到2050为止)
(7) 取消片选nCE
/* * src:源地址,为32位地址,因此用unsigend int表示 * dest:目的地址内容,因为这里是将数据读出到目的地址内容中,因此须要用到*指针, * 由于每一个地址里存的是一个字节,因此用unsigend char型 */ void nand_read(unsigned int src,unsigned char *dest,unsigned int len) { int col = src % 2048; //第一次读,可能不是读的页首地址,因此须要记录当前页的位置 int i=0; //当前读了0次 nand_select(); //1使能片选nCE while(i<len) { nand_cmd(0X00); //2发送读命令0X00 nand_addr(src); // 3发送yuan地址(分为5个周期) nand_cmd(0X30); //4发送读命令0X30 nand_wait_idle(); //5等待RnB信号为高电平 for(;(col<2048)&&(i<len);col++) //连续读页内数据 { dest[i]=nand_read_data(); //6.读数据 i++; src++; } col=0; } nand_deselect(); // 取消片选nCE }
/**************************************************/ /* 重定位函数 */ /**************************************************/ /* 复制代码段(长度为len = __bss_start- _start)到SDRAM连接地址dest(0x30000000)处*/ void copy_code_to_sdram(unsigned char* src, unsigned char* dest, unsigned int len) { unsigned int i = 0; /* 判断nor启动仍是nand启动 */ if (isBootFromNorFlash()) { while (i < len) /*Nor启动,则直接复制*/ { dest[i] = src[i]; i++; } } else { nand_read((unsigned int)src, dest, len); } }
/*******************************************************/ /* 判断是NOR启动仍是NAND启动 */ /*******************************************************/ int isBootFromNorFlash(void) { volatile int *p = (volatile int *)0; unsigned int tmp = *p; *p = 0x12345678; /*向flash写*/ if (*p == 0x12345678) { *p = tmp; /* 若能成功写入,就是nand flash */ return 0; } else { return 1; /* 写不成功,就是NOR flash*/ } }
/******************************************************/ /* bss段清除函数 */ /******************************************************/ void clear_bss(void) { extern int __bss_start, __bss_end; int *p = &__bss_start; for (; p < &__bss_end; p++) *p = 0; }
/*************************************************************/ /* 初始化串口,实现终端显示 */ /*************************************************************/ /* 串口引脚的GPIO设置寄存器 */ #define GPHCON (*(volatile unsigned long *)0x56000070) #define GPHUP (*(volatile unsigned long *)0x56000078) /* UART registers*/ #define ULCON0 (*(volatile unsigned long *)0x50000000) #define UCON0 (*(volatile unsigned long *)0x50000004) #define UFCON0 (*(volatile unsigned long *)0x50000008) #define UMCON0 (*(volatile unsigned long *)0x5000000c) #define UTRSTAT0 (*(volatile unsigned long *)0x50000010) #define UTXH0 (*(volatile unsigned char *)0x50000020) #define URXH0 (*(volatile unsigned char *)0x50000024) #define UBRDIV0 (*(volatile unsigned long *)0x50000028) /* 串口发送状态标志*/ #define TXD0READY (1<<2) #define PCLK 50000000 // PCLK = 50MHz #define UART_CLK PCLK // UART0的时钟=PCLK #define UART_BAUD_RATE 115200 // 波特率115200 #define UART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1) /* * 初始化UART0 * 115200,8N1,无校验 */ void uart0_init(void) { GPHCON |= 0xa0; // GPH2,GPH3����TXD0,RXD0 GPHUP = 0x0c; // GPH2,GPH3�ڲ����� ULCON0 = 0x03; // 8N1(8������λ�����飬1��ֹͣλ) UCON0 = 0x05; // ��ѯ��ʽ��UARTʱ��ԴΪPCLK UFCON0 = 0x00; // ��ʹ��FIFO UMCON0 = 0x00; // ��ʹ������ UBRDIV0 = UART_BRD; // ������Ϊ115200 } /* * 输出单个字符 */ void putc(unsigned char c) { /* 等待串口准备好 */ while (!(UTRSTAT0 & TXD0READY)); /*向串口输入字符 */ UTXH0 = c; } /* * 输出字符串 */ void puts(char *str) { int i = 0; while (str[i]) { putc(str[i]); i++; } } /* * 输出val的16进制数表示,主要用于调试检查某内存地址的值是否正确 */ void puthex(unsigned int val) { /* 0x1234abcd */ int i; int j; puts("0x"); for (i = 0; i < 8; i++) { j = (val >> ((7-i)*4)) & 0xf; if ((j >= 0) && (j <= 9)) putc('0' + j); else putc('A' + j - 0xa); } }
由于TAG结构体定义是存在u-boot-1.1.6/include/asm-arm/setup.h中,因此设置TAG参数须要用到这个文件,将setup.h复制到当前工程目录下。
修改setup.h文件,删除如下不须要的代码:
#define __tag __attribute__((unused, __section__(".taglist"))) #define __tagtable(tag, fn) \ static struct tagtable __tagtable_##fn __tag = { tag, fn } #define tag_member_present(tag,member) \ ((unsigned long)(&((struct tag *)0L)->member + 1) \ <= (tag)->hdr.size * 4)
获得如下setup.h文件:
#ifndef __ASMARM_SETUP_H #define __ASMARM_SETUP_H #define u8 unsigned char #define u16 unsigned short #define u32 unsigned long /* * Usage: * - do not go blindly adding fields, add them at the end * - when adding fields, don't rely on the address until * a patch from me has been released * - unused fields should be zero (for future expansion) * - this structure is relatively short-lived - only * guaranteed to contain useful data in setup_arch() */ #define COMMAND_LINE_SIZE 1024 /* 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]; }; /* * The new way of passing information: a list of tagged entries */ /* The list ends with an ATAG_NONE node. */ #define ATAG_NONE 0x00000000 struct tag_header { u32 size; u32 tag; }; /* The list must start with an ATAG_CORE node */ #define ATAG_CORE 0x54410001 struct tag_core { u32 flags; /* bit 0 = read-only */ u32 pagesize; u32 rootdev; }; /* it is allowed to have multiple ATAG_MEM nodes */ #define ATAG_MEM 0x54410002 struct tag_mem32 { u32 size; u32 start; /* physical start address */ }; /* VGA text type displays */ #define ATAG_VIDEOTEXT 0x54410003 struct tag_videotext { u8 x; u8 y; u16 video_page; u8 video_mode; u8 video_cols; u16 video_ega_bx; u8 video_lines; u8 video_isvga; u16 video_points; }; /* describes how the ramdisk will be used in kernel */ #define ATAG_RAMDISK 0x54410004 struct tag_ramdisk { u32 flags; /* bit 0 = load, bit 1 = prompt */ u32 size; /* decompressed ramdisk size in _kilo_ bytes */ u32 start; /* starting block of floppy-based RAM disk image */ }; /* describes where the compressed ramdisk image lives (virtual address) */ /* * this one accidentally used virtual addresses - as such, * its depreciated. */ #define ATAG_INITRD 0x54410005 /* describes where the compressed ramdisk image lives (physical address) */ #define ATAG_INITRD2 0x54420005 struct tag_initrd { u32 start; /* physical start address */ u32 size; /* size of compressed ramdisk image in bytes */ }; /* board serial number. "64 bits should be enough for everybody" */ #define ATAG_SERIAL 0x54410006 struct tag_serialnr { u32 low; u32 high; }; /* board revision */ #define ATAG_REVISION 0x54410007 struct tag_revision { u32 rev; }; /* initial values for vesafb-type framebuffers. see struct screen_info * in include/linux/tty.h */ #define ATAG_VIDEOLFB 0x54410008 struct tag_videolfb { u16 lfb_width; u16 lfb_height; u16 lfb_depth; u16 lfb_linelength; u32 lfb_base; u32 lfb_size; u8 red_size; u8 red_pos; u8 green_size; u8 green_pos; u8 blue_size; u8 blue_pos; u8 rsvd_size; u8 rsvd_pos; }; /* command line: \0 terminated string */ #define ATAG_CMDLINE 0x54410009 struct tag_cmdline { char cmdline[1]; /* this is the minimum size */ }; /* acorn RiscPC specific information */ #define ATAG_ACORN 0x41000101 struct tag_acorn { u32 memc_control_reg; u32 vram_pages; u8 sounddefault; u8 adfsdrives; }; /* footbridge memory clock, see arch/arm/mach-footbridge/arch.c */ #define ATAG_MEMCLK 0x41000402 struct tag_memclk { u32 fmemclk; }; struct tag { struct tag_header hdr; union { struct tag_core core; struct tag_mem32 mem; struct tag_videotext videotext; struct tag_ramdisk ramdisk; struct tag_initrd initrd; struct tag_serialnr serialnr; struct tag_revision revision; struct tag_videolfb videolfb; struct tag_cmdline cmdline; /* * Acorn specific */ struct tag_acorn acorn; /* * DC21285 specific */ struct tag_memclk memclk; } u; }; struct tagtable { u32 tag; int (*parse)(const struct tag *); }; #define tag_next(t) ((struct tag *)((u32 *)(t) + (t)->hdr.size)) #define tag_size(type) ((sizeof(struct tag_header) + sizeof(struct type)) >> 2) #define for_each_tag(t,base) \ for (t = base; t->hdr.size; t = tag_next(t)) /* * Memory map description */ #define NR_BANKS 8 struct meminfo { int nr_banks; unsigned long end; struct { unsigned long start; unsigned long size; int node; } bank[NR_BANKS]; }; extern struct meminfo meminfo; #endif
void main(void) { void (*theKernel)(int zero, int arch, unsigned int params); /*定义一个函数指针theKernel,其中第一个参数zero:0 */ /* arch:机器ID ,因为芯片类型不少,内核为了辨别芯片而定义的机器ID,其中2440芯片的ID号是362,*/ /* params :tag参数位置,这里咱们的tag起始地址=0x30000100*/ /*1 初 始 化 串 口 0 , 使 内 核 能 打 印 信 息 */ uart0_init(); //调用serial.h头文件里的uart0_init() puts(“uart0 init OK\r\n”); //打印uart0初始化 /*2从 nand flash 里 把 内 核 复 制 到 SDRAM 中 */ puts(“copy kernel from nand\r\n”); //打印内核复制 nand_read((0x60000+64),0X30008000,0X200000); //烧写2MB,多烧写点避免出错 /* 0x60000+64:表示内核在nand(存储)地址上位置, 0X30008000:内核在sdram(运行)地址上位置 0X200000:内核长度2MB 由于Flash上存的内核格式是:uImage(64B头部(header) + 真正的内核 ) 在uboot界面中输入mtd命令能够看到: kernel分区位于 nand的0X00060000~0x00260000 因此在nand中真正的内核地址=0x60000+64, 在uboot界面中输入boot命令能够看到: Data Size: 1848656 Bytes =1.8 MB Load Address: 30008000 因此内核目的地址=0X30008000 长度=1.8MB */ /*3 设 置 T A G 参 数 */ puts(“set boot params\r\n”); //打印设置参数信息 setup_start_tag (void); //在0X30000100地址保存start_tag数据, setup_memory_tags (void); //保存memory_tag数据,让内核知道内存多大 setup_commandline_tag (“boottargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0”); /*保存命令行bootargs参数,让内核知道根文件系统位置在/dev/mtdblock3,指定开机运行第一个脚本/linuxrc,指定打印串口0*/ setup_end_tag (void); //初始化tag结构体结束 /* 4 跳 转 执 行 */ puts(“boot kernel\r\n”); //打印启动内核 theKernel = (void (*)(int, int, unsigend int))0x30008000; // 设置theKernel地址=0x30008000,用于后面启动内核 theKernel(0,362,0x300000100); //362:机器ID, 0x300000100: params(tag)地址 /*传递参数跳转执行到0x30008000启动内核, */ /*至关于: mov r0,#0 */ /*ldr r1,=362 */ /*ldr r2,= 0x300000100 */ /*mov pc,#0x30008000 */ puts(“kernel ERROR\r\n”); //打印内核启动出错 }
建立tag参数函数代码以下:
#include “setup.h” static struct tag *params; //定义个tag结构体变量params指针 void setup_start_tag (void) //开始tag { params = (struct tag *) 0x30000100; //tag起始地址等于0X30000100 params->hdr.tag = ATAG_CORE; //头部常量tag=0x54410001 params->hdr.size = tag_size (tag_core); //size=5, params->u.core.flags = 0; params->u.core.pagesize = 0; params->u.core.rootdev = 0; params = tag_next (params); //parmas=( struct tag *)((u32 *)parmas+ params->hdr.size) } // setup_start_tag (bd)保存tag参数以下: setup_memory_tags (void) //内存tag { int i; params->hdr.tag = ATAG_MEM; //头部常量tag=0x54410002 params->hdr.size = tag_size (tag_mem32); //size=4 params->u.mem.start = 0x30000000; //SDRAM起始地址 params->u.mem.size = 0x4000000; //SDRAM内存大小64M params = tag_next (params); //指向下个tag } // setup_memory_tag s(bd)保存tag参数以下: int strlen(char *str) //uboot不依赖任何库,因此须要本身写strlen函数 { int i=0; while(str[i]) { i++; } return i; } void strcpy(char *dest, char *src) { while((*dest++=*src++)!=’\0’&&*dest!=’\0’); } setup_commandline_tag (char *cmdline) //命令行tag /**cmdline :指向命令行参数 */ /*通常为:“boottargs=noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0” */ { int len=strlen(cmdline)+1; //计算cmdline长度,并加上结束符 params->hdr.tag = ATAG_CMDLINE; //头部常量tag=0x54410009 params->hdr.size =(sizeof (struct tag_header) +len+3) >> 2; /*size=(字符串长度+头部长度) >>2 */ /*“+3”表示:按4字节对齐,好比当总长度=(1,2,3,4)时,size=(总长度+3)>>2=1,实现4字节对齐 */ strcpy (params->u.cmdline.cmdline, cmdline); //复制形参字符串到params->u.cmdline.cmdline params = tag_next (params); //执行下个tag } setup_end_tag (void) //结束tag { params->hdr.tag = 0; params->hdr.size = 0; }
一、1.lds连接脚本中每一个符号(:或者=)左右都要加上空格或TAB按键 ;
. = ALIGN(4); .rodata:{*(.rodata)} //这里:左右没有空格,将出错,改成 .rodata : {*(.rodata*)}二、lds连接脚本中{}和()后都不能加";"分号。
.rodata : {*(.rodata*)}; //这里"}"后面加了";"逗号,将出错
三、lds脚本sections中如果以当前地址.等于xxx时,.后面都要加空格;
.= 0x33f80000; //这里.后面没有加空格,出错 . = 0x33f80000; . = ALIGN(4);四、lds脚本中定义符号时,都要使符号在前。
__bss_start = .; .bss : { *(.bss) *(COMMON) } . = __bss_end; //这里定义"__bss_end"符号出错,应该改成 __bss_end = .;
连接脚本中的0x33f80000就是连接地址(即程序运行时,该代码段将被连接到内存的此地址处),共512K空间存放bootloader;
定义__bss_start和__bss_end符号,是用来在程序开始以前,将这些未定义的变量清0,节省内存且_bss_start-0x33f80000就等于代码的大小(即copy_code_tosdram函数中len值)。
SECTIONS { . = 0x33f80000; . = ALIGN(4); .text : { *(.text) } . = ALIGN(4); .rodata : {*(.rodata*)} . = ALIGN(4); .data : { *(.data) } . = ALIGN(4); __bss_start = .; .bss : { *(.bss) *(COMMON) } __bss_end = .; }
通常在头文件中会经过MTDPARTS_DEFAULT宏定义,明确flash的分区设置,通常来讲会分为4个区,依次存放bootloader、启动参数、内核映像、根文件系统。
bootloader | 一开机直接运行u-boot |
---|---|
boot parameters | 存放一些能够设置的参数,供u-boot使用 |
kernel | 存放内核区 |
root filesystem | 根文件系统,挂载(mount)后才能使用文件系统中的应用程序 |
启动流程为:
备注:在makefile中‘=’与‘:=’的区别:
‘=’ 无关位置的等于(好比:”x=a y=$(x) x=b”,那么y的值永远等于最后的值,等于 b ,而不是a)
‘:=’ 有关位置的等于(好比:”x:=a y:=$(x) x:=b”,那么y的值取决于当时位置的值,等于 a ,而不是b)
CC = arm-linux-gcc //定义CC变量=arm-linux-gcc,简化书写,编译命令,(*.C,*.S)文件生成*.O文件 LD = arm-linux-ld //链接命令,将多个*.O文件生成 boot.elf AR = arm-linux-ar //库管理命令,这里没有用到 OBJCOPY = arm-linux-objcopy //复制/格式转换命令, boot.elf生成boot.dis OBJDUMP = arm-linux-objdump //反汇编命令,boot.bin生成boot.dis //GCC编译参数,-Wall:显示全部错误和警告, -O2:采用2级编译优化 CFLAGS := -Wall -O2 //添加头文件参数,-nostdinc忽略缺省目录, -fno-builtin不链接系统标准启动文件和标准库文件(表示不用自带的strlen()等库函数) CPPFLAGS := -nostdinc -fno-builtin //定义objs变量,包含生成boot.bin目标文件须要的依赖文件 objs := start.o init.o boot.o //执行生成目标文件,首先是先知足objs全部依赖文件都拥有,才执行 boot.bin: $(objs) ${LD} -Tuboot.lds -o boot_elf $^ ${OBJCOPY} -O binary -S boot_elf $@ ${OBJDUMP} -D -m arm boot_elf > boot.dis //-c编译不链接。$@表示目标文件 $<表示第一个依赖文件 %.o:%.c ${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $< %.o:%.S ${CC} $(CPPFLAGS) $(CFLAGS) -c -o $@ $< clean: rm -f *.bin *.elf *.dis *.o
(1)新建一个文件夹,并命名为bootloader,将以上编写好的:boot.c、init.c、start.S、setup.h、boot.lds和Makefile文件复制进去。而后将该bootloader文件夹经过共享文件夹或FileZilla软件拷贝进虚拟机linux环境下。
(2)在Linux环境下经过终端命令行进入到该bootloader文件夹中,执行make命令,进行编译、连接:
(3)将获得的二进制文件boot.bin拷贝到Windows环境下的D盘根目录下。
(4)键盘“Win + R”组合键,输入cmd
回车,打开WIndows下的命令行终端,输入命令d:
进入D盘根目录。
(5)打开开发板电源,将JTAG-USB转接工具链接到电脑USB口,在WIndows命令行终端终端输入oflash boot.bin
命令,运行FLASH烧录工具,将二进制目标文件烧录进目标板的NAND Flash中:
(6)选择0回车,进入OpenJTAG模式,再依次键入1选择S3C2440目标板、后面都键入0完成烧录。
(7)用串口调试线链接开发板的COM口和电脑的USB口,打开MobaXterm串口链接工具,并链接到开发板。关闭开发板电源后,再从新接通电源,经过MobaXterm软件观察开发板启动状况。
(8)若是程序正确,将会看到开发板顺利启动内核并运行起来了linunx。
获取更多知识,请点击关注:
嵌入式Linux&ARM
CSDN博客
简书博客
知乎专栏