UEFI (Unified Extensible Firmware Interface)

UEFI: 부팅 단계(SEC/PEI/DXE/BDS), Boot Services/Runtime Services, EFI System Partition, Secure Boot, Linux EFI Stub, efivars, 커널 UEFI 구현을 다룹니다.

전제 조건: 커널 아키텍처부팅 과정(Boot Process) 문서를 먼저 읽으세요. 부팅 체인은 단계별 책임 분리가 핵심이므로, 펌웨어(Firmware)-부트로더(Bootloader)-커널 전환 지점을 먼저 고정해야 원인 추적이 쉬워집니다.
일상 비유: 이 주제는 릴레이 바통 전달과 비슷합니다. 한 구간에서 바통이 흔들리면 다음 구간이 모두 영향을 받듯이, 부팅 단계 간 인터페이스 검증이 중요합니다.

핵심 요약

  • 단계 분리 — 펌웨어, 부트로더, 커널 초기화 경계를 구분합니다.
  • 하드웨어 기술 — ACPI/DT 등 기술 정보가 어디서 소비되는지 확인합니다.
  • 신뢰 체인(Chain of Trust) — Secure Boot 등 검증 체인을 흐름으로 이해합니다.
  • 실패 지점 — 부팅 로그에서 단계별 실패 단서를 빠르게 찾습니다.
  • 호환성 관점 — 플랫폼 차이에 따른 초기화 분기를 함께 점검합니다.

단계별 이해

  1. 부팅 단계 식별
    현재 이슈가 어느 단계에서 발생하는지 먼저 고정합니다.
  2. 입력 데이터 확인
    펌웨어/테이블/이미지 메타데이터를 점검합니다.
  3. 전환 경계 검증
    단계 간 인자 전달과 상태 인계를 추적합니다.
  4. 플랫폼별 재검증
    다른 하드웨어 조건에서도 동일하게 동작하는지 확인합니다.

UEFI 부팅 단계, Boot/Runtime Services, Secure Boot, Linux EFI Stub, 커널 UEFI 구현까지 — 펌웨어와 커널의 인터페이스를 소스 코드 수준에서 분석합니다.

관련 표준: UEFI Specification 2.10 (Boot/Runtime Services, Secure Boot), GPT (GUID Partition Table), ACPI 6.5 — UEFI 펌웨어 인터페이스의 핵심 규격입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

UEFI 개요

UEFI(Unified Extensible Firmware Interface)는 운영체제와 플랫폼 펌웨어 사이의 소프트웨어 인터페이스를 정의하는 규격입니다. 전통적인 BIOS를 대체하며, GPT 파티션, 64-bit 지원, Secure Boot, 네트워크 부팅 등 현대 시스템에서 널리 사용되는 핵심 기능을 제공합니다.

특성Legacy BIOSUEFI
주소 공간(Address Space)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 (디지털 서명 검증(Signature Verification))
네트워크PXE (제한적)IPv4/IPv6 네트워크 스택(Network Stack) 내장
없음UEFI Shell (스크립팅 가능)
OS 인터페이스부팅 후 사라짐Runtime Services 지속 제공

UEFI 부팅 흐름 다이어그램

SEC Security / CAR PEI DRAM 초기화 DXE 드라이버 로드 BDS 부팅 장치 선택 TSL 부트로더 실행 Boot Services (부팅 중에만 사용 가능 — ExitBootServices() 호출 시 소멸) Runtime Services (OS 실행 중에도 사용 가능 — 변수, 시간, 리셋 등) ExitBootServices() Boot Services 종료, 커널이 하드웨어 제어권 획득 Linux Kernel Runtime Services만 사용 가능

UEFI 부팅 단계 (PI 아키텍처)

UEFI 펌웨어는 PI(Platform Initialization) 규격에 따라 여러 단계를 거쳐 실행됩니다. 각 단계는 이전 단계가 설정한 환경 위에서 동작합니다.

SEC (Security Phase)

파워 온 직후 가장 먼저 실행되는 단계입니다. CPU 리셋 벡터에서 시작하며, 메인 메모리(DRAM)가 아직 초기화되지 않은 상태입니다.

설명 요약:
  • SEC 단계 주요 동작:
  • CPU 리셋 벡터 (0xFFFFFFF0) 에서 실행 시작
  • CPU 캐시(Cache)를 RAM으로 사용 (CAR: Cache-As-RAM)
  • → L2/L3 캐시를 임시 스택/데이터 영역으로 활용
  • → No-Eviction Mode 설정
  • 최소한의 CPU 초기화 (마이크로코드 로드 등)
  • 보안 검증: 펌웨어 볼륨(FV)의 무결성(Integrity) 확인
  • PEI Foundation으로 핸드오프
  • CAR 메모리 레이아웃 (예시): 아래 SVG 참고
SEC 스택 SEC 데이터 PEI 임시 RAM CAR Base + CAR Size CAR Base SEC 단계에서 캐시(CAR)를 임시 메모리로 분할 사용

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 단계 주요 동작:
  • DXE Foundation (DXE Core) 초기화
  • Boot Services Table 생성
  • Runtime Services Table 생성
  • System Table 생성
  • DXE 드라이버 디스패치(Dispatch):
  • PCI 버스(Bus) 열거 (PCI Host Bridge Driver)
  • USB 컨트롤러 초기화
  • 그래픽 출력 (GOP: Graphics Output Protocol)
  • 디스크 I/O (Block I/O Protocol)
  • 네트워크 (SNP: Simple Network Protocol)
  • 콘솔 입출력 (Simple Text I/O)
  • UEFI 프로토콜 등록:
  • 각 드라이버가 프로토콜(인터페이스)을 핸들에 설치
  • → 다른 드라이버나 애플리케이션이 프로토콜을 통해 접근
  • SMM(System Management Mode) 초기화 (해당 시)
  • BDS(Boot Device Selection)로 전환

BDS (Boot Device Selection)

BDS는 부팅 장치를 선택하고 부트로더를 실행하는 단계입니다.

설명 요약:
  • BDS 단계:
  • UEFI 변수에서 부팅 순서 읽기:
  • BootOrder: 부팅 시도 순서 (예: 0001,0003,0002)
  • Boot0001: 첫 번째 부팅 옵션 (경로, 설명 등)
  • 부팅 장치 연결 (ConnectController):
  • 장치 경로(Device Path)에 따라 드라이버 바인딩
  • 파일시스템(Filesystem) 드라이버 로드 (FAT32)
  • EFI 바이너리 로드 및 실행:
  • ESP에서 부트로더 로드
  • 기본 경로: \EFI\BOOT\BOOTX64.EFI (x86_64)
  • 또는 Boot 변수에 지정된 경로
  • 부팅 실패 시 다음 Boot 항목으로 이동
  • 모든 항목 실패 시 UEFI Shell 또는 에러 화면

Boot Services & Runtime Services

UEFI는 두 종류의 서비스를 OS에 제공합니다. Boot Services는 부팅 중에만 사용 가능하고, Runtime Services는 OS 실행 중에도 사용할 수 있습니다.

Boot Services

OS 부트로더나 EFI Stub이 ExitBootServices()를 호출하기 전까지 사용 가능한 서비스입니다:

카테고리주요 함수설명
메모리AllocatePages(), FreePages()물리 메모리(Physical Memory) 페이지(Page) 할당/해제
메모리AllocatePool(), FreePool()메모리 풀 할당/해제
메모리GetMemoryMap()물리 메모리 맵 조회 (커널 초기화에 필수)
프로토콜HandleProtocol()핸들에서 프로토콜 인터페이스 획득
프로토콜LocateProtocol()특정 프로토콜을 제공하는 핸들 검색
이미지LoadImage(), StartImage()EFI 바이너리 로드/실행
이벤트CreateEvent(), SetTimer()이벤트/타이머(Timer) 관리
종료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 배포판에서 자주 보이는 ESP 구조입니다. 실제 경로는 배포판과 설치 방식에 따라 다를 수 있습니다.

# 마운트 지점: /boot/efi (또는 /efi)
EFI/
  BOOT/
    BOOTX64.EFI                # 폴백 부트로더 (기본 경로)
  fedora/                      # 배포판별 디렉터리 예시
    shimx64.efi                # Secure Boot shim
    grubx64.efi                # GRUB2 EFI 바이너리
    grub.cfg                   # GRUB 설정 (또는 /boot/grub2/)
  ubuntu/
    shimx64.efi
    grubx64.efi
    mmx64.efi                  # MOK Manager
tools/
  memtest86.efi               # 메모리 테스트 유틸리티
vmlinuz-6.x                   # EFI Stub 부팅 시 커널 이미지
initramfs-6.x.img             # EFI Stub 부팅 시 initramfs
읽는 순서: 먼저 EFI/BOOT/BOOTX64.EFI 같은 기본 경로를 확인하고, 그다음 배포판 디렉터리(EFI/fedora/, EFI/ubuntu/)와 실제 부팅 엔트리(efibootmgr -v)가 서로 일치하는지 점검하세요.

