ARM Linux Booting Process

We will look at  boot process of linux  kernel on AT91RM9200 system-on-chip, built around the ARM920T ARM Thumb processor. Kwickbyte builds an embedded board called kb9202 based on AT91RM9200. We will take this board as an example and see how Linux boots up on this board.

Before you start reading this you need to read AT91RM9200 data sheet (specification). You can download the data sheet with the following link.

www.keil.com/dd/docs/datashts/atmel/at91rm9200_ds.pdf

You also need to read ARM Architecture Reference Manual for better understanding the boot process.You can download it with the following link.

http://www.lysator.liu.se/~kjell-e/embedded/ARM-ARM.pdf

Components in Boot Process:

Linux boot sequence involves execution of  the following components.

  1.             Boot-loader initialization
  2.             Kernel initialization
  3.             User space initialization

Boot-loader:

A boot-loader is a small program which will load the kernel image into RAM and boots up the kernel image. This is also called bootstrap as it brings(pulls) up system by loading an operating system.  Boot-loader starts before any other software starts and initializes the processor and makes CPU ready to execute a program like an operating system. Most processors have a default address from which the first bytes of code are fetched upon power is applied or board is reset. Hardware designers use this information to store the boot-loader code at that address in ROM or flash. Since it should initialize the cpu and should run a program which is located at architecture specific address boot-loaders are highly processor specific and board specific.  Every embedded board comes with a bootstrap to download the kernel image or standalone application into the board and start executing the kernel image or application.  Boot-loader will be executed when power is applied to a processor board. Basically it will have some minimal features to load the image and boot it up.

It is also possible to control the system using a hardware debug interface such as J TAG. This interface may be used to write the boot loader program into boo-table non-volatile memory (e.g. flash) by instructing the processor core to perform the necessary actions to program non-volatile memory.  Generally done for first time to download the basic boot-loader and for some recovery process. J TAG is a standard and popular interface provided by many board vendors. Some micro-controllers provide special hardware interfaces which can’t be used to take arbitrary control of  a system or directly run code, but instead they allow the insertion of boot code into boot-able non-volatile memory (like flash memory) via simple protocols. Then at the manufacturing phase, such interfaces are used to inject boot code (and possibly other code) into non-volatile memory. After system reset, the micro-controller begins to execute code programmed into its non-volatile memory, just like usual processors are using ROM’s for booting. In many cases such interfaces are implemented by hardwired logic. In other cases such interfaces could be created by software running in integrated on-chip boot ROM from GPIO pins.

There are some other third party boot-loaders available which provide rich set of features and easy user interface. You can download these third party boot-loaders into board and can make them default boot-loaders for your board. Generally boot-loaders provided by board vendors are replaced with these third party boot-loader.  There are a quite few third party boot-loader available and some of them are open source (or free boot-loaders) and some are commercial. Some of them are Das U-Boot, Red boot, GRUB (for desktops),   LILO , Loadlin, , bootsect-loader, SYSLINUX,  EtherBoot, ELILO.

We will look at U-boot boot-loader . U-boot is the widely used boot-loader in embedded systems. I will explain code from the u-boot-2010.03  source. You can download U-boot from the following site.

http://www.denx.de/wiki/U-Boot

How U-boot is built:

————————-

 Based on the configuration of U-boot, all the assembly files (.S) and C files (.c) are compiled using  cross compiler which is built for a particular architecture and object files(.o) will be generated. All these object files are linked by linker and an executable file will be created. An object file or executable file is a collection of sections like .text, .data, .bss etc.  Object files and executable files have a file format like elf.  All the sections of the object files will be arranged in the executable file based on a script called linker script. This script tells where all the sections are to be loaded in the memory when it runs. Understanding this script is very important to know how boot-loader and kernel are composed and how different sections of boot-loader or kernel are loaded in the memory.

Generally,  when a program is run (executed) a loader reads executable file and loads different sections of the executable file in the specified memory location and starts executing the start function(entry point) specified in the linker script. But, if you want to run(load) a boot-loader there will not be any loader to load(basically to understand the file format) different sections of executable file into the memory.  Then you need to use a tool called objcopy which will take all sections from the executable file and create a binary file which doesn’t have any file format. This binary file can be loaded into the memory and executed or can be written in to the ROM at a particular address (specific to the architecture) which will be executed by CPU when power is applied to the board. You can find good tutorial on linker script in the following location.

http://www.redhat.com/docs/manuals/enterprise/RHEL-4-Manual/gnu-linker/scripts.html

File: cpu/arm920t/u-boot.lds

 32 OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
 33 OUTPUT_ARCH(arm)
 34 ENTRY(_start)
 35 SECTIONS
 36 {
 37         . = 0x00000000;
 38 
 39         . = ALIGN(4);
 40         .text :
 41         {
 42                 cpu/arm920t/start.o     (.text)
 43                 *(.text)
 44         }
 45 
 46         . = ALIGN(4);
 47         .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }
 48 
 49         . = ALIGN(4);
 50         .data : { *(.data) }
 51 
 52         . = ALIGN(4);
 53         .got : { *(.got) }
 54 
 55         . = .;
 56         __u_boot_cmd_start = .;
 57         .u_boot_cmd : { *(.u_boot_cmd) }
 58         __u_boot_cmd_end = .;
 59 
 60         . = ALIGN(4);
 61         __bss_start = .;
 62         .bss (NOLOAD) : { *(.bss) . = ALIGN(4); }
 63         _end = .;
 64 }

OUTPUT_FORMAT in line #32 specify the file format of the executable file. Here the executable   file format is elf32 and endianness is little endian.  OUTPUT_ARCH in line # 33 specify the architecture on which this code runs. ENTRY in line #34 specifies the start function(entry point) of  u-boot program.  Here the entry point is _start. SECTIONS in line #35 defines how different sections are mapped in the executable file. Loader uses the addresses specified in this section to load different section of the program into the memory.  ‘.’ in the line #37 specifies the start address where the following sections should be loaded. In this case start address is 0x00000000. After this in line #39 the memory is aligned by 4 bytes and the .text section follows in the line #40.

 40    .text :
 41         {
 42                 cpu/arm920t/start.o     (.text)
 43                 *(.text)
 44         }

At the ‘.’ position (0x00000000) the code in the cpu/arm920t/start.o is mapped and follows the code that is there in .text sections of all other object (.o) files. cpu/arm920t/start.o contains the _start() function(in assembly language) which is entry point of this program.

Now the ‘.’ will be at 0x00000000 + sizeof (.text).  Again memory is aligned by 4 bytes and .rodata section follows in line #47.

. = ALIGN(4);

 47         .rodata : { *(SORT_BY_ALIGNMENT(SORT_BY_NAME(.rodata*))) }

.rodata sections from all objects files are mapped at this address. Follows the .data and  .git sections.

 49         . = ALIGN(4);
 50         .data : { *(.data) }
 51 
 52         . = ALIGN(4);
 53         .got : { *(.got) }

Each U-boot command is an object of type ‘cmd_tbl_t’ which contains command name, help string  and function pointer to be executed when this command is run. All these command objects are placed in the memory sequentilly. Each of this command object is built into an U-boot defined section called  .u_boot_cmd in the object file.  These all .u_boot_cmd  sections are placed in the memory after the above sections(.data and .git).

 

. = .;
 56         __u_boot_cmd_start = .;
 57         .u_boot_cmd : { *(.u_boot_cmd) }
 58         __u_boot_cmd_end = .;

__u_boot_cmd_start contains the start of the commands objects and __u_boot_cmd_end contains the end of the command objects. And next follows the .bss (uninitialized global variables) sections.

 60         . = ALIGN(4);
 61         __bss_start = .;
 62         .bss (NOLOAD) : { *(.bss) . = ALIGN(4); }
 63         _end = .;

__bss_start points to the .bss start address and _end contains the end of the all sections.

Using this linker script linker will generate an executable file called u-boot. Objcopy tool is used to generate a binary file from the u-boot executable file

u-boot.bin:   u-boot

$(OBJCOPY) ${OBJCFLAGS} -O binary $< $@

U-boot binary file will be  copied to the board RAM or written in the flash disk. At91rm9200 board comes with a boot programmer  A tiny program called atmel bootstrap that can be used to download the image into flash or RAM and start execute or to reset a corrupted board. U-boot will be copied to flash disk or internal RAM( if it is less size) and will be downloaded to RAM and will be executed when the board power is applied to the board. For this board(at91rm9200) the code is always downloaded from device address 0x0000_0000 to the address 0x0000_0000 of the SRAM after remap. That ‘s why we have given the start address of the .text section as 0x00000000.  If you want to load the code any where in the RAM and want to execute U-boot you need to build you code as position independent code(PIC).   Then the instructions addresses will be offset into to PC(cpu register) value. So the downloaded code must be position-independent or  linked at address 0x0000_0000. For our explanation purpose  assume that U-boot code is linked at 0x00000000 and the Boot program downloaded U-boot from the data flash(Check AT91RM9200 spec for downloading process))  and call entry point into the U-boot.

U-boot Execution:

———————-

As specified in the linker script U-boot starting function(entry point) will be _start():cpu/arm920t/start.S.

File: cpu/arm920t/start.S

_start is written in assembly language. First instuction executed by _start() is a call to start_code:cpu/arm920t/start.S.

.globl _start

	_start: b       start_code

start_code: Will perform the following tasks.

1) Set the cpu in supervisor mode. The current operating processor status is in the Current Program Status Register (CPSR). The CPSR holds:

• four ALU flags (Negative, Zero, Carry, and Overflow),

• two interrupt disable bits (one for each type of interrupt (FIQ and IRQ),

• one bit to indicate ARM or Thumb execution

• five bits to encode the current processor mode

Check ARM processor data sheet for more details on the CPSR register.

        /*
         * set the cpu to SVC32 mode
         */

        mrs     r0, cpsr
             // Load cpsr register into r0 
        bic     r0, r0, #0x1f      // Clear first 5 bits of cpsr(encode processor mode).
        orr     r0, r0, #0xd3      // supervisor mode (10011) + disable all interupts (110)
        msr     cpsr, r0             // write r0 to cpsr

Try to relocate the U-boot code to RAM if we are running from flash.

#ifndef CONFIG_SKIP_RELOCATE_UBOOT
relocate:                               /* relocate U-Boot to RAM           */
        adr     r0, _start              /* r0 <- current position of code   */
        ldr     r1, _TEXT_BASE          /* test if we run from flash or RAM */
        cmp     r0, r1                  /* don't reloc during debug         */
        beq     stack_setup

        ldr     r2, _armboot_start     /* _armboot_start defined 
                                                     * as _start  at line #436 in cpu/arm920t/start.S
                                                     */
        ldr     r3, _bss_start          /* _bss_start is defined in the linker script
        sub     r2, r3, r2              /* r2 <- size of armboot (find out size of all sections) */
        add     r2, r0, r2              /* r2 <- source end address        */

copy_loop:
        ldmia   r0!, {r3-r10}           /* copy from source address [r0]    */
        stmia   r1!, {r3-r10}           /* copy to   target address [r1]    */
        cmp     r0, r2                  /* until source end addreee [r2]    */
        ble     copy_loop
#endif  /* CONFIG_SKIP_RELOCATE_UBOOT */

Setup the stack now. U-boot code has been relocated to _TEXT_BASE(Defined in board/kb9202/configs.mk). Stack will be setup below this address.

stack_setup:

        ldr     r0, _TEXT_BASE          /* Load r0 with _TEXT_BASE */

        sub     r0, r0, #CONFIG_SYS_MALLOC_LEN  /* r0 = 
                                                                                       * r0 – CONFIG_SYS_MALLOC_LEN)         
                                                                                       * malloc area
                                                                                       */

        sub     r0, r0, #CONFIG_SYS_GBL_DATA_SIZE /* r0 = 
                                                                                           *r0 - CONFIG_SYS_GBL_DATA_SIZE        
                                                                                           * (Contains an object of board data)

Point stack pointer sp  to r0.

sub     sp, r0, #12             /* sp = r0 – 12 (leave 3 words(12 bytes) for abort-stack */

clear_bss:

        ldr     r0, _bss_start          /* load start of bss segment into r0.
                                                  * _bss_start defined in linker script */

        ldr     r1, _bss_end            /* load end of bss segment into r1.
                                                   * _bss_end defined in linker script       */

        mov     r2, #0x00000000         /* 0 will be written in the bss memory  */   

clbss_l:str     r2, [r0]                /* store 0's in 4 bytes at r0 ( r0 = r2) */

        add     r0, r0, #4                /* move 4 bytes ahead,  r0 = r0 + 4 */
        cmp     r0, r1                     /*  compare r0 and r1 */
        ble     clbss_l                   /* if ( r0 <= r1) then goto clbss_l; */

Call  start_armboot(), a C function.

