項目步驟: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博客
簡書博客
知乎專欄