U-Boot의 초기 부팅 단계에서 하드웨어와 소프트웨어 환경을 설정하는 핵심 메커니즘은 init_sequence라는 함수 포인터 배열을 통해 이루어집니다. 이 배열은 시스템이 본格的인 OS 부팅을 수행하기 전에 반드시 거쳐야 하는 초기화 작업들을 순차적으로 정의하고 있습니다. 본 글에서는 S3C2410 기반의 ARM9 보드를 예시로 들어, init_sequence의 구성 요소와 각 함수의 내부 동작 방식을 심층적으로 분석합니다.
init_sequence 배열의 구조
init_sequence는 초기화 함수들의 주소를 담고 있는 배열로, 각 함수는 특정 하드웨어 또는 소프트웨어 서브시스템을 초기화하는 역할을 담당합니다. 배열의 끝은 항상 NULL로 마무리되어 반복문이 안전하게 종료되도록 설계되어 있습니다.
typedef int (*init_func_t)(void);
init_func_t boot_init_sequence[] = {
setup_cpu_core, /* CPU 코어 기본 설정 및 스택 초기화 */
setup_board_hardware, /* 보드 고유 하드웨어(클럭, GPIO) 설정 */
setup_interrupts, /* 예외 및 인터럽트 컨트롤러 설정 */
load_environment_vars, /* 환경 변수 스토리지 초기화 및 로드 */
configure_baud_rate, /* 시리얼 통신 속도(보율) 설정 */
init_serial_port, /* UART 시리얼 포트 활성화 */
setup_console_early, /* 1단계 콘솔 출력 환경 구축 */
print_boot_banner, /* 부팅 배너 및 버전 정보 출력 */
#if defined(SHOW_CPU_DETAILS)
show_cpu_specs, /* CPU 클럭 및 스펙 정보 표시 */
#endif
#if defined(SHOW_BOARD_DETAILS)
verify_board_revision, /* 보드 리비전 및 정보 표시 */
#endif
init_memory_banks, /* 사용 가능한 RAM 뱅크 설정 */
show_memory_layout, /* 최종 메모리 구성 정보 출력 */
NULL,
};
CPU 코어 초기화 (setup_cpu_core)
가장 먼저 수행되는 CPU 초기화 루틴입니다. 주로 인터럽트 처리를 위한 IRQ 및 FIQ 스택 포인터를 설정하는 작업이 포함되며, 예외 상황 발생 시 시스템이 안전하게 대응할 수 있는 기반을 마련합니다.
int setup_cpu_core(void)
{
/* 인터럽트 발생 시 사용할 스택 영역 할당 */
#ifdef ENABLE_IRQ_HANDLING
unsigned long irq_stack_base = _uboot_text_base - CONFIG_MALLOC_AREA_SIZE
- CONFIG_GLOBAL_DATA_SIZE - 4;
unsigned long fiq_stack_base = irq_stack_base - CONFIG_IRQ_STACK_SIZE;
/* 어셈블리 레벨에서 스택 포인터 레지스터 업데이트 */
update_stack_pointers(irq_stack_base, fiq_stack_base);
#endif
return 0;
}
보드 하드웨어 초기화 (setup_board_hardware)
S3C2410 SoC의 클럭 및 전력 관리, 그리고 GPIO 핀 멀티플렉싱을 설정합니다. PLL 락 타임을 최적화하고 시스템 클럭을 안정화한 후, 각 포트의 방향과 풀업/풀다운 저항을 구성합니다.
int setup_board_hardware(void)
{
s3c2410_clock_pwr *clk_regs = get_clock_pwr_base();
s3c2410_gpio *gpio_regs = get_gpio_base();
/* PLL 안정화 시간 단축을 위한 락 타임 카운터 최대화 */
clk_regs->lock_time = 0x00FFFFFF;
/* 메인 PLL(MPLL) 주파수 설정 */
clk_regs->mpllcon = MAKE_PLL_VAL(MDIV_MAIN, PDIV_MAIN, SDIV_MAIN);
udelay(4000); /* MPLL 안정화 대기 */
/* USB PLL(UPLL) 주파수 설정 */
clk_regs->upllcon = MAKE_PLL_VAL(MDIV_USB, PDIV_USB, SDIV_USB);
udelay(8000); /* UPLL 안정화 대기 */
/* GPIO 포트 기능 및 풀업 저항 설정 */
gpio_regs->gpa_con = 0x007FFFFF;
gpio_regs->gpb_con = 0x00044555;
gpio_regs->gpb_up = 0x000007FF;
gpio_regs->gpc_con = 0xAAAAAAAA;
gpio_regs->gpc_up = 0x0000FFFF;
/* ... 기타 포트(GPD~GPH) 설정 생략 ... */
gpio_regs->gph_con = 0x002AFAAA;
gpio_regs->gph_up = 0x000007FF;
/* 부팅 파라미터 및 아키텍처 머신 번호 설정 */
global_data->board_info->arch_id = MACH_TYPE_SMDK2410;
global_data->board_info->boot_params_addr = 0x30000100;
/* 캐시 활성화 */
enable_instruction_cache();
enable_data_cache();
return 0;
}
인터럽트 및 타이머 설정 (setup_interrupts)
OS가 부팅되기 전의 시간 지연 및 타임스탬프 관리를 위해 하드웨어 타이머(PWM Timer 4)를 설정합니다. 출력 핀이 없는 Timer 4를 선택하여 시스템 틱을 생성합니다.
int setup_interrupts(void)
{
s3c2410_timers *pwm = get_timers_base();
unsigned long pclk_freq = get_peripheral_clock();
/* Timer 4 프리스케일러를 16으로 설정 (TCFG0) */
pwm->tcfg0 = (pwm->tcfg0 & ~0xFF00) | 0x0F00;
if (system_tick_reload == 0) {
/* 10ms 주기를 위한 카운트 값 계산: PCLK / (Divider * Prescaler * 100) */
system_tick_reload = pclk_freq / (2 * 16 * 100);
}
/* 초기 카운트 값 로드 및 수동 업데이트 */
last_tick_count = pwm->tcntb4 = system_tick_reload;
/* 자동 리로드 및 수동 업데이트 비트 설정 */
pwm->tcon = (pwm->tcon & ~0x00700000) | 0x00600000;
/* 타이머 시작 */
pwm->tcon = (pwm->tcon & ~0x00700000) | 0x00500000;
global_timestamp = 0;
return 0;
}
환경 변수 로드 (load_environment_vars)
U-Boot는 NOR Flash, NAND, EEPROM 등 다양한 매체에 환경 변수를 저장할 수 있습니다. 이 함수는 컴파일 시 설정된 매체로부터 환경 변수를 읽어와 CRC 무결성을 검증합니다.
/* 환경 변수가 NOR Flash에 저장된다고 가정할 때의 구현 */
int load_environment_vars(void)
{
/* CRC 검사를 통한 환경 변수 유효성 검증 */
if (calculate_crc32(0, env_storage->data, ENV_DATA_SIZE) == env_storage->crc) {
global_data->env_addr = (unsigned long)&(env_storage->data);
global_data->env_valid = 1;
} else {
/* CRC 불일치 시 기본 환경 변수 사용 */
global_data->env_addr = (unsigned long)&default_env_settings[0];
global_data->env_valid = 0;
}
return 0;
}
시리얼 보율 및 포트 초기화 (configure_baud_rate, init_serial_port)
콘솔 출력을 위해 시리얼 통신 속도를 결정하고 UART 레지스터를 구성합니다. 보율(Baudrate)은 환경 변수에서 가져오며, 없을 경우 기본값을 사용합니다.
static int configure_baud_rate(void)
{
char baud_str[32];
int len = getenv_f("baudrate", baud_str, sizeof(baud_str));
global_data->baud_rate = (len > 0)
? simple_strtoul(baud_str, NULL, 10)
: DEFAULT_BAUD_RATE;
return 0;
}
int init_serial_port(void)
{
s3c2410_uart *uart = get_uart_base(CONFIG_UART_PORT);
unsigned long pclk = get_peripheral_clock();
unsigned int divisor = (pclk / (16 * global_data->baud_rate)) - 1;
/* FIFO 활성화 및 Tx/Rx 버퍼 초기화 */
uart->ufcon = 0x07;
uart->umcon = 0x00;
/* 8비트 데이터, 1 스톱 비트, 패리티 없음 */
uart->ulcon = 0x03;
/* 인터럽트/폴링 모드 설정 및 Rx 에러 인터럽트 활성화 */
uart->ucon = 0x245;
/* 보율 분주기 레지스터 설정 */
uart->ubrdiv = divisor;
return 0;
}
초기 콘솔 및 배너 출력 (setup_console_early, print_boot_banner)
메모리 재배치(Relocation) 이전 단계에서 시리얼 포트를 활용한 최소한의 콘솔 환경을 설정하고, U-Boot의 버전 정보를 출력하여 시스템이 정상적으로 부팅 중임을 사용자에게 알립니다.
int setup_console_early(void)
{
global_data->console_ready = 1;
/* 무음(Silent) 부팅 모드 확인 */
if (getenv("silent_mode") != NULL) {
global_data->flags |= GD_FLAG_SILENT_CONSOLE;
}
return 0;
}
static int print_boot_banner(void)
{
printf("\n\r%s\n\r", uboot_version_string);
debug("Memory Map: Text[0x%08lX - 0x%08lX] BSS[0x%08lX]\n",
_text_start, _text_end, _bss_end);
return 0;
}
DRAM 초기화 및 구성 정보 표시 (init_memory_banks, show_memory_layout)
시스템에 장착된 물리 메모리(RAM)의 시작 주소와 크기를 전역 데이터 구조체에 기록하고, 최종적으로 부팅 로그에 메모리 용량을 출력합니다.
int init_memory_banks(void)
{
global_data->board_info->dram_bank[0].start = PHYSICAL_RAM_BASE;
global_data->board_info->dram_bank[0].size = PHYSICAL_RAM_SIZE;
return 0;
}
static int show_memory_layout(void)
{
unsigned long total_ram = 0;
int bank_idx;
for (bank_idx = 0; bank_idx < CONFIG_MAX_DRAM_BANKS; bank_idx++) {
total_ram += global_data->board_info->dram_bank[bank_idx].size;
}
printf("System RAM: ");
print_size(total_ram, "\n\r");
return 0;
}