ldr     pc, _start_armboot

        _start_armboot: .word start_armboot

start_armboot() function is defined in lib_arm/board.c which is common function for all arm based boards. The following are the tasks performed by start_armboot().

void start_armboot(void):

1)  Allocate memory for a global data structure gd_t. This is defined in include/asm-arm/global_data.h. When we setup the stack we left some space for gd_t data structure ( CONFIG_SYS_GBL_DATA_SIZE) below the _TEXT_BASE where U-boot has been relocated.

gd = (gd_t*)(_armboot_start - CONFIG_SYS_MALLOC_LEN – sizeof(gd_t));
 memset ((void*)gd, 0, sizeof (gd_t));

2)  Some information like architecture number (unique board id), boot params that have to be passed to kernel image and baud rate etc are stored in a data structure called bd_t whose pointer is stored in gd_t. Allocate memory for this bd_t after the gd_t.

gd->bd = (bd_t*)((char*)gd - sizeof(bd_t));

memset (gd->bd, 0, sizeof (bd_t));

3)  Call a set of functions which initialize all subsystems.

for (init_fnc_ptr = init_sequence; *init_fnc_ptr; ++init_fnc_ptr) {

              if ((*init_fnc_ptr)() != 0) {

                      hang ();

                }
            }

init_sequence is an array of function pointers defined in lib_arm/board.c. The above loop takes each function pointer and calls it.

The following are some of important functions called.

board_init():

int board_init (void)

	{

        		/* Enable Ctrlc */

      		  console_init_f ();

      		  /* memory and cpu-speed are setup before relocation */

        		/* so we do _nothing_ here */

        		gd->bd->bi_arch_number = MACH_TYPE_KB9200;

        		/* adress of boot parameters */

        		gd->bd->bi_boot_params = PHYS_SDRAM + 0x100;

        		return 0;

	}

This is board specific function and should definitely be defined by each board. This function should some board specific initialization if there are any. When you are porting u-boot to a new board you must define this function. For kb9202 board this function is defined in board/kb9202/kb9202.c. This function just sets its board number and tells where the boot params for Linux are stored.

timer_init():

          This is a cpu specific function and each cpu code must define it. It should basically initilize the timer services in the cpu.  timer_init() for  AT91RM9200 cpu defined in cpu/arm920t/at91rm9200/timer.c.

init_baudrate():

This architecture specific function defines the default baud rate for the serial port communication. For ARM this function is defined in  lib_arm/board.c.

static int init_baudrate (void)

{

        char tmp[64];   /* long enough for environment variables */

        int i = getenv_r ("baudrate", tmp, sizeof (tmp));

        gd->bd->bi_baudrate = gd->baudrate = (i > 0)

                        ? (int) simple_strtoul (tmp, NULL, 10)

                        : CONFIG_BAUDRATE;

        return (0);

}

If the ‘baudrate’ enviroment varable is set baud rate is taken from that, otherwise, taken from the CONFIG_BAUDRATE macro defined in board specific header file include/configs/kb9202.h.

serial_init():

  This is a common function called to setup the serial port. This function internally calls cpu or board specific serial_init() function

int serial_init (void)          
	{       
        		if (!(gd->flags & GD_FLG_RELOC) || !serial_current) {
                		struct serial_device *dev = default_serial_console ();

                		return dev->init ();
         		}

        		return serial_current->init ();
	}

As of now we did not relocate the u-boot code to ram and is running from flash.

GD_FLG_RELOC will be set when the u-boot is relocated to RAM. serial_current will point to an object of type struct serial_device of current serial device that we are using. As we have not yet initialized any serial device serial_current will be NULL. default_serial_console() is a function which points to __default_serial_console() defined in common/serial.c.

                 console_init_f,         /* stage 1 init of console */

     display_banner,         /* say that we are here */

	     dram_init,              /* configure available RAM banks */

            Based on configurations the following functions are also called.
	      #if defined(CONFIG_ARCH_CPU_INIT)

        		arch_cpu_init,          /* basic arch cpu dependent setup */

	      #endif

 		board_init,             /* basic board dependent setup */

	      #if defined(CONFIG_USE_IRQ)

        		interrupt_init,         /* set up exceptions */

	      #endif

 	      #if defined(CONFIG_HARD_I2C) || defined(CONFIG_SOFT_I2C)

                       init_func_i2c,

	      #endif

4) Initialize NAND if configured.

#if defined(CONFIG_CMD_NAND)

        puts ("NAND:  ");

        nand_init();            /* go init the NAND */

#endif

nand_init() function is defined in drivers/mtd/nand/nand.c

5) Initialize if dataflash is configured

#ifdef CONFIG_HAS_DATAFLASH
AT91F_DataflashInit();

        dataflash_print_info();

#endif

6) Get all  input and output devices list.

stdio_init ();  /* get the devices list */
stdio_init() is defined in common/stdio.c.

7) Call console_init_r () to setup the console info like where the input(stdin) should be taken and where the output(stdout) to be returned.

console_init_r ();      /* fully init console as a device */

8)  Enable the interrupts

enable_interrupts ();

enable_interrupt() for arm boards is defined in lib_arm/interrupts.c. Writing 0x80 into cpsr register will enable the interrupts. enable_interrupts() is the following assembly code.

__asm__ __volatile__("mrs %0, cpsrn"

                             "bic %0, %0, #0x80n"

                             "msr cpsr_c, %0"

                             : "=r" (temp)

                             :

                             : "memory");

9) Get the ‘loadaddr’ environment variable defined in u-boot through setenv. Kernel image is loaded at the load address.

        /* Initialize from environment */

if ((s = getenv ("loadaddr")) != NULL) {

                load_addr = simple_strtoul (s, NULL, 16);

}

10) All initialization has been done, now enter a main loop where we accept commands from the user and execute them.

 /* main_loop() can return to retry autoboot, if so just run it again. */

        for (;;) {

                main_loop ();

        }

main_loop() function is defined in common/main.c. This function is common for all boards

When the U-boot is booting up it will take a default command that is given in the U-boot enviroment variable bootcmd and executes that command. If this variable is not defined U-boot will provide U-boot prompt where user can enter the commands. U-boot can be made to go to its  boot prompt even if the bootcmd is defined by giving some bootdelay. U-boot waits until the bootdelay expires or user presses any key. If user does not press any key U-boot will execute the default command defined in bootcmd else U-boot goes to its prompt.

