当前位置: 首页 > 工具软件 > Coreboot > 使用案例 >

【coreboot】基础流程

易祯
2023-12-01

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%正确,如有错误请见谅。

 类似资料: