Full explanation of ARM Linux bootloader

Source: Internet
Author: User

Many articles about Linux bootloader, but most of them are about Vivi, blob and other large programs. It is not easy to read and the compiled files are large, in addition, it is more development-oriented guiding code that needs to be reduced when made into a product, which affects the development speed to a certain extent, and has a relatively high learning overhead for beginners, here we analyze a simple bootloader, which is the result of a slight modification on the S3C2410 Bootloader provided by Samsung. The size of the compiled file cannot exceed 4 kb, which is helpful to everyone.
1. Several Important Concepts

Compressed kernel and decompressed Kernel
The compressed kernel, according to the documentation, does not advocate the use of decompressed kernel, but uses the compressed kernel, which includes understanding of the pressure. therefore, we need to provide sufficient space for the compressed and decompressed Kernel During Ram allocation so that they will not overlap.
After the command is executed to jump to the compressed kernel, The decompress starts to work. If the decompressed code is detected by the decompress, it overwrites the compressed kernel, and then directly jumps to the compressed kernel to store data, and re-locate the kernel, so if there is not enough space, an error will occur.
Jffs2 File System
The data generated in the armlinux application can be stored in flash, which is not used on my board.
Ramdisk
Ramdisk enables the root file system to start without other devices. generally, there are two loading methods. I will introduce the most commonly used method. Put the compressed ramdisk image to the specified address, and then the bootloader will pass the address atag_initrd2 to the kernel through the startup parameter. for more information, see code analysis.
Startup parameters (from IBM Developer)
Before calling the kernel, make one step of preparation, that is, set the Linux kernel startup parameters. Linux 2.4.x and later kernels all expect to pass startup parameters in the form of tagged list. Start the parameter tag list to mark atag_core and end with atag_none. Each tag consists of the tag_header structure that identifies the passed parameter and the subsequent parameter value data structure. The data structure tag and tag_header are defined in the include/ASM/setup. h header file of the Linux kernel source code.
In Embedded Linux systems, the following common startup parameters are required by Bootloader: atag_core, atag_mem, atag_cmdline, atag_ramdisk, and atag_initrd.
(Note) parameters can also be set using CommandLine. In my bootloader, I use both of them.
2. Development Environment and Development Board Configuration:
CPU: S3C2410 and bank6 have 64 MB of SDRAM (two blocks), 32 MB of nor flash on bank0, and the serial port cannot be escaped. In this way, according to the data manual, the address allocation is as follows:
0x4000_0000 is a 4 K in-chip dram.
0x0000_0000 start with 32 M Flash 16bit width
0x3000_0000 start with 64 m sdram 32bit width
Note: The bank6 and bank7 in the control register must be the same.
0x4000_0000 (in-chip Dram) Stores bootloader image within 4 K
0x3000_0100 start to store startup parameters
0x3120_0000 store compressed kernel Image
0x3200_0000 store compressed ramdisk
0x3000_8000 is specified as decompressed kernel image address
0x3040_0000 is specified as decompressed ramdisk image address
Development Environment: RedHat Linux, armgcc toolchain, and armlinux Kernel

How to Build the compilation environment of armgcc: We recommend that you use toolchain instead of compiling armgcc by yourself. If you have tried it many times, it will end with a failure.
First download arm-GCC 3.3.2 toolchain
Extract arm-linux-gcc-3.3.2.tar.bz2 to/toolchain
# Tar jxvf arm-linux-gcc-3.3.2.tar.bz2
# Mv/usr/local/ARM/3.3.2/toolchain
In makefile, set arch = arm cross_compile to the toolchain path.
Also, include =-I ../include-I/root/My/usr/local/ARM/3.3.2/include. Otherwise, the library function cannot be used.

3. Startup Mode:
It can be started in flash or a JTAG simulator. due to the use of nor flash, according to The 2410 manual, the 4 k DRAM in the chip can be directly used without any configuration, and other memory must be initialized first, such as telling memory controller, in bank6, there are two SDRAM blocks, with the data width being 32bit, =. otherwise, memory control processes the memory according to the default value after resetting. in this way, read/write errors will occur.
So the first step is to put the Execution Code in 0x4000_0000 through the simulator (during compilation, set text_bas
E = 0x40000000 ).
Step 2: Use axd to place the Linux kernel image in the target address (SDRAM) and wait for the call
Step 3: Execute the bootloader code to obtain debugging data from the serial port and guide armlinux

4. Code Analysis
After talking about so many execution steps, I want to give everyone a general impression on the startup, followed by the analysis of the code inside the bootloader. The content of the bootloader article is a lot online, and I have simplified it here, unnecessary functions are deleted.
Bootloader is generally divided into two parts: the Assembly part and the C language part. The Assembly part performs simple hardware initialization. The C part is responsible for copying data, setting startup parameters, serial communication, and other functions.
Bootloader lifecycle:
1. initialize the hardware, for example, set UART (at least one) and check memory =.
2. Set the startup parameters to inform the kernel hardware, for example, which startup interface is used, the baud rate =.
3. Jump to the first address of Linux kernel.
4. Extinction

Of course, virtual addresses are used in the pilot phase, such as Vivi. If you are bored, real addresses are the same.
Let's look at the Code:
2410init. s
. Global _ start // execution start
_ Start:
// The following is the interrupt vector.
B reset @ supervisor mode // jump after restart
......
......
Reset:
LDR r0, = wtcon/wtcon address: 53000000, watchdog control register */
LDR R1, = 0x0/* off watchdog */
STR R1, [R0]

LDR r0, = intmsk
LDR R1, = 0 xffffffff/* shield all interrupts */
STR R1, [R0]

LDR r0, = intsubmsk
LDR R1, = 0x3ff/* sub-interrupt is the same */
STR R1, [R0]
/* Initialize ports... for display led .*/
LDR r0, = gpfcon
LDR R1, = 0x55aa
STR R1, [R0]
LDR r0, = gpfup
LDR R1, = 0xff
STR R1, [R0]
LDR r0, = gpfdat
LDR R1, = poweroffled1
STR R1, [R0]
/* Setup clock divider control register
* You must configure clkdivn before locktime or mpll upll
* Because default clkdivn 1, 1 set the sdmram timing conflict
NOP
* Fclk: hclk: pclk = in this case
*/
LDR r0, = clkdivn
LDR R1, = 0x3
STR R1, [R0]

/* To reduce PLL lock time, adjust the locktime register .*/
LDR r0, = locktime
LDR R1, = 0 xffffff
STR R1, [R0]
/* Configure mpll */
LDR r0, = mpllcon
LDR R1, = (m_mdiv <12) + (m_pdiv <4) + m_sdiv) // fin = 12 MHz, fout = 203 MHz
STR R1, [R0]
LDR R1, = gstatus2
LDR R10, [R1]
Tst R10, # offrst
BNE 1000f
// In the above section, I did not change it, so I wrote it using Samsung. below is the main point to be changed.
/* Memory c0ntroller (MC) setting */
Add r0, PC, # McDATA-(. + 8) // R0 points to the McDATA address, where the data required for MC Initialization is stored.
LDR R1, = bwscon // R1 points to the first address of the MC controller register
Add R2, R0, #52 // number of copies, offset 52 characters

1: // perform cyclic replication according to the offset
LDR R3, [R0], #4
STR R3, [R1], #4
CMP R2, R0
BNE 1b
. Align 2

McDATA:
. Word (0 + (bw.bwscon <4) + (b2_bwscon <8) + (b3_bwscon <12)
+ (B4_bwscon <16) + (b5_bwscon <20) + (b6_bwscon <24) + (b7_bwscon <28 ))
The above line is the data of bwscon. The specific parameter meanings are as follows:

You need to set both dw6 and dw7 to 10, namely, 32bit, and dw0 to 01, that is, 16bit.
The following table lists the Controller data of each bank, most of which are clock-related. You can use the default value. After setting MC, jump to the part that calls the main function.
. Word (b0_tacs <13) + (b0_tcos <11) + (b0_tacc <8) + (b0_tcoh <6)
+ (B0_tah <4) + (b0_tacp <2) + (b0_pmc ))
. Word (b1_tacs <13) + (b1_tcos <11) + (b1_tacc <8) + (b1_tcoh <6)
+ (Bw.tah <4) + (bw.tacp <2) + (bw.pmc ))
. Word (b2_tacs <13) + (b2_tcos <11) + (b2_tacc <8) + (b2_tcoh <6)
+ (B2_tah <4) + (b2_tacp <2) + (b2_pmc ))
. Word (b3_tacs <13) + (b3_tcos <11) + (b3_tacc <8) + (b3_tcoh <6)
+ (B3_tah <4) + (b3_tacp <2) + (b3_pmc ))
. Word (b4_tacs <13) + (b4_tcos <11) + (b4_tacc <8) + (b4_tcoh <6)
+ (B4_tah <4) + (b4_tacp <2) + (b4_pmc ))
. Word (b5_tacs <13) + (b5_tcos <11) + (b5_tacc <8) + (b5_tcoh <6)
+ (B5_tah <4) + (b5_tacp <2) + (b5_pmc ))
. Word (b6_mt <15) + (b6_trcd <2) + (b6_scan ))
. Word (b7_mt <15) + (b7_trcd <2) + (b7_scan ))
. Word (refen <23) + (trefmd <22) + (Trp <20) + (TRC <18) + (TCHR <16) + refcnt)
. Word 0xb2/* refresh control register */
. Word 0x30/* banksize register: burst mode */
. Word 0x30/* SDRAM mode register */

