lk–kernel for android startup

lk–kernel started by android

Pre-loader runs in ISRAM. After the initialization of DRAM is completed, lk is loaded into DRAM, and finally jumps to the entrance of lk through a special sys call method, and officially enters the lk initialization stage

lk execution entry

Located in the section (section) of .text.boot, the specific definition location is:

./lk/arch/arm/system-onesegment.ld:10: .text.boot : {<!-- --> *(.text.boot) }
./lk/arch/arm/system-twosegment.ld:10: .text.boot : {<!-- --> *(.text.boot) }

The code execution entry of this section is crt0.S file, the location is

./lk/arch/arm/crt0.S

The source code is as follows:

.section ".text.boot"
 
...
 
.Lstack_setup:
/* ==set up the stack for irq, fi==q, abort, undefined, system/user, and lastly supervisor mode */
mrs r0, cpsr
bic r0, r0, #0x1f
 
ldr r2, = abort_stack_top
orr r1, r0, #0x12 // irq
msr cpsr_c, r1
ldr r13, =irq_save_spot /* save a pointer to a temporary dumping spot used during irq delivery */
\t    
orr r1, r0, #0x11 // fiq
msr cpsr_c, r1
mov sp, r2
\t            
orr r1, r0, #0x17 // abort
msr cpsr_c, r1
mov sp, r2
\t    
orr r1, r0, #0x1b // undefined
msr cpsr_c, r1
mov sp, r2
\t    
orr r1, r0, #0x1f // system
msr cpsr_c, r1
mov sp, r2
 
orr r1, r0, #0x13 // supervisor
msr cpsr_c, r1
mov sp, r2
...
 
bl kmain

ctr0.S Summary: The main thing to do here is to create stacks in various modes such as fiq/irq/abort, initialize the vector table, and then switch to the management mode (pre-loader runs on EL3, lk runs on EL1), and finally jumps to The C code entry kmain is executed.

kmain function:

void kmain(void)
{<!-- -->
boot_time = get_timer(0);
 
/* Initialize the context of the thread pool early, including the establishment of the run queue, thread list, etc.,
The lk architecture supports multi-threading, but only one CPU is online at this stage, so there is only one code execution path.
*/
thread_init_early();
 
/* Architecture initialization, including DRAM, MMU initialization enable, enable coprocessor,
The preloader runs in ISRAM, which belongs to the physical address, and lk runs in DRAM, and you can choose to turn on or off the MMU. Turning on the MMU can speed up the loading process of lk.
*/
arch_early_init();
 
/*
Early initialization of platform hardware, including irq, timer, wdt, uart, led, pmic, i2c, gpio, etc.,
Initialize the platform hardware and establish the basic operating environment of lk.
*/
platform_early_init();
 
boot_time = get_timer(0);
 
// This is a reserved empty function.
target_early_init();
 
dprintf(CRITICAL, "welcome to lk\
\
");
\t
/*
Execute the constructor defined in the description section of system-onesegment.ld, the specific mechanism is not clear:
__ctor_list = .;
.ctors : { *(.ctors) }
__ctor_end = .;
*/
call_constructors();
 
//Kernel heap linked list context initialization, etc.
heap_init();
 
// Thread pool initialization, provided that PLATFORM_HAS_DYNAMIC_TIMER needs to be supported.
thread_init();
 
// What is the dpc system? It is said that it is something similar to work_queue. It is not clear what the abbreviation of dpc is.
dpc_init();
 
// Initialize the kernel timer
timer_init();
 
    // Create a system initialization worker thread, execute app initialization, lk regards the business part as an app.
thread_resume(thread_create("bootstrap2", & bootstrap2, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
    thread_resume(thread_create("iothread", &iothread, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
 
// Enable interrupts.
exit_critical_section();
 
// become the idle thread
thread_become_idle();
}

Summary of kmain:

  • Initialize thread pool, establish thread management linked list, run queue, etc.
  • Initialize various platform hardware, including irq, timer, wdt, uart, led, pmic, i2c, gpio, etc., and establish the basic operating environment of lk.
  • Initialize the kernel heap, kernel timer
  • Create the system initialization main thread, enter bootstrap2 execution, enable interrupt, and the current thread enters idle
boostrap analysis
static int bootstrap2(void *arg)
{<!-- -->
...
arch_init: empty function
/*
  Platform-related initialization, including nand/emmc, LCM display driver, boot mode selection, loading logo resources,
  The specific code flow is shown in the sequence diagram below.
*/
platform_init();
...
   target_init: empty function
/*
  App initialization, jump to the mt_boot_init entry to start execution, corresponding to the ".apps" section.
*/
apps_init();
 
return 0;
}

The jump mechanism of apps_init here is a bit special:

extern const struct app_descriptor __apps_start;
extern const struct app_descriptor __apps_end;
void apps_init(void)
{<!-- -->
const struct app_descriptor *app;
 
/* What exactly is done here? How to jump to the mt_boot_init entry? a bit incomprehensible
Traversing in turn from __apps_start to __apps_end What is it?
*/
for (app = & amp;__apps_start; app != & amp;__apps_end; app ++ ) {<!-- -->
if (app->init)
app->init(app);
}
 
...
}

Where is this __apps_start and __apps_end defined? It is how it happened? Here we need to know a little bit about compiling and linking principles and memory layout. This actually refers to the start & amp; end address range of a read-only data segment in memory, which is defined in this file:

/lk/arch/arm/system-onesegment.ld:47: __apps_start = .;
 
.rodata : {<!-- -->
...
. = ALIGN(4);
__apps_start = .;
KEEP (*(.apps))
__apps_end = .;
. = ALIGN(4);
__rodata_end = .;
}

The mem address range is [__apps_start, __apps_end], Obviously the range is the content of the “.apps” section. So where is this section initialized? Continue reading:

./lk/app/mt_boot/mt_boot.c:1724:
 
APP_START(mt_boot)
.init = mt_boot_init,

Expand APP_START:

#define APP_START(appname) struct app_descriptor _app_##appname __SECTION(".apps") = {<!-- --> .name = #appname,
#define APP_END };

It is obvious here that the compiling and linking system will record the address of mt_boot_init in the “.apps” section! So what the following code is going to do is very clear. After executing app->init(app), it is equivalent to calling the void mt_boot_init(const struct app_descriptor *app) function.

for (app = & amp;__apps_start; app != & amp;__apps_end; app ++ ) {<!-- -->
if (app->init)
app->init(app);
}

bootstrap2 function summary

Platform-related initialization, including nand/emmc, display related drivers, boot mode selection, load logo resources, check whether it is in DA mode, check whether there is KE information in the partition, if there is KE information, load it from the partition to DRAM, and light up Backlight, display logo, disable I/D-cache and MMU, jump to DA, configure the size of the secondary cache to obtain bat voltage, judge whether the battery is low, whether to display the charging logo, in short, this function does many things, compare the timing diagram Be clear and just describe the specific details.

Jump to the mt_boot_init function, the corresponding .app section, and the relevant mechanism has been described in detail above.

mt_boot_init analysis
void mt_boot_init(const struct app_descriptor *app)
{<!-- -->

/* Get the serial number string */
key = get_devinfo_with_index(13);
key = (key << 32) | (unsigned int)get_devinfo_with_index(12);
\t
set_serial_num();
/* Obtain the product sn number from a specific partition, if the acquisition fails, use the default value DEFAULT_SERIAL_NUM */
#ifdef SERIAL_NUM_FROM_BARCODE The stuff here is called in set_serial_num
ser_len = read_product_info(tmp);
if (ser_len == 0) {<!-- -->
ser_len = strlen(DEFAULT_SERIAL_NUM);
strncpy(tmp, DEFAULT_SERIAL_NUM, ser_len);
}
memset( sn_buf, 0, sizeof(sn_buf));
strncpy( sn_buf, tmp, ser_len);
sn_buf[SN_BUF_LEN] = '\0';
surf_udc_device.serialno = sn_buf;
#endif

g_boardid = target_get_boardid();
g_hwid = target_get_hwid();
 
    /* chip code */
chip_code = board_machtype();

/* mtk platform does not support fastboot by default */
if (g_boot_mode == FASTBOOT)
goto fastboot;
 
/* secure boot related */
#ifdef MTK_SECURITY_SW_SUPPORT
#if MTK_FORCE_VERIFIED_BOOT_SIG_VFY
g_boot_state = BOOT_STATE_RED;
#else
if (0 != sec_boot_check(0)) {<!-- -->
g_boot_state = BOOT_STATE_RED;
}
#endif
#endif
 
/* There are more things to do here, follow up g_boot_mode to choose various boot modes, for example:
normal, factory, fastboot, recovery, etc., and then find (decompress) from the boot.img partition in the ROM
The address loader of ramdisk and zImage is loaded to a specific address in DRAM, and the kernel is finally loaded to the address in DRAM
(DRAM_PHY_ADDR + 0x8000) == 0x00008000.
read the data of boot (size = 0x811800)
*/
boot_linux_from_storage();
 
fastboot:
target_fastboot_init();
if (!usb_init)
/*Hong-Rong: wait for porting*/
udc_init( &surf_udc_device);
 
mt_part_dump();
sz = target_get_max_flash_size();
fastboot_init(target_get_scratch_address(), sz);
udc_start();
 

mt_boot_init analysis summary

  • Obtain the device serial number string, chip code, sn number, etc.
  • If the secure boot is implemented, perform the check of the sec boot;
  • Enter the initialization of the boot_linux_from_strorage function, which is very important and does a lot of things
boot_linux_from_storage
int boot_linux_from_storage(void)
{<!-- -->
int ret=0;
...
 
switch (g_boot_mode) {<!-- -->
case NORMAL_BOOT:
case META_BOOT:
case ADVMETA_BOOT:
case SW_REBOOT:
case ALARM_BOOT:
case KERNEL_POWER_OFF_CHARGING_BOOT:
case LOW_POWER_OFF_CHARGING_BOOT:
\t\t
load_vfy_boot: The content is roughly as follows:
                        /* Check if there is a bootopt logo in the head of the boot partition, if not, report an error */
ret = mboot_android_load_bootimg_hdr("boot", CFG_BOOTIMG_LOAD_ADDR);
if (ret < 0) {<!-- -->
msg_header_error("Android Boot Image");
}
\t\t\t
kimg_load_addr = kimg_load_addr + get_page_sz();
\t\t\t
                       /*
                        Take out the bootimage from the boot partition of EMMC and load it into DRAM
                        dprintf(CRITICAL, " > from - 0x 6llx (skip boot img hdr)\
", start_addr);
                        dprintf(CRITICAL, " > to - 0x%x (starts with kernel img hdr)\
", addr);
                        len = dev->read(dev, start_addr, (uchar*)addr, g_bimg_sz); <<= system call load to DRAM
                       Boot log:
                              [3380] > from - 0x0000000001d20800 (skip boot img hdr)
                              [3380] > to - 0x80008000 (starts with kernel img hdr)
                       */
ret = mboot_android_load_bootimg("boot", kimg_load_addr);
if (ret < 0) {<!-- -->
msg_img_error("Android Boot Image");
}
 load_bootinfo_bootimg(kimg_load_addr);
 prepare_kernel_dtb();
 set_bootimg_loaded(kimg_load_addr);
dprintf(CRITICAL, "[PROFILE] ------- load boot.img takes %d ms -------- \
", (int)get_timer(time_load_bootimg));
 
break;
 
case RECOVERY_BOOT:
...
break;
 
case FACTORY_BOOT:
case ATE_FACTORY_BOOT:
...
 
break;
...
 
}
 
 kernel_target_addr = get_kernel_target_addr();
 tags_target_addr = get_tags_addr();
 
reloacate_ramdisk( & amp;ramdisk_target_addr, & amp;ramdisk_real_sz); //Get the ramdisk address after relocation
bootargs_init((void*)tags_target_addr); //Incorporate the boot parameters of the device
...
 
/* Pass in cmdline, set selinux */
#if SELINUX_STATUS == 1
cmdline_append("androidboot.selinux=disabled");
#elif SELINUX_STATUS == 2
cmdline_append("androidboot.selinux=permissive");
#endif
 
/* Prepare to start the linux kernel */
boot_linux((void *)CFG_BOOTIMG_LOAD_ADDR, (unsigned *)CFG_BOOTARGS_ADDR,
(char *)cmdline_get(), board_machtype(), (void *)CFG_RAMDISK_LOAD_ADDR, g_rimg_sz);
 
while (1) ;
 
return 0;
}

boot_linux_from_storage summary

  • Select various boot modes according to g_boot_mode, such as: normal, factory, fastboot, recovery, etc., and then find (decompress) the address of ramdisk and zImage from the boot partition in EMMC and load it into the DRAM address through the system call, and finally load the kernel into the DRAM Address: (DRAM_PHY_ADDR + 0x8000);
  • Relocate the root file system address
  • Jump to boot_linux, officially pull up the kernel
boot_linux analysis:

boot_linux actually runs boot_linux_fdt. This function parses and loads dtb. The operation is very complicated. Here we only focus on the main process.

void boot_linux(void *kernel, unsigned *tags,
                char *cmdline, unsigned machtype,
                void *ramdisk, unsigned ramdisk_size)
{<!-- -->
...
// The new architecture is to take the fdt branch.
#ifdef DEVICE_TREE_SUPPORT
boot_linux_fdt((void *)kernel, (unsigned *)tags,
(char *)cmdline, machtype,
(void *)ramdisk, ramdisk_size);
 
while (1) ;
#endif
...
 
int boot_linux_fdt(void *kernel, unsigned *tags,
                   char *cmdline, unsigned machtype,
                   void *ramdisk, unsigned ramdisk_size)
{<!-- -->
...
void (*entry)(unsigned,unsigned,unsigned*) = kernel;
...
 
// find dt from kernel img
if (fdt32_to_cpu(*(unsigned int *)dtb_addr) == FDT_MAGIC) {<!-- -->
dtb_size = fdt32_to_cpu(*(unsigned int *)(dtb_addr + 0x4));
} else {<!-- -->
dprintf(CRITICAL,"Can't find device tree. Please check your kernel image\
");
while (1) ;
}
...
 
if (!has_set_p2u) {<!-- -->
/* Control the output of uart after entering the kernel. The non-eng version is closed by default. If you need to debug, you can change this to
   "printk.disable_uart=0"
 */
 
#ifdef USER_BUILD
sprintf(cmdline,"%s%s",cmdline,"printk.disable_uart=1");
#else
sprintf(cmdline,"%s%s",cmdline," printk.disable_uart=0 ddebug_query="file *mediatek* + p ; file *gpu* =_"");
#endif
...
}
 
...
 
// led, irq off
platform_uninit();
\t
// Close I/D-cache, close MMU, today's kernel conditions.
arch_disable_cache(UCACHE);
arch_disable_mmu();
 
// sec init
extern void platform_sec_post_init(void)__attribute__((weak));
if (platform_sec_post_init) {<!-- -->
platform_sec_post_init();
}
 
// If it is charging, execute reset after detecting the power key.
if (kernel_charging_boot() == 1) {<!-- -->
if (pmic_detect_powerkey()) {<!-- -->
dprintf(CRITICAL, "[%s] PowerKey Pressed in Kernel Charging Mode Before Jumping to Kernel, Reboot Os\
", __func__);
mtk_arch_reset(1);
}
}
#endif
...
 
// Output key information.
dprintf(CRITICAL,"cmdline: %s\
", cmdline);
dprintf(CRITICAL,"lk boot time = %d ms\
", lk_t);
dprintf(CRITICAL,"lk boot mode = %d\
", g_boot_mode);
dprintf(CRITICAL,"lk boot reason = %s\
", g_boot_reason[boot_reason]);
dprintf(CRITICAL,"lk finished --> jump to linux kernel %s\
\
", g_is_64bit_kernel ? "64Bit" : "32Bit");
 
// Execute the system call and jump to the kernel. The entry here is actually the entry address of the previous kernel in DRAM.
if (g_is_64bit_kernel) {<!-- -->
lk_jump64((u32)entry, (u32)tags, 0, KERNEL_64BITS);
} else {<!-- -->
                dprintf(CRITICAL, "[mt_boot] boot_linux_fdt entry: 0x x, machtype: %d\
", entry, machtype);
entry(0, machtype, tags);
}
while (1);
return 0;
}

Print log information at startup

[4260] cmdline: console=tty0 console=ttyMT0,921600n1 root=/dev/ram vmalloc=496M androidboot.hardware=mt6580 androidboot.verifiedbootstate=green bootopt=64S3,32S1,32S1 printk.disable_uart=1 pl_tprof. =1718 bootprof.lk_t=2178 boot_reason=0 androidboot.serialno=0123456789ABCDEF androidboot.bootreason=power_key gpt=1
 
[4260] lk boot time = 2178 ms
 
[4260] lk boot mode = 0
 
[4260] lk boot reason = power_key
 
[4260] lk finished --> jump to linux kernel 32Bit
 
[4260] [mt_boot] boot_linux_fdt entry: 0x80008000, machtype: 6580

boot_linux summary

  • Initialize DTB (device tree block)
  • Prepare various cmdline parameters to pass into the kernel
  • Disable I/D-cache, MMU
  • Print key information, officially pull up the kernel

At this point, the two stages of the bootloader are analyzed

bootloader starts a simple connection

Pre-loader->lk main thing

  1. Initialize hardware such as DRAM
  2. Handshake with flashtool USB, download related detection & sec_boot detection
  3. Load lk into DRAM, if EL3 is implemented, load atf into memory
  4. Jump to lk, if EL3 is implemented, jump to atf first, initialize atf and then jump back to lk to initialize

lk->kernel main thing

  1. Open MMU, enable I/D-cache, speed up lk execution, display logo, charging related
  2. Extract boot.img from the boot partition in emmc and decompress it, and load the root file system (ramdisk) and zImage to DRAM
  3. Parse dtb and write to DRAM designated area
  4. Close MMU, irq/fiq, close I/D-cache, pull up kernel