ESP 점검 명령

# ESP 파티션 타입 GUID, 파일시스템, 마운트 지점 확인
$ lsblk -o NAME,PARTTYPE,FSTYPE,MOUNTPOINT | grep -i efi

# 현재 ESP 마운트 상태 확인
$ findmnt /boot/efi

# UEFI 부팅 항목(경로 포함) 확인
$ 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 키 저장소는 역할이 다른 4개 축으로 이해하면 빠릅니다.

키/DB역할실무 포인트
PK (Platform Key)플랫폼 소유자 키. Secure Boot 신뢰 체인의 최상위일반적으로 1개만 존재하며 KEK 갱신 권한의 루트
KEK (Key Exchange Key)db/dbx 업데이트 권한을 가진 키OEM/OS 벤더 키가 여기에 등록됨
db (Signature Database)허용된 서명/해시(Hash) 목록Windows CA, 3rd Party CA, 사용자 인증서 등이 위치
dbx (Forbidden Database)차단된 서명/해시 목록취소된 인증서, 알려진 취약 바이너리 해시 저장
검증 순서:
  1. EFI 바이너리의 서명/해시를 추출합니다.
  2. dbx에 있으면 즉시 거부합니다.
  3. db에 있으면 허용합니다.
  4. 둘 다 없으면 거부합니다.

Shim 부트로더

Shim은 Microsoft의 3rd Party UEFI CA로 서명된 미니 부트로더로, Linux 배포판의 Secure Boot를 가능하게 합니다:

부팅 체인:
  1. UEFI 펌웨어가 shimx64.efi 서명을 검증합니다.
  2. shimx64.efi가 배포판 키 또는 MOK로 grubx64.efi를 검증합니다.
  3. grubx64.efi가 커널 이미지를 로드하고, 정책에 따라 서명을 검증합니다.
  4. 커널 부팅 후 lockdown 정책이 연동되며 모듈 서명 강제가 적용됩니다.
# 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. 사용자 키 쌍을 생성합니다.
  2. mokutil --import로 등록 요청을 넣습니다.
  3. 재부팅 후 MokManager에서 등록을 승인합니다.
  4. 등록된 키로 모듈을 서명하고 로드합니다.
# 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 모드에 진입하여, 커널 무결성을 우회할 수 있는 기능을 제한합니다:

모드주요 제한
integrity (기본)/dev/mem, /dev/kmem, /dev/port 쓰기/접근 제한, 비서명 kexec_load() 금지, 모듈 서명 강제, ACPI override 차단
confidentialityintegrity의 제한 + /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 없이 직접 부팅 → 부팅 시간 단축
  • 공격 표면 감소 (부트로더 취약점(Vulnerability) 제거)
  • 간결한 부팅 체인
  • 진입점(Entry Point): 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()을 통해 제공하는 물리 메모리 맵은 커널의 메모리 관리(Memory Management) 초기화에 핵심입니다. Legacy BIOS의 E820 맵을 대체합니다.

EFI 메모리 타입