void main_loop (void)
{
 #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)

 s = getenv ("bootdelay");

 bootdelay = s ? (int)simple_strtol(s, NULL, 10) : CONFIG_BOOTDELAY;

       debug ("### main_loop entered: bootdelay=%dnn", bootdelay);

       s = getenv ("bootcmd");

       debug ("### main_loop: bootcmd="%s"n", s ? s : "<UNDEFINED>");

       if (bootdelay >= 0 && s && !abortboot (bootdelay)) {

       run_command (s, 0);
 }

If U-boot is interrupted by user by pressing a key U-boot enters into an infinite while loop and accepts user commands and executes them.

        for (;;) {

		/* Read the command */
		len = readline (CONFIG_SYS_PROMPT);
                  if (len > 0)
                        strcpy (lastcommand, console_buffer);

                   /* Execute the command */
		rc = run_command (lastcommand, flag);
        }

CONFIG_BOOTDELAY is the number of seconds the u-boot should wait for user interrupt before it takes its default action. CONFIG_BOOTDELAY  is defined in the  board specific file header file, in this case in include/configs/kb9202.h.

As we have explained each U-boot command is an object of type struct cmd_tbl_s. The command name and function to be executed will be stored in this structure. All commands structures are kept in memory at perticular memroy,  __u_boot_cmd_start and __u_boot_cmd_end contain start and end address of this memory section called command table.

run_command() function takes the command name and finds the data structure belonging to this command in the command table and calls the corresponding command function. Lets assume that bootcmd  is not configured and U-boot provided the prompt to enter commands. And also assume that the linux image is loaded into the ram in a perticular address using tftpboot command or using some ymodem command and bootm command is given at the U-boot prompt.

kb-9202# bootm 0x00280000

The function do_bootm() that is responsible for executing bootm command. bootm  loads kernel image.

kernel image (zImage) decompression

    arch/arm/boot/compressed/head.S: start (108)
        First code executed, jumped to by the bootloader, at label "start" (108)
        save contents of registers r1 and r2 in r7 and r8 to save off architecture ID and atags pointer passed in by bootloader (118)
        execute arch-specific code (inserted at 146)
            arch/arm/boot/compressed/head-xscale.S or other arch-specific code file
            added to build in arch/arm/boot/compressed/Makefile
            linked into head.S by linker section declaration:  .section “start”
            flush cache, turn off cache and MMU
        load registers with stored parameters (152)
            sp = stack pointer for decompression code (152)
            r4 = zreladdr = kernel entry point physical address
        check if running at link address, and fix up global offset table if not (196)
        zero decompression bss (205)
        call cache_on to turn on cache (218)
            defined at arch/arm/boot/compressed/head.S (320)
            call call_cache_fn to turn on cache as appropriate for processor variant
                defined at arch/arm/boot/compressed/head.S (505)
                walk through proc_types list (530) until find corresponding processor
                call cache-on function in list item corresponding to processor (511)
                    for ARMv5tej core, cache_on function is __armv4_mmu_cache_on (417)
                        call setup_mmu to set up initial page tables since MMU must be on for cache to be on (419)
                        turn on cache and MMU (426)
        check to make sure won't overwrite image during decompression; assume not for this trace (232)
        call decompress_kernel to decompress kernel to RAM (277)
        branch to call_kernel (278)
            call cache_clean_flush to flush cache contents to RAM (484)
            call cache_off to turn cache off as expected by kernel initialization routines (485)
            jump to start of kernel in RAM (489)
                jump to address in r4 = zreladdr from previous load
                    zreladdr = ZRELADDR = zreladdr-y
                    zreladdr-y specified in arch/arm/mach-vx115/Makefile.boot

ARM-specific kernel code

    arch/arm/kernel/head.S: stext (72)
        call __lookup_processor_type (76)
            defined in arch/arm/kernel/head-common.S (146)
            search list of supported processor types __proc_info_begin (176)
                kernel may be built to support more than one processor type
                list of proc_info_list structs 
                    defined in arch/arm/mm/proc-arm926.S (467) and other corresponding proc-*.S files
                    linked into list by section declaration:  .section ".proc.info.init"
            return pointer to proc_info_list struct corresponding to processor if found, or loop in error if not
        call __lookup_machine_type (79)
            defined in arch/arm/kernel/head-common.S (194)
            search list of supported machines (boards)
                kernel may be built to support more than one board
                list of machine_desc structs 
                    machine_desc struct for boards defined in board-specific file vx115_vep.c
                    linked into list by section declaration that's part of MACHINE_DESC macro
            return pointer to machine_desc struct corresponding to machine (board)
        call __create_page_tables to set up initial MMU tables (82)
        set lr to __enable_mmu, r13 to address of __switch_data (91, 93)
            lr and r13 used for jumps after the following calls
            __switch_data defined in arch/arm/kernel/head-common.S (15)
        call the __cpu_flush function pointer in the previously returned proc_info_list struct (94)
            offset is #PROCINFO_INITFUNC into struct
            this function is __arm926_setup for the ARM 926EJ-S, defined in arch/arm/mm/proc-arm926.S (392)
                initialize caches, writebuffer
                jump to lr, previously set to address of __enable_mmu
        __enable_mmu (147)
            set page table pointer (TTB) in MMU hardware so it knows where to start page-table walks (167)
            enable MMU so running with virtual addresses (185)
            jump to r13, previously set to address of __switch_data, whose first field is address of __mmap_switched
                __switch_data defined in arch/arm/kernel/head-common.S (15)

    arch/arm/kernel/head-common.S: __mmap_switched (35)
        copy data segment to RAM (39)
        zero BSS (45)
        branch to start_kernel (55)

Processor-independent kernel code

init/main.c: start_kernel (456)

Tags: , , ,

6 Responses to “ARM Linux Booting Process”

  1. kesineedi says:

    very useful…

    Current score: 0
  2. Naveen says:

    good work, but would like to suggest that summarizing stuff makes learning faster , detailed commenting of code makes it boring, and also takes away fun in learning.

    Current score: 0
  3. Elias says:

    Top post. I look forward to reading more. Cheers

    Current score: 0
  4. harish says:

    Howdy=) I simply love this blog ! The post is surely relevant. I was surfing the internet for quite a long time thanks

    Current score: 0
  5. Kevin says:

    I am very enjoyed for this blog. Its an informative topic. It help me very much to solve some problems. Its opportunity are so fantastic and working style so speedy. I think it may be help all of you. Thanks.

    Current score: 0
  6. Elizabeth says:

    Hi,I’ve been a participant around your blog for a few months. I love this article and your entire site! Looking forward to reading more!

    Current score: 0

Leave a Reply