. Align 2
. Global call_main // call the main function. All function parameters are 0.
Call_main:
LDR sp, stack_start
MoV FP, #0/* no previous frame, so fp = 0 */
MoV A1, #0/* Set argc to 0 */
MoV A2, #0/* Set argv to NUL */
BL main/* Call Main */
Stack_start:
. Word stack_base
Undefined_instruction:
Software_interrupt:
Prefetch_abort:
Data_abort:
Not_used:
IRQ:
FIQ:
/* The above is the main assembly part. The clock settings are implemented, the watchdog function is set for the serial port, and the shutdown function is interrupted (if necessary, downgrading can be used), and then transferred to main */
2410init. c file
Int main (INT argc, char ** argv)
{
U32 test = 0;
// Compressed image address
Void (* Thekernel) (INT zero, Int Arch, unsigned long params_addr) =
(Void (*) (INT, Int, unsigned long) ram_compressed_kernel_base;
Int I, K = 0;
// Downpt = (ram_compressed_kernel_base );
Chkbs = (_ ram_startaddress); // The place where the SDRAM starts.
// Frompt = (flash_linuxkernel );
Mmu_enableicache ();
Changeclockdivider (); //
Changempllvalue (m_mdiv, m_pdiv, m_sdiv); // fin = 12 MHz fclk = 200 MHz
Port_init (); // set the I/O port. You must call this function before using the comport. Otherwise, the communication chip cannot obtain data.
Uart_init (pclk, 115200); // use the default 200000 value for pclk, with a dial rate of 115200.
/******************* (Check the ram space) *******************/
Uart_sendstring ("/n/tlinux S3C2410 nor bootloader/N ");
Uart_sendstring ("/n/tchecking SDRAM 2410loader. C.../N ");
For (; chkbs <0x33fa0140; chkbs = chkbs + 0x4, test ++ )//
/*
Based on my experience, it is best to increment by one byte. Our board is okay during the byte incremental detection, but an error occurs when it increments by 1 byte, 13th "1" may occur with the number of data lines, which is detected as a hardware problem. The phenomenon is as follows:
The code in the simulator is used to test the SDRAM, And the 28f128a3j flash film is not pasted at the beginning. The test results are good. However, after the flash film is installed, when the test data (data) is 0x00000400, an error occurs when the operation takes about 1 kb of memory space and is random. The error data always changes to 0x00002400, and the 10-bit and 13-bit data bus are not short-circuited. Use other data // for testing, such as 0x00000200; 0x00000800. DX help.
So far, I cannot use flash .*/

{
Chkpt1 = chkbs;
* (U32 *) chkpt1 = test; // write data
If (* (u32 *) chkpt1 = 1024) // is the data read and written the same?
{
Chkpt1 + = 4;
Led_display (1 );
Led_display (2 );
Led_display (3 );
Led_display (4 );
}
Else
Goto error;
}
Uart_sendstring ("/n/tsdram check successful! /N/tmemory maping ...");
Get_memory_map ();

// Obtain available memory information and make it a list, which will be passed to the kernel as the startup parameter.
// Memory ing refers to the address ranges allocated in the 4 GB physical address space to address the system's Ram unit.
Uart_sendstring ("/n/tmemory map successful! /N ");
/* I use the simulator to directly place the kernel and ramdisk on the SDRAM, so the following section is not required, but if the kernel and ramdisk are in the flash, then we need .*/

/******************* (Copy Linux kernel) *******************/
Uart_sendstring ("/tloading kernel image from flash.../N ");
Uart_sendstring ("/Tand copy kernel image to SDRAM at 0x31000000/N ");
Uart_sendstring ("/T/tby leijun Dong dongleijun4000@hotmail.com/N ");

// 3*1024*1024/32 Linux kernel des, SRC, length = 3 m
For (k = 0; k <196608; k ++, downpt + = 1, frompt + = 1)
* (U32 *) downpt = * (u32 *) frompt;

/******************* (Load ramdisk) *******************/
Uart_sendstring ("/T/tloading compressed ramdisk.../N ");
Downpt = (ram_compressed_ramdisk_base );
Frompt = (flash_ramdisk_base );

// 3*1024*1024/32 Linux kernel des, SRC, length = 3 m
For (k = 0; k <196608; k ++, downpt + = 1, frompt + = 1)
* (U32 *) downpt = * (u32 *) frompt;

/****** Jffs2 file system. If Flash is not used during development, do not ********/
Uart_sendstring ("/T/tloading jffs2. ../N ");
Downpt = (ram_jffs2 );
Frompt = (flash_jffs2 );
For (k = 0; k <(1024*1024/32); k ++, downpt + = 1, frompt + = 1)
* (U32 *) downpt = * (u32 *) frompt;
Uart_sendstring ("load success... run.../N ");
/******************** (Setup PARAM) *******************/
Setup_start_tag (); // start to set the startup Parameter
Setup_memory_tags (); // memory impression
Setup_commandline_tag ("console = ttys0, 115200n8"); // start the command line
Setup_initrd2_tag (); // root device
Setup_ramdisk_tag (); // ramdisk Image
Setup_end_tag ();

/* Turn Off I-Cache */
ASM ("MRC P15, 0, % 0, C1, C0, 0": "= r" (I ));
I & = ~ 0x1000;
ASM ("MCR P15, 0, % 0, C1, C0, 0": "R" (I ));
/* Flush I-Cache */
ASM ("MCR P15, 0, % 0, C7, C5, 0": "R" (I ));

// The following line jumps to the first address of the compressed kernel.
Thekernel (0, arch_number, (unsigned long *) (ram_boot_params ));

/* When the kernel is started, I-Cache can be turned on or off. R0 must be 0 and R1 must be the CPU model.
(This can be found in Linux/ARCH/ARM/tools/Mach-types). R2 must be the physical start address of the parameter */
/******************* End *******************/
Error:
Uart_sendstring ("/n/npanic SDRAM check error! /N ");
Return 0;
}

Static void setup_start_tag (void)
{
Params = (struct tag *) ram_boot_params; // address at which the startup parameter starts
Params-> HDR. Tag = atag_core;
Params-> HDR. size = tag_size (tag_core );
Params-> U. Core. Flags = 0;
Params-> U. Core. pagesize = 0;
Params-> U. Core. rootdev = 0;
Params = tag_next (Params );
}

Static void setup_memory_tags (void)
{
Int I;

For (I = 0; I <num_mem_areas; I ++ ){
If (memory_map [I]. Used ){
Params-> HDR. Tag = atag_mem;
Params-> HDR. size = tag_size (tag_mem32 );
Params-> U. mem. Start = memory_map [I]. Start;
Params-> U. mem. size = memory_map [I]. Len;
Params = tag_next (Params );
}
}
}

Static void setup_commandline_tag (char * CommandLine)
{
Int I = 0;
/* Skip non-existent command lines so the kernel will still
* Use its default command line.
*/
Params-> HDR. Tag = atag_cmdline;
Params-> HDR. size = 8;
// Console = ttys0, 115200n8
Strcpy (Params-> U. marshline. marshline, P );
Params = tag_next (Params );
}

Static void setup_initrd2_tag (void)
{
/* An atag_initrd node tells the kernel where the compressed
* Ramdisk can be found. atag_rdimg is a better name, actually.
*/
Params-> HDR. Tag = atag_initrd2;
Params-> HDR. size = tag_size (tag_initrd );
Params-> U. initrd. Start = ram_compressed_ramdisk_base;
Params-> U. initrd. size = 2047; // K byte
Params = tag_next (Params );
}

 

Static void setup_ramdisk_tag (void)
{
/* An atag_ramdisk node tells the kernel how large
* Decompressed ramdisk will become.
*/
Params-> HDR. Tag = atag_ramdisk;
Params-> HDR. size = tag_size (tag_ramdisk );
Params-> U. ramdisk. Start = ram_decompressed_ramdisk_base;
Params-> U. ramdisk. size = 7.8*1024; // K byte
Params-> U. ramdisk. Flags = 1; // automatically load ramdisk
Params = tag_next (Params );
}

Static void setup_end_tag (void)
{
Params-> HDR. Tag = atag_none;
Params-> HDR. size = 0;
} Void uart_init (INT pclk, int baud) // the serial port is very important.
{
Int I;
If (pclk = 0)
Pclk = pclk;
Rufcon0 = 0x0; // UART channel 0 FIFO control register, FIFO disable
Rumcon0 = 0x0; // UART chaneel 0 modem control register, AFC disable

// Uart0
Rulcon0 = 0x3; // line control register: normal, no parity, 1 stop, 8 bits
The following section of Samsung is not quite correct, but I calculated it as 0x245 according to normal, no parity, 1 stop, and 8 bits.

// [10] [9] [8] [7] [6] [5] [4] [3: 2] [1:0]
// Clock Sel, TX int, RX int, RX time out, RX err, loop-back, send break, transmit mode, receive mode
// 0 1 0, 0 1 0 0, 01 01
// Pclk Level Pulse disable generate normal interrupt or polling
Rucon0 = 0x245; // control register
Rubrdiv0 = (INT) (pclk/16./baud)-1); // baud rate divisior register 0
Delay (10 );
}

After the above ups and downs, the next step is the kernel activity. it depends on the level of kernel compilation. this bootloader does not require interactive information like blob. It uses virtual addresses, which are very concise and clear in general.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.