[U-Boot 5] crt0.S

김태현·2024년 8월 17일

U-boot

목록 보기
5/6

저번 포스트에서 U-Boot의 시작 과정인 vectors.Sstart.S의 동작 과정을 살펴봤다. start.S_main 함수를 마지막으로 끝이 났는데, 이 포스트에서는 _main이 시작하는 crt0.S 코드를 살펴보도록 할 것이다.

crt0.S 코드는 처음으로 C 언어와 연결되는 코드이다. 또한 start.S에서 _main을 호출하기 전에 sp 레지스터에 미리 정해진 Stack Pointer 값을 넣었다는 것을 생각하면서 분석을 시작해보자.

crt0.S 파일을 부르기 전 Stack Pointer Register가 활성화 됨.

arch > arm > lib > crt0.S

_main 함수가 호출된 후, CONFIG이 세팅되어 있을 때 가장 먼저 실행되는 코드는 arch_very_early_init 함수이다. crt0.S 파일 시작 가장 처음에 초기화되어야 하는 사항이 있으면 해당 함수를 수정해서 사용하면 된다.

그 이후로는 다시 한 번 sp 레지스터에 Stack Pointer 값을 넣고 8-byte alignment를 진행한 후에 board_init_f_alloc_reserve 함수와 board_init_f_init_reserve 함수를 실행한다. 두 함수에 관하여 각각 살펴보도록 하겠다.

ENTRY(_main)

/* Call arch_very_early_init before initializing C runtime environment. */
#if CONFIG_IS_ENABLED(ARCH_VERY_EARLY_INIT)
	bl	arch_very_early_init 		@ CONFIG 되어있지 않아서 호출하지 않음
#endif

ldr	r0, =(SYS_INIT_SP_ADDR)		

bic	r0, r0, #7	/* 8-byte alignment for ABI compliance */
mov	sp, r0							@ sp 레지스터에 값 대입
bl	board_init_f_alloc_reserve 
mov	sp, r0
/* set up gd here, outside any C code */
mov	r9, r0
bl	board_init_f_init_reserve

우선 board_init_f_alloc_reserve 함수를 살펴보도록 하자.

ulong board_init_f_alloc_reserve(ulong top)
{
	top = rounddown(top-sizeof(struct global_data), 16);

	return top;
}

해당 함수의 역할은 부팅에 필요한 정보를 담은 구조체인 global_data의 크기만큼 Stack에 공간을 만들어주는 것이다. 해당 함수를 실행하기 전과 후의 Stack Pointer 레지스터의 변화는 다음과 같다.

Before : 0x6000_0F10
After : 0x6000_0610

해당 결과를 통해 Reserved space의 크기는 0x900인 것을 알 수 있다. 그리고 Stack Pointer가 가리키는 주소를 r9에 대입하고, board_init_f_init_reserve 함수를 실행한다.

void board_init_f_init_reserve(ulong base)
{
	struct global_data *gd_ptr;

	/*
	 * clear GD entirely and set it up.
	 * Use gd_ptr, as gd may not be properly set yet.
	 */

	gd_ptr = (struct global_data *)base;
	/* zero the area */
	memset(gd_ptr, '\0', sizeof(*gd));

	/* next alloc will be higher by one GD plus 16-byte alignment */
	base += roundup(sizeof(struct global_data), 16);

	/*
	 * record early malloc arena start.
	 * Use gd as it is now properly set for all architectures.
	 */

#if CONFIG_IS_ENABLED(SYS_MALLOC_F)
	/* go down one 'early malloc arena' */
	gd->malloc_base = base;
#endif

	if (CONFIG_IS_ENABLED(SYS_REPORT_STACK_F_USAGE))
		board_init_f_init_stack_protection();
}

*실제 함수는 더 길지만 실행되는 코드 외에 나머지는 다 삭제했음

board_init_f_init_reserve 함수는 이전 board_init_f_alloca_reserve 함수에서 할당했던 공간을 초기화하는 일을 한다. 그리고 base의 값을 증가시키는데, 이는 동적 할당을 위해 사용할 수 있는 다음 공간을 설정하는 역할을 한다.

그 후에, board_init_f 함수를 호출한다. 해당 함수에서는 init_sequence_f라는 함수 포인터 배열에 저장된 함수를 순서대로 실행한다. init_sequence_f 배열은 엄청 길다...

