coreboot执行流程
以上内容参考自wiki中的《coreboot Overview》章节以及coreboot源代码。
在wiki中执行流程是按照intel平台来的,但是中间有部分并不是很清楚。
这里根据代码有增加部分内容,但是并不是一定按照intel平台来的。
coreboot执行流程大致分为四个阶段:
bootblock->romstage->ramstage->payload
1. At 0xFFFFFFF0, start execution at reset_vector from src/cpu/x86/16bit/reset16.inc,which simply jumps to _start.
对应代码:
.section ".reset", "ax", %progbits
.code16
.globl _start
_start:
.byte 0xe9
.int _start16bit - ( . + 2 )
/* Note: The above jump is hand coded to work around bugs in binutils.
* 5 byte are used for a 3 byte instruction. This works because x86
* is little endian and allows us to use supported 32bit relocations
* instead of the weird 16 bit relocations that binutils does not
* handle consistenly between versions because they are used so rarely.
*/
.previous
如果不关注具体的细节,这里的代码就是一个跳转语句,跳转到_start16bit的位置。
2. _start from src/cpu/x86/16bit/entry16.inc, invalidates the TLBs, sets up a GDT for selector 0x08 (code) and 0x10 (data), switches to protected mode, and jumps to__protected_start (setting the CS to the new selector 0x08). The selectors provide full flat access to the entire physical memory map.
entry16.inc中有_start16bit,它就是第一步的跳转位置。在entry16.inc中的主要操作就是进入保护模式,然后跳转的32位的代码中:
/* Now that we are in protected mode jump to a 32 bit code segment. */
ljmpl $ROM_CODE_SEG, $__protected_start
3. __protected_start fromsrc/cpu/x86/32bit/entry32.inc, sets all other segment registers to the 0x10 selector.
__protected_start:
/* Save the BIST value */
movl %eax, %ebp
#if !IS_ENABLED(CONFIG_NO_EARLY_BOOTBLOCK_POSTCODES)
post_code(POST_ENTER_PROTECTED_MODE)
#endif
movw $ROM_DATA_SEG, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movw %ax, %fs
movw %ax, %gs
/* Restore the BIST value to %eax */
movl %ebp, %eax
ROM_DATA_SEG的值就是0x10。
以上涉及的到的文件都包含在bootblock_romcc.S或者bootblock_crt0.S文件中,这属于coreboot的第一个阶段,称为bootblock阶段。
bootblock_romcc.S或者bootblock_crt0.S对应着两种不同的bootblock,参看说明:
bootblock_romcc.S对应的是:
/*
* This is the original bootblock used by coreboot on x86 systems. It contains
* a monolithic code flow, assembled from the following stages:
* - reset16.inc: the reset vector
* - entry16.inc: protected mode setup
* - entry32.inc: segment descriptor setup
* - CONFIG_CHIPSET_BOOTBLOCK_INCLUDE: chipset-specific initialization
* - generated/bootblock.inc: ROMCC part of the bootblock
*
* This is used on platforms which do not select C_ENVIRONMENT_BOOTBLOCK, and it
* tries to do the absolute minimum before walking CBFS and jumping to romstage.
*
* This file assembles the bootblock program by the order of the includes. Thus,
* it's extremely important that one pays very careful attention to the order
* of the includes.
*/
bootblock_romcc.S的代码:
#include <arch/x86/prologue.inc>
#include <cpu/x86/16bit/entry16.inc>
#include <cpu/x86/16bit/reset16.inc>
#include <cpu/x86/32bit/entry32.inc>
#ifdef CONFIG_CHIPSET_BOOTBLOCK_INCLUDE
#include CONFIG_CHIPSET_BOOTBLOCK_INCLUDE
#endif
#if IS_ENABLED(CONFIG_SSE)
#include <cpu/x86/sse_enable.inc>
#endif
/*
* This bootblock.inc file is generated by ROMCC. The above program flow
* falls through to this point. ROMCC assumes the last function it parsed
* is the main function and it places its instructions at the beginning of
* the generated file. Moreover, any library/common code needed in bootblock
* needs to come after bootblock.inc.
*/
#include <generated/bootblock.inc>
这里有个问题就是bootblock.inc并不是现成的代码,而是ROMCC生成的文件,而ROMCC是coreboot的一个工具,具体作用还不明。
bootblock_crt0.S对应的是:
/*
* This is the modern bootblock. It is used by platforms which select
* C_ENVIRONMENT_BOOTBLOCK, and it prepares the system for C environment runtime
* setup. The actual setup is done by hardware-specific code.
*
* It provides a bootflow similar to other architectures, and thus is considered
* to be the modern approach.
*/
对应的代码:
#include <cpu/x86/cr.h>
/*
* Include the old code for reset vector and protected mode entry. That code has
* withstood the test of time.
*/
#include <arch/x86/prologue.inc>
#include <cpu/x86/16bit/entry16.inc>
#include <cpu/x86/16bit/reset16.inc>
#include <cpu/x86/32bit/entry32.inc>
#if IS_ENABLED(CONFIG_BOOTBLOCK_DEBUG_SPINLOOP)
/* Wait for a JTAG debugger to break in and set EBX non-zero */
xor %ebx, %ebx
debug_spinloop:
cmp $0, %ebx
jz debug_spinloop
#endif
bootblock_protected_mode_entry:
/* BIST result in eax */
movl %eax, %ebx
/* Get an early timestamp */
rdtsc
#if IS_ENABLED(CONFIG_BOOTBLOCK_SAVE_BIST_AND_TIMESTAMP)
lea 1f, %ebp
/* eax: Low 32-bits of timestamp
* ebx: BIST result
* ebp: return address
* edx: High 32-bits of timestamp
*/
jmp bootblock_save_bist_and_timestamp
1:
#else
movd %ebx, %mm0
movd %eax, %mm1
movd %edx, %mm2
#endif
#if IS_ENABLED(CONFIG_SSE)
enable_sse:
mov %cr4, %eax
or $CR4_OSFXSR, %ax
mov %eax, %cr4
#endif /* IS_ENABLED(CONFIG_SSE) */
/* We're done. Now it's up to platform-specific code */
jmp bootblock_pre_c_entry
两种类型的bootblock前面的resetvector和进入保护模式的方式都是一样的,之后modern bootblock跳转到了bootblock_pre_c_entry:
/*
* C code entry point for the boot block.
*/
void asmlinkage bootblock_c_entry(uint64_t base_timestamp);
bootblock_c_entry()最终会调用:
/*
* This is a the same as the bootblock main(), with the difference that it does
* not collect a timestamp. Instead it accepts the first timestamp as an
* argument. This can be used in cases where an earlier stamp is available
* Note that this function is designed to be entered from C code.
* This function assumes that the timer has already been initialized, so it
* does not call init_timer().
*/
void asmlinkage bootblock_main_with_timestamp(uint64_t base_timestamp);
这个函数是bootblock阶段的c语言入口。
它的一个代码实现:
void asmlinkage bootblock_main_with_timestamp(uint64_t base_timestamp)
{
/* Initialize timestamps if we have TIMESTAMP region in memlayout.ld. */
if (IS_ENABLED(CONFIG_COLLECT_TIMESTAMPS) && _timestamp_size > 0)
timestamp_init(base_timestamp);
bootblock_soc_early_init();
bootblock_mainboard_early_init();
if (IS_ENABLED(CONFIG_BOOTBLOCK_CONSOLE)) {
console_init();
exception_init();
}
bootblock_soc_init();
bootblock_mainboard_init();
run_romstage();
}
最终它跳转到了romstage阶段,这是coreboot的第二个阶段。
4. Execution continues with various mainboard init fragments:
a) __fpu_start fromcpu/x86/fpu_enable.inc.
b) (unlabeled) fromcpu/x86/sse_enable.inc
c) Some CPUs enable their on-chipcache to be used temporarily as a scratch RAM (stack), e.g.cpu/amd/model_lx/cache_as_ram.inc.
相关的代码都能够找到,但是并不是在所有平台上都会使用,即使使用,也会有不同的实现,所以会对应不同的代码。
这一部分仍属于bootblock阶段,并且根据不同的平台代码分布位置可能不同。
5. The final mainboard init fragment is mainboard-specific, in C, called romstage.c. For non-cache-as-RAM targets, it is compiled with romcc. It includes and uses other C-code fragments for:
a) Initializing MSRs, MTRRs, APIC.
b) Setting up the southbridge minimally ("early setup").
c) Setting up Super I/O serial.
d) Initializing the console.
e) Initializing RAM controller and RAM itself.
这一部分属于romstage阶段。
void run_romstage(void)
{
struct prog romstage =
PROG_INIT(PROG_ROMSTAGE, CONFIG_CBFS_PREFIX "/romstage");
if (prog_locate(&romstage))
goto fail;
timestamp_add_now(TS_START_COPYROM);
if (cbfs_prog_stage_load(&romstage))
goto fail;
timestamp_add_now(TS_END_COPYROM);
prog_run(&romstage);
fail:
if (IS_ENABLED(CONFIG_BOOTBLOCK_CONSOLE))
die("Couldn't load romstage.\n");
halt();
}
从代码上看,应该是获取到二进制的romstage文件并执行。
romstage文件通过romstage.c文件编译得到,它有很多个,因为要对应不同的平台。
6. Execution continues at __main from src/arch/x86/init/crt0_romcc_epilogue.inc, where the non-romcc C coreboot code is copied (possibly decompressed) to RAM, then the RAM entry point is jumped to.
romstage阶段最后的步骤就是初始化内存并拷贝相关代码到内存中,而后再跳转到内存中去执行剩余的代码。
下面就进入ramstage阶段:
7. The RAM entry point is _startin src/arch/x86/lib/c_start.S, where new descriptor tables are set up, the stack and BSS are cleared, the IDT is initialized, and hardwaremain() is called(operation is now full 32-bit protected mode C program with stack).
c_start.S中有如下的代码:
/*
* Now we are finished. Memory is up, data is copied and
* bss is cleared. Now we call the main routine and
* let it do the rest.
*/
post_code(POST_PRE_HARDWAREMAIN) /* post fe */
#if CONFIG_GDB_WAIT
call gdb_hw_init
call gdb_stub_breakpoint
#endif
call main
/* NOTREACHED */
这里的main就是hardwaremain.c中的main()函数。
8. hardwaremain() is from src/boot/hardwaremain.c, the console is initialized, devices are enumerated and initialized, configured and enabled.
在hardwaremain.c中,会按照如下的过程执行:
static struct boot_state boot_states[] = {
BS_INIT_ENTRY(BS_PRE_DEVICE, bs_pre_device),
BS_INIT_ENTRY(BS_DEV_INIT_CHIPS, bs_dev_init_chips),
BS_INIT_ENTRY(BS_DEV_ENUMERATE, bs_dev_enumerate),
BS_INIT_ENTRY(BS_DEV_RESOURCES, bs_dev_resources),
BS_INIT_ENTRY(BS_DEV_ENABLE, bs_dev_enable),
BS_INIT_ENTRY(BS_DEV_INIT, bs_dev_init),
BS_INIT_ENTRY(BS_POST_DEVICE, bs_post_device),
BS_INIT_ENTRY(BS_OS_RESUME_CHECK, bs_os_resume_check),
BS_INIT_ENTRY(BS_OS_RESUME, bs_os_resume),
BS_INIT_ENTRY(BS_WRITE_TABLES, bs_write_tables),
BS_INIT_ENTRY(BS_PAYLOAD_LOAD, bs_payload_load),
BS_INIT_ENTRY(BS_PAYLOAD_BOOT, bs_payload_boot),
};
最终到达加载payload那一步:
9. The payload is called, either via elfboot() from boot/elfboot.c, or filo() from boot/filo.c
PS:以上属于根据wiki和coreboot得到的个人理解,不保证100%正确,如有错误请见谅。