UEFI 심화 (Unified Extensible Firmware Interface)
UEFI 부팅 단계, Boot/Runtime Services, Secure Boot, Linux EFI Stub, 커널 UEFI 구현까지 — 펌웨어와 커널의 인터페이스를 소스 코드 수준에서 분석합니다.
UEFI 개요
UEFI(Unified Extensible Firmware Interface)는 운영체제와 플랫폼 펌웨어 사이의 소프트웨어 인터페이스를 정의하는 규격입니다. 전통적인 BIOS를 대체하며, GPT 파티션, 64-bit 지원, Secure Boot, 네트워크 부팅 등 현대 시스템에 필수적인 기능을 제공합니다.
| 특성 | Legacy BIOS | UEFI |
|---|---|---|
| 주소 공간 | 16-bit 리얼 모드 (1MB) | 32/64-bit 보호/롱 모드 |
| 파티션 테이블 | MBR (최대 2TB, 4개 primary) | GPT (최대 9.4ZB, 128개 파티션) |
| 부트 엔트리 | 고정 LBA 0 (MBR 512B) | ESP의 EFI 바이너리 (파일 경로) |
| 드라이버 모델 | INT 13h (디스크), INT 10h (비디오) | UEFI 프로토콜 기반 드라이버 |
| 보안 | 없음 | Secure Boot (디지털 서명 검증) |
| 네트워크 | PXE (제한적) | IPv4/IPv6 네트워크 스택 내장 |
| 셸 | 없음 | UEFI Shell (스크립팅 가능) |
| OS 인터페이스 | 부팅 후 사라짐 | Runtime Services 지속 제공 |
UEFI 부팅 흐름 다이어그램
UEFI 부팅 단계 (PI 아키텍처)
UEFI 펌웨어는 PI(Platform Initialization) 규격에 따라 여러 단계를 거쳐 실행됩니다. 각 단계는 이전 단계가 설정한 환경 위에서 동작합니다.
SEC (Security Phase)
파워 온 직후 가장 먼저 실행되는 단계입니다. CPU 리셋 벡터에서 시작하며, 메인 메모리(DRAM)가 아직 초기화되지 않은 상태입니다.
/*
* SEC 단계 주요 동작:
*
* 1. CPU 리셋 벡터 (0xFFFFFFF0) 에서 실행 시작
* 2. CPU 캐시를 RAM으로 사용 (CAR: Cache-As-RAM)
* → L2/L3 캐시를 임시 스택/데이터 영역으로 활용
* → No-Eviction Mode 설정
* 3. 최소한의 CPU 초기화 (마이크로코드 로드 등)
* 4. 보안 검증: 펌웨어 볼륨(FV)의 무결성 확인
* 5. PEI Foundation으로 핸드오프
*
* CAR 메모리 레이아웃 (예시):
* +---------------------------+ ← CAR Base + CAR Size
* | SEC 스택 |
* +---------------------------+
* | SEC 데이터 |
* +---------------------------+
* | PEI 임시 RAM |
* +---------------------------+ ← CAR Base
*/
PEI (Pre-EFI Initialization)
PEI는 메인 메모리(DRAM)를 초기화하는 핵심 단계입니다. PEIM(PEI Module)이라는 작은 모듈들이 실행됩니다.
/*
* PEI 단계 주요 동작:
*
* 1. PEI Foundation (PEI Core) 초기화
* 2. PPI (PEI-to-PEI Interface) 서비스 등록
* 3. PEIM(PEI Module) 디스패치:
* - CPU PEIM: CPU 기능 설정, 캐시 구성
* - 메모리 PEIM: DRAM 컨트롤러 초기화 (SPD 읽기, 트레이닝)
* - 칩셋 PEIM: PCH/SoC 초기 설정
* 4. 메모리 초기화 완료 → CAR에서 DRAM으로 전환
* 5. HOB(Hand-Off Block) 생성 → DXE에 정보 전달
* - 메모리 맵, CPU 정보, 플랫폼 데이터
* 6. DXE Foundation으로 핸드오프
*/
/* HOB에 포함되는 주요 정보 */
- PHIT (Phase Handoff Information Table)
- 메모리 할당 HOB (사용 가능/예약 영역)
- 리소스 서술자 HOB (MMIO, I/O 포트 영역)
- 펌웨어 볼륨 HOB
- CPU HOB (코어 수, 기능)
DXE (Driver Execution Environment)
DXE는 UEFI의 핵심 단계로, 대부분의 하드웨어 초기화와 서비스 등록이 이루어집니다.
/*
* DXE 단계 주요 동작:
*
* 1. DXE Foundation (DXE Core) 초기화
* - Boot Services Table 생성
* - Runtime Services Table 생성
* - System Table 생성
*
* 2. DXE 드라이버 디스패치:
* - PCI 버스 열거 (PCI Host Bridge Driver)
* - USB 컨트롤러 초기화
* - 그래픽 출력 (GOP: Graphics Output Protocol)
* - 디스크 I/O (Block I/O Protocol)
* - 네트워크 (SNP: Simple Network Protocol)
* - 콘솔 입출력 (Simple Text I/O)
*
* 3. UEFI 프로토콜 등록:
* 각 드라이버가 프로토콜(인터페이스)을 핸들에 설치
* → 다른 드라이버나 애플리케이션이 프로토콜을 통해 접근
*
* 4. SMM(System Management Mode) 초기화 (해당 시)
*
* 5. BDS(Boot Device Selection)로 전환
*/
BDS (Boot Device Selection)
BDS는 부팅 장치를 선택하고 부트로더를 실행하는 단계입니다.
/*
* BDS 단계:
*
* 1. UEFI 변수에서 부팅 순서 읽기:
* - BootOrder: 부팅 시도 순서 (예: 0001,0003,0002)
* - Boot0001: 첫 번째 부팅 옵션 (경로, 설명 등)
*
* 2. 부팅 장치 연결 (ConnectController):
* - 장치 경로(Device Path)에 따라 드라이버 바인딩
* - 파일시스템 드라이버 로드 (FAT32)
*
* 3. EFI 바이너리 로드 및 실행:
* - ESP에서 부트로더 로드
* - 기본 경로: \EFI\BOOT\BOOTX64.EFI (x86_64)
* - 또는 Boot 변수에 지정된 경로
*
* 4. 부팅 실패 시 다음 Boot 항목으로 이동
* 5. 모든 항목 실패 시 UEFI Shell 또는 에러 화면
*/
Boot Services & Runtime Services
UEFI는 두 종류의 서비스를 OS에 제공합니다. Boot Services는 부팅 중에만 사용 가능하고, Runtime Services는 OS 실행 중에도 사용할 수 있습니다.
Boot Services
OS 부트로더나 EFI Stub이 ExitBootServices()를 호출하기 전까지 사용 가능한 서비스입니다:
| 카테고리 | 주요 함수 | 설명 |
|---|---|---|
| 메모리 | AllocatePages(), FreePages() | 물리 메모리 페이지 할당/해제 |
| 메모리 | AllocatePool(), FreePool() | 메모리 풀 할당/해제 |
| 메모리 | GetMemoryMap() | 물리 메모리 맵 조회 (커널 초기화에 필수) |
| 프로토콜 | HandleProtocol() | 핸들에서 프로토콜 인터페이스 획득 |
| 프로토콜 | LocateProtocol() | 특정 프로토콜을 제공하는 핸들 검색 |
| 이미지 | LoadImage(), StartImage() | EFI 바이너리 로드/실행 |
| 이벤트 | CreateEvent(), SetTimer() | 이벤트/타이머 관리 |
| 종료 | ExitBootServices() | Boot Services 종료, OS에 제어권 이전 |
ExitBootServices() — 핵심 전환점
ExitBootServices()는 UEFI와 OS 간 제어권 전환의 핵심입니다. 호출 후에는 Boot Services를 사용할 수 없으며, 펌웨어의 하드웨어 접근이 중단됩니다.
/* drivers/firmware/efi/libstub/efi-stub-helper.c */
/*
* ExitBootServices() 호출 전후 상태 변화:
*
* [호출 전]
* - 펌웨어가 인터럽트, 타이머, 디바이스 제어
* - Boot Services + Runtime Services 모두 사용 가능
* - 펌웨어 메모리 맵이 변경될 수 있음
*
* [호출 후]
* - 모든 Boot Services 함수 포인터 무효화
* - 펌웨어 인터럽트/타이머 비활성화
* - 커널이 모든 하드웨어를 직접 제어해야 함
* - Runtime Services만 사용 가능
* - 메모리 맵 고정 (더 이상 변경 없음)
*
* 주의: GetMemoryMap()의 map_key가 일치해야 함
* → 메모리 맵을 가져온 후 다른 Boot Services 호출 금지
*/
/* EFI Stub에서의 ExitBootServices 호출 흐름 */
efi_status_t efi_exit_boot_services(...)
{
/* 1. GetMemoryMap()으로 최종 메모리 맵 획득 */
status = efi_bs_call(GetMemoryMap,
&map_size, map, &map_key,
&desc_size, &desc_ver);
/* 2. ExitBootServices() 호출 (map_key 전달) */
status = efi_bs_call(ExitBootServices,
handle, map_key);
if (status == EFI_INVALID_PARAMETER) {
/* map_key 불일치 → 메모리 맵 재획득 후 재시도 */
status = efi_bs_call(GetMemoryMap, ...);
status = efi_bs_call(ExitBootServices,
handle, map_key);
}
/* 이후 Boot Services 호출 불가 */
}
Runtime Services
OS 실행 중에도 사용할 수 있는 서비스입니다. 리눅스 커널은 이를 통해 UEFI 변수, 시간, 리셋 등을 처리합니다:
| 카테고리 | 주요 함수 | Linux 활용 |
|---|---|---|
| 변수 | GetVariable() | /sys/firmware/efi/efivars/ 읽기 |
| 변수 | SetVariable() | UEFI 변수 쓰기 (부팅 순서 변경 등) |
| 변수 | GetNextVariableName() | UEFI 변수 열거 |
| 시간 | GetTime(), SetTime() | RTC 접근 (hwclock) |
| 시간 | GetWakeupTime() | 알람 시간 조회/설정 |
| 리셋 | ResetSystem() | reboot 명령 처리 |
| 캡슐 | UpdateCapsule() | 펌웨어 업데이트 (fwupd) |
| 기타 | QueryVariableInfo() | 변수 저장소 용량 확인 |
/* include/linux/efi.h — Linux에서 Runtime Services 호출 */
extern struct efi {
efi_runtime_services_t *runtime; /* RT 서비스 테이블 */
unsigned long runtime_version; /* EFI 규격 버전 */
unsigned long mps; /* MPS 테이블 주소 */
unsigned long acpi; /* ACPI 1.0 RSDP */
unsigned long acpi20; /* ACPI 2.0+ XSDP */
unsigned long smbios; /* SMBIOS 테이블 */
unsigned long smbios3; /* SMBIOS 3.0+ */
/* ... */
} efi;
/* Runtime Services 호출 시 주의사항:
* - 가상 주소 매핑 필요 (SetVirtualAddressMap)
* - 일부 RT 호출은 non-preemptible
* - RT 서비스는 동시 호출 불가 (직렬화 필요)
* - efi_runtime_lock 으로 보호됨
*/
EFI System Partition (ESP)
ESP는 UEFI 부트로더, 드라이버, 유틸리티가 저장되는 특수 파티션입니다. FAT32 파일시스템을 사용하며, GPT 파티션 타입 GUID C12A7328-F81F-11D2-BA4B-00A0C93EC93B로 식별됩니다.
# ESP 구조 (전형적인 Linux 시스템)
/boot/efi/ # 또는 /efi/
├── EFI/
│ ├── BOOT/
│ │ └── BOOTX64.EFI # 폴백 부트로더 (기본 경로)
│ ├── fedora/ # 배포판별 디렉토리
│ │ ├── grubx64.efi # GRUB2 EFI 바이너리
│ │ ├── shimx64.efi # Secure Boot shim
│ │ └── grub.cfg # GRUB 설정 (또는 /boot/grub2/)
│ ├── ubuntu/
│ │ ├── grubx64.efi
│ │ ├── shimx64.efi
│ │ └── mmx64.efi # MOK Manager
│ └── tools/
│ └── memtest86.efi # 메모리 테스트 유틸리티
├── vmlinuz-6.x # EFI Stub 부팅 시 커널 이미지
└── initramfs-6.x.img # EFI Stub 부팅 시 initramfs
# ESP 관련 명령어
$ lsblk -o NAME,PARTTYPE,FSTYPE,MOUNTPOINT | grep -i efi
$ findmnt /boot/efi
$ efibootmgr -v # 부팅 항목 확인 (상세)
EFI 바이너리 형식
UEFI 애플리케이션과 드라이버는 PE32+ (Portable Executable) 형식을 사용합니다:
# EFI 바이너리 확인
$ file /boot/efi/EFI/fedora/grubx64.efi
grubx64.efi: PE32+ executable (EFI application) x86-64, for MS Windows
# PE 헤더 분석
$ objdump -x /boot/efi/EFI/fedora/shimx64.efi | head -30
# Subsystem: EFI Application (10)
# Machine: Advanced Micro Devices X86-64 (8664)
# 아키텍처별 기본 부팅 경로
x86_64 : \EFI\BOOT\BOOTX64.EFI
ARM64 : \EFI\BOOT\BOOTAA64.EFI
IA-32 : \EFI\BOOT\BOOTIA32.EFI
RISC-V : \EFI\BOOT\BOOTRISCV64.EFI
UEFI 변수 (efivars)
UEFI 변수는 NVRAM(비휘발성 메모리)에 저장되는 키-값 쌍으로, 부팅 설정, OS 데이터, Secure Boot 키 등을 포함합니다. Linux에서는 efivarfs를 통해 접근합니다.
# efivarfs 마운트 및 변수 목록
$ mount -t efivarfs efivarfs /sys/firmware/efi/efivars/
$ ls /sys/firmware/efi/efivars/ | head
Boot0000-8be4df61-93ca-11d2-aa0d-00e098032b8c
Boot0001-8be4df61-93ca-11d2-aa0d-00e098032b8c
BootCurrent-8be4df61-93ca-11d2-aa0d-00e098032b8c
BootOrder-8be4df61-93ca-11d2-aa0d-00e098032b8c
SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c
# 변수 이름 형식: 이름-벤더GUID
# 8be4df61-93ca-11d2-aa0d-00e098032b8c = EFI_GLOBAL_VARIABLE
efibootmgr — 부팅 관리
# 현재 부팅 항목 확인
$ efibootmgr -v
BootCurrent: 0001
Timeout: 3 seconds
BootOrder: 0001,0003,0002,0000
Boot0000* EFI Network PciRoot(0x0)/Pci(0x1f,0x6)/MAC(...)
Boot0001* Fedora HD(1,GPT,...)/File(\EFI\fedora\shimx64.efi)
Boot0002* Windows HD(1,GPT,...)/File(\EFI\Microsoft\Boot\bootmgfw.efi)
Boot0003* UEFI Shell FvVol(...)
# 새 부팅 항목 추가
$ efibootmgr -c -d /dev/sda -p 1 \
-L "Linux Direct" \
-l '\vmlinuz-6.x' \
-u 'root=/dev/sda2 ro quiet'
# 부팅 순서 변경
$ efibootmgr -o 0001,0002,0003
# 다음 부팅만 특정 항목으로
$ efibootmgr -n 0003 # 한 번만 UEFI Shell로 부팅
# 부팅 항목 삭제
$ efibootmgr -b 0003 -B
UEFI 변수 구조
/* include/linux/efi.h — UEFI 변수 속성 */
#define EFI_VARIABLE_NON_VOLATILE 0x00000001
#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x00000002
#define EFI_VARIABLE_RUNTIME_ACCESS 0x00000004
#define EFI_VARIABLE_HARDWARE_ERROR_RECORD 0x00000008
#define EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS 0x00000010
#define EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS 0x00000020
#define EFI_VARIABLE_APPEND_WRITE 0x00000040
/*
* efivarfs에서 변수 파일 형식:
* [4 bytes: attributes (little-endian)] + [가변 길이: 데이터]
*
* 예시: BootOrder 변수 읽기
* $ hexdump -C /sys/firmware/efi/efivars/BootOrder-8be4df61-...
* 00000000 07 00 00 00 01 00 03 00 02 00 00 00
* ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* attributes Boot0001, Boot0003, Boot0002, Boot0000
* (0x07=NV|BS|RT)
*/
/sys/firmware/efi/efivars/에서 변수를 삭제하면 NVRAM이 영구적으로 변경됩니다. 잘못된 변수 삭제는 시스템 부팅 불가를 초래할 수 있습니다. 특히 rm -rf /sys/firmware/efi/efivars/는 절대 실행하지 마세요 — 일부 시스템이 벽돌이 될 수 있습니다.
Secure Boot
Secure Boot은 UEFI 규격의 보안 기능으로, 펌웨어가 신뢰할 수 있는 키로 서명된 EFI 바이너리만 실행하도록 합니다. 부트킷과 루트킷을 방지하는 신뢰 체인(Chain of Trust)을 구축합니다.
키 계층 구조
/*
* Secure Boot 키 데이터베이스 계층:
*
* PK (Platform Key) ← 플랫폼 소유자 키 (1개만)
* │ OEM이 설정, 다른 키를 관리
* └── KEK (Key Exchange Key) ← PK가 서명한 키들
* │ OS 벤더가 db/dbx를 수정할 권한
* ├── db (Signature Database) ← 허용된 서명/해시 목록
* │ ├── Microsoft UEFI CA (Windows 부트로더 서명)
* │ ├── Microsoft 3rd Party CA (shim, GRUB 등 서명)
* │ └── 사용자 추가 인증서
* │
* └── dbx (Forbidden Database) ← 차단된 서명/해시 목록
* ├── 취소된 인증서
* └── 알려진 악성 바이너리 해시
*
* 검증 순서:
* 1. 바이너리의 서명 추출
* 2. dbx에 있으면 → 거부
* 3. db에 있으면 → 허용
* 4. 둘 다 없으면 → 거부
*/
Shim 부트로더
Shim은 Microsoft의 3rd Party UEFI CA로 서명된 미니 부트로더로, Linux 배포판의 Secure Boot를 가능하게 합니다:
/*
* Secure Boot 부팅 체인 (Linux):
*
* UEFI Firmware
* ↓ (db의 Microsoft 3rd Party CA로 서명 검증)
* shimx64.efi
* ↓ (내장된 배포판 키 또는 MOK으로 서명 검증)
* grubx64.efi
* ↓ (shim의 프로토콜을 통해 서명 검증)
* vmlinuz (커널)
* ↓ (커널 lockdown 모드 적용)
* 모듈 (.ko) — 서명 필요 (CONFIG_MODULE_SIG_FORCE)
*/
# Secure Boot 상태 확인
$ mokutil --sb-state
SecureBoot enabled
# Secure Boot 키 확인
$ mokutil --pk # Platform Key
$ mokutil --kek # Key Exchange Key
$ mokutil --db # Allowed Signature Database
$ mokutil --dbx # Forbidden Signature Database
MOK (Machine Owner Key)
MOK는 shim이 관리하는 추가 키 저장소로, 사용자가 자체 서명한 커널이나 모듈을 Secure Boot 환경에서 사용할 수 있게 합니다:
# MOK 키 생성 및 등록
# 1. 키 쌍 생성
$ openssl req -new -x509 -newkey rsa:2048 \
-keyout MOK.priv -outform DER -out MOK.der \
-nodes -days 36500 \
-subj "/CN=My Module Signing Key/"
# 2. MOK 등록 요청 (재부팅 시 MokManager에서 확인)
$ mokutil --import MOK.der
# 비밀번호 입력 (재부팅 시 동일 비밀번호 필요)
# 3. 재부팅 → MokManager 화면에서 키 등록 확인
# 4. 커널 모듈 서명
$ /usr/src/kernels/$(uname -r)/scripts/sign-file \
sha256 MOK.priv MOK.der my_module.ko
# MOK 목록 확인
$ mokutil --list-enrolled
# MOK 삭제
$ mokutil --delete MOK.der
Kernel Lockdown 모드
Secure Boot가 활성화되면 커널은 자동으로 lockdown 모드에 진입하여, 커널 무결성을 우회할 수 있는 기능을 제한합니다:
/* security/lockdown/lockdown.c */
/*
* Lockdown 모드에서 제한되는 기능:
*
* [integrity] 모드 (기본):
* - /dev/mem, /dev/kmem 쓰기 금지
* - /dev/port 접근 금지
* - kexec_load() 비서명 커널 금지
* - 커널 모듈 서명 필수 (CONFIG_MODULE_SIG_FORCE)
* - ACPI 테이블 오버라이드 금지
* - ioperm/iopl 금지
*
* [confidentiality] 모드:
* - integrity 모드의 모든 제한 포함
* - /proc/kcore 접근 금지
* - perf 이벤트 접근 제한
* - tracefs 접근 금지
* - BPF 접근 제한
*/
# lockdown 상태 확인
$ cat /sys/kernel/security/lockdown
[none] integrity confidentiality
# dmesg에서 lockdown 메시지 확인
$ dmesg | grep -i lockdown
Lockdown: Kernel is locked down from EFI Secure Boot;
see man kernel_lockdown.7
Linux EFI Stub
EFI Stub은 리눅스 커널이 직접 UEFI 애플리케이션으로 동작할 수 있게 하는 기능입니다. GRUB 같은 별도 부트로더 없이 UEFI가 직접 커널을 실행합니다.
/* CONFIG_EFI_STUB=y 활성화 시:
*
* bzImage/vmlinuz에 PE32+ 헤더가 포함됨
* → UEFI 펌웨어가 EFI 애플리케이션으로 인식/실행 가능
*
* 장점:
* - GRUB 없이 직접 부팅 → 부팅 시간 단축
* - 공격 표면 감소 (부트로더 취약점 제거)
* - 간결한 부팅 체인
*
* 진입점: drivers/firmware/efi/libstub/
*/
EFI Stub 부팅 흐름
/* drivers/firmware/efi/libstub/ — EFI Stub 실행 순서 */
/* 1. UEFI가 커널 PE 이미지를 메모리에 로드 */
/* 2. efi_pe_entry() 진입 (x86) 또는 efi_entry() (ARM64) */
efi_status_t __efiapi efi_pe_entry(
efi_handle_t handle,
efi_system_table_t *sys_table_arg)
{
/* 3. System Table 포인터 저장 */
efi_system_table = sys_table_arg;
/* 4. 커널 커맨드 라인 처리 */
efi_handle_cmdline(image, &cmdline_ptr);
/* 5. initrd 로드 (LINUX_EFI_INITRD_MEDIA_GUID 또는 파일) */
efi_load_initrd(image, ...);
/* 6. 커널 이미지 재배치 (KASLR 적용) */
efi_relocate_kernel(...);
/* 7. GOP(Graphics Output Protocol)에서 프레임버퍼 정보 획득 */
efi_setup_gop(...);
/* 8. EFI Memory Map 획득 */
status = efi_get_memory_map(&map);
/* 9. ExitBootServices() 호출 */
efi_exit_boot_services(handle, &map);
/* 10. 일반 커널 부팅 경로로 진입 */
/* → startup_32/startup_64 (x86) */
/* → primary_entry (ARM64) */
}
EFI Stub 직접 부팅 설정
# EFI Stub 직접 부팅 설정
# 1. 커널에 CONFIG_EFI_STUB=y 확인
$ grep EFI_STUB /boot/config-$(uname -r)
CONFIG_EFI_STUB=y
# 2. 커널과 initramfs를 ESP에 복사
$ cp /boot/vmlinuz-6.x /boot/efi/
$ cp /boot/initramfs-6.x.img /boot/efi/
# 3. efibootmgr로 부팅 항목 등록
$ efibootmgr -c -d /dev/nvme0n1 -p 1 \
-L "Linux EFI Stub" \
-l '\vmlinuz-6.x' \
-u 'root=UUID=xxxx-xxxx ro quiet \
initrd=\initramfs-6.x.img'
# 또는 systemd-boot 사용 (loader.conf + entries/)
# /boot/efi/loader/entries/linux.conf:
title Linux 6.x
linux /vmlinuz-6.x
initrd /initramfs-6.x.img
options root=UUID=xxxx-xxxx ro quiet
EFI Memory Map
UEFI 펌웨어가 GetMemoryMap()을 통해 제공하는 물리 메모리 맵은 커널의 메모리 관리 초기화에 핵심입니다. Legacy BIOS의 E820 맵을 대체합니다.
EFI 메모리 타입
| 타입 | 값 | 설명 | OS 사용 |
|---|---|---|---|
EfiReservedMemoryType | 0 | 사용 불가 (펌웨어 예약) | 접근 금지 |
EfiLoaderCode | 1 | 부트로더 코드 | ExitBS 후 사용 가능 |
EfiLoaderData | 2 | 부트로더 데이터 | ExitBS 후 사용 가능 |
EfiBootServicesCode | 3 | Boot Services 코드 | ExitBS 후 사용 가능 |
EfiBootServicesData | 4 | Boot Services 데이터 | ExitBS 후 사용 가능 |
EfiRuntimeServicesCode | 5 | Runtime Services 코드 | 보존 필수 |
EfiRuntimeServicesData | 6 | Runtime Services 데이터 | 보존 필수 |
EfiConventionalMemory | 7 | 사용 가능한 일반 메모리 | 자유 사용 |
EfiUnusableMemory | 8 | 오류 있는 메모리 | 접근 금지 |
EfiACPIReclaimMemory | 9 | ACPI 테이블 메모리 | ACPI 파싱 후 회수 가능 |
EfiACPINVS | 10 | ACPI NVS 메모리 | 보존 필수 |
EfiMemoryMappedIO | 11 | MMIO 영역 | 디바이스 I/O용 |
EfiPersistentMemory | 14 | 비휘발성 메모리 (NVDIMM) | DAX 사용 가능 |
# dmesg에서 EFI 메모리 맵 확인
$ dmesg | grep "efi: mem"
efi: mem00: [Conventional Memory| | | | | | | |WB|WT|WC|UC] range=[0x0000000000000000-0x000000000009ffff] (0MB)
efi: mem01: [Reserved | | | | | | | |WB|WT|WC|UC] range=[0x000000000009f000-0x00000000000fffff] (0MB)
efi: mem02: [Loader Data | | | | | | | |WB|WT|WC|UC] range=[0x0000000000100000-0x0000000001ffffff] (31MB)
efi: mem10: [Runtime Data |RUN| | | | | | |WB|WT|WC|UC] range=[0x000000007b000000-0x000000007b0fffff] (1MB)
# RUN = Runtime Services 영역 (커널이 보존해야 함)
# 속성 플래그:
# WB = Write-Back 캐싱
# WT = Write-Through
# WC = Write-Combining
# UC = Uncacheable
# WP = Write-Protected
# RP = Read-Protected
# XP = Execute-Protected
# RUN = Runtime (SetVirtualAddressMap 매핑 필요)
SetVirtualAddressMap()
/* arch/x86/platform/efi/efi.c */
/*
* ExitBootServices() 후, Runtime Services 코드/데이터는
* 물리 주소에 매핑된 상태입니다.
*
* 커널은 SetVirtualAddressMap()을 호출하여
* Runtime Services 영역을 커널 가상 주소 공간에 매핑합니다.
* 이후 펌웨어의 RT 함수들은 가상 주소를 사용합니다.
*
* 이 호출은 부팅 중 한 번만 가능하며,
* 호출 후에는 되돌릴 수 없습니다.
*/
static void __init efi_map_regions(void)
{
/* EFI 메모리 맵의 Runtime 영역만 가상 주소 매핑 */
for_each_efi_memory_desc(md) {
if (!(md->attribute & EFI_MEMORY_RUNTIME))
continue;
/* ioremap 또는 memremap으로 매핑 */
efi_map_region(md);
}
}
/* Runtime Services 가상 주소 설정 */
efi.runtime->SetVirtualAddressMap(
map_size, desc_size, desc_ver, virtual_map);
커널 UEFI 구현
소스 트리
| 경로 | 설명 |
|---|---|
drivers/firmware/efi/ | UEFI 코어 — Runtime Services, efivars, 시스템 테이블 |
drivers/firmware/efi/libstub/ | EFI Stub 부트 코드 (아키텍처 독립) |
arch/x86/boot/compressed/efi_mixed.S | x86 32/64-bit UEFI mixed 모드 진입 |
arch/x86/platform/efi/ | x86 UEFI 플랫폼 코드 (메모리 맵, 초기화) |
arch/arm64/kernel/efi.c | ARM64 UEFI 지원 |
include/linux/efi.h | UEFI 자료구조, 프로토콜 GUID, 매크로 |
fs/efivarfs/ | efivarfs 파일시스템 구현 |
security/integrity/platform_certs/ | Secure Boot 인증서 처리 |
관련 CONFIG 옵션
# 필수
CONFIG_EFI=y # UEFI 런타임 서비스 지원
CONFIG_EFI_STUB=y # EFI Stub (직접 부팅)
CONFIG_EFI_VARS=y # UEFI 변수 접근 (/sys/firmware/efi/)
CONFIG_EFIVAR_FS=y # efivarfs 파일시스템
# Secure Boot
CONFIG_EFI_SECURE_BOOT_SIG_ENFORCE=y # Secure Boot 서명 강제
CONFIG_MODULE_SIG=y # 모듈 서명 지원
CONFIG_MODULE_SIG_FORCE=y # 서명 없는 모듈 거부
CONFIG_SECURITY_LOCKDOWN_LSM=y # Lockdown LSM
# 기타
CONFIG_EFI_MIXED=y # 32-bit UEFI + 64-bit 커널 (x86)
CONFIG_EFI_CAPSULE_LOADER=m # 펌웨어 업데이트 캡슐
CONFIG_EFI_RCI2_TABLE=y # RCI2 테이블 지원
CONFIG_EFI_ESRT=y # EFI System Resource Table
CONFIG_FB_EFI=y # EFI 프레임버퍼
CONFIG_EFI_EARLYCON=y # EFI 초기 콘솔
캡슐 업데이트 (Firmware Update)
UEFI 캡슐 업데이트는 OS에서 펌웨어(BIOS)를 업데이트하는 표준 메커니즘입니다. Linux에서는 fwupd 서비스가 이를 활용합니다.
/*
* 캡슐 업데이트 흐름:
*
* 1. OS가 UpdateCapsule() Runtime Service 호출
* 또는 EFI 변수에 캡슐 위치 기록
* 2. 시스템 재부팅
* 3. 펌웨어가 캡슐을 감지하고 업데이트 수행
* 4. 업데이트 결과를 EFI 변수에 기록
* 5. OS 부팅 후 결과 확인
*/
# fwupd를 통한 펌웨어 업데이트
$ fwupdmgr get-devices # 업데이트 가능 장치 목록
$ fwupdmgr get-updates # 사용 가능한 업데이트 확인
$ fwupdmgr update # 업데이트 적용 (재부팅 필요)
# ESRT (EFI System Resource Table) 확인
$ cat /sys/firmware/efi/esrt/entries/entry0/fw_version
$ cat /sys/firmware/efi/esrt/entries/entry0/last_attempt_status
# 0 = 성공, 1 = 리소스 부족, 2 = 잘못된 형식 ...
# 수동 캡슐 업데이트 (커널 인터페이스)
# CONFIG_EFI_CAPSULE_LOADER=m 필요
$ modprobe efi_capsule_loader
$ cat firmware.cap > /dev/efi_capsule_loader
# 재부팅 시 펌웨어가 캡슐 적용
ACPI와 UEFI의 관계
ACPI(Advanced Configuration and Power Interface) 테이블은 UEFI 시스템에서 EFI Configuration Table을 통해 커널에 전달됩니다.
/* UEFI System Table → Configuration Table에서 ACPI 테이블 위치 */
/*
* UEFI System Table
* └── ConfigurationTable[]
* ├── {EFI_ACPI_20_TABLE_GUID, XSDP 주소} ← ACPI 2.0+
* ├── {EFI_ACPI_TABLE_GUID, RSDP 주소} ← ACPI 1.0
* ├── {SMBIOS_TABLE_GUID, SMBIOS 주소}
* ├── {EFI_MEMATTR_TABLE_GUID, 주소}
* └── ...
*
* Legacy BIOS에서는:
* RSDP를 0xE0000-0xFFFFF 범위에서 검색
*
* UEFI에서는:
* Configuration Table에서 GUID로 정확히 찾음
*/
# ACPI 테이블 확인
$ dmesg | grep ACPI
ACPI: RSDP 0x000000007BF7E014 000024 (v02 DELL )
ACPI: XSDT 0x000000007BF7D0E8 0000CC (v01 DELL ...)
ACPI: FACP 0x000000007BF49000 00010C (v06 DELL ...)
ACPI: DSDT 0x000000007BF0A000 03D2B3 (v02 DELL ...)
# EFI 시스템 테이블 정보
$ dmesg | grep "efi:"
efi: EFI v2.70 by American Megatrends
efi: ACPI 2.0=0x7bf7e014 ACPI=0x7bf7e000 SMBIOS=0x7bf9f000
SMBIOS 3.0=0x7bf9e000 ESRT=0x7ac4c018 MOKvar=0x7ac1e000
UEFI 디버깅
dmesg 확인
# EFI 관련 커널 메시지
$ dmesg | grep -i efi
# EFI 부팅 여부 확인
$ [ -d /sys/firmware/efi ] && echo "UEFI" || echo "BIOS"
# EFI 런타임 서비스 상태
$ dmesg | grep "EFI runtime"
efi: EFI Runtime Services are enabled
# EFI 런타임 서비스 비활성화 시 (efi=noruntime)
efi: EFI Runtime Services are disabled!
# Secure Boot 상태
$ dmesg | grep -i "secure boot"
secureboot: Secure boot enabled
# EFI Stub 메시지 (직접 부팅 시)
$ dmesg | grep "EFI stub"
EFI stub: Loaded initrd from LINUX_EFI_INITRD_MEDIA_GUID
EFI stub: Measured initrd data into PCR 9
EFI stub: KASLR enabled
EFI stub: Using DTB from configuration table
EFI stub: Exiting boot services...
디버깅 도구
# efivar — UEFI 변수 직접 조작
$ efivar -l # 모든 변수 나열
$ efivar -p -n 8be4df61-93ca-11d2-aa0d-00e098032b8c-SecureBoot
# OVMF — QEMU용 UEFI 펌웨어 (개발/디버깅)
$ qemu-system-x86_64 \
-bios /usr/share/OVMF/OVMF_CODE.fd \
-drive if=pflash,format=raw,file=OVMF_VARS.fd \
-drive file=disk.img,format=qcow2 \
-debugcon file:debug.log -global isa-debugcon.iobase=0x402
# OVMF 디버그 출력이 debug.log에 기록됨
# EFI Shell에서 디버깅
Shell> map -b # 블록 디바이스 매핑
Shell> bcfg boot dump # 부팅 항목 덤프
Shell> dmpstore BootOrder # 변수 내용 덤프
Shell> memmap # EFI 메모리 맵
Shell> drivers # 로드된 드라이버 목록
Shell> devtree # 디바이스 트리
# 커널 EFI 디버그 파라미터
# efi=debug → EFI 관련 상세 로그
# efi=noruntime → Runtime Services 비활성화
# efi=old_map → 이전 방식 메모리 매핑 (호환성)
# efi=nochunk → 메모리 맵 분할 비활성화
자주 발생하는 UEFI 문제
| 증상 | 원인 | 해결 |
|---|---|---|
| 부팅 시 "Secure Boot violation" | 서명되지 않은 부트로더/커널 | MOK 등록 또는 Secure Boot 비활성화 |
| efibootmgr 명령 실패 | efivarfs 미마운트 또는 권한 부족 | mount -t efivarfs efivarfs /sys/firmware/efi/efivars/ |
| Runtime Services 크래시 | 펌웨어 버그, 메모리 매핑 문제 | efi=noruntime 또는 펌웨어 업데이트 |
| ESP를 찾지 못함 | GPT 파티션 타입 잘못 설정 | gdisk로 파티션 타입 EF00 확인 |
| UEFI Shell로 빠짐 | 부팅 항목 경로 오류 | \EFI\BOOT\BOOTX64.EFI 존재 확인 |
| 32-bit UEFI + 64-bit 커널 | Mixed mode 미지원 | CONFIG_EFI_MIXED=y 또는 32-bit 커널 |
| NVRAM 가득 참 | 변수 축적 (오류 로그 등) | 불필요 변수 삭제, 펌웨어 NVRAM 초기화 |
-DDEBUG_ON_SERIAL_PORT)를 사용하면 펌웨어 내부 동작을 시리얼 콘솔로 추적할 수 있습니다.
UEFI 프로토콜 모델 심화
UEFI의 핵심 설계 철학은 프로토콜(Protocol) 기반 추상화입니다. 모든 하드웨어 접근과 서비스는 GUID로 식별되는 프로토콜 인터페이스를 통해 이루어집니다. 이는 객체지향의 인터페이스 패턴과 유사합니다.
프로토콜 아키텍처
/* UEFI 프로토콜 = GUID + 함수 포인터 테이블 (C 인터페이스)
*
* 핵심 개념:
* Handle — 프로토콜 인스턴스를 담는 컨테이너 (불투명 포인터)
* Protocol — GUID로 식별되는 함수 포인터 구조체
* Handle Database — 모든 핸들-프로토콜 매핑을 관리하는 전역 데이터베이스
*
* Handle ─┬── Protocol A (GUID_A → 함수 테이블 A)
* ├── Protocol B (GUID_B → 함수 테이블 B)
* └── Protocol C (GUID_C → 함수 테이블 C)
*/
/* 예: Block I/O Protocol — 디스크 섹터 읽기/쓰기 */
typedef struct _EFI_BLOCK_IO_PROTOCOL {
UINT64 Revision;
EFI_BLOCK_IO_MEDIA *Media; /* 디스크 정보 */
EFI_BLOCK_RESET Reset; /* 디바이스 리셋 */
EFI_BLOCK_READ ReadBlocks; /* 섹터 읽기 */
EFI_BLOCK_WRITE WriteBlocks; /* 섹터 쓰기 */
EFI_BLOCK_FLUSH FlushBlocks; /* 캐시 플러시 */
} EFI_BLOCK_IO_PROTOCOL;
/* GUID: 964E5B21-6459-11D2-8E39-00A0C969723B */
/* 프로토콜 사용 흐름 */
EFI_BLOCK_IO_PROTOCOL *BlockIo;
EFI_HANDLE *HandleBuffer;
UINTN HandleCount;
/* 1. Block I/O 프로토콜을 제공하는 모든 핸들 검색 */
gBS->LocateHandleBuffer(
ByProtocol,
&gEfiBlockIoProtocolGuid, /* 찾을 프로토콜 GUID */
NULL,
&HandleCount, /* 발견된 핸들 수 */
&HandleBuffer /* 핸들 배열 */
);
/* 2. 핸들에서 프로토콜 인터페이스 획득 */
gBS->HandleProtocol(
HandleBuffer[0], /* 첫 번째 디스크 핸들 */
&gEfiBlockIoProtocolGuid,
(VOID **)&BlockIo /* 프로토콜 인터페이스 반환 */
);
/* 3. 프로토콜 함수 호출 */
BlockIo->ReadBlocks(BlockIo, MediaId, Lba, BufferSize, Buffer);
Handle Database와 프로토콜 검색
/* Handle Database 구조 (EDK II 구현)
*
* gHandleList (이중 연결 리스트)
* │
* ├── IHANDLE {
* │ Signature: 'hndl'
* │ AllHandles: (리스트 링크)
* │ Protocols: ─── PROTOCOL_INTERFACE {
* │ Key: uint64 Protocol: → PROTOCOL_ENTRY (GUID)
* │ } Interface: → 프로토콜 함수 테이블
* │ OpenList: → 열린 프로토콜 추적
* │ }
* ├── IHANDLE { ... }
* └── IHANDLE { ... }
*
* 검색 방법 3가지:
* ByProtocol — 특정 GUID를 제공하는 모든 핸들
* AllHandles — 모든 핸들
* ByRegisterNotify — 프로토콜 등록 이벤트 통지
*/
/* 주요 Boot Services 프로토콜 조작 함수 */
gBS->InstallProtocolInterface( /* 핸들에 프로토콜 설치 */
&Handle, &ProtocolGuid, EFI_NATIVE_INTERFACE, Interface);
gBS->OpenProtocol( /* 프로토콜 열기 (참조 추적) */
Handle, &ProtocolGuid, &Interface,
AgentHandle, ControllerHandle, Attributes);
/* Attributes:
* BY_HANDLE_PROTOCOL — 단순 조회
* GET_PROTOCOL — 독점 접근
* BY_DRIVER — 드라이버 바인딩
* BY_CHILD_CONTROLLER — 자식 컨트롤러 관리
* EXCLUSIVE — 배타적 접근 (기존 BY_DRIVER 해제) */
gBS->InstallMultipleProtocolInterfaces( /* 여러 프로토콜 원자적 설치 */
&Handle,
&gEfiBlockIoProtocolGuid, &BlockIo,
&gEfiDiskInfoProtocolGuid, &DiskInfo,
NULL);
gBS->LocateProtocol( /* 첫 번째 일치 프로토콜 반환 */
&ProtocolGuid, Registration, &Interface);
UEFI Driver Binding Protocol
/* UEFI Driver Model — 핫플러그/드라이버 재사용 지원
*
* 모든 UEFI 드라이버는 Driver Binding Protocol을 구현:
* Supported() — 이 컨트롤러를 지원하는지 검사
* Start() — 드라이버 바인딩 (프로토콜 설치)
* Stop() — 드라이버 분리 (프로토콜 제거)
*/
typedef struct _EFI_DRIVER_BINDING_PROTOCOL {
EFI_DRIVER_BINDING_SUPPORTED Supported;
EFI_DRIVER_BINDING_START Start;
EFI_DRIVER_BINDING_STOP Stop;
UINT32 Version;
EFI_HANDLE ImageHandle;
EFI_HANDLE DriverBindingHandle;
} EFI_DRIVER_BINDING_PROTOCOL;
/* 드라이버 디스패치 흐름:
*
* 1. DXE Dispatcher가 FV(Firmware Volume)에서 드라이버 이미지 로드
* 2. 드라이버 EntryPoint() 실행 → Driver Binding Protocol 등록
* 3. BDS 또는 ConnectController()가 컨트롤러 연결 시도:
* a. 등록된 모든 Driver Binding의 Supported() 호출
* b. TRUE 반환한 드라이버의 Start() 호출
* c. Start()에서 하위 프로토콜 소비 + 상위 프로토콜 생산
*
* 예: AHCI 드라이버
* Supported(): PCI I/O Protocol → Class Code 0x0106(AHCI) 확인
* Start(): PCI I/O 소비 → Block I/O Protocol 생산
*
* 드라이버 스택:
* PCI Host Bridge → PCI Bus → AHCI Controller → AHCI Bus → Disk
* 각 계층이 하위 프로토콜을 소비하고 상위 프로토콜을 생산 */
핵심 UEFI 프로토콜 목록
| 프로토콜 | 용도 | 커널 관련성 |
|---|---|---|
EFI_BLOCK_IO_PROTOCOL | 블록 디바이스 섹터 I/O | EFI Stub 커널/initrd 로드 |
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL | FAT32 파일 접근 | ESP에서 파일 읽기 |
EFI_GRAPHICS_OUTPUT_PROTOCOL | 프레임버퍼/해상도 제어 | efifb/simplefb 드라이버 |
EFI_PCI_IO_PROTOCOL | PCI 장치 접근 | 부팅 전 PCI 장치 초기화 |
EFI_SIMPLE_NETWORK_PROTOCOL | 네트워크 인터페이스 | PXE/HTTP Boot |
EFI_LOAD_FILE2_PROTOCOL | initrd 로드 (커널 5.7+) | EFI Stub initrd 메커니즘 |
EFI_DEVICE_PATH_PROTOCOL | 장치 경로 표현 | 부팅 장치 식별 |
EFI_RNG_PROTOCOL | 하드웨어 난수 생성 | KASLR 엔트로피 소스 |
EFI_TCG2_PROTOCOL | TPM 2.0 접근 | Measured Boot PCR 측정 |
EFI_MEMORY_ATTRIBUTE_PROTOCOL | 메모리 속성 조회/설정 | W^X 메모리 보호 |
Device Path 프로토콜
Device Path는 UEFI에서 장치의 물리적/논리적 위치를 표현하는 데이터 구조입니다. 부팅 항목, 드라이버 바인딩, 파일 경로 등에 광범위하게 사용됩니다.
/* Device Path = 노드 체인 (연결 리스트처럼 순차적 노드 나열)
*
* 각 노드: Type(1B) + SubType(1B) + Length(2B) + Data(가변)
* 체인 끝: End Device Path 노드 (Type=0x7F, SubType=0xFF)
*/
typedef struct {
UINT8 Type; /* 노드 타입 */
UINT8 SubType; /* 하위 타입 */
UINT16 Length; /* 노드 전체 길이 (헤더 포함) */
} EFI_DEVICE_PATH_PROTOCOL;
/* 주요 노드 타입 */
0x01 Hardware Device Path
0x01 PCI /* Function, Device 번호 */
0x04 Memory Mapped
0x02 ACPI Device Path
0x01 ACPI /* HID, UID (예: PNP0A03=PCI Host Bridge) */
0x03 Messaging Device Path
0x01 ATAPI /* Primary/Secondary, Master/Slave */
0x02 SCSI /* Target, LUN */
0x05 USB /* Port, Interface */
0x0C MAC Address /* 네트워크 장치 */
0x12 SATA /* Port, Multiplier, LUN */
0x17 NVMe /* Namespace ID, EUI-64 */
0x18 URI /* HTTP Boot 대상 URI */
0x04 Media Device Path
0x01 Hard Drive /* 파티션 번호, GPT/MBR, 파티션 GUID */
0x04 File Path /* \EFI\fedora\shimx64.efi */
0x7F End Device Path
0xFF End Entire /* 체인 종료 */
/* 전형적인 NVMe 디스크 부팅 경로:
*
* PciRoot(0x0)/Pci(0x1D,0x0)/Pci(0x0,0x0)/NVMe(0x1,...)/
* HD(1,GPT,C12A7328-...)/File(\EFI\fedora\shimx64.efi)
*
* ← ACPI → ← PCI Bus → ← PCI Func → ← NVMe NS →
* ← GPT Partition 1 → ← ESP 내 파일 경로 →
*/
/* efibootmgr -v 출력의 Device Path 해석 예시:
*
* Boot0001* Fedora
* HD(1,GPT,abcd1234-...,0x800,0x100000)/File(\EFI\fedora\shimx64.efi)
* ^^ ^^^^^^^^^^^^^^^^ ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
* 파티션1 MBR/GPT 파티션GUID 시작LBA,크기 파일경로
*/
GOP (Graphics Output Protocol) 심화
GOP는 UEFI에서 VGA BIOS(INT 10h)를 대체하는 그래픽 출력 인터페이스입니다. 커널은 ExitBootServices() 전에 GOP에서 프레임버퍼 정보를 획득하고, 이후 efifb/simplefb 드라이버가 이를 사용합니다.
/* GOP 프로토콜 구조 */
typedef struct _EFI_GRAPHICS_OUTPUT_PROTOCOL {
EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE QueryMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE SetMode;
EFI_GRAPHICS_OUTPUT_PROTOCOL_BLT Blt; /* Block Transfer */
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode; /* 현재 모드 정보 */
} EFI_GRAPHICS_OUTPUT_PROTOCOL;
typedef struct {
UINT32 MaxMode;
UINT32 Mode; /* 현재 모드 번호 */
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
UINTN SizeOfInfo;
EFI_PHYSICAL_ADDRESS FrameBufferBase; /* 프레임버퍼 물리 주소 */
UINTN FrameBufferSize;
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;
typedef struct {
UINT32 Version;
UINT32 HorizontalResolution; /* 예: 1920 */
UINT32 VerticalResolution; /* 예: 1080 */
EFI_GRAPHICS_PIXEL_FORMAT PixelFormat;
EFI_PIXEL_BITMASK PixelInformation; /* 커스텀 포맷 시 */
UINT32 PixelsPerScanLine; /* stride (패딩 포함) */
} EFI_GRAPHICS_OUTPUT_MODE_INFORMATION;
/* PixelFormat 종류 */
PixelRedGreenBlueReserved8BitPerColor /* RGBx (32bpp) */
PixelBlueGreenRedReserved8BitPerColor /* BGRx (32bpp) — 가장 일반적 */
PixelBitMask /* PixelInformation의 비트마스크 사용 */
PixelBltOnly /* 직접 프레임버퍼 접근 불가, Blt()만 사용 */
/* drivers/firmware/efi/libstub/gop.c — EFI Stub에서 GOP 정보 수집 */
static efi_status_t setup_gop(struct screen_info *si,
efi_guid_t *proto, unsigned long size,
void **handles)
{
/* 1. 모든 GOP 핸들 순회하여 프레임버퍼가 있는 것 선택 */
for (i = 0; i < nr_gops; i++) {
efi_gop_t *gop;
status = efi_bs_call(HandleProtocol, handles[i], proto, (void **)&gop);
/* PixelBltOnly는 직접 프레임버퍼 접근 불가 → 건너뜀 */
if (info->pixel_format == PIXEL_BLT_ONLY)
continue;
/* 가장 높은 해상도의 GOP 선택 */
if (pixels > best_pixels) {
best = gop;
best_pixels = pixels;
}
}
/* 2. screen_info 구조체에 프레임버퍼 정보 기록 */
si->orig_video_isVGA = VIDEO_TYPE_EFI;
si->lfb_base = fb_base; /* 프레임버퍼 물리 주소 */
si->lfb_size = fb_size;
si->lfb_width = info->horizontal_resolution;
si->lfb_height = info->vertical_resolution;
si->lfb_linelength = info->pixels_per_scan_line * 4;
si->lfb_depth = 32;
/* → boot_params.screen_info → efifb/simplefb 드라이버가 사용 */
}
/* GOP → 커널 디스플레이 드라이버 전달 경로:
*
* [UEFI 펌웨어]
* GOP.FrameBufferBase/Size + 해상도/픽셀 포맷
* ↓ (EFI Stub이 screen_info에 기록)
* [boot_params.screen_info]
* ↓
* [efifb 또는 simplefb 드라이버] → /dev/fb0
* ↓ (DRM 드라이버 로드 시)
* [i915/amdgpu/nouveau 등 DRM] → 네이티브 해상도 전환
*
* efifb는 모드 변경 불가 (ExitBootServices 후 GOP 사용 불가)
* → GOP가 설정한 해상도로 고정, DRM 드라이버가 로드될 때까지 유지 */
EFI 프레임버퍼 해상도 변경: 커널 파라미터 video=efifb:mode=1로 GOP 모드를 지정할 수 있으나, 이는 EFI Stub이 ExitBootServices() 호출 전에 GOP.SetMode()를 실행할 때만 유효합니다. GRUB에서는 gfxpayload=1920x1080으로 설정합니다.
SMM (System Management Mode)
SMM은 x86의 가장 높은 권한 수준(-2 ring)에서 실행되는 특수 CPU 모드입니다. OS와 하이퍼바이저도 SMM 내부를 볼 수 없으며, 펌웨어 전용 기능(전원 관리, 하드웨어 에러 처리 등)에 사용됩니다.
/* SMM 아키텍처 개요
*
* 권한 레벨 (Ring):
* Ring 3 — 유저스페이스 프로세스
* Ring 0 — OS 커널
* Ring -1 — 하이퍼바이저 (VMX root)
* Ring -2 — SMM (System Management Mode) ← 가장 높은 권한
* Ring -3 — Intel ME / AMD PSP (별도 프로세서)
*
* SMM 진입:
* 1. SMI(System Management Interrupt) 발생
* - 하드웨어 SMI: 전원 버튼, 온도 알림, 하드웨어 에러
* - 소프트웨어 SMI: I/O 포트 0xB2 쓰기로 트리거
* 2. CPU가 현재 상태를 SMRAM에 저장 (State Save Area)
* 3. SMBASE(기본 0x30000)의 SMI 핸들러로 점프
* 4. 16-bit 리얼 모드와 유사한 환경에서 시작
* → 즉시 보호 모드/롱 모드로 전환 (현대 구현)
* 5. RSM(Resume) 명령으로 원래 상태 복귀
*/
/* SMRAM (System Management RAM)
*
* SMM 코드/데이터가 저장되는 보호 메모리 영역
* 일반적으로 TOLUD(Top of Low Usable DRAM) 근처에 위치
* 물리 주소 예: 0x7B000000 ~ 0x7BFFFFFF (16MB)
*
* OS에서 접근 불가:
* - SMRAM 영역은 메모리 컨트롤러가 하드웨어적으로 보호
* - SMRR(System Management Range Register) MSR로 캐시 속성 제어
* - D_OPEN/D_LOCK 비트가 설정되면 DMA로도 접근 불가
*
* State Save Area (RSM 복귀용):
* SMBASE + 0xFE00 ~ 0xFFFF 영역
* RAX~R15, RIP, RFLAGS, CR0~CR4, GDTR, IDTR, ... 모두 저장
*/
/* SMM에서 처리하는 주요 작업 */
1. ACPI SCI 에뮬레이션 /* 레거시 하드웨어 이벤트 → ACPI SCI 변환 */
2. 전원 버튼 처리 /* 전원 버튼 → S3/S4/S5 상태 전환 */
3. 온도 관리 (TCO) /* 과열 감지 → 스로틀링/셧다운 */
4. 에러 로깅 /* ECC 메모리 에러 → 로그 기록 */
5. UEFI 변수 쓰기 /* SPI Flash 접근 (일부 펌웨어) */
6. USB 레거시 에뮬레이션 /* USB 키보드 → PS/2 에뮬레이션 (부팅 시) */
/* 커널과 SMM의 상호작용 */
/* 1. SMI 트리거 (소프트웨어) — 포트 0xB2 쓰기 */
outb(smi_cmd, APM_CNT); /* APM_CNT = 0xB2, smi_cmd = ACPI FADT에서 획득 */
/* 커널의 acpi_enable()에서 ACPI 모드 전환 시 사용 */
/* 2. SMI 지연 시간 문제 — 커널 관점
*
* SMI가 발생하면 CPU가 "도둑맞음" — OS가 인지 못하는 시간 공백
* 실시간(RT) 워크로드에서 치명적:
* - SMI 처리 시간: 수십 us ~ 수 ms (펌웨어에 따라 다름)
* - 이 시간 동안 인터럽트 처리, 스케줄링 모두 정지
* - hwlatdetect/cyclictest로 탐지 가능
*
* 커널 대응:
* - CONFIG_X86_SME_PROTECTION (탐지)
* - tsc=reliable → SMI에 의한 TSC 왜곡 무시
* - msr 0x1AD: MSR_SMI_COUNT (SMI 발생 횟수)
*/
# SMI 카운트 확인 (Intel)
$ rdmsr 0x34 # MSR_SMI_COUNT — 총 SMI 발생 횟수
$ rdmsr 0x34 # 일정 시간 후 다시 읽어 증가분 확인
# SMI 지연 탐지
$ hwlatdetect --duration=60 --threshold=10
# 10us 이상 하드웨어 지연 탐지 (SMI가 주 원인)
# BIOS 설정에서 불필요한 SMI 소스 비활성화:
# - USB Legacy Support: Disabled
# - Serial Port SMI: Disabled
# - Periodic SMI: Disabled (가능한 경우)
SMM 보안 위협: SMM은 OS보다 높은 권한에서 실행되므로, SMRAM에 악성 코드가 주입되면 OS 수준에서 탐지/방어가 불가능합니다. 이를 SMM 루트킷이라 하며, Intel은 이를 방지하기 위해 STM(SMI Transfer Monitor), SMRR, SMM_BWP(BIOS Write Protection) 등의 하드웨어 보호 메커니즘을 제공합니다.
플랫폼 보안 기술
현대 시스템은 UEFI Secure Boot 외에도 하드웨어 수준의 부팅 무결성 보장 기술을 갖추고 있습니다. 이들은 커널보다 먼저 실행되어 펌웨어 자체의 무결성을 검증합니다.
Intel Boot Guard
/* Intel Boot Guard — CPU 마이크로코드 수준의 펌웨어 검증
*
* OEM이 공장에서 CPU FPF(Field Programmable Fuse)에 키 해시를 퓨징
* → 이후 해당 키로 서명되지 않은 펌웨어는 실행 불가 (영구적, 변경 불가)
*
* 두 가지 프로파일:
*
* 1. Verified Boot (VB):
* - IBB(Initial Boot Block) 무결성 검증
* - 검증 실패 시 → 부팅 정지 (shutdown)
* - ACM(Authenticated Code Module)이 검증 수행
*
* 2. Measured Boot (MB):
* - IBB 해시를 TPM PCR에 측정 (기록)
* - 검증 실패해도 부팅은 계속 (원격 증명으로 사후 판단)
* - PCR 0에 IBB 측정값 확장
*
* 실행 흐름:
* CPU 리셋 → 마이크로코드 → ACM(ROM에서 로드)
* → ACM이 FPF의 키 해시로 IBB 서명 검증
* → 성공 시 SEC → PEI → DXE → ... 정상 부팅
* → 실패 시 셧다운 또는 복구 모드
*
* 커널에서 확인:
* /sys/kernel/security/tpm0/binary_bios_measurements
* → PCR[0] = CRTM(Core Root of Trust for Measurement) 값
* Boot Guard가 기록한 IBB 측정값 포함
*/
AMD PSP (Platform Security Processor)
/* AMD PSP (Platform Security Processor) / AMD Secure Technology
*
* ARM Cortex-A5 기반 독립 보안 프로세서
* x86 CPU보다 먼저 실행되어 펌웨어 무결성 검증
*
* 부팅 순서:
* 1. PSP 부트 ROM 실행 (on-chip ROM)
* 2. PSP OS(Trustlet 환경) 로드
* 3. x86 코어 리셋 해제 전 DRAM 초기화 시작
* 4. AGESA(AMD Generic Encapsulated Software Architecture) 검증
* 5. BIOS 이미지 서명 검증
* 6. x86 코어 리셋 해제 → 일반 부팅 시작
*
* PSP 기능:
* - fTPM (firmware TPM 2.0): 별도 TPM 칩 없이 TPM 기능 제공
* - SEV (Secure Encrypted Virtualization): VM 메모리 암호화
* - SEV-SNP: VM 무결성까지 보장 (페이지 수준)
* - 메모리 암호화 키 관리 (SME/TSME)
*
* 커널에서의 상호작용:
* drivers/crypto/ccp/ — PSP/CCP(Cryptographic Coprocessor) 드라이버
* CONFIG_CRYPTO_DEV_CCP=y — PSP 디바이스 드라이버
* CONFIG_CRYPTO_DEV_SP_PSP=y — PSP 보안 프로세서 지원
* CONFIG_AMD_MEM_ENCRYPT=y — SME/SEV 메모리 암호화
*/
# PSP/fTPM 상태 확인
$ dmesg | grep -i psp
ccp 0000:04:00.2: psp enabled
ccp 0000:04:00.2: psp: tee enabled
$ dmesg | grep -i sev
SEV: Using SNP CPUID table, 31 entries present
SEV-SNP: Reserving start/end of RMP table
TPM과 Measured Boot
/* TPM (Trusted Platform Module) — 하드웨어 보안 칩
*
* 펌웨어 → 부트로더 → 커널 → 유저스페이스 측정 체인
* 각 단계에서 다음 단계를 해싱하여 TPM PCR에 확장(extend)
*
* PCR (Platform Configuration Register) 할당:
*/
PCR 0 /* CRTM, BIOS, Host Platform Extensions (Boot Guard 측정값) */
PCR 1 /* Host Platform Configuration (BIOS 설정) */
PCR 2 /* UEFI 드라이버, 옵션 ROM 코드 */
PCR 3 /* UEFI 드라이버, 옵션 ROM 설정 데이터 */
PCR 4 /* IPL (부트로더 코드) — shimx64.efi, grubx64.efi */
PCR 5 /* IPL 설정/데이터 — GPT 테이블, 부팅 시도 */
PCR 6 /* Host Platform Manufacturer 전용 */
PCR 7 /* Secure Boot Policy — PK, KEK, db, dbx, 검증 결과 */
PCR 8 /* 커널 커맨드 라인 (GRUB 측정) */
PCR 9 /* 커널 이미지, initrd (GRUB/EFI Stub 측정) */
PCR 10 /* Reserved (IMA 측정에 사용 가능) */
PCR 11 /* IMA (Integrity Measurement Architecture) 측정 값 */
PCR 14 /* shim MOK 측정 */
/* PCR 확장(extend) 연산:
* PCR_new = Hash(PCR_old || measurement)
*
* → PCR 값은 리셋 없이 초기화 불가 (단방향)
* → 모든 측정의 순서가 PCR에 누적되어 반영
* → 하나라도 다르면 최종 PCR 값이 달라짐 (변조 탐지)
*/
/* 커널에서 TPM 접근 */
$ cat /sys/class/tpm/tpm0/pcr-sha256/0 # PCR 0 값
$ cat /sys/class/tpm/tpm0/pcr-sha256/7 # Secure Boot 정책
# tpm2-tools로 PCR 읽기
$ tpm2_pcrread sha256:0,1,2,3,4,5,7
# 이벤트 로그 (각 측정의 상세 기록)
$ tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements
# 어떤 UEFI 이미지가 어떤 PCR에 측정되었는지 확인 가능
/* CONFIG_TCG_TPM=y — TPM 드라이버
* CONFIG_TCG_TIS=y — TIS(TPM Interface Specification)
* CONFIG_TCG_CRB=y — CRB(Command Response Buffer) — TPM 2.0 표준
* CONFIG_TCG_FTPM_TEE=y — fTPM (TEE 기반, AMD PSP 등)
* CONFIG_IMA=y — Integrity Measurement Architecture
* CONFIG_IMA_MEASURE_PCR_IDX=11 — IMA가 사용할 PCR 번호 */
Remote Attestation: TPM PCR 값을 원격 검증 서버에 전송하여 시스템의 부팅 무결성을 증명할 수 있습니다. tpm2_quote 명령으로 PCR에 대한 서명된 증거(quote)를 생성하고, 검증자가 이를 기대값과 비교합니다. 클라우드 환경에서 VM의 펌웨어/커널 무결성을 보장하는 데 활용됩니다(Azure Attestation, AWS Nitro Attestation 등).
Intel FSP (Firmware Support Package)
Intel FSP는 Intel이 바이너리 형태로 제공하는 실리콘 초기화 코드입니다. coreboot, EDK II 등의 오픈소스 펌웨어가 DRAM 초기화와 실리콘 설정을 위해 FSP를 호출합니다.
/* Intel FSP 아키텍처 (FSP 2.0+)
*
* FSP = 3개의 바이너리 컴포넌트:
*
* FSP-T (TempRamInit):
* - CAR(Cache-As-RAM) 설정
* - 호출 시점: 리셋 직후, DRAM 없음
* - 반환 후: 임시 스택/데이터용 메모리 사용 가능
*
* FSP-M (MemoryInit):
* - DRAM 컨트롤러 초기화 (가장 복잡)
* - SPD 읽기, 메모리 트레이닝, 타이밍 최적화
* - 호출 시점: CAR 사용 가능
* - 반환 후: 실제 DRAM 사용 가능
* - HOB(Hand-Off Block)로 메모리 맵 정보 반환
*
* FSP-S (SiliconInit):
* - CPU, PCH, PCI 등 실리콘 초기화
* - 인터럽트 라우팅, GPIO, USB, SATA 설정
* - 호출 시점: DRAM 사용 가능
* - 반환 후: 모든 실리콘 초기화 완료
*
* 호출자(coreboot/EDK II)가 UPD(Updatable Product Data)를 통해
* 플랫폼별 설정(메모리 채널, GPIO 핀맵 등)을 FSP에 전달
*/
/* FSP 호출 흐름 (coreboot 예시)
*
* bootblock:
* ├── CPU 리셋 벡터 → 최소 초기화
* └── FSP-T 호출 (TempRamInit) → CAR 설정
*
* romstage:
* ├── FSP-M 호출 (MemoryInit) → DRAM 초기화
* ├── HOB 파싱 → 메모리 맵 획득
* └── cbmem 초기화 (coreboot 메모리 테이블)
*
* ramstage:
* ├── FSP-S 호출 (SiliconInit) → 실리콘 초기화
* ├── PCI 열거, ACPI 테이블 생성
* └── payload 로드 (SeaBIOS/GRUB/LinuxBoot)
*
* FSP는 바이너리 블롭이므로 디버깅이 어려움
* → FSP 디버그 빌드 요청 또는 시리얼 로그로 추적 */
/* UPD (Updatable Product Data) 설정 예시 */
typedef struct {
UINT8 PcdFspDebugPrintErrorLevel; /* 디버그 레벨 */
UINT8 PcdSpdAddressTable[4]; /* DIMM SPD I2C 주소 */
UINT32 PcdTsegSize; /* SMRAM 크기 */
UINT8 PcdEnableHyperThreading; /* HT 활성화 */
UINT8 PcdEnableTurboMode; /* 터보 부스트 */
UINT32 PcdSerialIoUartDebugEnable; /* 시리얼 디버그 */
/* ... 수백 개의 설정 파라미터 ... */
} FSP_M_CONFIG;
ACPI AML과 DSDT/SSDT 심화
ACPI 테이블 중 DSDT(Differentiated System Description Table)와 SSDT(Secondary System Description Table)는 AML(ACPI Machine Language) 바이트코드를 포함합니다. 커널의 ACPI 인터프리터(ACPICA)가 이를 실행하여 하드웨어 정보를 동적으로 조회합니다.
ACPI 테이블 계층
/* ACPI 테이블 구조
*
* RSDP (Root System Description Pointer)
* └── XSDT (Extended System Description Table)
* ├── FADT (Fixed ACPI Description Table)
* │ └── DSDT (Differentiated System Description Table) ← AML 코드
* ├── SSDT (Secondary System Description Table) × N ← AML 코드
* ├── MADT (Multiple APIC Description Table)
* │ ├── Local APIC 엔트리 (CPU 코어별)
* │ ├── I/O APIC 엔트리
* │ └── Interrupt Source Override
* ├── MCFG (PCI Express ECAM 기본 주소)
* ├── SRAT (System Resource Affinity Table — NUMA)
* ├── SLIT (System Locality Information Table — NUMA 거리)
* ├── HPET (High Precision Event Timer)
* ├── DMAR (DMA Remapping — Intel VT-d)
* │ ├── DRHD (DMA Remapping Hardware Unit)
* │ └── RMRR (Reserved Memory Region Reporting)
* ├── BGRT (Boot Graphics Resource Table — 부팅 로고)
* ├── BERT (Boot Error Record Table — 하드웨어 에러 로그)
* ├── EINJ (Error Injection Table — 에러 주입 테스트)
* ├── ERST (Error Record Serialization Table)
* ├── HEST (Hardware Error Source Table)
* ├── IVRS (I/O Virtualization Reporting — AMD IOMMU)
* ├── LPIT (Low Power Idle Table — C-state)
* ├── PPTT (Processor Properties Topology Table — CPU 토폴로지)
* └── TPM2 (TPM 2.0 테이블)
*
* 고정 테이블: 바이너리 데이터 (C 구조체로 직접 파싱)
* AML 테이블: DSDT, SSDT — 인터프리터가 실행
*/
ASL과 AML — ACPI 프로그래밍
/* ASL (ACPI Source Language) → iasl 컴파일러 → AML (바이트코드)
*
* ASL은 ACPI 하드웨어 기술 언어:
* - 디바이스 트리 (Device/Scope/Name)
* - 메서드 (Method — 실행 가능한 AML 함수)
* - 연산 영역 (OperationRegion — MMIO/PIO/EC 접근)
* - 패키지, 버퍼, 정수 등 데이터 타입
*/
/* ASL 예시: PCI 호스트 브리지 + 디바이스 정의 */
DefinitionBlock ("dsdt.aml", "DSDT", 2, "VENDOR", "BOARD", 0x00000001)
{
Scope (\_SB) /* System Bus */
{
Device (PCI0) /* PCI 호스트 브리지 */
{
Name (_HID, EisaId("PNP0A08")) /* PCI Express Root */
Name (_CID, EisaId("PNP0A03")) /* PCI 호환 */
Name (_ADR, Zero)
/* _CRS: 현재 리소스 설정 반환 */
Method (_CRS, 0, Serialized)
{
Name (RBUF, ResourceTemplate()
{
WordBusNumber (,,, 0x00, 0xFF,,,) /* 버스 0~255 */
DWordMemory (,,, 0xF0000000, 0xFEFFFFFF,,,) /* MMIO */
WordIO (,,, 0x0000, 0xFFFF,,,) /* I/O 포트 */
})
Return (RBUF)
}
/* 내장 GPU */
Device (GFX0)
{
Name (_ADR, 0x00020000) /* Bus 0, Dev 2, Func 0 */
/* _DSM: Device-Specific Method (벤더별 기능) */
Method (_DSM, 4, Serialized)
{
If (Arg0 == ToUUID("3e5b41c6-...")) {
/* 백라이트 제어, 모드 전환 등 */
}
}
}
}
/* 전원 버튼 */
Device (PWRB) {
Name (_HID, EisaId("PNP0C0C"))
}
/* 노트북 덮개 */
Device (LID0) {
Name (_HID, EisaId("PNP0C0D"))
Method (_LID, 0) { /* 덮개 열림/닫힘 상태 */
Return (\_SB.PCI0.LPCB.EC0.LIDS) /* EC 레지스터 읽기 */
}
}
/* 배터리 */
Device (BAT0) {
Name (_HID, EisaId("PNP0C0A"))
Method (_BIF, 0) { /* 배터리 정보 (설계 용량, 전압 등) */ }
Method (_BST, 0) { /* 배터리 상태 (충전율, 전류 등) */ }
}
/* 열 관리 */
ThermalZone (TZ00) {
Method (_TMP, 0) { /* 현재 온도 (10분의 1 켈빈) */ }
Method (_PSV, 0) { Return (0x0E94) } /* Passive 임계값 (80°C) */
Method (_CRT, 0) { Return (0x0FA2) } /* Critical 임계값 (100°C) */
}
}
}
커널의 ACPI 처리
/* 커널의 ACPI 스택 구조
*
* drivers/acpi/
* ├── acpica/ — ACPICA (ACPI Component Architecture)
* │ ├── nsxfeval.c — AML 네임스페이스 평가
* │ ├── exoparg*.c — AML 오피코드 실행기
* │ ├── psparse.c — AML 파서
* │ └── uteval.c — 유틸리티 평가
* ├── scan.c — ACPI 디바이스 트리 스캔 → Linux 디바이스 등록
* ├── bus.c — ACPI 버스 드라이버
* ├── battery.c — 배터리 드라이버
* ├── thermal.c — 열 관리 드라이버
* ├── processor_*.c — CPU P-state/C-state 드라이버
* ├── ec.c — Embedded Controller 드라이버
* └── tables.c — ACPI 테이블 초기화/매핑
*
* ACPICA는 Intel이 제공하는 참조 구현으로,
* Linux/Windows/FreeBSD 등 여러 OS에서 공유하는 크로스플랫폼 코드
*/
/* 커널에서 AML 메서드 호출 예시 */
acpi_status status;
struct acpi_object_list arg_list;
union acpi_object args[1];
struct acpi_buffer return_buf = { ACPI_ALLOCATE_BUFFER, NULL };
args[0].type = ACPI_TYPE_INTEGER;
args[0].integer.value = brightness_level;
arg_list.count = 1;
arg_list.pointer = args;
/* _BCM(Set Brightness Level) 메서드 호출 */
status = acpi_evaluate_object(handle, "_BCM", &arg_list, NULL);
/* _BIF(Battery Information) 메서드 호출 */
status = acpi_evaluate_object(battery->device->handle,
"_BIF", NULL, &return_buf);
# ACPI 테이블 추출/디컴파일/분석
# 1. 시스템의 ACPI 테이블 추출
acpidump > acpi.dat # 모든 테이블 덤프
acpixtract -a acpi.dat # 개별 테이블 파일로 분리
# 2. DSDT 디컴파일 (AML → ASL)
iasl -d dsdt.dat # dsdt.dsl 생성 (읽을 수 있는 ASL 소스)
# 3. 커스텀 DSDT 오버라이드 (디버깅/버그 우회)
iasl -tc dsdt.dsl # 수정된 ASL → AML + C 헤더 컴파일
# initramfs에 포함: /kernel/firmware/acpi/dsdt.aml
# 커널 파라미터: acpi_table_override=force
# 4. SSDT 오버라이드 (런타임, 커널 4.6+ CONFIG_ACPI_TABLE_UPGRADE=y)
mkdir -p /lib/firmware/acpi
cp custom-ssdt.aml /lib/firmware/acpi/
# initramfs에 포함 → 부팅 시 자동 로드
# 5. ACPI 네임스페이스 탐색
ls /sys/firmware/acpi/tables/ # 원시 테이블 바이너리
ls /sys/bus/acpi/devices/ # ACPI 디바이스 목록
cat /sys/bus/acpi/devices/PNP0C0A:00/path # 배터리 디바이스 ACPI 경로
# 6. AML 런타임 디버깅
# CONFIG_ACPI_DEBUGGER=y 필요
echo "method \_SB.PCI0.GFX0._DSM" > /sys/module/acpi/parameters/debug_level
dmesg | grep ACPI # AML 실행 추적
ACPI 펌웨어 버그: DSDT/SSDT의 AML 코드 버그는 리눅스 커널에서 다양한 문제를 유발합니다 (배터리 상태 오류, 절전 복귀 실패, 팬 제어 오작동 등). 커널은 drivers/acpi/blacklist.c와 acpi_osi= 파라미터로 문제 있는 펌웨어를 우회합니다. acpi_osi="Windows 2022"로 Windows로 위장하면 일부 벤더 DSDT의 Linux 차별 분기를 우회할 수 있습니다.
UEFI 네트워크 부팅
UEFI는 펌웨어 수준에서 완전한 네트워크 스택(IPv4/IPv6, TCP/UDP, HTTP, TLS)을 제공하여 PXE 및 HTTP Boot을 지원합니다.
UEFI PXE Boot
/* UEFI PXE Boot 흐름
*
* 1. UEFI 네트워크 스택 초기화:
* SNP(Simple Network Protocol) → MNP → ARP → DHCP → PXE
*
* 2. DHCP 서버에서 부팅 파일 경로 획득:
* Option 66: TFTP 서버 주소
* Option 67: 부팅 파일 이름 (예: shimx64.efi)
* 또는 DHCPv6 + Option 59 (OPT_BOOTFILE_URL)
*
* 3. TFTP로 부팅 파일 다운로드
* 4. EFI 바이너리 실행 (shimx64.efi → grubx64.efi → vmlinuz)
*
* Legacy PXE vs UEFI PXE:
* Legacy: INT 18h/19h → PXE ROM → TFTP → NBP(Network Bootstrap Program)
* UEFI: SNP Protocol → DHCP → TFTP/HTTP → EFI Application
*
* UEFI PXE는 Secure Boot와 통합 가능:
* 서명된 shimx64.efi를 네트워크에서 로드 → 검증 → 실행
*/
/* 프로토콜 스택 계층:
*
* Application (GRUB, shim, OS loader)
* │
* EFI_PXE_BASE_CODE_PROTOCOL — PXE 클라이언트
* │
* EFI_MTFTP4_PROTOCOL — TFTP 파일 전송
* │
* EFI_UDP4/6_PROTOCOL — UDP 트랜스포트
* │
* EFI_IP4/6_PROTOCOL — IP 네트워크
* │
* EFI_ARP_PROTOCOL — 주소 해석
* │
* EFI_MANAGED_NETWORK_PROTOCOL — 프레임 수신/필터링
* │
* EFI_SIMPLE_NETWORK_PROTOCOL — NIC 드라이버
*/
HTTP Boot (UEFI 2.5+)
/* UEFI HTTP Boot — TFTP 대체, 더 빠르고 유연한 네트워크 부팅
*
* 장점:
* - HTTP/HTTPS 사용 → 기존 웹 인프라 활용 가능
* - TLS 지원 → 전송 중 암호화 (TFTP는 암호화 없음)
* - TCP 기반 → 대용량 이미지 안정적 전송
* - URL 기반 → 유연한 경로 지정
*
* 부팅 흐름:
* 1. DHCP에서 HTTP Boot URL 획득
* DHCPv4: vendor-class "HTTPClient" + Option 67에 URL
* DHCPv6: Option 59 (OPT_BOOTFILE_URL)
*
* 2. DNS로 서버 주소 해석
* 3. HTTP GET으로 EFI 바이너리 다운로드
* 4. (HTTPS 시) TLS 인증서 검증
* 5. EFI 바이너리 실행
*
* 예시 DHCP 설정 (ISC dhcpd):
*/
class "httpboot" {
match if substring (option vendor-class-identifier, 0, 10) = "HTTPClient";
option bootfile-name "http://boot.example.com/efi/shimx64.efi";
}
/* 커널에서 HTTP Boot initrd 로드 (커널 5.7+):
*
* LINUX_EFI_INITRD_MEDIA_GUID를 통해 EFI Stub이 initrd를 로드
* systemd-boot이나 GRUB가 EFI_LOAD_FILE2_PROTOCOL로 initrd 제공
* → 부트로더가 HTTP로 initrd를 가져와 프로토콜을 통해 커널에 전달
*/
# HTTP Boot 서버 설정 (간단한 구성)
# 1. DHCP 서버 (dnsmasq 예시)
dnsmasq \
--dhcp-range=192.168.1.100,192.168.1.200 \
--dhcp-option=vendor:HTTPClient,60,"HTTPClient" \
--dhcp-boot=tag:HTTPClient,"http://192.168.1.1:8080/efi/shimx64.efi"
# 2. HTTP 서버 (nginx 예시)
server {
listen 8080;
root /srv/netboot;
# /srv/netboot/efi/shimx64.efi
# /srv/netboot/efi/grubx64.efi
# /srv/netboot/vmlinuz
# /srv/netboot/initramfs.img
}
# UEFI 펌웨어 설정에서:
# - Network Boot: Enabled
# - IPv4/IPv6 PXE Support: Enabled
# - HTTP Boot: Enabled (별도 옵션인 경우)
펌웨어 업데이트 메커니즘 심화
SPI Flash — 펌웨어 저장소
/* UEFI/BIOS 펌웨어는 SPI NOR Flash에 저장
*
* 물리적 구성:
* SPI Flash (보통 8~32MB)
* ├── Descriptor Region — Flash 레이아웃 정보 (4KB)
* ├── Intel ME/AMD PSP Region — 관리 엔진 펌웨어
* ├── GbE Region — 이더넷 NVM (MAC 주소 등)
* └── BIOS Region — UEFI 펌웨어 이미지
* ├── FV_RECOVERY — SEC + PEI (복구 부팅에 필수)
* ├── FV_MAIN — DXE 드라이버
* ├── FV_NVRAM — UEFI 변수 저장소
* └── Microcode — CPU 마이크로코드 패치
*
* SPI Flash 접근:
* - 부팅 시: CPU가 직접 접근 (메모리 매핑, 0xFF000000 근처)
* - OS 런타임: SPI 컨트롤러 레지스터 (BIOS_CNTL, PR 레지스터)
* - 보호: BIOS Write Enable(BWE) 비트, SPI Protected Ranges
*
* 커널 SPI Flash 접근:
* drivers/mtd/spi-nor/ — SPI NOR Flash 드라이버
* drivers/spi/spi-intel.c — Intel SPI 컨트롤러
* /dev/mtd0 — SPI Flash raw 접근 (주의: 위험!)
*/
# SPI Flash 정보 확인
$ dmesg | grep -i spi
intel-spi 0000:00:1f.5: BIOS locked, using software sequencing
intel-spi 0000:00:1f.5: Found chip with 16384 KiB total flash
# flashrom — SPI Flash 읽기/쓰기 도구 (오픈소스)
$ flashrom -p internal -r backup.rom # 현재 펌웨어 백업
$ flashrom -p internal --ifd -i bios -r bios.bin # BIOS 영역만
# 주의: 잘못된 쓰기는 시스템을 벽돌로 만들 수 있음
fwupd와 LVFS 생태계
/* fwupd — Linux Vendor Firmware Service 클라이언트
*
* 아키텍처:
* LVFS (Linux Vendor Firmware Service) ← 벤더가 펌웨어 업로드
* ↓ (cabinet 파일 = .cab, 내부에 capsule + metainfo.xml)
* fwupd 데몬 (D-Bus 서비스)
* ↓
* 다양한 업데이트 플러그인:
* ├── uefi-capsule — UEFI UpdateCapsule() RT Service
* ├── flashrom — SPI Flash 직접 쓰기
* ├── redfish — BMC/IPMI 기반 원격 업데이트
* ├── nvme — NVMe 펌웨어 업데이트
* ├── thunderbolt — Thunderbolt 컨트롤러
* ├── logitech-hidpp — Logitech 무선 장치
* └── ...
*
* UEFI Capsule 업데이트 상세 흐름:
* 1. fwupd가 capsule 파일을 ESP에 복사
* 2. EFI 변수 CapsuleUpdateData에 capsule 경로 설정
* 3. EFI 변수 OsIndications에 CAPSULE_UPDATE 비트 설정
* 4. 재부팅 → 펌웨어가 capsule 감지
* 5. 펌웨어 이미지 검증 (서명 확인)
* 6. SPI Flash에 새 이미지 쓰기
* 7. 결과를 CapsuleResultData 변수에 기록
* 8. 정상 부팅 → fwupd가 결과 확인
*/
# fwupd 업데이트 명령어
$ fwupdmgr get-devices # 업데이트 가능 장치 목록
$ fwupdmgr refresh # LVFS에서 메타데이터 갱신
$ fwupdmgr get-updates # 사용 가능한 업데이트 확인
$ fwupdmgr update # 모든 업데이트 적용
$ fwupdmgr get-history # 업데이트 이력
# ESRT (EFI System Resource Table) — 업데이트 가능 펌웨어 목록
$ ls /sys/firmware/efi/esrt/entries/
entry0/ entry1/ entry2/
$ cat /sys/firmware/efi/esrt/entries/entry0/fw_type # 0=Unknown, 1=System, 2=Device, 3=Driver
$ cat /sys/firmware/efi/esrt/entries/entry0/fw_version # 현재 버전
$ cat /sys/firmware/efi/esrt/entries/entry0/capsule_flags
$ cat /sys/firmware/efi/esrt/entries/entry0/last_attempt_status # 마지막 업데이트 결과
펌웨어 관련 커널 소스 트리
| 경로 | 설명 |
|---|---|
drivers/firmware/efi/ | UEFI 코어 — Runtime Services, efivars, 시스템 테이블, 캡슐 |
drivers/firmware/efi/libstub/ | EFI Stub 부트 코드 (아키텍처 독립 + 아키텍처별) |
drivers/firmware/dmi_scan.c | SMBIOS/DMI 테이블 파싱 |
drivers/firmware/memmap.c | 펌웨어 메모리 맵 sysfs 인터페이스 |
drivers/acpi/ | ACPI 서브시스템 전체 (ACPICA + 드라이버) |
drivers/acpi/acpica/ | ACPICA AML 인터프리터 (Intel 참조 구현) |
drivers/acpi/tables.c | ACPI 테이블 초기화, 오버라이드 |
arch/x86/platform/efi/ | x86 UEFI 플랫폼 초기화, 메모리 맵 |
arch/x86/kernel/acpi/ | x86 ACPI 통합 (SMP 부팅, 슬립 등) |
fs/efivarfs/ | efivarfs 파일시스템 구현 |
security/integrity/platform_certs/ | Secure Boot 인증서 → 커널 키링 로드 |
security/lockdown/ | Lockdown LSM (Secure Boot 연동) |
drivers/crypto/ccp/ | AMD PSP/CCP 드라이버 |
drivers/char/tpm/ | TPM 드라이버 (TIS, CRB, fTPM) |
drivers/mtd/spi-nor/ | SPI NOR Flash 드라이버 |
drivers/spi/spi-intel.c | Intel SPI 컨트롤러 (Flash 접근) |