타입설명OS 사용
EfiReservedMemoryType0사용 불가 (펌웨어 예약)접근 금지
EfiLoaderCode1부트로더 코드ExitBS 후 사용 가능
EfiLoaderData2부트로더 데이터ExitBS 후 사용 가능
EfiBootServicesCode3Boot Services 코드ExitBS 후 사용 가능
EfiBootServicesData4Boot Services 데이터ExitBS 후 사용 가능
EfiRuntimeServicesCode5Runtime Services 코드보존 필수
EfiRuntimeServicesData6Runtime Services 데이터보존 필수
EfiConventionalMemory7사용 가능한 일반 메모리자유 사용
EfiUnusableMemory8오류 있는 메모리접근 금지
EfiACPIReclaimMemory9ACPI 테이블 메모리ACPI 파싱 후 회수 가능
EfiACPINVS10ACPI NVS 메모리보존 필수
EfiMemoryMappedIO11MMIO 영역디바이스 I/O용
EfiPersistentMemory14비휘발성 메모리 (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.Sx86 32/64-bit UEFI mixed 모드 진입
arch/x86/platform/efi/x86 UEFI 플랫폼 코드 (메모리 맵(Memory Map), 초기화)
arch/arm64/kernel/efi.cARM64 UEFI 지원
include/linux/efi.hUEFI 자료구조, 프로토콜 GUID, 매크로(Macro)
fs/efivarfs/efivarfs 파일시스템 구현
security/integrity/platform_certs/Secure Boot 인증서 처리

관련 CONFIG 옵션

# 일반적인 x86_64 UEFI Linux 환경에서 권장
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 ConfigurationTable[] 엔트리로 펌웨어 테이블 노출 ACPI 관련 GUID 엔트리 EFI_ACPI_20_TABLE_GUID - XSDP EFI_ACPI_TABLE_GUID - RSDP SMBIOS_TABLE_GUID - SMBIOS Linux 커널 초기화 GUID로 ACPI 주소를 직접 조회 XSDT/FADT/DSDT 파싱 시작 전원/장치 구성 초기화 Legacy BIOS와 달리 스캔(0xE0000-0xFFFFF) 없이 GUID 기반으로 정확 조회
/* 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 디버깅(Debugging)

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 펌웨어 (개발/디버깅)
# 경로와 파일명은 배포판마다 다를 수 있습니다.
$ OVMF_CODE=/path/to/OVMF_CODE.fd
$ OVMF_VARS_TEMPLATE=/path/to/OVMF_VARS.fd
$ cp "${OVMF_VARS_TEMPLATE}" ./OVMF_VARS.debug.fd
$ qemu-system-x86_64 \
    -machine q35,smm=on \
    -drive if=pflash,format=raw,readonly=on,file=${OVMF_CODE} \
    -drive if=pflash,format=raw,file=./OVMF_VARS.debug.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      → 메모리 맵 분할 비활성화
실무 메모: OVMF의 VARS.fd는 VM마다 별도 사본을 써야 하며, Secure Boot나 TPM 2.0까지 포함한 재현 가능한 실습은 QEMU의 OVMF + Secure Boot + swtpm 실습libvirt의 UEFI Secure Boot + TPM 2.0 XML 예제를 함께 보는 편이 안전하다.

자주 발생하는 UEFI 문제

증상원인해결
부팅 시 "Secure Boot violation"서명되지 않은 부트로더/커널MOK 등록 또는 Secure Boot 비활성화
efibootmgr 명령 실패efivarfs 미마운트 또는 권한 부족mount -t efivarfs efivarfs /sys/firmware/efi/efivars/
Runtime Services 크래시펌웨어 버그, 메모리 매핑(Mapping) 문제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 초기화
OVMF 활용: UEFI 관련 커널 개발/디버깅에는 QEMU + OVMF(Open Virtual Machine Firmware)가 매우 유용한 조합입니다. OVMF의 디버그 빌드(-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/OEFI Stub 커널/initrd 로드
EFI_SIMPLE_FILE_SYSTEM_PROTOCOLFAT32 파일 접근ESP에서 파일 읽기
EFI_GRAPHICS_OUTPUT_PROTOCOL프레임버퍼/해상도 제어efifb/simplefb 드라이버
EFI_PCI_IO_PROTOCOLPCI 장치 접근부팅 전 PCI 장치 초기화
EFI_SIMPLE_NETWORK_PROTOCOL네트워크 인터페이스PXE/HTTP Boot
EFI_LOAD_FILE2_PROTOCOLinitrd 로드 (커널 5.7+)EFI Stub initrd 메커니즘
EFI_DEVICE_PATH_PROTOCOL장치 경로 표현부팅 장치 식별
EFI_RNG_PROTOCOL하드웨어 난수 생성KASLR 엔트로피 소스
EFI_TCG2_PROTOCOLTPM 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와 하이퍼바이저(Hypervisor)도 SMM 내부를 볼 수 없으며, 펌웨어 전용 기능(전원 관리(Power Management), 하드웨어 에러 처리 등)에 사용됩니다.

/* 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 참고: ACPI 네임스페이스(Namespace), 전원 관리(G/S/D-state), 열 관리(Thermal Management), GPE/SCI, Embedded Controller, Operation Region, 핫플러그(Hotplug), 커널 ACPI 서브시스템 등 ACPI 전반에 대한 내용은 ACPI 페이지를 참조하세요.

ACPI 테이블 중 DSDT(Differentiated System Description Table)와 SSDT(Secondary System Description Table)는 AML(ACPI Machine Language) 바이트코드를 포함합니다. 커널의 ACPI 인터프리터(ACPICA)가 이를 실행하여 하드웨어 정보를 동적으로 조회합니다.

ACPI 테이블 계층

ACPI 테이블 계층 (UEFI 부팅) RSDP XSDT (64-bit 포인터 배열) 전원/AML 영역 FADT (고정 전원 레지스터) DSDT (주요 AML 코드) SSDT x N (확장 AML 코드) EC/GPIO/전원 메서드 정의 커널 ACPICA 인터프리터 실행 CPU/인터럽트/버스 MADT (APIC 토폴로지) MCFG (PCIe ECAM) SRAT/SLIT (NUMA) HPET / PPTT / LPIT 스케줄러/IRQ 초기화 입력 가상화/오류/보안 DMAR / IVRS (IOMMU) HEST / BERT / ERST / EINJ TPM2 (신뢰 부트 체인) BGRT (부팅 그래픽 정보) 플랫폼 기능 식별 커널 해석 경로 고정 테이블: C 구조체로 직접 파싱 (MADT/MCFG/DMAR/...) AML 테이블: ACPICA가 바이트코드 실행 (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.cacpi_osi= 파라미터로 문제 있는 펌웨어를 우회합니다. acpi_osi="Windows 2022"로 Windows로 위장하면 일부 벤더 DSDT의 Linux 차별 분기를 우회할 수 있습니다.

UEFI 네트워크 부팅

UEFI 스펙은 펌웨어 수준의 네트워크 프로토콜 집합(IPv4/IPv6, UDP/TCP, TFTP, HTTP, TLS 관련 서비스)을 정의하여 PXE 및 HTTP Boot을 지원합니다. 다만 실제 펌웨어가 어디까지 구현하는지는 장비와 벤더별로 차이가 있습니다.

UEFI PXE Boot

/* UEFI PXE Boot 흐름
 *
 * 1. UEFI 네트워크 스택 초기화:
 *    SNP(Simple Network Protocol) → MNP → ARP → DHCP → PXE
 *
 * 2. DHCP 서버에서 TFTP 부팅 정보 획득:
 *    DHCPv4: next-server/siaddr + file 필드 또는 Option 66/67
 *    DHCPv6: Option 59 (OPT_BOOTFILE_URL) 기반 netboot6 경로 가능
 *
 * 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 PXE: SNP Protocol → DHCP → TFTP → EFI Application
 *   UEFI HTTP Boot: DHCP → HTTP/HTTPS URI → 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 URI 획득
 *      DHCPv4: vendor-class "HTTPClient" + BOOTP file 필드(또는 Option 67)에 URI
 *      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";
    filename "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-match=set:HTTPClient,option:vendor-class,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 (별도 옵션인 경우)
구분 포인트: UEFI PXE와 UEFI HTTP Boot는 모두 UEFI 네트워크 부팅이지만 동일한 절차가 아닙니다. 전자는 주로 TFTP 경로를, 후자는 HTTPClient Vendor Class와 Boot URI를 사용합니다. 실무 상세는 PXE 문서의 HTTP Boot DHCP 규칙을 함께 보는 편이 정확합니다.
HTTP Boot와 Secure Boot는 다른 계층입니다: HTTP Boot는 "어떤 URI에서 EFI 이미지를 받아올지"를 정하는 전송 경로이고, Secure Boot는 "받아온 EFI 바이너리를 실행해도 되는지"를 서명으로 검증하는 신뢰 체인입니다. 즉 https://.../shimx64.efi를 사용한다고 해서 Secure Boot가 대체되는 것은 아니고, 반대로 Secure Boot가 켜져 있어도 펌웨어의 HTTPS/TLS 구현 품질은 별도 문제입니다.
UEFI 네트워크 설치 대상첫 EFI 이미지그 다음 단계대표 커널 인자
Ubuntu 24.04 LTSshimx64.efi 또는 grubx64.efiGRUB가 live installer 커널과 initrd 로드autoinstall ds=nocloud-net;s=...
RHEL 10 계열shimx64.efi 또는 grubx64.efiAnaconda installer 커널로 진입inst.repo=... + inst.ks=...
Debian 13 stablegrubx64.efiDebian Installer 커널과 initrd 로드auto=true priority=critical url=...
운영 관점: UEFI HTTP Boot는 대개 shimx64.efigrubx64.efi 같은 "첫 번째 EFI 애플리케이션"까지 전달하는 역할을 담당합니다. 실제 자동설치 성공 여부는 그 다음 단계에서 어떤 부트로더가 어떤 커널 인자를 넘기는지에 달려 있으므로, PXE/HTTP Boot 설정 문서와 배포판 설치 문서를 함께 관리하는 편이 좋습니다. 실무 예시는 PXE 문서의 Kickstart, Autoinstall, Preseed 예시를 참고하십시오.

펌웨어 업데이트 메커니즘

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.cSMBIOS/DMI 테이블 파싱
drivers/firmware/memmap.c펌웨어 메모리 맵 sysfs 인터페이스
drivers/acpi/ACPI 서브시스템 전체 (ACPICA + 드라이버)
drivers/acpi/acpica/ACPICA AML 인터프리터 (Intel 참조 구현)
drivers/acpi/tables.cACPI 테이블 초기화, 오버라이드
arch/x86/platform/efi/x86 UEFI 플랫폼 초기화, 메모리 맵
arch/x86/kernel/acpi/x86 ACPI 통합 (SMP 부팅, 슬립(Sleep) 등)
fs/efivarfs/efivarfs 파일시스템 구현
security/integrity/platform_certs/Secure Boot 인증서 → 커널 키링(Keyring) 로드
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.cIntel SPI 컨트롤러 (Flash 접근)

UEFI 운영 점검 플레이북

UEFI 환경에서의 장애는 NVRAM 변수, 부트 엔트리, ESP 파일 상태가 얽혀서 발생하는 경우가 많습니다. 아래 점검 순서를 따르면 복구 시간을 크게 줄일 수 있습니다.

점검 항목 확인 명령 정상 기준
UEFI 부팅 모드 test -d /sys/firmware/efi 디렉토리 존재
부트 엔트리 efibootmgr -v BootOrder와 실제 EFI 경로 일치
ESP 마운트(Mount) findmnt /boot/efi FAT32 ESP 정상 마운트
efivarfs 상태 mount | grep efivarfs 읽기/쓰기 가능 상태
# UEFI 변수/부트엔트리 점검
sudo efibootmgr -v
sudo ls /sys/firmware/efi/efivars | head

# ESP 파일 무결성 점검
sudo fsck.vfat -n /dev/nvme0n1p1
sudo ls /boot/efi/EFI

# 부트 엔트리 재생성 예시
sudo efibootmgr -c -d /dev/nvme0n1 -p 1 \
  -L "Linux" -l "\\EFI\\linux\\grubx64.efi"
주의: UEFI 변수 저장공간(NVRAM)이 가득 차면 부트 엔트리 갱신이 실패할 수 있습니다. 사용하지 않는 오래된 엔트리를 정리한 뒤 다시 등록하세요.

PI 아키텍처 7단계

UEFI Platform Initialization(PI) 스펙은 펌웨어 실행을 7단계로 정의합니다. 각 단계는 명확한 책임 범위와 인터페이스(PPI, HOB, Protocol)로 분리되어 있어, 단계별 모듈을 독립적으로 교체·검증할 수 있습니다.

PI 아키텍처 7단계 전체 흐름 SEC 리셋 벡터 PEI 메모리 초기화 DXE 드라이버 실행 BDS 부팅 선택 TSL OS 로더 RT 런타임 PPI PEI-to-PEI Interface Protocol Handle/Protocol DB HOB (Hand-Off Block) List SEC 핵심 • 리셋 벡터 진입 • CAR (Cache-as-RAM) • FV 무결성 확인 • Temporary RAM 설정 • PEI Core로 전환 PEI 핵심 • PEIM 디스패치 • DRAM 컨트롤러 초기화 • PPI 서비스 등록 • HOB 생성 • CAR→DRAM 전환 DXE 핵심 • Boot/Runtime Services • PCI/USB/네트워크 초기화 • Protocol 등록 • Architectural Protocols • SMM 초기화 BDS → TSL → RT • BootOrder 읽기 • OS Loader 실행 • ExitBootServices() • Boot Services 해제 • Runtime Services 유지 AL (AfterLife): S3 Resume / Capsule SEC/PEI: 임시 메모리(CAR) 사용 | DXE 이후: 시스템 메모리(DRAM) 사용 ExitBootServices() 이후 OS가 메모리 소유권 획득, Runtime 영역만 펌웨어 예약 파워온 메모리 Ready ExitBootServices() OS 실행 중
PI 단계와 UEFI 스펙의 관계: UEFI 스펙은 Boot Services/Runtime Services 인터페이스를 정의하고, PI 스펙은 그 인터페이스를 구현하는 펌웨어 내부 구조(SEC→PEI→DXE→BDS)를 정의합니다. EDK II(TianoCore)는 PI 스펙의 참조 구현입니다.

HOB (Hand-Off Block) 상세 구조

PEI→DXE 핸드오프의 핵심은 HOB 리스트입니다. PEI 단계에서 생성된 모든 플랫폼 정보가 HOB에 담겨 DXE Core로 전달됩니다.

PEI/DXE 핸드오프 HOB 구조 PEI Phase Memory PEIM CPU PEIM Chipset PEIM Platform PEIM 각 PEIM이 HOB 생성 → BuildHob() 호출 HOB List PHIT HOB (Phase HIT) Memory Alloc HOB Resource Descriptor HOB FV (Firmware Volume) HOB CPU HOB End-of-HOB-List Linked List 구조 DXE Phase HOB 파싱 Memory Map 구축 Protocol DB 초기화 DXE 드라이버 디스패치 Boot/Runtime Services 테이블 생성
/* EDK II PEI Module (PEIM) 엔트리 포인트 예시 */
EFI_STATUS
EFIAPI
PlatformPeiEntryPoint(
  IN EFI_PEI_FILE_HANDLE  FileHandle,
  IN CONST EFI_PEI_SERVICES  **PeiServices
)
{
    EFI_STATUS  Status;

    /* PPI(PEI-to-PEI Interface) 설치 — 다른 PEIM이 사용 */
    Status = (**PeiServices).InstallPpi(
        PeiServices, &mPlatformPpiList
    );

    /* HOB 생성 — DXE에 플랫폼 정보 전달 */
    BuildGuidDataHob(
        &gPlatformInfoHobGuid,
        &PlatformInfo,
        sizeof(PlatformInfo)
    );

    return Status;
}
/* EDK II DXE Driver Binding Protocol 예시 */
EFI_DRIVER_BINDING_PROTOCOL gMyDriverBinding = {
    .Supported  = MyDriverSupported,
    .Start      = MyDriverStart,
    .Stop       = MyDriverStop,
    .Version    = 0x10,
    .ImageHandle = NULL,
    .DriverBindingHandle = NULL
};

/* Supported — 이 드라이버가 해당 핸들을 지원하는지 검사 */
EFI_STATUS EFIAPI MyDriverSupported(
    IN EFI_DRIVER_BINDING_PROTOCOL *This,
    IN EFI_HANDLE ControllerHandle,
    IN EFI_DEVICE_PATH_PROTOCOL *RemainingDevicePath OPTIONAL
)
{
    EFI_STATUS  Status;
    Status = gBS->OpenProtocol(
        ControllerHandle,
        &gEfiPciIoProtocolGuid,
        NULL, This->DriverBindingHandle,
        ControllerHandle, EFI_OPEN_PROTOCOL_TEST_PROTOCOL
    );
    return Status;
}
/* HOB List 순회 — DXE Core 내부 */
VOID ProcessHobList(EFI_HOB_HANDOFF_INFO_TABLE *HobStart)
{
    EFI_PEI_HOB_POINTERS  Hob;

    Hob.Raw = (UINT8 *)HobStart;
    while (!END_OF_HOB_LIST(Hob)) {
        switch (GET_HOB_TYPE(Hob)) {
        case EFI_HOB_TYPE_MEMORY_ALLOCATION:
            /* 메모리 할당 HOB — 사용 가능/예약 영역 등록 */
            ProcessMemoryAllocation(Hob.MemoryAllocation);
            break;
        case EFI_HOB_TYPE_RESOURCE_DESCRIPTOR:
            /* 리소스 HOB — MMIO, I/O, 시스템 메모리 영역 */
            ProcessResourceDescriptor(Hob.ResourceDescriptor);
            break;
        case EFI_HOB_TYPE_FV:
            /* 펌웨어 볼륨 HOB — 추가 FV 등록 */
            ProcessFirmwareVolume(Hob.FirmwareVolume);
            break;
        }
        Hob.Raw = GET_NEXT_HOB(Hob);
    }
}
HOB 타입용도DXE에서의 소비
PHIT부팅 모드, 메모리 범위DXE Core 초기 설정
Memory Allocation예약 메모리, DXE Core 위치메모리 맵 구축
Resource Descriptor시스템 메모리, MMIO, I/OGCD(Global Coherency Domain) 등록
Firmware Volume추가 FV 위치DXE 드라이버 디스패치 대상
CPU주소 비트 폭, I/O 비트 폭CPU Arch Protocol
GUID Extension플랫폼 고유 데이터GUID별 소비자 드라이버

EFI Stub 소스 분석

Linux EFI Stub은 drivers/firmware/efi/libstub/에 위치하며, UEFI 펌웨어에서 직접 커널 이미지를 부팅할 수 있게 합니다. 부트로더 없이 펌웨어가 커널 PE/COFF 이미지를 로드하면, EFI Stub이 최소한의 초기화를 수행하고 커널 본체로 점프합니다.

소스 위치: EFI Stub 코드는 커널 이미지에 링크되지만, 실행 시점은 ExitBootServices() 이전입니다. 따라서 Boot Services를 사용할 수 있으며, 커널의 일반 API(kmalloc 등)는 사용할 수 없습니다.
/* drivers/firmware/efi/libstub/efi-stub-entry.c
 * x86 EFI Stub 진입점 — UEFI 펌웨어가 PE 엔트리로 호출 */
efi_status_t __efiapi
efi_pe_entry(efi_handle_t handle,
             efi_system_table_t *sys_table_arg)
{
    struct boot_params            *boot_params;
    struct setup_header           *hdr;
    efi_status_t                  status;
    unsigned long                 alloc_size;

    /* 시스템 테이블 저장 — 이후 모든 EFI 호출에 사용 */
    sys_table = sys_table_arg;

    /* boot_params 메모리 할당 (Boot Services 사용) */
    alloc_size = 0x4000;  /* 16 KiB */
    status = efi_allocate_pages(alloc_size,
                               (unsigned long *)&boot_params,
                               ULONG_MAX);

    /* setup_header 복사 및 커널 cmdline 처리 */
    memcpy(&boot_params->hdr, hdr, sizeof(*hdr));
    status = efi_parse_options(cmdline_ptr);

    /* efi_main으로 이동 — 공통 EFI Stub 로직 */
    return efi_main(handle, boot_params);
}
/* drivers/firmware/efi/libstub/efi-stub.c
 * efi_main — 아키텍처 공통 EFI Stub 핵심 로직 */
efi_status_t
efi_main(efi_handle_t handle,
         struct boot_params *boot_params)
{
    efi_status_t          status;
    unsigned long         map_size, desc_size;
    struct efi_boot_memmap map;

    /* 1. 커널 이미지를 적절한 주소로 재배치 */
    status = handle_kernel_image(&image_addr,
                                &image_size,
                                &reserve_addr,
                                &reserve_size);

    /* 2. initrd 로드 (LINUX_EFI_INITRD_MEDIA 프로토콜 또는 cmdline) */
    status = efi_load_initrd(boot_params, ULONG_MAX,
                              efi_get_max_initrd_addr(image_addr));

    /* 3. 그래픽 설정 (GOP 정보 수집) */
    setup_graphics(boot_params);

    /* 4. EFI Memory Map 획득 & ExitBootServices() */
    status = exit_boot_services(handle, &map);

    /* 5. 커널 본체로 점프 (돌아오지 않음) */
    goto_kernel(image_addr, boot_params);
}
/* drivers/firmware/efi/libstub/x86-stub.c
 * handle_kernel_image — 커널 이미지 메모리 배치 */
static efi_status_t
handle_kernel_image(unsigned long *image_addr,
                     unsigned long *image_size,
                     unsigned long *reserve_addr,
                     unsigned long *reserve_size)
{
    /* KASLR: 커널 로드 주소 랜덤화 */
    status = efi_random_alloc(alloc_size, CONFIG_PHYSICAL_ALIGN,
                              &addr, seed, EFI_LOADER_CODE,
                              EFI_ALLOC_LIMIT);
    if (status != EFI_SUCCESS)
        /* fallback: 기본 주소 사용 */
        addr = LOAD_PHYSICAL_ADDR;

    *image_addr = addr;
    return EFI_SUCCESS;
}
/* drivers/firmware/efi/libstub/gop.c
 * setup_graphics — GOP에서 프레임버퍼 정보 수집 */
static void
setup_graphics(struct boot_params *boot_params)
{
    efi_graphics_output_protocol_t *gop;
    efi_status_t  status;

    /* GOP 프로토콜 핸들 검색 */
    status = efi_bs_call(LocateProtocol,
                         &efi_graphics_output_protocol_guid,
                         NULL, (void **)&gop);
    if (status != EFI_SUCCESS)
        return;

    /* boot_params->screen_info에 GOP 정보 기록 */
    struct screen_info *si = &boot_params->screen_info;
    si->lfb_base   = gop->Mode->FrameBufferBase;
    si->lfb_size   = gop->Mode->FrameBufferSize;
    si->lfb_width  = gop->Mode->Info->HorizontalResolution;
    si->lfb_height = gop->Mode->Info->VerticalResolution;
    si->lfb_depth  = 32;
    si->orig_video_isVGA = VIDEO_TYPE_EFI;
}
EFI Stub 소스 파일역할
efi-stub-entry.cPE 엔트리 포인트, boot_params 설정
efi-stub.cefi_main 공통 로직, ExitBootServices 호출
x86-stub.cx86 커널 이미지 배치, KASLR
arm64-stub.cARM64 커널 이미지 배치
gop.cGOP 프레임버퍼 정보 수집
file.cESP 파일 시스템 접근
mem.cEFI 메모리 할당/해제 래퍼
random.cEFI_RNG_PROTOCOL, KASLR 시드
relocate.c커널 이미지 재배치(Relocation) 처리

Memory Map 처리

UEFI 펌웨어가 전달하는 EFI Memory Map은 커널이 물리 메모리를 관리하기 위한 기초 정보입니다. x86에서는 이 맵을 e820 테이블로 변환하여 커널의 기존 메모리 관리 인프라와 통합합니다.

EFI Memory Map → e820 변환 흐름 EFI Memory Map EfiConventionalMemory EfiLoaderCode/Data EfiRuntimeServicesCode EfiRuntimeServicesData EfiACPIReclaimMemory EfiACPIMemoryNVS EfiMemoryMappedIO EfiReservedMemoryType 변환 로직 efi_memmap _init_early() e820 Table E820_TYPE_RAM E820_TYPE_RESERVED E820_TYPE_ACPI E820_TYPE_NVS E820_TYPE_PMEM 커널 메모리 관리 memblock → buddy allocator 변환 규칙 Conventional → RAM Loader* → RAM Runtime* → Reserved ACPI Reclaim → ACPI ACPI NVS → NVS MMIO → Reserved Reserved → Reserved Persistent → PMEM
/* EFI Stub에서 GetMemoryMap 호출 — ExitBootServices 직전 */
efi_status_t
efi_get_memory_map(struct efi_boot_memmap *map)
{
    efi_status_t status;

    /* 첫 호출: 필요한 버퍼 크기 확인 */
    map->map_size = 0;
    status = efi_bs_call(GetMemoryMap,
                         &map->map_size,
                         NULL,
                         &map->map_key,
                         &map->desc_size,
                         &map->desc_ver);
    /* EFI_BUFFER_TOO_SMALL 반환 — map_size에 필요 크기 기록 */

    /* 여유분 추가 (할당 중 맵이 변할 수 있으므로) */
    map->map_size += 2 * map->desc_size;

    /* 버퍼 할당 후 재호출 */
    status = efi_bs_call(AllocatePool, EfiLoaderData,
                         map->map_size, (void **)&map->map);
    status = efi_bs_call(GetMemoryMap,
                         &map->map_size,
                         map->map,
                         &map->map_key,
                         &map->desc_size,
                         &map->desc_ver);
    return status;
}
/* arch/x86/platform/efi/efi.c — EFI 메모리 맵 초기 처리 */
void __init efi_memmap_init_early(struct efi_memory_map_data *data)
{
    struct efi_memory_map map = {
        .phys_map  = data->phys_map,
        .nr_map    = data->size / data->desc_size,
        .desc_size = data->desc_size,
        .desc_version = data->desc_version,
    };

    /* early_memremap으로 EFI 맵을 커널 가상 주소에 매핑 */
    map.map = early_memremap(data->phys_map, data->size);

    /* 전역 efi.memmap에 설정 */
    efi.memmap = map;
}
/* EFI_MEMORY_DESCRIPTOR 파싱 — 커널 e820 변환 */
static void __init
do_add_efi_memmap(void)
{
    efi_memory_desc_t *md;

    for_each_efi_memory_desc(md) {
        unsigned long long start = md->phys_addr;
        unsigned long long size  = md->num_pages << EFI_PAGE_SHIFT;
        int e820_type;

        switch (md->type) {
        case EFI_LOADER_CODE:
        case EFI_LOADER_DATA:
        case EFI_BOOT_SERVICES_CODE:
        case EFI_BOOT_SERVICES_DATA:
        case EFI_CONVENTIONAL_MEMORY:
            e820_type = E820_TYPE_RAM;
            break;
        case EFI_ACPI_RECLAIM_MEMORY:
            e820_type = E820_TYPE_ACPI;
            break;
        case EFI_ACPI_MEMORY_NVS:
            e820_type = E820_TYPE_NVS;
            break;
        case EFI_PERSISTENT_MEMORY:
            e820_type = E820_TYPE_PMEM;
            break;
        default:
            e820_type = E820_TYPE_RESERVED;
            break;
        }
        e820__range_add(start, size, e820_type);
    }
    e820__update_table(e820_table);
}
디버깅 팁: 커널 부팅 인자 efi=debug를 추가하면 EFI Memory Map 전체를 커널 로그로 출력합니다. dmesg | grep "efi: mem"으로 각 영역의 타입, 주소, 크기, 속성을 확인할 수 있습니다.

Runtime Services 래퍼 구현

ExitBootServices() 이후에도 커널은 UEFI Runtime Services(GetTime, SetVariable, ResetSystem 등)를 호출합니다. 이를 위해 커널은 런타임 영역을 가상 주소(Virtual Address)로 재매핑하고, 전용 래퍼 함수를 통해 안전하게 호출합니다.

Runtime Services 커널 래퍼 아키텍처 커널 공간 efi.get_variable() efi.set_variable() efi.get_time() efi.reset_system() EFI Runtime Wrapper spinlock + page table switch efi_scratch (전용 CR3) EFI 페이지 테이블 call SetVirtual AddressMap() 부팅 초기 1회 호출 Runtime Services 펌웨어 코드 (NVRAM R/W) 가상 주소로 재매핑됨 EFI_MEMORY_RUNTIME 영역 Runtime Fault Handling Page Fault → efi_recover_from_page_fault() Runtime 호출 중 fault 감지 → 서비스 비활성화 + 경고 로그 가상 주소 매핑 Physical: 0xF000_0000 (예시) ↓ SetVirtualAddressMap Virtual: 0xFFFF_xxxx_xxxx_xxxx 전용 EFI 페이지 테이블(efi_pgd) CR3 전환으로 격리
/* arch/x86/platform/efi/efi_64.c
 * Runtime Services 호출 래퍼 — 페이지 테이블 전환 */
efi_status_t efi_thunk(efi_runtime_services_t *runtime,
                       void *func, ...)
{
    unsigned long flags;
    efi_status_t  status;

    /* 1. 인터럽트 비활성화 + 스핀락 획득
     *    Runtime Services는 재진입 불가 */
    spin_lock_irqsave(&efi_runtime_lock, flags);

    /* 2. EFI 전용 페이지 테이블로 CR3 전환
     *    Runtime 영역이 매핑된 efi_pgd 사용 */
    efi_switch_mm(&efi_mm);

    /* 3. 실제 Runtime Service 함수 호출 */
    status = efi_call(func, ...);

    /* 4. 커널 페이지 테이블로 복원 */
    efi_switch_mm(current->active_mm);

    /* 5. 스핀락 해제 + 인터럽트 복원 */
    spin_unlock_irqrestore(&efi_runtime_lock, flags);

    return status;
}
/* drivers/firmware/efi/runtime-wrappers.c
 * SetVirtualAddressMap — 부팅 초기 1회 호출 */
static void __init
efi_set_virtual_address_map(void)
{
    efi_memory_desc_t *md;
    int count = 0;

    /* Runtime 영역에 가상 주소 할당 */
    for_each_efi_memory_desc(md) {
        if (!(md->attribute & EFI_MEMORY_RUNTIME))
            continue;

        /* 물리 → 가상 주소 매핑 계산 */
        md->virt_addr = (u64)(unsigned long)
                        efi_map_region(md);
        count++;
    }

    /* 펌웨어에 가상 주소 맵 전달
     * 이후 펌웨어도 가상 주소를 사용 */
    efi.runtime->SetVirtualAddressMap(
        count * efi.memmap.desc_size,
        efi.memmap.desc_size,
        efi.memmap.desc_version,
        virtual_map
    );
}
/* drivers/firmware/efi/runtime-wrappers.c
 * Runtime fault 복구 — 펌웨어 버그 대응 */
void efi_recover_from_page_fault(unsigned long phys_addr)
{
    /* Runtime Service 호출 중 page fault 발생 */
    pr_err("Page fault in EFI runtime service at 0x%lx\n",
           phys_addr);

    /* 해당 Runtime Service를 영구적으로 비활성화 */
    clear_bit(EFI_RUNTIME_SERVICES, &efi.flags);

    /* 호출자에게 에러 반환
     * 커널 패닉 대신 graceful degradation */
    schedule_work(&efi_rts_work->work);
}
Runtime Service커널 래퍼주요 사용처
GetVariableefi.get_variable()efivarfs, Secure Boot 키 읽기
SetVariableefi.set_variable()부트 엔트리 갱신, pstore
GetNextVariableNameefi.get_next_variable()변수 열거
GetTime/SetTimeefi.get_time()RTC 대체 (ACPI 없을 때)
ResetSystemefi.reset_system()reboot, shutdown
UpdateCapsuleefi.update_capsule()펌웨어 업데이트
QueryCapsuleCapabilitiesefi.query_capsule_caps()캡슐 지원 확인

efivars sysfs 상세

Linux 커널은 UEFI 변수를 두 가지 인터페이스로 노출합니다: 레거시 /sys/firmware/efi/vars/(sysfs)와 현대적 /sys/firmware/efi/efivars/(efivarfs). 현재 efivarfs가 표준이며, 변수의 CRUD 연산을 파일 I/O로 수행합니다.

# /sys/firmware/efi/efivars/ 구조
# 각 변수는 "{이름}-{벤더GUID}" 형식의 파일
$ ls /sys/firmware/efi/efivars/
Boot0001-8be4df61-93ca-11d2-aa0d-00e098032b8c
BootCurrent-8be4df61-93ca-11d2-aa0d-00e098032b8c
BootOrder-8be4df61-93ca-11d2-aa0d-00e098032b8c
SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c
PK-8be4df61-93ca-11d2-aa0d-00e098032b8c
KEK-8be4df61-93ca-11d2-aa0d-00e098032b8c
db-d719b2cb-3d3a-4596-a3bc-dad00e67656f

# 변수 읽기 — 첫 4바이트는 속성(attributes), 나머지가 데이터
$ hexdump -C /sys/firmware/efi/efivars/BootCurrent-8be4df61-*
00000000  07 00 00 00 01 00    # attrs=0x07, data=0x0001 (Boot0001)

# 변수 속성 비트마스크
# 0x01 = NON_VOLATILE
# 0x02 = BOOTSERVICE_ACCESS
# 0x04 = RUNTIME_ACCESS
# 0x20 = AUTHENTICATED_WRITE_ACCESS
# 0x40 = TIME_BASED_AUTHENTICATED_WRITE
/* fs/efivarfs/super.c — efivarfs 마운트 시 변수 열거 */
static int efivarfs_fill_super(struct super_block *sb,
                                struct fs_context *fc)
{
    /* 모든 EFI 변수를 순회하며 inode 생성 */
    while ((efivar_entry_iter_begin()) == 0) {
        /* GetNextVariableName으로 변수 열거 */
        status = efi.get_next_variable(&name_size, name, &vendor);
        if (status == EFI_NOT_FOUND)
            break;

        /* "{이름}-{GUID}" 형식으로 dentry 생성 */
        efivarfs_create_dentry(sb, name, &vendor);
    }
    return 0;
}
/* drivers/firmware/efi/efi-pstore.c — pstore EFI 백엔드
 * 커널 패닉 시 콘솔 로그를 UEFI 변수에 저장 */
static struct pstore_info efi_pstore_info = {
    .owner = THIS_MODULE,
    .name  = "efi",
    .open  = efi_pstore_open,
    .close = efi_pstore_close,
    .read  = efi_pstore_read,
    .write = efi_pstore_write,  /* panic 시 호출 */
    .erase = efi_pstore_erase,
};

/* pstore write — UEFI 변수에 크래시 로그 기록 */
static int efi_pstore_write(struct pstore_record *record)
{
    char name[DUMP_NAME_LEN];

    /* 변수 이름: "dump-type0-N-ID-crc32" */
    sprintf(name, "dump-type%u-%u-%llu-%lu",
            record->type, record->part,
            record->id, crc32(0, record->buf, record->size));

    /* EFI SetVariable로 NVRAM에 기록 */
    return efivar_entry_set_safe(
        name, vendor,
        EFI_VARIABLE_NON_VOLATILE |
        EFI_VARIABLE_BOOTSERVICE_ACCESS |
        EFI_VARIABLE_RUNTIME_ACCESS,
        record->size, record->buf);
}
주의: UEFI NVRAM의 저장 공간은 제한적입니다(일반적으로 64KB~256KB). pstore가 반복적으로 크래시 로그를 저장하면 공간이 부족해질 수 있으므로, pstore.update_ms와 최대 로그 수를 적절히 설정해야 합니다.

TPM Event Log 처리

UEFI 펌웨어는 부팅 과정에서 측정한 컴포넌트(부트로더, 드라이버, 설정 등)의 해시를 TPM PCR에 확장(extend)하고, 각 측정 이벤트를 Event Log에 기록합니다. 커널은 이 Event Log를 파싱하여 부팅 무결성을 검증할 수 있습니다.

TCG Event Log 처리 흐름 UEFI 펌웨어 SEC/PEI/DXE 측정 UEFI 드라이버 해시 부트로더 해시 Secure Boot 변수 OS 로더 코드 TPM PCR Banks PCR[0]: 펌웨어 코드 PCR[1]: 펌웨어 설정 PCR[4]: 부트 로더 PCR[7]: Secure Boot 정책 PCR[11]: 커널/initrd Extend TCG Event Log Event #1: CRTM Version Event #2: FW Blob Event #3: BL Image Event #4: SB Policy Event #N: ... 기록 Linux 커널: EFI Configuration Table → TCG2 Event Log 파싱 drivers/char/tpm/eventlog/ — EFI_TCG2_EVENT_LOG_FORMAT_TCG_2 securityfs 노출 /sys/kernel/security/tpm0/binary_bios_measurements /sys/kernel/security/ima/ascii_runtime_measurements 원격 증명 (Attestation) Event Log + PCR Quote 비교 → 부팅 체인 무결성 검증 PCR 재현 검증 Event 해시 순차 extend → PCR 값과 일치 확인
/* drivers/char/tpm/eventlog/efi.c
 * UEFI TCG2 Event Log 파싱 — EFI Configuration Table에서 획득 */
int __init tpm_read_log_efi(struct tpm_chip *chip)
{
    struct efi_tcg2_final_events_table *final_tbl;
    struct linux_efi_tpm_eventlog *log_tbl;
    int ret;

    /* EFI Configuration Table에서 Event Log 위치 획득 */
    log_tbl = memremap(efi.tpm_log, sizeof(*log_tbl),
                       MEMREMAP_WB);
    if (!log_tbl)
        return -ENOMEM;

    /* TCG 2.0 Event Log 형식 확인 */
    if (log_tbl->version != EFI_TCG2_EVENT_LOG_FORMAT_TCG_2) {
        pr_warn("Unsupported event log format: %d\n",
                log_tbl->version);
        return -EINVAL;
    }

    /* Event Log 데이터를 tpm_chip 구조체에 복사 */
    chip->log.bios_event_log = kmemdup(
        log_tbl->log, log_tbl->log_size, GFP_KERNEL);
    chip->log.bios_event_log_end =
        chip->log.bios_event_log + log_tbl->log_size;

    /* Final Events Table 처리 (ExitBootServices 이후 이벤트) */
    if (efi.tpm_final_log) {
        final_tbl = memremap(efi.tpm_final_log,
                             sizeof(*final_tbl), MEMREMAP_WB);
        /* ... final events 추가 ... */
    }

    return 0;
}
# PCR bank 열거 및 읽기
$ tpm2_pcrread sha256
  sha256:
    0 : 0x3D4E88B44ECAE38C...  # 펌웨어 코드
    1 : 0x1A2B3C4D5E6F7890...  # 펌웨어 설정
    4 : 0xABCD1234EFGH5678...  # 부트 로더
    7 : 0x9F8E7D6C5B4A3210...  # Secure Boot 정책

# Event Log 바이너리 읽기
$ cat /sys/kernel/security/tpm0/binary_bios_measurements | \
  tpm2_eventlog

# Event 예시 출력:
# PCRIndex: 0
# EventType: EV_S_CRTM_VERSION
# DigestCount: 1
#   AlgorithmId: sha256
#   Digest: "3d4e88b44ecae38c..."
# EventData: "UEFI FW v2.80"

# PCR 재현 검증 스크립트
$ tpm2_eventlog /sys/kernel/security/tpm0/binary_bios_measurements \
  | python3 -c "
import sys, hashlib
# Event Log의 각 digest를 순서대로 extend하여
# 최종 PCR 값과 비교 → 무결성 검증
"
PCR 인덱스측정 대상수정 시점
PCR[0]CRTM, BIOS, Host Platform Extensions펌웨어 업데이트
PCR[1]Host Platform ConfigurationBIOS 설정 변경
PCR[2]UEFI Option ROM Code어댑터 추가/제거
PCR[3]UEFI Option ROM DataOption ROM 설정 변경
PCR[4]IPL (Initial Program Loader)부트로더 변경
PCR[5]IPL Code Configuration부트로더 설정 변경
PCR[7]Secure Boot PolicyPK/KEK/db/dbx 변경
PCR[11]systemd-stub: 커널, initrd, cmdlineUKI 갱신
PCR[14]shim/MOK 관련MOK 키 변경

Capsule Update 커널 구현

커널의 캡슐 업데이트 메커니즘은 /dev/efi_capsule_loader 디바이스 또는 fwupd/LVFS를 통해 펌웨어 이미지를 UEFI 런타임에 전달합니다. 재부팅 시 펌웨어가 캡슐을 감지하고 플래싱을 수행합니다.

/* drivers/firmware/efi/capsule-loader.c
 * 캡슐 로더 드라이버 — /dev/efi_capsule_loader */
static ssize_t
efi_capsule_write(struct file *file,
                   const char __user *buff,
                   size_t count, loff_t *offp)
{
    struct capsule_info *cap_info = file->private_data;
    efi_capsule_header_t *header;
    int ret;

    /* 사용자 공간에서 캡슐 데이터 수신 */
    ret = copy_from_user(cap_info->pages[pg], buff, count);

    /* 첫 번째 페이지에서 캡슐 헤더 검증 */
    if (*offp == 0) {
        header = cap_info->pages[0];
        /* GUID, 헤더 크기, 플래그 검증 */
        if (header->HeaderSize < sizeof(*header))
            return -EINVAL;
        cap_info->total_size = header->CapsuleImageSize;
    }

    /* 전체 수신 완료 시 커널에 등록 */
    if (cap_info->count == cap_info->total_size) {
        /* EFI 변수에 캡슐 위치 기록
         * 재부팅 시 펌웨어가 이 위치에서 캡슐을 읽음 */
        ret = efi_capsule_setup_info(cap_info);
    }

    return count;
}
# fwupd/LVFS를 통한 캡슐 업데이트 실전 흐름

# 1. LVFS에서 펌웨어 메타데이터 다운로드
$ sudo fwupdmgr refresh

# 2. 업데이트 가능 장치 확인
$ fwupdmgr get-devices
  System Firmware
    DeviceId:     xxxxxxxx
    Vendor:       Dell Inc.
    Version:      1.12.0
    UpdateState:  success

# 3. 사용 가능한 업데이트 확인
$ fwupdmgr get-updates
  System Firmware has firmware update 1.14.0
  Description: Security fixes for...

# 4. 업데이트 적용 (캡슐을 ESP에 복사 + EFI 변수 설정)
$ sudo fwupdmgr update
# → /boot/efi/EFI/UpdateCapsule/ 에 .cap 파일 복사
# → EFI 변수 OsIndicationsSupported & OsIndications에
#   EFI_OS_INDICATIONS_FILE_CAPSULE_DELIVERY_SUPPORTED 설정

# 5. 재부팅 → 펌웨어가 캡슐 감지 → 플래싱
$ sudo reboot

# 6. 재부팅 후 결과 확인
$ fwupdmgr get-results
$ cat /sys/firmware/efi/esrt/entries/entry0/last_attempt_status
# 0 = SUCCESS
$ cat /sys/firmware/efi/esrt/entries/entry0/fw_version
# 새 버전 확인
ESRT (EFI System Resource Table): UEFI 펌웨어는 ESRT를 통해 업데이트 가능한 컴포넌트 목록, 현재 버전, 마지막 업데이트 결과를 커널에 노출합니다. fwupd는 이 정보를 기반으로 업데이트 대상을 식별합니다. /sys/firmware/efi/esrt/에서 확인할 수 있습니다.

GOP→efifb 전환

UEFI 펌웨어의 Graphics Output Protocol(GOP)은 ExitBootServices() 이전까지만 사용 가능합니다. 커널은 EFI Stub에서 GOP 프레임버퍼 정보를 수집하여 efifb 또는 simpledrm 드라이버로 전달하고, 최종적으로 네이티브 DRM 드라이버로 핸드오프합니다.

GOP framebuffer → efifb/simplefb → DRM handoff GOP (UEFI) FrameBufferBase Resolution, BPP PixelFormat PixelsPerScanLine EFI Stub setup_graphics() → screen_info 구조체 GOP 정보 저장 boot_params에 기록 efifb fbdev 프레임버퍼 CONFIG_FB_EFI=y simpledrm DRM simple-pipe CONFIG_DRM_SIMPLEDRM 네이티브 DRM i915 / amdgpu / nouveau 모드세팅 + 가속 efifb/simpledrm 교체 교체 Boot Services 단계 ExitBootServices 초기 커널 GPU 드라이버 로드 screen_info 전달 데이터 lfb_base, lfb_size, lfb_width, lfb_height, lfb_depth, orig_video_isVGA = VIDEO_TYPE_EFI, stride DRM Handoff 과정 1. drm_aperture_remove_framebuffers() 2. efifb/simpledrm 제거 → 네이티브 DRM 활성화
/* drivers/video/fbdev/efifb.c — efifb 프로브 */
static int efifb_probe(struct platform_device *dev)
{
    struct fb_info *info;
    struct screen_info *si = &screen_info;

    /* screen_info에서 GOP 프레임버퍼 정보 획득 */
    if (si->orig_video_isVGA != VIDEO_TYPE_EFI)
        return -ENODEV;

    info = framebuffer_alloc(0, &dev->dev);
    info->fix.smem_start = si->lfb_base;
    info->fix.smem_len   = si->lfb_size;
    info->var.xres       = si->lfb_width;
    info->var.yres       = si->lfb_height;

    /* 프레임버퍼 메모리를 ioremap (WC 매핑) */
    info->screen_base = ioremap_wc(
        info->fix.smem_start, info->fix.smem_len);

    /* fbdev 등록 — /dev/fb0 생성 */
    register_framebuffer(info);
    return 0;
}
/* drivers/gpu/drm/tiny/simpledrm.c — simpledrm DRM 핸드오프
 * 네이티브 GPU 드라이버가 로드되면 simpledrm이 양보 */
static int simpledrm_probe(struct platform_device *pdev)
{
    struct simpledrm_device *sdev;
    struct drm_device *dev;

    /* screen_info 기반 simple framebuffer */
    sdev = simpledrm_device_create(&pdev->dev, &screen_info);

    /* DRM 디바이스 등록 */
    dev = &sdev->dev;
    drm_dev_register(dev, 0);
    drm_fbdev_generic_setup(dev, 0);
    return 0;
}

/* 네이티브 DRM 드라이버(i915 등)가 프로브 시:
 * drm_aperture_remove_framebuffers("simpledrm")
 * → simpledrm 플랫폼 디바이스 제거
 * → 네이티브 DRM이 GPU 하드웨어 직접 제어 */

ARM64 UEFI 부팅 특수성

ARM64(AArch64) 플랫폼에서의 UEFI 부팅은 x86과 몇 가지 중요한 차이가 있습니다: Device Tree와 ACPI의 공존, PE/COFF Image 헤더의 특수 구조, UEFI Memory Attribute Protocol을 통한 보안 강화 등입니다.

ARM64 UEFI 부팅 흐름 UEFI 펌웨어 TF-A (BL1→BL2→BL31) → BL33: UEFI (EDK II) ACPI 또는 DT 제공 Boot Services 실행 PSCI/SCMI 지원 EFI Stub (ARM64) PE/COFF 헤더 검증 DTB 또는 ACPI 감지 커널 이미지 재배치 initrd 로드 ExitBootServices() Linux 커널 EL1에서 실행 EFI Runtime Services ACPI/DT 파싱 PSCI로 SMP 부팅 Memory Attribute 적용 x86 대비 차이 • DT + ACPI 공존 • TF-A 부팅 체인 • PSCI (SMP/전원) • Memory Attribute • 2MB 정렬 요구 • EL2/EL3 전환 ARM64 Linux Image 레이아웃 MZ 매직 PE/COFF 헤더 ARM64 헤더 • text_offset: 커널 텍스트 오프셋 (2MB 정렬) • image_size: 커널 전체 크기 • flags: Little-endian, 4KB 페이지, EFI Stub 유무 • magic: ARM\x64 (0x644d5241) PE/COFF subsystem: EFI_APPLICATION (0x0A) UEFI Memory Attribute Protocol UEFI 2.10+: EFI_MEMORY_ATTRIBUTE_PROTOCOL • SetMemoryAttributes(addr, size, EFI_MEMORY_RO) • SetMemoryAttributes(addr, size, EFI_MEMORY_XP) 커널 이미지를 코드(RX)와 데이터(RW)로 분리 → W^X 보안 원칙 적용 ARM64에서 특히 중요: PXN/UXN 비트로 실행 권한 세밀 제어
/* drivers/firmware/efi/libstub/arm64-stub.c
 * ARM64 EFI Stub 진입 — 커널 이미지 메모리 배치 */
efi_status_t handle_kernel_image(
    unsigned long *image_addr,
    unsigned long *image_size,
    unsigned long *reserve_addr,
    unsigned long *reserve_size,
    efi_loaded_image_t *image,
    efi_handle_t image_handle)
{
    efi_status_t status;
    unsigned long kernel_size, kernel_codesize;

    /* ARM64 커널은 2MB 정렬 필요 */
    unsigned long min_kimg_align = SZ_2M;

    /* KASLR: 랜덤 오프셋으로 커널 배치 */
    status = efi_random_alloc(
        *image_size, min_kimg_align,
        image_addr,
        efi_get_kimg_min_align(),
        EFI_LOADER_CODE, EFI_ALLOC_LIMIT);

    /* 이미지를 새 주소로 복사 */
    memcpy((void *)*image_addr,
           image->ImageBase, kernel_size);

    /* Memory Attribute Protocol로 W^X 적용 */
    efi_adjust_memory_range_protection(
        *image_addr, kernel_codesize,
        EFI_MEMORY_RO | EFI_MEMORY_XP);

    return EFI_SUCCESS;
}
/* drivers/firmware/efi/libstub/mem.c
 * UEFI Memory Attribute Protocol — W^X 적용 */
efi_status_t
efi_adjust_memory_range_protection(
    unsigned long addr, unsigned long size,
    unsigned long attributes)
{
    efi_memory_attribute_protocol_t *memattr;
    efi_status_t status;

    /* Memory Attribute Protocol 검색 */
    status = efi_bs_call(LocateProtocol,
        &efi_memory_attribute_protocol_guid,
        NULL, (void **)&memattr);
    if (status != EFI_SUCCESS)
        return status;

    /* 코드 영역: RO (쓰기 불가, 실행 가능)
     * 데이터 영역: XP (실행 불가, 쓰기 가능)
     * → W^X: 동시에 쓰기+실행 불가 */
    status = memattr->SetMemoryAttributes(
        memattr, addr, size, attributes);

    return status;
}
항목x86_64ARM64
부팅 체인SEC→PEI→DXE→BDSTF-A BL1→BL2→BL31→BL33(UEFI)
하드웨어 기술ACPI 전용ACPI 또는 Device Tree
SMP 부팅APIC/INIT-SIPIPSCI (SMC 호출)
이미지 정렬CONFIG_PHYSICAL_ALIGN2MB 필수
PE/COFFbzImage PE 스텁Image에 PE 헤더 내장
W^X페이지 테이블(Page Table)로 적용Memory Attribute Protocol
Secure BootPK/KEK/db/dbx동일 + TF-A Trusted Boot
런타임 격리(Isolation)efi_pgd CR3 전환별도 TTBR0 전환

UEFI 디버깅 코드 실전

UEFI 관련 문제를 진단할 때 활용할 수 있는 커널 파라미터, 디버그 로그, OVMF 가상 환경 설정, 실전 스크립트를 정리합니다.

# efi=debug 커널 파라미터 — EFI 관련 상세 로그 활성화

# GRUB에서 설정
# /etc/default/grub:
GRUB_CMDLINE_LINUX="efi=debug"
# sudo update-grub && reboot

# 부팅 후 EFI 디버그 로그 확인
$ dmesg | grep -i efi
[    0.000000] efi: EFI v2.80 by EDK II
[    0.000000] efi: ACPI 2.0=0x7fbfe000 ACPI=0x7fbfe014 SMBIOS=0x7f9e1000
[    0.000000] efi: ESRT=0x7f623018 MOKvar=0x7f608000
[    0.000000] efi: mem00: [Conventional Memory|   |  |  |  |  |   |WB|WT|WC|UC] range=[0x0000000000000000-0x000000000009ffff] (640KiB)
[    0.000000] efi: mem01: [Reserved           |   |  |  |  |  |   |WB|WT|WC|UC] range=[0x00000000000a0000-0x00000000000fffff] (384KiB)
[    0.000000] efi: mem02: [Loader Data        |   |  |  |  |  |   |WB|WT|WC|UC] range=[0x0000000000100000-0x0000000007ffffff] (127MiB)

# 기타 유용한 efi= 옵션
# efi=noruntime     — Runtime Services 비활성화 (펌웨어 버그 우회)
# efi=nosoftreserve — Soft Reserved 메모리 무시
# efi=old_map       — 레거시 가상 주소 매핑 사용 (x86)
# efi=nochunk       — SetVirtualAddressMap 청크 분할 비활성화
# EFI 변수 덤프/분석 스크립트
#!/bin/bash
# uefi-dump.sh — UEFI 시스템 정보 종합 수집

echo "=== UEFI 시스템 정보 ==="
echo "EFI 지원: $([ -d /sys/firmware/efi ] && echo 'Yes' || echo 'No')"
echo "Secure Boot: $(od -An -tx1 /sys/firmware/efi/efivars/SecureBoot-* 2>/dev/null | tail -c3)"
echo

echo "=== 부트 엔트리 ==="
efibootmgr -v 2>/dev/null || echo "efibootmgr not available"
echo

echo "=== EFI 변수 통계 ==="
echo "총 변수 수: $(ls /sys/firmware/efi/efivars/ 2>/dev/null | wc -l)"
echo "총 크기: $(du -sh /sys/firmware/efi/efivars/ 2>/dev/null | cut -f1)"
echo

echo "=== ESRT (System Resource Table) ==="
if [ -d /sys/firmware/efi/esrt ]; then
    for entry in /sys/firmware/efi/esrt/entries/entry*; do
        echo "  $(basename $entry):"
        echo "    Type: $(cat $entry/fw_type)"
        echo "    Version: $(cat $entry/fw_version)"
        echo "    Last Status: $(cat $entry/last_attempt_status)"
    done
fi
echo

echo "=== EFI Memory Map (from dmesg) ==="
dmesg | grep "efi: mem" | head -20

echo
echo "=== Runtime Services 상태 ==="
dmesg | grep -i "efi.*runtime"
# OVMF (Open Virtual Machine Firmware) 디버그 환경 구축

# 1. OVMF 설치
sudo apt install ovmf                # Debian/Ubuntu
sudo dnf install edk2-ovmf           # Fedora/RHEL

# 2. QEMU + OVMF로 UEFI 가상머신 실행
qemu-system-x86_64 \
    -bios /usr/share/ovmf/OVMF.fd \
    -drive file=disk.qcow2,format=qcow2 \
    -m 2G -smp 2 \
    -serial stdio \
    -debugcon file:debug.log \
    -global isa-debugcon.iobase=0x402

# 3. OVMF 디버그 빌드 (상세 로그)
# DEBUG 레벨 OVMF 사용 시 Port 0x402로 디버그 출력
qemu-system-x86_64 \
    -bios /usr/share/ovmf/OVMF_CODE.debug.fd \
    -drive if=pflash,format=raw,file=OVMF_VARS.fd \
    -chardev file,id=debuglog,path=ovmf-debug.log \
    -device isa-debugcon,iobase=0x402,chardev=debuglog \
    -serial mon:stdio \
    -nographic

# 4. 디버그 로그 분석
# ovmf-debug.log 에서 단계별 로그 확인
grep "DXE" ovmf-debug.log        # DXE 단계 이벤트
grep "BDS" ovmf-debug.log        # BDS 부팅 선택
grep "SecureBoot" ovmf-debug.log # Secure Boot 검증
grep "ERROR\|ASSERT" ovmf-debug.log # 에러/assert
# efibootmgr 실전 예제 — 부트 엔트리 관리

# 현재 부트 엔트리 상세 표시
$ sudo efibootmgr -v
BootCurrent: 0001
BootOrder: 0001,0003,0000
Boot0000* EFI Network  PciRoot(0x0)/Pci(0x1C,0x0)/...
Boot0001* ubuntu       HD(1,GPT,...)/File(\EFI\ubuntu\shimx64.efi)
Boot0003* UEFI Shell   FvVol(...)

# 새 부트 엔트리 생성 (직접 EFI Stub 커널 부팅)
$ sudo efibootmgr -c \
    -d /dev/nvme0n1 -p 1 \
    -L "Linux Direct" \
    -l "\EFI\Linux\vmlinuz-6.8.0.efi" \
    -u "root=UUID=xxx ro quiet"

# 부트 순서 변경
$ sudo efibootmgr -o 0004,0001,0000

# 엔트리 삭제
$ sudo efibootmgr -B -b 0003

# 다음 1회만 특정 엔트리로 부팅
$ sudo efibootmgr -n 0000

# 타임아웃 설정 (초)
$ sudo efibootmgr -t 5

# Secure Boot 상태 확인
$ mokutil --sb-state
SecureBoot enabled

# Secure Boot 키 목록
$ mokutil --list-enrolled
[key 1]
  SHA1 Fingerprint: xx:xx:xx:...
  Issuer: C=US, ST=..., O=Canonical Ltd.
UEFI Shell 디버깅: OVMF 환경에서 UEFI Shell을 사용하면 Boot Services 단계에서 메모리 맵(memmap), 디바이스 경로(devtree), 프로토콜(dh -p), 변수(dmpstore)를 직접 검사할 수 있습니다. bcfg boot dump으로 부트 엔트리도 확인 가능합니다.
디버깅 도구용도커널/유저 공간
efi=debugEFI 메모리 맵, 런타임 서비스 상세 로그커널 파라미터
efibootmgrUEFI 부트 엔트리 관리유저 공간
mokutilMOK/Secure Boot 키 관리유저 공간
efivar개별 EFI 변수 읽기/쓰기유저 공간
fwupdmgr펌웨어 업데이트 (LVFS)유저 공간
OVMF + QEMUUEFI 가상 환경 디버깅호스트
UEFI ShellBoot Services 단계 직접 검사펌웨어
/sys/firmware/efi/EFI 변수, ESRT, 런타임 상태sysfs

참고 자료

UEFI 펌웨어와 Linux 커널 EFI 서브시스템을 깊이 이해하려면 아래 공식 사양, 커널 문서, 소스 코드를 함께 참조하는 것이 좋습니다.

UEFI와 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.