static const init_fnc_t init_sequence_f[] = {
	setup_mon_len,
#ifdef CONFIG_OF_CONTROL
	fdtdec_setup,
#endif
#ifdef CONFIG_TRACE_EARLY
	trace_early_init,
#endif
	initf_malloc,
	log_init,
	initf_bootstage,	/* uses its own timer, so does not need DM */
	event_init,
	bloblist_maybe_init,
	setup_spl_handoff,
#if defined(CONFIG_CONSOLE_RECORD_INIT_F)
	console_record_init,
#endif
	INITCALL_EVENT(EVT_FSP_INIT_F),
	arch_cpu_init,		/* basic arch cpu dependent setup */
	mach_cpu_init,		/* SoC/machine dependent CPU setup */
	initf_dm,
    
    ...

다양한 함수들이 있지만, 그 중에서 내가 가장 중요하다고 생각하 는것은 dram_init 함수이다. board_init_f에서 DRAM이 처음으로 사용 가능해진다. 즉, Stack에 저장된 gd가 사용 가능해진다는 것이다. DRAM이 사용 가능해짐에 따라 Stack과 Heap을 사용할 수 있는 것은 물론이고 디버깅을 위해 사용될 수 있는 정보 또한 DRAM에 저장할 수 있다.

board_init_f에서 DRAM을 활성화했으니 이제는 ROM에서 작동하고 있던 Process를 DRAM에서 실행할 수 있게 된다. 그것이 relocate_code가 호출되는 이유다.

지금부터 코드로 relocate_code가 사용되는 Sequence를 살펴보자.

	ldr	r0, [r9, #GD_START_ADDR_SP]	/* sp = gd->start_addr_sp */
	bic	r0, r0, #7	/* 8-byte alignment for ABI compliance */
	mov	sp, r0
	ldr	r9, [r9, #GD_NEW_GD]		/* r9 <- gd->new_gd */

	adr	lr, here
#if defined(CONFIG_POSITION_INDEPENDENT)
	adr	r0, _main
	ldr	r1, _start_ofs
	add	r0, r1
	ldr	r1, =CONFIG_TEXT_BASE
	sub	r1, r0
	add	lr, r1
#if defined(CONFIG_SYS_RELOC_GD_ENV_ADDR)
	ldr	r0, [r9, #GD_ENV_ADDR]		/* r0 = gd->env_addr */
	add	r0, r0, r1
	str	r0, [r9, #GD_ENV_ADDR]
#endif
#endif
	ldr	r0, [r9, #GD_RELOC_OFF]		/* r0 = gd->reloc_off */
	add	lr, lr, r0
#if defined(CONFIG_CPU_V7M)
	orr	lr, #1				/* As required by Thumb-only */
#endif
	ldr	r0, [r9, #GD_RELOCADDR]		/* r0 = gd->relocaddr */
	b	relocate_code
here:

	bl	relocate_vectors

/* Set up final (full) environment */

	bl	c_runtime_cpu_setup	/* we still call old routine here */
#endif
#if !defined(CONFIG_SPL_BUILD) || CONFIG_IS_ENABLED(FRAMEWORK)

#if !defined(CONFIG_SPL_BUILD) || !defined(CONFIG_SPL_EARLY_BSS)
	CLEAR_BSS
#endif

	/* call board_init_r(gd_t *id, ulong dest_addr) */
	mov     r0, r9                  /* gd_t */
	ldr	r1, [r9, #GD_RELOCADDR]	/* dest_addr */
	/* call board_init_r */
#if CONFIG_IS_ENABLED(SYS_THUMB_BUILD)
	ldr	lr, =board_init_r	/* this is auto-relocated! */
	bx	lr

sp 레지스터에 새로운 스택 포인터의 값을 넣어주고 gd 또한 new_gd로 바꿔서 저장해준다. 여기서 lr 레지스터에 here이라는 label의 주소를 넣어주는데, relocate_code가 작동을 완료하면 here으로 Return하게 될 것이다.

ROM에서 DRAM으로 재배치가 끝난다면 here에서 vector table의 위치를 재배치한 후, BSS 영역을 초기화 한 후 마지막으로 board_init_r 함수를 호출함으로써 U-Boot의 초기화 과정이 마무리된다.

board_init_r 함수에서도 board_init_f 함수와 마찬가지로 실행될 함수의 주소를 저장한 배열인 init_sequence_r 이라는 배열이 있는데, 주요한 일들 중 몇 가지를 꼽자면 1. Cache 초기화 2. 전역 변수 값 초기화 3. 동적 할당을 위한 메모리 초기화 4. Interrupt 활성화 5. Main loop 호출 이렇게 꼽을 수 있다.

static init_fnc_t init_sequence_r[] = {
	initr_trace,
	initr_reloc,
	event_init,
	/* TODO: could x86/PPC have this also perhaps? */
#if defined(CONFIG_ARM) || defined(CONFIG_RISCV)
	initr_caches,
	/* Note: For Freescale LS2 SoCs, new MMU table is created in DDR.
	 *	 A temporary mapping of IFC high region is since removed,
	 *	 so environmental variables in NOR flash is not available
	 *	 until board_init() is called below to remap IFC to high
	 *	 region.
	 */
#endif
	initr_reloc_global_data,
#if defined(CONFIG_SYS_INIT_RAM_LOCK) && defined(CONFIG_E500)
	initr_unlock_ram_in_cache,
#endif
	initr_barrier,
	initr_malloc,
	log_init,
	initr_bootstage,	/* Needs malloc() but has its own timer */
#if defined(CONFIG_CONSOLE_RECORD)
	console_record_init,
#endif
#ifdef CONFIG_SYS_NONCACHED_MEMORY
	noncached_init,
#endif
	initr_of_live,
#ifdef CONFIG_DM
	initr_dm,

...

이렇게 해서 U-Boot의 main 진입 전 초기화 과정을 살펴보았다. relocate_code 이후로는 Symbol이 재배치되어서인지 아니면 Symbol이 가리키는 위치에 해당 함수가 없어서인지 breakpoint를 잡고 디버깅을 해보려고 해도 breakpoint가 잡히지 않는다. 그래서 그냥 코드만 읽으면서 리뷰를 해보았는데 대략 작동 순서가 이해되는 것 같다.

이후 계획으로는 나만의 Secure Boot 기능을 코드에 추가하고 싶다. 그러기 위해서는 우선 Memory Map을 살펴보고, 그에 맞는 설계를 시작해야 한다. 다음 포스트는 아마 내가 사용하고 있는 Memory Map을 분석하는 포스트가 되지 않을까 싶다.

0개의 댓글