Build the bootloader from scratch

Project steps:

Stage 1:

1, close the watchdog;

2. Clock initialization;

3. Memory initialization;

4. NandFlash initialization;

5. Code relocation (copy the code in Flash to the specified memory address, that is, the code segment link address);

6. Skip to main function;

Stage 2:

7. Write the main function and set the parameters to be passed to the kernel.

8. Jump to the kernel entry and start the kernel

9. Make a link script

Stage 3:

10. Write the Makefile file;

11. Compile, download and run


1. Write the start.S file to initialize the on-chip hardware

Objectives to be accomplished in this document:

1. Shut the watchdog

Set the clock

3. Open instruction cache and initialize SDRAM

4. Relocate (copy the bootloader’s own code from Flash to its link address (C function writing), then empty the BSS segment (C function writing))

5. Jump to main.

#define S3C2440_MPLL_200MHz / #define S3C2440_MPLL_200MHz / #define MPLLCON 0x4C000004 / #define S3C2440_MPLL_200MHz ((0x5c<<12)|(0x01<<4)|(0x02)) #define S3C2440_MPLL_400MHZ ((0x5c<<12)|(0x01<<4)|(0x01)) #define WTCON 0x53000000 / guard dog register / #define BWSCON 0X48000000 /BANK register /.text/set code snippet /.global _start/define global variable to be used by linked script / _start: /_start jumps here to implement hardware initialization / / 1. / LDR r0, =WTCON mov r1, #0 STR r1, [r0] / 2 Set clock (must be set to asynchronous bus mode)/LDR r0, =CLKDIVN mov r1, #5 / FCLK:HCLK:PCLK=1:4:8 / STR r1, [r0] MRC p15, 0, r1, C1, c0 0 / set for asynchronous bus mode/ORR r1, r1, # 0 xc0000000 MCR p15, 0, r1, c1, c0, 0 LDR r0, =MPLLCON/Set the clock frequency FCLK to 400MHz/LDR R1, = S3C2440_MPLL_400MHz STR R1, [R0] / 3. Enable ICache to speed up instruction access; Since the MMU is not currently enabled, 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/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. Relocation/LDR sp, =0x34000000 / Because the size of SDRAM is 64MB, / bl nand_init mov r0, #0 / SRC = r0 / LDR r1, =_start/dest = r1, address 0x30000000 / LDR R2, =__bss_start sub R2, R2, r1 / len = R2, copy_code_to_sdram/copy_code_to_sdram(SRC, dest, dest, dest, Len)/ bl clear_bss/Clear the BSS segment / / 5. Main/LDR lr, =halt LDR PC, =main mov PC, lr/If main pops up, make PC equal to the lr link register to prevent the program from crashing/halt: B halt/dead-end loop, avoid running/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


  • About the clock frequency setting explanation:

    • Set the CPU frequency to a maximum of 400MHz (the kernel boot time of 7S becomes 6S because the HCLK and PCLK frequencies have not changed) and then the frequency division coefficient FCLK:HCLK:PCLK needs to be set to 1:4:8.
    • Since the maximum value of HCLK is 133MHz, it needs to be set to 100MHz here, and the maximum value of PCLK is 50MHz, so it needs to be set to 50Hz here, so it can be concluded that the register CLKDIVN needs to be equal to 0X5.
    • The specific reason is equal to 0x5, please refer to the following figure:
! [image-20210109132950976](https://cdn.jsdelivr.net/gh/Leon1023/leon_pics/img/20210109132958.png)

According to the data manual, when FCLK takes 400MHz, MDIV is set to 0x5C, PDIV is 0x1, and SDIV is 0x1.

  • Explainer about Cache Settings:

    • Through the cache memory can speed up the access to the data in memory, in CAHE there are ICAHE (instruction cache) and DCAHE (data cache) : ICAHE: instruction cache, used to store the execution of these data instructions; DCAHE: This is used to store data. MMU is required to enable DCAHE. Before ICAHE is turned on, the CPU reading SDRAM address data needs to access the address value first and read the data each time. When ICAHE is turned on and the address data of SDRAM is read for the first time,ICAHE finds that there is no such address data in the cache. Then,ICAHE copies a large chunk of memory data that needs to be read in SDRAM into the cache. After successively reading data,ICAHE will no longer access SDRAM until the CPU fails to find the address data Recopy in DRAM
    • ICAHE is enabled through the CP15 coprocessor: The ICAHE control bit is located at bit 12 in CP15’s register C1 (as shown in the figure below), and then ICAHE is enabled by setting 1 to this bit 12 through MRS and MSR. So the code is as follows (before SDRAM initialization):
    • MRC p15,0, R0, C1, C0,0 // Read the value of register C1 in CP15 into R0 ORR r0, R0, #(1<<12) // Set R0 medium 12 to 1 MCR p15,0, R0, C1, C0,0 // Open ICAHE

2. Write init.c for relocation, BSS segment removal, and initialization of NandFlash

(1) Write nand_init() function

Preparation knowledge:

The NandFlash model I use is K9F2G08U0M. By referring to the chip manual, I know that the size of the flash =2048 Block=128KPages=256MB= 2GB. And its composition is:

1 device =2048 (Block)

1 Block=64 (Pages)

1 page =(2K+64) (Byte); 64B = OOB (ECC); 64B = OOB (ECC); 64B = OOB (ECC);

  • Writing process:

    • Write a page
    • Generate check code ECC
    • Write the check code to the OOB page
  • Reading process:

    • Read out the page data and calculate the ECC for the current data
    • Read out the original ECC stored in the OOB page
    • Compare two check codes. If the same check code is read successfully, if the different check code is read again, there is a bit reversal.

Determine communication timing:

  • As can be seen from Figure 2 and Figure 1:

TCS: Waiting time for chip enable CE, TCS =20nS

Tcls and tALS: The time to wait for the end of the WE (write signal), tCLS=tALS=15nS

TWP: We (write signal) duration, TWP =15nS

TALH: The time to wait for the command to be written successfully, tALH=5nS

TCLH: The time to wait for the address to be written successfully, tCLH=5nS

  • By looking at the 2440 chip manual, NandFlash timing diagram, you need to set TACLS, TWRPH0 and TWRPH1

TACLS: belongs to the time waiting for WE(write signal) to be ready. By comparing with Figure 2, TACLS= TCLS-TWP = 0NS

TWRPH0: Time belonging to WE(write signal). By comparing Figure 2, TWRPH0= TWP =15nS

TWRPH1: belongs to the time waiting for the command to be written successfully. Compared with Figure 2, it can be concluded that TWRPH1=tALH=tCLH=5nS

  • Set these three parameters in the NFCONF register

TACLS[13:12] : indicates Duration= HCLK*TACLS, TACLS=0 since Duration=0nS

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

  • The code is as follows:

NAND FLASH register /#define TWRPH0 1 #define TWRPH1 0 / NAND FLASH register /#define NFCONF ((volatile unsigend) long )0X4E000000); #define NFCONT ((volatile unsigend long)0X4E000000); #define NFCMMD ((volatile unsigend char) 0x4E000000); #define NFCMMD ((volatile unsigend char) 0x4E000000); #define NFADDR ((volatile unsigend char)0X4E000000); #define NFADDR ((volatile unsigend char)0X4E000000); #define NFDATA ((volatile unsigend int)0X4E000000); #define NFSTAT ((volatile unsigend int)0X4E000000); #define NFSTAT ((volatile unsigend int)0X4E000000); // Run Status Register (for RNB pins)/Because NAND Flash only has 8-bit I/O pins, Void nand_init(void) {/ set the sequence/NFCONF =. NFCMMD/ NFADDR/ NFDATA = (TACLS<<12)|(TWRPH0<<8)|(TWRPH1<<4); / bit4 = 1: initialize the ECC, bit1 = 1: ban choose bit0 = 1: start the nandflash controller/NFCONT = (1 < < 4) | | (1 < < 1) (1 < < 0); }

(2) Write nand_read() function

Before implementing the nand_read() function, the following subfunctions need to be implemented: nand_select(), nand_deselect(), nand_cmd(), nand_waite_idle(), nand_read_data();

  • 1. Slices selection enabler function (before reading and writing Flash should be selected slice selection)

Void nand_select(void) // Enable slice {int I; NFCONT&=~(1<<1); // NFCONT for(I =0; i<10; i++); // wait for the chip to enable}

  • 2. Cancel the slice selection function (Cancel the slice selection when exIt ‘reads and writes FLASH)

Void nand_deselect(void) {int I; NFCONT&=~(1<<1); // NFCONT for(I =0; i<10; i++); // wait for the chip to enable}

  • 3. NAND writes commands

 void nand_cmd(unsigned char cmd)

 {

  volatile int i;

  NFCMMD = cmd;

  for (i = 0; i < 10; i++);

 }

  • 4. Determine the RNB state function (after writing all commands, determine whether the RNB pin is high level ready)

void nand_wait_ready(void) { while (! (NFSTAT & 1)); }

  • 5. Read data command

 unsigned char nand_data(void)

 {

  return NFDATA;

 }

  • 6. Write address commands

First of all, NAND Flash pins only have 8 bits. However, addresses total 2048(blocks)_64(pages)_2KB. In order to read multiple addresses, as shown in the figure below, it takes 5 cycles to send addresses:

As shown in the figure above, where A10~A0 corresponds to the page size (column), since nandflash is 2048B per page, only A10~A0 is used;

A28~A11 correspond to page directories (rows), representing a total of 2048 blocks *64 directories (each block has 64 pages)

For example, the 4097 address would be:

A10~A0=4097%2048= 1(A0=1, the rest are 0)

A28~A11=4097/2048=2 (A13=1, the rest are 0)

void nand_addr(unsigned int addr) { unsigned int col = addr % 2048; unsigned int page = addr / 2048; volatile int i; NFADDR = col & 0xff; / for (I = 0; i < 10; i++); NFADDR = (col >> 8) & 0xff; / for (I = 0; i < 10; i++); NFADDR = page & 0xff; / for (I = 0; i < 10; i++); NFADDR = (page >> 8) & 0xff; / for (I = 0; i < 10; i++); NFADDR = (page >> 16) & 0xff; / for (I = 0; i < 10; i++); }

  • 7. NAND read data command

For example, when reset reset NAND FLASH:

1) Enable slice nand_select();

2) Send the 0xFF reset command NAND_CMD (0xFF);

3) Wait for RNB state to be ready nand_wait_idle();

4) Unselect nand_deselect();

NAND Flash reads data in the following steps:

(1)Select CE on the enabling slice, set CLE to 1, and wait for the command to be sent

(2) Set WE low, set IO to 0X00, then pull WE high, trigger a rising edge, 0X00 will be written to flash

(3) Set CLE to 0 to represent the sending address (divided into 5 cycles)

(4) Send read command 0X30

(5) Wait for RNB signal to be at high level

(6) Read data (on the same page, data can be read continuously, the next page to read, need to send a new address to do so. For example: read 1000 address to 2050 address,

1. Send the address 1000 to the address 1000 on page 0, and then read it continuously (2048-1000) until you reach the address 2047 on page 0.

2. Then send the address 2048 to the address 0 on page 1, and read it continuously (2051-2048) until you read to 2050.

(7) Cancel film selection NCE

/* * SRC: source address, unsigend int, dest: destination address, unsigend int; /* * SRC: source address, unsigend int, dest: destination address, unsigend int; Unsigend char */ void nand_read(unsigned int SRC,unsigned char *dest,unsigned int len) {int col = unsigend char */ void nand_read(unsigned int SRC,unsigned char *dest,unsigned int len) src % 2048; Int I =0; int I =0; int I =0; int I =0; int I =0; // Nand_select () is currently read 0 times; //1 while(I

Copy_code_tosdram ()

// The relocation function / // / copies the code segment (Len = __bss_start- _start) to the SDRAM link address dest(0x30000000)/void copy_code_to_sDRAM (unsigned char) src, unsigned char dest, unsigned int len) { unsigned int i = 0; / if (isBootfromNorFlash ()) {while (I < len) /Nor BootfromNorFlash ()) / {dest[I] = SRC [I]; i++; } } else { nand_read((unsigned int)src, dest, len); }}


(4)Write the isBootFramNorFlash() function to determine whether NAND starts or NOR starts

/ / int isBootFromNorFlash(void) {volatile int p = (volatile int)0; unsigned int tmp = *p; p = 0x12345678; If (*p == 0x12345678) {p = TMP; Nand flash */ return 0; } else { return 1; NOR flash/}}


(5)Write the clear_bss() function

// void clear_bss(void) {extern int __bss_start, __bss_end; int *p = &__bss_start; for (; p < &__bss_end; p++) *p = 0; }

3. Add headers: setup.h and Serial. h

(1) Add serial port support files

  • Add the serial port UART0 initialization file Serial. c to the current project directory and modify it.

/*/ / Initialize the serial port, / / #define GPHCON ((volatile unsigned long)0x56000070) #define GPHUP ((volatile unsigned long)0x56000070) #define GPHUP ((volatile unsigned long)0x56000070 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 clock =PCLK #define #define UART_BRD ((UART_CLK/(UART_BAUD_RATE * 16)) -1) /* * initialize UART0 * 115200, 8 N1, no check * / void uart0_init (void) {GPHCON | = 0 xa0; // 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} /* * Output a single character */ void putc(unsigned char c) {/ wait for the serial port to be ready/while (! (UTRSTAT0 & TXD0READY)); / enter the string into the serial port/UTXH0 = c; */ void puts(char * STR) {int I = 0; while (str[i]) { putc(str[i]); i++; */ void puthex(unsigned int val) {/ 0x1234abcd/int I; */ void puthex(unsigned int val); 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); }}

(2) Add setup.h header file

  • The setup.h file is used to setup the setup.h file. The setup.h file is used to setup the setup.h file.
  • Modify the setup.h file to remove the following unnecessary code:
  •  #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)

    Get the following setup.h file:

#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 paths8; 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

4. Write a file called boot.c to store the main function

(1) Write the main function code:

void main(void) { void (*theKernel)(int zero, int arch, unsigned int params); / / Arch: the machine ID that is defined by theKernel in order to identify the chip. / / Params: Tag address =0x30000100/ /1 initiates the serial port 0, so that the inner core can print the message/uart0_init(); // Call uart0_init() puts(” uart0 init OKrn “) in Serial. h header; // puts(” copy kernel from nandrn “) puts(” copy kernel from nandrn “); // Copy nand_read((0x60000+64),0X30008000,0X200000); /* 0x60000+64: represents the location of the kernel at the NAND (memory) address. 0X30008000: represents the location of the kernel at the SDRAM (memory) address. 0X200000: The kernel length is 2MB. UImage (64B header + real kernel) NAND: 0X00060000 +64; NAND: 0x00260000 +64; NAND: 0X00060000 +64 1848656 Bytes = 1.8MB LOAD ADDRESS: / puts(” set boot paramsrn “); / puts(” set boot paramsrn “); // Print setup_start_tag (void); // Save start_tag at 0X30000100, setup_memory_tags (void); Setup_commandLine_tag (” boottargs=noinitrd root=/dev/mtdblock3 init=/linuxrc” The console = ttySAC0 “); /dev/MTDBLOCK3: /linuxrc: 0/ setup_end_tag (void) : 0/ setup_end_tag (void); // puts(” boot kernelrn “); TheKernel = (void (*)(int, int, unsigend int))0x30008000; // Set theKernel address =0x30008000 to boot theKernel(0,362,0×300000100); //362: machine ID, 0x300000100: params(tag) address/pass parameter jump execution to 0x30008000 start kernel, // equivalent to: Mov r0,#0 / / LDR r1,=362 / / LDR r2,= 0x300000100 / /mov PC,#0x30008000 / puts(” kernel errrin “); // Error printing kernel boot}

(2) Create the Tag parameter function

The function code to create the tag parameter is as follows:

#include “setup.h” static struct tag *params; // void setup_start_tag (void); // void setup_start_tag (struct tag *) 0x30000100; Tag = ATAG_CORE; // Tag starting address = 0X30000100 params->hdr.tag = ATAG_CORE; // The header constant 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); // setup_start_tag (bd) =(struct tag)((u32)parmas+ params->hdr.size)} // setup_start_tag (bd) Setup_memory_tags (void) // memory tag {int I; params->hdr.tag = ATAG_MEM; // The header constant tag=0x54410002 params->hdr.size = tag_size (tag_mem32); //size=4 params->u.mem.start = 0x30000000; //SDRAM starting address params-> u.em.size = 0x4000000; Params = tag_next (params); Int strlen(char * STR) int strlen(char * STR) int strlen(char * STR) int strlen(char * STR); while(str[i]) { i++; } return i; } void strcpy(char dest, char src) { while((dest++=src++)! = ‘0’ && * dest! = ‘0’); } setup_commandLINE_TAG (char *cmdline) /*cmdline: “boottargs=noinitrd root=/dev/mtdblock3” Init =/linuxrc console=ttySAC0 “/ {int len=strlen(cmdline)+1; Params-> hdr.tag = ATAG_CMDLINE; params->hdr.tag = ATAG_CMDLINE; Sizeof (struct tag_header +len+3) >>; / size = (string length + head length) > > 2 / / “+ 3”, said: at 4 byte alignment, such as when the total length = (1, 2, 3, 4), size = (total length + 3) > > = 1, 2 Cmdline/strcpy (params->u.cmdline.cmdline, cmdline); Cmdline params = tag_next (params); // Copy the parameter string to params-> u.mdline.cmdline params = tag_next (params); // Executes the next tag} setup_end_tag (void) // Executes the next tag {params->hdr.tag = 0; params->hdr.size = 0; }

5. Write a link script: Boot.lds

(1) Link script syntax prompts:

1. Add space or TAB key around each symbol (: or =) in the LDS link script;

. = ALIGN(4); Rodata: {(. Rodata)} / / here: no Spaces, will go wrong, instead. Rodata: {(. Rodata *)}

2. You can’t add “;” after {} and () in LDS link script. A semicolon.

.rodata : {*(.rodata*)}; // Here "}" is followed by ";" Comma, will be an error

LDS script sections with the current address. Is equal to XXX, followed by a space;

.= 0x33f80000; = 0x33F80000; // 0x33F80000 = 0x33F80000; . = ALIGN(4);

4. When defining symbols in the LDS script, always make the symbol first.

__bss_start = .; .bss : { (.bss) (COMMON) } . = __bss_end; __bss_end =.; // __bss_end =.;

(2) Link script description

  • The 0x33F80000 in the link script is the link address (that is, when the program runs, the code segment will be linked to this address in memory), and 512K space is stored in the bootloader.
  • The __bss_start and __bss_end symbols are defined to clear these undefined variables to zero before the program starts, saving memory and _bss_start-0x33f80000 is equal to the size of the code (i.e., the len value in copy_code_tosdram).
  •  SECTIONS {

      . = 0x33f80000;

     

      . = ALIGN(4);

      .text : { *(.text) }

     

      . = ALIGN(4);

      .rodata : {(.rodata)}

     

      . = ALIGN(4);

      .data : { *(.data) }

     

      . = ALIGN(4);

      __bss_start = .;

      .bss : { (.bss) (COMMON) }

      __bss_end = .;

     }

(3) NAND FLASH partition description

Generally in the header file will be through the MTDPARTS_DEFAULT macro definition, clear Flash partition Settings, generally can be divided into four areas, in order to store the bootloader, boot parameters, kernel image, root file system.

bootloader

A boot directly run U-Boot

boot parameters

Stores some configurable parameters for u-boot use

kernel

Storage kernel area

root filesystem

Root file system to use applications on the file system after being mounted

(4) The architecture and process of NAND Flash launch

The startup process is as follows:

  • Once powered on, the CPU’s built-in program reads the boot-loader program into the CPU’s internal memory from the specific address of the NAND Flash (usually the address of the first block).
  • The CPU gives control to the boot-loader in internal memory;
  • Boot-loader initializes SDRAM, and then loads the main program into SDRAM from NAND Flash.
  • The boot-loader gives control to the main program.

6. Write the makefile

Remark:The difference between ‘=’ and ‘:=’ in a makefile:

‘=’ is equal to position independent (for example: “x=a y=$(x) x=b”, then the value of y is always equal to the final value, equal to b, not a).

‘: =’ about the location is equal to (such as: “x: = a: y = $(x) x: = b”, then the value of y depends on the value of the position at the time, is equal to a, not b)

CC =arm-linux-gcc // Define the CC variable =arm-linux-gcc, simplify writing, compile the command,(.c,.s) file to generate *.O file LD = arm-linux-ld // Connect the command to generate multiple *.O files to generate boot.elf = OBJCOPY = arm-linux-objcopy; OBJCOPY = arm-linux-objcopy; Elf generates boot.dis OBJDUMP = arm-linux-objdump // disassembler command,boot.bin generates boot.dis //GCC compilation parameter, -wall: displays all errors and warnings, -O2: uses level 2 compilation to optimize CFlags := -wall-O2 // to add header parameters,-nostdinc ignores the default directory, CPPFLAGS := -nostdinc -fno-builtin // Define the objs variable, if the system’s standard startup file is not connected to the standard library (the accompanying strlen() library functions are not used). Objs := start. O init. O boot. O // To create the object file, first of all, it is necessary to have all the dependent files of objs, before executing the boot.bin: $(objs) ${LD} -Tuboot.lds -o boot_elf $^ ${OBJCOPY} -O binary -S boot_elf $@ ${OBJDUMP} -D -m arm boot_elf > boot.dis // -C compiler does not connect. The target file $$@ said < said the first depend on file %. O: %. C ${CC} $$(CFLAGS) – c (CPPFLAGS) -o $@ $< %. O: %. S ${CC} $$(CFLAGS) – c (CPPFLAGS) -o $@ $< clean: rm -f .bin .elf .dis .o

7. Download and compile

C, start.S, setup.h, boot.lds and Makefile files should be copied into it. The bootloader folder is then copied into the virtual machine Linux environment via a shared folder or Filezilla software.

(2) Enter the bootloader folder through the terminal command line in Linux environment, execute make command, compile and link:

(3) Copy the binary file Boot.bin to the root directory of D disk under Windows environment.

(4) Keyboard “Win + R” key combination, enter CMD enter, open the command line terminal under Windows, enter the command D: enter the root directory of D disk.

(5) Open the power supply of the development board, connect the JTAG-USB transfer tool to the USB port of the computer, enter the OFlash Boot. Bin command in the Windows command line terminal, run the Flash burning tool, and burn the binary object file into the NAND Flash of the target board:

(6) Select 0 enter to enter the OpenJTAG mode, then type 1 successively to select the S3C2440 target board, and then type 0 to complete the burning.

(7) Connect the COM port of the development board with the USB port of the computer with the serial debugging line, open the MobaXterm serial port connection tool and connect it to the development board. After turning off the power supply of the development board, reconnect the power supply, and observe the startup situation of the development board through MobaXterm software.

(8) If the program is correct, you will see that the development board starts the kernel smoothly and Linunx is up and running.

Refer to the article


For more knowledge, please click on: embedded Linux&ARM CSDN blog brief book blog Zhihu column