ACPI (Advanced Configuration and Power Interface)

ACPI 심화: 아키텍처 4계층, 네임스페이스, FADT/MADT/DMAR/HMAT/BERT 테이블, G/S/D-state 전원 관리, 열 관리, GPE/SCI, Embedded Controller, Operation Region, 핫플러그, 커널 ACPI 서브시스템, 디버깅

전제 조건: UEFI, 인터럽트, 전원 관리 문서를 먼저 읽으세요. ACPI는 "펌웨어가 남긴 하드웨어 설명"과 "OS가 그 설명을 실제 제어로 바꾸는 실행 경로"를 같이 이해해야 읽힙니다.
일상 비유: 이 개념은 건물 관리실의 설비 도면과 운영 매뉴얼과 비슷합니다. 전기실, 냉난방, 비상 버튼, 출입문 제어 규칙이 문서로 정리되어 있어야 관리자가 설비를 올바르게 다루듯이, ACPI도 테이블과 AML 규칙을 통해 OS가 장치, 절전, 열, 깨우기 동작을 수행하게 만듭니다.

핵심 요약

  • RSDP/XSDT — ACPI 테이블 탐색의 시작점입니다.
  • FADT — SCI, PM 레지스터, 저전력 S0 지원 여부 같은 전역 능력을 담습니다.
  • DSDT/SSDT + AML — 장치 구조와 제어 메서드를 정의합니다.
  • _HID/_STA/_CRS — 장치 식별, 존재 여부, 자원 기술의 핵심입니다.
  • SCI/GPE/Notify — 런타임 이벤트가 커널로 들어오는 경로입니다.

단계별 이해

  1. 테이블 발견
    펌웨어가 제공한 RSDP를 찾고 XSDT/RSDT로 루트 테이블 집합을 읽습니다.
  2. 네임스페이스 구성
    ACPICA가 DSDT/SSDT의 AML을 해석해 장치 트리를 만듭니다.
  3. 장치 열거
    _STA, _HID, _CID, _CRS 결과로 커널 디바이스 모델과 연결합니다.
  4. 런타임 제어
    절전, 열, EC, 핫플러그, 깨우기 이벤트를 메서드와 테이블에 따라 수행합니다.
  5. 디버깅
    acpidump, iasl, /sys/firmware/acpi/interrupts를 함께 봅니다.

ACPI 아키텍처 4계층, 네임스페이스, 주요 테이블 심화, 전원 상태 계층(G/S/D), 열 관리, GPE/SCI, Embedded Controller, Operation Region, 핫플러그, 커널 ACPI 서브시스템까지 — 리눅스 커널의 ACPI 구현을 소스 코드 수준에서 분석합니다.

관련 표준: UEFI Forum 기준 최신 규격은 ACPI Specification 6.6(2025년 5월)과 UEFI 2.11(2024년 12월)입니다. 이 페이지는 ACPI 6.6의 테이블, 전원 상태, GPE/SCI, Operation Region 개념과 Linux kernel ACPI 문서의 동작 모델을 함께 반영합니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

ACPI 아키텍처 개요

ACPI(Advanced Configuration and Power Interface)는 OS가 하드웨어 구성 탐색, 전원 관리, 열 관리를 수행하기 위한 플랫폼 독립적 인터페이스 규격입니다. 1996년 Intel/Microsoft/Toshiba가 공동 개발했으며, 2013년부터 UEFI Forum이 관리합니다.

스펙 버전 역사

버전연도주요 변경
1.01996최초 공개 — APM 대체, AML/DSDT 도입
2.0200064-bit XSDT, Generic Address Structure, Processor Aggregator
3.02004NUMA(SRAT/SLIT), PCI Express, x2APIC 지원
4.02009HW-Reduced ACPI(ARM 서버), USB3, SD 카드
5.02011CPPC(Collaborative Processor Performance Control), LPIT
6.02015PPTT(CPU 토폴로지), HMAT(이기종 메모리), IORT(ARM SMMU)
6.32019PCC Operation Region, HMAT 개선, I3C 호스트, Generic Initiator Affinity 추가
6.42021CXL 2.0 지원, Arm AEST/MPAM, 배터리 충전 제한, PRM 플랫폼 런타임 메커니즘
6.52022LoongArch, CXL 메모리 객체, CCEL, USB4, 더 정교한 CPER/EINJ 확장
6.5A2024_DMA, PCC, PM1 PCIEXP_WAKE, _Lxx/_PRW 동작 명확화
6.62025RISC-V MADT/SRAT/IOMMU 확장, CPPC 레지스터 추가, 핫플러그 메모리 기술, 새로운 전원 객체

4대 구성요소

ACPI 규격은 네 가지 핵심 구성요소로 이루어집니다:

  1. ACPI Tables — RSDP → XSDT → {FADT, MADT, DMAR, HMAT, ...} 계층 구조의 정적 데이터 테이블. 펌웨어가 메모리에 배치합니다.
  2. ACPI Namespace — 디바이스, 메서드, 오브젝트를 트리 구조로 조직하는 계층적 이름 공간. AML 코드가 정의합니다.
  3. AML Interpreter — DSDT/SSDT의 AML 바이트코드를 실행하는 커널 내 인터프리터. 리눅스는 ACPICA(ACPI Component Architecture) 코드를 사용합니다.
  4. ACPI Event Model — SCI(System Control Interrupt)와 GPE(General Purpose Event)를 통한 비동기 하드웨어 이벤트 처리.
ACPICA: Intel이 유지보수하는 OS 독립적 ACPI 참조 구현체입니다. 리눅스, FreeBSD, Haiku 등 여러 OS가 동일한 ACPICA 코드(drivers/acpi/acpica/)를 공유합니다. 별도의 릴리스 주기를 갖습니다.
OSPM (Operating System-directed configuration and Power Management) 리눅스 커널 ACPI 서브시스템 — drivers/acpi/ ACPI Tables FADT, MADT, DMAR, HMAT, DSDT ... AML Interpreter ACPICA — 바이트코드 실행 엔진 ACPI Registers PM1a/PM1b Status/Enable, GPE0/GPE1 블록, Fixed Events Platform Hardware SCI, GPE, EC, RTC, Power Button, Thermal Sensor, PCI ... ACPI Namespace
ACPI 아키텍처 4계층 — OSPM이 Tables/AML/Registers를 통해 하드웨어를 제어하며, Namespace가 전 계층을 관통합니다

RSDP에서 네임스페이스까지

부팅 초기에 커널은 먼저 ACPI의 "입구"를 찾아야 합니다. UEFI 환경에서는 EFI Configuration Table을 통해 RSDP를 얻고, 전통적인 x86 환경에서는 EBDA 또는 BIOS 고메모리 영역을 스캔해 RSDP 시그니처를 찾습니다. 이후 RSDP가 가리키는 XSDT/RSDT를 검증한 뒤, FADT와 DSDT/SSDT를 로드해 네임스페이스와 실행 메서드를 준비합니다.

부팅 시 ACPI 발견과 로딩 순서 펌웨어 엔트리 UEFI Configuration Table 또는 EBDA / BIOS 고메모리 RSDP 체크섬, revision, XSDT/RSDT 포인터 ACPI 루트 포인터 XSDT / RSDT 서명된 테이블 포인터 배열 64-bit는 XSDT 우선 FADT / FACS SCI, PM1/GPE, S0ix, waking vector DSDT / SSDT AML 메서드와 장치 트리 _HID, _STA, _CRS, _PRW 보조 테이블 MADT, MCFG, DMAR, HMAT LPIT, PPTT, CEDT, ECDT ACPICA 테이블 로딩 AML 해석 커널 결과물 네임스페이스 구성, acpi_device 생성, IRQ/PM/NUMA/PCIe 설정, 런타임 메서드 실행 준비
ACPI 부팅 진입 경로 — 커널은 RSDP를 통해 루트 테이블을 찾고, FADT/DSDT/SSDT와 보조 테이블을 로드해 네임스페이스와 각 서브시스템 입력을 만듭니다
구간주요 결과물왜 중요한가
RSDP 검증revision, 체크섬, XSDT/RSDT 포인터루트 포인터가 틀리면 이후 모든 테이블이 무효가 됩니다.
XSDT 순회FADT, MADT, MCFG, DMAR, HMAT, SSDT 목록하드웨어 토폴로지와 전원/IRQ/IOMMU 초기화 입력을 모읍니다.
FADT 해석SCI 번호, PM1/GPE 주소, LOW_POWER_S0 등 플래그런타임 ACPI 이벤트와 sleep 경로의 전역 성격을 규정합니다.
DSDT/SSDT 로딩AML 메서드, 장치 오브젝트, Operation Region장치 열거와 제어 메서드 실행이 여기서 시작됩니다.

ACPI 네임스페이스

ACPI 네임스페이스는 역 트리(inverted tree) 구조로, 루트 스코프(\) 아래에 모든 ACPI 오브젝트가 계층적으로 배치됩니다. DSDT/SSDT의 ASL 코드가 네임스페이스를 정의하며, 커널의 ACPICA 인터프리터가 이를 파싱하여 내부 트리로 구축합니다.

루트 스코프 트리

읽는 포인트:
  • 위에서 아래로 \_SB → 디바이스/프로세서/열 노드 순서로 내려갑니다.
  • 각 노드의 _HID, _STA, _CST, _TMP 같은 메서드가 커널 정책 입력이 됩니다.
  • 마지막 박스는 AML 해석 결과가 acpi_device 생성으로 이어지는 실행 경로를 요약합니다.
ACPI 네임스페이스 루트 트리 \ (루트) _SB (System Bus) 버스/디바이스 노드 PCI0 (Host Bridge) SATA / GFX0 / RP01 / NVME LPCB / EC0 / SIO1 / MEM0 _HID/_CID/_STA 메서드 프로세서/전원 노드 _PR / CPU0 ... CPUn _CST, _CPC, _PCT, _PSS 성능/전력/idle 상태 기술 cpufreq/cpuidle 입력 열/이벤트/표시 노드 _TZ / TZ00 (_TMP,_CRT,_HOT) _GPE / _Lxx / _Exx _SI / _MSG 비동기 이벤트와 사용자 표시 커널 해석 ACPICA가 AML 파싱 후 네임스페이스 트리를 구성 scan.c가 acpi_device를 생성하고 Notify를 드라이버에 전달
노드 그룹대표 오브젝트커널 연결 지점
버스/디바이스PCI0, RPxx, NVME, EC0drivers/acpi/scan.c 열거 후 디바이스 바인딩
프로세서/전원_PR, _CST, _CPC, _PSScpuidle, cpufreq 정책 입력
열/이벤트_TZ, _GPE, _Lxx, _ExxSCI/Notify 경로로 thermal 및 이벤트 처리

디바이스 열거 흐름

커널 부팅 시 ACPI 서브시스템은 네임스페이스를 순회하며 모든 디바이스를 열거합니다:

/* drivers/acpi/scan.c — ACPI 디바이스 열거 핵심 흐름 */

/* 1단계: 네임스페이스 순회 — 각 노드마다 콜백 호출 */
int acpi_bus_scan(acpi_handle handle)
{
    /* 루트부터 DFS(깊이 우선)로 네임스페이스 순회 */
    acpi_walk_namespace(ACPI_TYPE_ANY, handle,
                        ACPI_UINT32_MAX,
                        acpi_bus_check_add,  /* descending */
                        acpi_bus_attach,     /* ascending  */
                        NULL, (void **)&device);
    return 0;
}

/* 2단계: acpi_device 생성 */
static acpi_status acpi_bus_check_add(acpi_handle handle, ...)
{
    /* _STA 메서드 평가 — 디바이스 존재/활성 확인 */
    acpi_bus_get_status(device);

    /* _HID/_CID 평가 — 하드웨어 ID 추출 */
    acpi_device_add(device, ...);
}

/* 3단계: 드라이버 매칭 — acpi_device_id 테이블 비교 */
static void acpi_bus_attach(struct acpi_device *device)
{
    /* ACPI 드라이버 또는 platform_device로 바인딩 */
    device_attach(&device->dev);
}

핵심 오브젝트: 식별, 자원, 속성

네임스페이스의 모든 노드가 동일하게 취급되지는 않습니다. Linux는 _HID, _CID, _ADR, _STA, _CRS 같은 표준 오브젝트를 우선 읽어 열거 여부와 자원 배치를 결정합니다. 반대로 PCI나 USB처럼 본래 자체 열거 메커니즘이 있는 버스는 ACPI가 "보조 구성 정보"를 제공하는 역할을 맡습니다.

오브젝트의미커널에서 쓰이는 지점
_HID / _CID주 하드웨어 ID와 호환 IDacpi_device_id 매칭, platform/ACPI 드라이버 바인딩
_UID동일 HID를 가진 여러 인스턴스 구분멀티 인스턴스 장치 식별, sysfs 이름 구성
_ADR버스 상 주소 또는 슬롯 위치PCI Root Port, 그래픽, 메모리 슬롯 같은 주소형 장치 식별
_STAPresent / Enabled / UI-visible / Functioning 비트맵부팅 시 열거 여부, 핫플러그 재검사, 오류 진단
_CRSCurrent Resource SettingsIRQ, MMIO, I/O port, DMA 자원 추출
_PRS / _SRS가능 자원 집합 / 선택 자원 설정재균형 또는 브리지 재설정 시 자원 협상
_DSDUUID 기반 device propertiesGPIO, clock, interrupt 이름 등 보조 속성 전달
_PRW웨이크업 가능 상태와 wake GPE/proc/acpi/wakeup, suspend wake source 설정
_DEP선행 의존 장치 목록GPIO 컨트롤러, power resource, I2C host 준비 후 자식 바인딩
_DSD와 Device Tree 속성 재사용: Linux는 ACPI의 _DSD를 UUID 기반 property bag으로 취급하며, PRP0001 HID가 있으면 Device Tree 스타일 속성 이름을 그대로 재사용할 수 있습니다. 그래서 compatible, clock-names, interrupt-names 같은 이름이 ACPI 펌웨어에도 등장합니다.

펌웨어와 OS의 협상: _OSI, _REV, _OSC

ACPI는 단순 기술 테이블만이 아니라 펌웨어와 OS가 서로 능력을 타협하는 인터페이스도 정의합니다. 이 협상은 장치 열거보다 더 미묘한 호환성 문제를 일으키므로, 부팅 옵션과 PCIe 네이티브 제어 문제를 볼 때 특히 중요합니다.

메서드누가 호출하는가실무 의미
_OSI("...")펌웨어가 OS에 질의펌웨어가 특정 동작 경로를 Windows 버전 문자열 기준으로 분기하는 데 자주 사용합니다. Linux는 호환성 때문에 여러 Windows 문자열을 지원하며, acpi_osi=로 조정할 수 있습니다.
_REV펌웨어가 OS에 질의ACPI revision을 물어보는 메서드이지만, 현실의 펌웨어 오용 때문에 Linux는 호환성 목적으로 보수적으로 응답합니다.
_OSCOS와 펌웨어가 공동 호출PCIe Native Hotplug, AER, PME, SHPC, ASPM 같은 기능 제어권을 OS가 가져올지 펌웨어가 유지할지 협상합니다. 협상이 실패하면 해당 영역은 펌웨어 소유로 남습니다.
호환성 함정:
  • acpi_osi=!로 모든 문자열을 끄면 숨겨져 있던 BIOS 버그를 피할 때도 있지만, 반대로 백라이트·터치패드·배터리 경로가 깨질 수도 있습니다.
  • PCIe 포트에서 AER 또는 Native Hotplug가 보이지 않으면 _OSC 협상이 펌웨어 쪽에 남아 있는지 먼저 확인해야 합니다.
  • 동일 장치가 Device Tree에서는 정상인데 ACPI에서만 실패한다면, 실제 원인은 _DSD 속성 누락이나 _DEP 의존성 순서일 가능성이 큽니다.

Notify 메커니즘

ACPI Notify는 펌웨어가 OS에 비동기 이벤트를 알리는 메커니즘입니다. AML 코드에서 Notify(device, value)를 호출하면 커널의 등록된 핸들러가 실행됩니다.

Notify 값의미용도
0x00Bus Check버스에 디바이스 변경 감지 (핫플러그)
0x01Device Check특정 디바이스 상태 변경
0x02Device Wake디바이스가 시스템을 깨움
0x03Eject Request디바이스 꺼내기 요청
0x80Status Change열 상태, 배터리 상태 등 변경
0x81Information Change디바이스 정보 업데이트
디버깅:
  • cat /sys/firmware/acpi/interrupts/notify로 누적 Notify 카운터를 확인합니다.
  • 상세 로그는 acpi.debug_layer, acpi.debug_level 파라미터로 단계적으로 활성화합니다.
  • 과도한 로그는 성능에 영향을 줄 수 있으므로 필요한 레이어만 선택하세요.

주요 ACPI 테이블 심화

ACPI는 수십 종의 테이블을 정의합니다. 여기서는 UEFI 페이지에서 다룬 테이블 계층 구조를 전제로, 각 테이블의 내부 구조와 커널 파싱 로직을 심화합니다.

FADT (Fixed ACPI Description Table)

FADT는 ACPI의 "마스터 테이블"로, PM 레지스터 주소, SCI 인터럽트 번호, FACS/DSDT 포인터, 그리고 시스템 전체의 ACPI 동작 플래그를 담습니다.

/* include/acpi/actbl.h — FADT 핵심 필드 (ACPI 6.6 기준) */
struct acpi_table_fadt {
    struct acpi_table_header header;  /* "FACP" 시그니처 */
    u32 facs;                         /* FACS 물리 주소 (32-bit) */
    u32 dsdt;                         /* DSDT 물리 주소 (32-bit) */
    u8  preferred_profile;             /* PM 프로필: Desktop/Mobile/Server */
    u16 sci_interrupt;                 /* SCI IRQ 번호 (보통 9) */
    u32 smi_command;                   /* SMI 커맨드 포트 (ACPI enable) */
    u8  acpi_enable;                   /* SMI로 보낼 ACPI 활성화 값 */
    u8  acpi_disable;                  /* SMI로 보낼 ACPI 비활성화 값 */

    /* PM1a/PM1b 레지스터 블록 */
    u32 pm1a_event_block;              /* PM1a_STS + PM1a_EN */
    u32 pm1b_event_block;              /* PM1b_STS + PM1b_EN (선택) */
    u32 pm1a_control_block;            /* SLP_TYP, SLP_EN 비트 */
    u32 pm1b_control_block;

    /* GPE 레지스터 블록 */
    u32 gpe0_block;                    /* GPE0_STS + GPE0_EN */
    u32 gpe1_block;                    /* GPE1_STS + GPE1_EN (선택) */
    u8  gpe0_block_length;
    u8  gpe1_block_length;

    u32 flags;                         /* 주요 플래그 비트 */
#define ACPI_FADT_HW_REDUCED      (1 << 20)  /* HW-Reduced 모드 (ARM) */
#define ACPI_FADT_LOW_POWER_S0    (1 << 21)  /* S0ix/Modern Standby */

    /* ACPI 2.0+ 64-bit 확장 주소 */
    struct acpi_generic_address xfacs;
    struct acpi_generic_address xdsdt;
    struct acpi_generic_address xpm1a_event_block;
    /* ... */
};
HW-Reduced ACPI: ARM 서버 등 고정 하드웨어 레지스터가 없는 플랫폼을 위한 모드입니다. ACPI_FADT_HW_REDUCED 플래그가 설정되면 PM1x/GPE 레지스터 대신 GPIO 기반 이벤트를 사용합니다. 커널은 acpi_gbl_reduced_hardware 전역 변수로 이를 확인합니다.

MADT (Multiple APIC Description Table)

MADT는 시스템의 인터럽트 컨트롤러(APIC, GIC) 토폴로지를 기술합니다. 가변 길이 서브타입 엔트리의 배열로 구성됩니다.

/* MADT 서브타입 — include/acpi/actbl1.h */
enum acpi_madt_type {
    ACPI_MADT_TYPE_LOCAL_APIC           = 0,  /* 프로세서 Local APIC */
    ACPI_MADT_TYPE_IO_APIC              = 1,  /* I/O APIC */
    ACPI_MADT_TYPE_INTERRUPT_OVERRIDE   = 2,  /* ISA IRQ 재매핑 */
    ACPI_MADT_TYPE_NMI_SOURCE           = 3,  /* NMI 소스 */
    ACPI_MADT_TYPE_LOCAL_APIC_NMI       = 4,  /* Local APIC NMI (LINT) */
    ACPI_MADT_TYPE_LOCAL_X2APIC         = 9,  /* x2APIC (APIC ID > 255) */
    ACPI_MADT_TYPE_LOCAL_X2APIC_NMI     = 10, /* x2APIC NMI */
    ACPI_MADT_TYPE_GENERIC_INTERRUPT    = 11, /* ARM GIC CPU Interface */
    ACPI_MADT_TYPE_GENERIC_DISTRIBUTOR  = 12, /* ARM GIC Distributor */
    ACPI_MADT_TYPE_GENERIC_REDISTRIBUTOR = 14, /* ARM GICv3 Redistributor */
    ACPI_MADT_TYPE_GENERIC_ITS          = 15, /* ARM GIC ITS */
    ACPI_MADT_TYPE_MULTIPROC_WAKEUP    = 16, /* Multiprocessor Wakeup (v6.4) */
};

/* Local APIC 서브타입 구조체 */
struct acpi_madt_local_apic {
    struct acpi_subtable_header header;  /* type=0, length=8 */
    u8  processor_id;                    /* ACPI Processor UID */
    u8  id;                               /* Local APIC ID */
    u32 lapic_flags;                     /* bit 0: Enabled */
};

DMAR / IVRS (IOMMU 테이블)

DMA Remapping(Intel VT-d)과 I/O Virtualization(AMD-Vi)의 하드웨어 유닛 정보를 기술합니다.

/* Intel DMAR — include/acpi/actbl2.h */
struct acpi_dmar_hardware_unit {       /* DRHD — DMA Remapping HW Unit */
    struct acpi_dmar_header header;
    u8  flags;
#define ACPI_DMAR_INCLUDE_ALL    (1)    /* 모든 디바이스에 적용 */
    u8  reserved;
    u16 segment;                        /* PCI 세그먼트 번호 */
    u64 address;                        /* 레지스터 베이스 주소 */
    /* 이후 가변 길이 Device Scope 엔트리 */
};

/* RMRR — Reserved Memory Region Reporting */
struct acpi_dmar_reserved_memory {
    struct acpi_dmar_header header;
    u16 reserved;
    u16 segment;
    u64 base_address;                   /* 예약 영역 시작 */
    u64 end_address;                    /* 예약 영역 끝 */
};

/* 커널 DMAR 초기화 — drivers/iommu/intel/dmar.c */
int __init dmar_table_init(void)
{
    dmar_walk_dmar_table((struct acpi_table_dmar *)dmar_tbl,
                         DMAR_MAX_TYPE, dmar_cb);
    /* DRHD → iommu 할당, RMRR → identity mapping 설정 */
}

HMAT (Heterogeneous Memory Attribute Table)

HMAT는 이기종 메모리 토폴로지(HBM, CXL 메모리, PMEM 등)의 대역폭/지연 속성을 기술합니다. NUMA 페이지에서 다룬 SRAT/SLIT이 노드 간 거리를 표현하는 반면, HMAT는 이니시에이터(CPU)↔타겟(메모리) 쌍의 정량적 성능 수치를 제공합니다.

/* HMAT 메모리 근접성 도메인 속성 — include/acpi/actbl2.h */
struct acpi_hmat_locality {
    struct acpi_hmat_structure header;
    u8  flags;
    u8  data_type;                       /* 0=Access Latency, 1=Read Latency */
                                          /* 2=Write Latency, 3=Access BW    */
                                          /* 4=Read BW, 5=Write BW           */
    u8  min_transfer_size;
    u8  reserved;
    u32 number_of_initiator_pds;         /* 이니시에이터 도메인 수 */
    u32 number_of_target_pds;             /* 타겟 도메인 수 */
    u64 entry_base_unit;                  /* 나노초 또는 MB/s 기본 단위 */
    /* 이후 initiator PD 배열 + target PD 배열 + entry 행렬 */
};

/* 커널 HMAT 파싱 — drivers/acpi/numa/hmat.c */
static int __init hmat_parse_locality(union acpi_subtable_headers *header, ...)
{
    /* 지연/대역폭 행렬을 memory target에 연결 */
    hmat_update_target_access(target, locality, entry);
    /* → /sys/devices/system/node/nodeN/access0/initiators/ */
}
CXL 메모리 티어링: CXL 3.0 디바이스는 CEDT(CXL Early Discovery Table)와 HMAT를 함께 사용하여 메모리 티어를 정의합니다. 커널의 memory_tier 프레임워크(mm/memory-tiers.c)가 HMAT 대역폭/지연 정보를 기반으로 핫/콜드 페이지 마이그레이션 정책을 자동 설정합니다.

BERT / HEST / ERST / EINJ (하드웨어 에러 처리)

APEI(ACPI Platform Error Interface) 프레임워크는 4개의 테이블로 구성되며, 머신체크(MCE)를 넘어선 플랫폼 수준 에러 처리를 지원합니다.

테이블용도커널 파일
BERTBoot Error Record — 이전 부팅에서 발생한 에러 로그drivers/acpi/apei/bert.c
HESTHardware Error Source — 에러 소스(MCE, PCIe AER, GHES) 기술drivers/acpi/apei/hest.c
ERSTError Record Serialization — 에러 로그 영속 저장(NVRAM)drivers/acpi/apei/erst.c
EINJError Injection — 에러 주입 테스트 인터페이스drivers/acpi/apei/einj.c
GHES (Generic Hardware Error Source):
  • HEST에서 가장 중요한 에러 소스 유형입니다.
  • NMI/SCI/IRQ로 전달된 에러를 CPER(Common Platform Error Record) 형식으로 처리합니다.
  • 펌웨어가 공유 메모리에 기록한 에러 정보를 커널이 수집합니다.
  • 커널 설정은 CONFIG_ACPI_APEI_GHES로 활성화합니다.

APEI / GHES 로그를 읽는 순서

GHES는 "에러가 났다"는 사실만 던지는 것이 아니라, 펌웨어가 기록한 CPER(Common Platform Error Record)를 커널이 해석해 printk로 풀어주는 구조입니다. 따라서 실제 분석은 "HEST가 어떤 source를 정의했는가"와 "runtime에 어떤 CPER section이 올라왔는가"를 함께 봐야 합니다.

로그 요소무엇을 뜻하는가다음 확인 대상
{Hardware Error} 헤더GHES/APEI가 수집한 플랫폼 에러 프레임 시작severity가 corrected인지 fatal인지 확인
Section type메모리, PCIe, processor, firmware error record 등 세부 분류section별 추가 필드와 장치 위치 해석
FRU / socket / node 정보플랫폼이 제공한 물리 위치 힌트실제 DIMM 슬롯, CPU 소켓, PCIe 포트와 매칭
physical address오류가 난 메모리 주소 또는 BAR 근처 주소EDAC, DIMM 맵, CXL/NUMA 토폴로지와 대조
error severitycorrected, recoverable, fatal 등panic 정책, RAS daemon, firmware-first 경로 확인
# 전형적인 GHES 로그 흐름 예시
{Hardware Error}: Hardware error from APEI Generic Hardware Error Source: 0
{Hardware Error}: It has been corrected by h/w and requires no further action
{Hardware Error}: event severity: corrected
{Hardware Error}: section_type: PCIe error
{Hardware Error}: port_type: PCIe end point
{Hardware Error}: device_id: 0000:5e:00.0
분석 순서: corrected 에러가 반복되면 먼저 dmesg만 볼 것이 아니라, lspci -vv의 AER 카운터, /sys/firmware/acpi/tables/HEST, 해당 디바이스의 링크 상태와 전원 관리 상태를 함께 비교하는 편이 좋습니다.

놓치기 쉬운 보조 테이블

실제 플랫폼 디버깅에서는 FADT나 MADT만으로 끝나지 않습니다. 아래 테이블들은 "왜 이 플랫폼만 이렇게 동작하는가"를 설명하는 단서가 되는 경우가 많습니다.

테이블핵심 내용커널에서 보는 위치
LPITLow Power Idle residency 카운터와 지연 정보drivers/acpi/acpi_lpit.c, s2idle 진단
PPTTCPU 토폴로지와 캐시 계층 구조drivers/acpi/pptt.c, 스케줄러/캐시 토폴로지
ECDT초기 부팅용 Embedded Controller 포트와 GPEdrivers/acpi/ec.c, early EC 초기화
MCFGPCIe Enhanced Configuration Space 영역drivers/acpi/pci_mcfg.c, ECAM 설정
PCCTPlatform Communication Channel mailboxdrivers/acpi/acpi_pcc.c, CPPC/협력형 성능 제어
CEDTCXL host bridge와 early discovery 구조drivers/cxl/acpi.c, CXL 메모리 계층 초기화
실전 팁: 절전 복귀만 실패하면 LPITLOW_POWER_S0 플래그를 먼저 보고, CPU 토폴로지가 이상하면 PPTT, PCIe BAR 접근이 비정상이면 MCFG, 노트북 EC 초기화가 흔들리면 ECDT를 먼저 확인하는 편이 빠릅니다.

ACPI 전원 관리 심화

ACPI는 시스템 전체(Global/Sleep)부터 개별 디바이스(Device)까지 계층적 전원 상태를 정의합니다.

G-states (Global States)

G-states는 시스템 전체의 전원 상태를 나타냅니다:

S-states (Sleep States)

S-state이름CPU메모리디바이스복원 시간
S0Working실행 중활성활성
S1Power On Suspend정지, 캐시 유지유지D1 이상매우 빠름
S2전원 차단유지D2 이상빠름
S3Suspend to RAM전원 차단자체 리프레시D3수 초
S4Hibernate전원 차단디스크 저장전원 차단수십 초
S5Soft Off전원 차단전원 차단전원 차단전체 부팅

Linux 절전 인터페이스와 ACPI 매핑

리눅스 사용자는 보통 /sys/power/state/sys/power/mem_sleep를 통해 절전을 보지만, 이 인터페이스는 ACPI S-state와 완전히 1:1 대응하지는 않습니다. 특히 s2idle은 소프트웨어적으로 모든 장치를 저전력으로 내리는 일반 메커니즘이며, 플랫폼이 충분히 협조할 때만 실제 S0ix residency가 깊게 쌓입니다.

리눅스 인터페이스의미보통 연결되는 ACPI 상태
echo freeze > /sys/power/statesuspend-to-idle명시적 S-state 없음. 보통 S0 내부 저전력 경로를 사용합니다.
echo shallow > /sys/power/mem_sleep
echo mem > /sys/power/state
standby플랫폼이 지원하면 대체로 S1
echo deep > /sys/power/mem_sleep
echo mem > /sys/power/state
suspend-to-RAM플랫폼이 지원하면 대체로 S3
echo disk > /sys/power/statehibernate복귀는 S4 semantics를 따르지만, 종료 경로는 플랫폼에 따라 S5 전원 차단을 사용할 수 있습니다.
Modern Standby 오해 방지: 많은 최신 노트북은 BIOS가 S3를 아예 숨기고 s2idle만 노출합니다. 이 경우 사용자는 "mem suspend"를 실행해도 실제로는 S3가 아니라 Low Power S0 Idle 경로를 타게 됩니다.

S0ix / Modern Standby

S0ix는 ACPI의 Low Power S0 Idle capability를 실무에서 부르는 표현입니다. 인텔의 Modern Standby 용어와 자주 함께 쓰이지만, 본질은 "시스템이 S0에 머문 채 플랫폼 전체가 깊은 저전력 residency로 진입하는 것"입니다. FADT의 LOW_POWER_S0 플래그(bit 21)는 이 가능성을 알리며, 실제 residency 품질은 LPIT와 SoC 전원 컨트롤러 구현에 좌우됩니다.

/* drivers/acpi/sleep.c — S0ix 지원 확인 */
static bool acpi_s2idle_supported(void)
{
    /* FADT LOW_POWER_S0 플래그 확인 */
    if (acpi_gbl_FADT.flags & ACPI_FADT_LOW_POWER_S0) {
        /* LPIT (Low Power Idle Table) 파싱 */
        lpit_read_residency_count_address();
        return true;
    }
    return false;
}

/* /sys/power/mem_sleep 에서 s2idle 선택:
 * echo s2idle > /sys/power/mem_sleep
 * echo mem > /sys/power/state
 *
 * Intel PMC 기반 시스템이라면 S0ix residency 확인:
 * cat /sys/kernel/debug/pmc_core/slp_s0_residency_usec */

여기서 중요한 점은 s2idle이 "진입 방법"이고 S0ix는 "플랫폼이 실제 달성한 전력 상태"라는 것입니다. 장치 드라이버 한 개가 runtime suspend에 실패해도 사용자는 절전에 들어간 것처럼 보이지만, 실제 전력은 S0ix에 못 미치는 경우가 흔합니다.

D-states (Device Power States)

각 디바이스는 독립적으로 D0~D3cold 전원 상태를 가집니다:

/* drivers/acpi/device_pm.c — 디바이스 전원 상태 전환 */
int acpi_device_set_power(struct acpi_device *device, int state)
{
    /* 현재 상태 확인 */
    int cur_state;
    acpi_device_get_power(device, &cur_state);

    if (state == ACPI_STATE_D3_COLD) {
        /* D3cold: _PS3 실행 후 전원 리소스 해제 */
        acpi_evaluate_object(device->handle, "_PS3", NULL, NULL);
        acpi_power_off_list(&device->power.states[state].resources);
    } else if (state == ACPI_STATE_D0) {
        /* D0 복원: 전원 리소스 켜기 → _PS0 실행 */
        acpi_power_on_list(&device->power.states[state].resources);
        acpi_evaluate_object(device->handle, "_PS0", NULL, NULL);
    }
    return 0;
}

Power Resource Objects와 _PR0/_PR3

D-state 설명만 보면 장치 전원이 개별 메서드 _PS0/_PS3로만 전환되는 것처럼 보이지만, 실제 플랫폼은 하나의 전원 레일이나 클럭 공급원을 여러 장치가 공유하는 경우가 많습니다. 이때 ACPI는 Power Resource 오브젝트를 별도로 만들고, 각 장치가 D-state별로 어떤 리소스를 필요로 하는지 _PR0~_PR3로 연결합니다.

오브젝트역할실무 의미
PowerResource공유 전원, 레귤레이터, 클럭, reset 라인의 추상화여러 장치가 함께 쓰는 리소스를 참조 수 방식으로 관리할 수 있습니다.
_PR0D0 상태에서 필요한 Power Resource 목록장치가 완전 동작 상태가 되기 전에 먼저 켜야 하는 리소스를 정의합니다.
_PR3D3hot/D3cold 계열에서 유지하거나 해제할 리소스 정보_PR0가 있으면 반대 방향의 off 경로도 함께 생각해야 하므로, 펌웨어 품질 점검 때 꼭 같이 봐야 합니다.
_ON / _OFFPower Resource 자체의 on/off 메서드ACPI core가 공유 사용자 수를 추적해 필요할 때만 호출합니다.
_PS0 / _PS3장치 자체의 진입/이탈 메서드리소스와 장치 내부 초기화 순서를 분리할 수 있습니다.
장치 전원 리소스와 wake 경로 장치 A _PR0 = {PRW0, PRC0} _PR3 = {PRW0} 장치 B _PR0 = {PRC0} _PRW = {GPE 0x6E, S0} PowerResource PRW0 _ON / _OFF wake logic, aux rail 유지 PowerResource PRC0 shared clock / regulator 참조 수가 0이 되면 _OFF 런타임 D0 _PS0 후 장치 활성 절전 D3 _PS3 후 일부 리소스 해제 wake 경로 _PRW가 wake GPE와 최저 wake state를 선언 SCI/GPE가 armed 되면 suspend에서 깨움
Power Resource 모델 — 장치는 D-state 메서드와 별도로 공유 전원 리소스를 _PRx로 선언하고, wake 능력은 _PRW로 GPE와 최저 wake state를 연결합니다
펌웨어 규칙:
  • _PS0가 있으면 _PS3도 함께 구현되어야 하고, 반대도 마찬가지입니다.
  • _PR0를 정의했다면 실제로 켜지는 Power Resource가 어떤 순서로 _ON 되는지와 _PS0 호출 순서를 함께 봐야 합니다.
  • wake가 안 되는데 D-state 전환은 정상이라면 _PRW, wake GPE 번호, suspend 시 enable mask를 분리해서 확인해야 합니다.
읽는 포인트:
  • 왼쪽 G-state는 시스템 전체 전원 상태입니다.
  • 가운데 S-state는 절전 수준이며, S0ix는 S0 내부 저전력 상태입니다.
  • 오른쪽 D-state는 디바이스별 상태로 시스템 상태와 독립적으로 전환될 수 있습니다.
G-states G0 Working G1 Sleeping G2 Soft Off G3 Mech. Off S-states S0 Working S0ix S1 Standby S3 STR S4 Hibernate S5 Soft Off D-states D0 Fully On D1 D2 D3hot D3cold 관계 G0=S0 | G1=S1~S4 | G2=S5 | G3=Mechanical Off S0ix: S0 내부 저전력, D-state: 디바이스별 독립 전원
G/S/D 상태 전환도 — G-states는 시스템 전체, S-states는 절전 수준, D-states는 디바이스별 전원을 제어합니다
관점핵심
시스템 상태G-stateS-state가 시스템 전체 전원/절전 레벨을 정의합니다.
디바이스 상태D-state는 디바이스별 상태이며 시스템 상태와 독립적으로 전환될 수 있습니다.
Modern StandbyS0ix는 S0 내부 저전력 상태로, 사용자 체감은 빠른 복귀를 목표로 합니다.

C/P-states와 ACPI 관계

C-states(CPU Idle)와 P-states(CPU Performance)는 ACPI가 정의한 프로세서 전원 상태입니다. 자세한 P-state/CPPC 구현은 ktime/Clock 페이지를 참조하세요.

/* ACPI _CST (C-State) 메서드 — 커널 cpuidle 연동 */

/* ASL에서 _CST 반환 형식:
 * Package {
 *   NumEntries,
 *   Package { Register, Type, Latency, Power },  // C1
 *   Package { Register, Type, Latency, Power },  // C2
 *   Package { Register, Type, Latency, Power },  // C3
 * }
 */

/* drivers/acpi/processor_idle.c */
static int acpi_processor_get_cstate_info(struct acpi_processor *pr)
{
    /* _CST 평가하여 C-state 정보 추출 */
    status = acpi_evaluate_object(pr->handle, "_CST", NULL, &buffer);

    /* cpuidle 프레임워크에 C-state 등록 */
    for (i = 1; i <= pr->power.count; i++) {
        cx = &pr->power.states[i];
        /* C1: MWAIT, C2: IO port, C3: MWAIT + BM check */
    }
}
cpuidle governor: 커널은 menu(기본) 또는 teo(Timer Events Oriented) governor로 다음 C-state를 예측합니다. cat /sys/devices/system/cpu/cpuidle/current_governor로 확인할 수 있습니다.

CPPC와 PPTT: 추상 성능 스케일과 토폴로지

전통적인 ACPI P-state는 소수의 성능 점을 정의하는 방식이라 현대 CPU의 세밀한 제어에는 한계가 있습니다. CPPC(Collaborative Processor Performance Control)는 주파수 대신 "연속적이고 단위 없는 추상 성능 스케일"을 제공해, OS가 목표 성능만 제시하고 하드웨어가 세부 동작을 조정할 수 있게 합니다. Linux는 이를 cppc_acpi, amd-pstate, 일부 플랫폼의 pcc-cpufreq 경로로 소비합니다.

요소설명커널 연결점
_CPCCPU별 CPPC 레지스터 집합과 한계값drivers/acpi/cppc_acpi.c
highest_perf이 CPU가 낼 수 있는 최대 추상 성능boost 가능 범위와 capacity 계산의 기준
nominal_perf지속 가능한 최고 성능열적 제약 없이 유지 가능한 기준점
lowest_nonlinear_perf전력 대비 효율 특성이 비선형으로 바뀌기 시작하는 지점governor가 효율 구간을 판단할 때 참고
feedback_ctrsreference / delivered 성능 카운터실제 전달 성능 추정과 조율 품질 분석
PPTTCPU 패키지, 클러스터, 캐시 계층 기술스케줄러 topology, cacheinfo, capacity 분배
# CPU0의 CPPC 노출 확인
$ ls /sys/devices/system/cpu/cpu0/acpi_cppc/
feedback_ctrs  highest_perf  lowest_freq  lowest_nonlinear_perf
lowest_perf    nominal_freq  nominal_perf reference_perf

# 대표 값 확인
$ cat /sys/devices/system/cpu/cpu0/acpi_cppc/highest_perf
$ cat /sys/devices/system/cpu/cpu0/acpi_cppc/nominal_perf
$ cat /sys/devices/system/cpu/cpu0/acpi_cppc/feedback_ctrs
CPPC를 어떻게 읽어야 하는가:
  • highest_perf는 "터보 포함 최대치", nominal_perf는 "지속 가능한 최고치"에 가깝습니다.
  • AMD 플랫폼에서 amd-pstate는 CPPC를 적극적으로 활용해 기존 acpi-cpufreq보다 더 세밀한 제어를 수행합니다.
  • PPTT가 잘못되면 CPU 토폴로지, shared cache 인식, 스케줄링 capacity 균형이 함께 흔들릴 수 있습니다.

ACPI 열 관리

ACPI Thermal Zone은 펌웨어와 OS 간 열 관리 인터페이스를 정의합니다. ASL 코드로 온도 임계값과 쿨링 정책을 선언하면, 커널의 thermal 프레임워크가 이를 실행합니다.

Thermal Zone ASL 메서드

/* Thermal Zone 정의 예시 (ASL) */
ThermalZone (TZ00, 0) {
    /* 현재 온도 반환 (단위: 0.1K, 3000 = 27°C) */
    Method (_TMP, 0, Serialized) {
        Return (\_SB.EC0.RTMP())
    }

    /* Passive cooling 임계 온도 */
    Method (_PSV, 0) { Return (3630) }  /* 90°C */

    /* Passive cooling 대상 프로세서 리스트 */
    Method (_PSL, 0) {
        Return (Package () { \_PR.CPU0, \_PR.CPU1 })
    }

    /* Passive cooling 계수: 온도 추적 반응 속도 */
    Name (_TC1, 1)   /* 현재 온도에 대한 가중치 */
    Name (_TC2, 5)   /* 이전 온도에 대한 가중치 */
    Name (_TSP, 100) /* 폴링 주기 (0.1초 단위, 10초) */

    /* Critical 온도 — 이 온도 초과 시 즉시 셧다운 */
    Method (_CRT, 0) { Return (3830) }  /* 110°C */

    /* Hot 온도 — S4(hibernate) 전환 트리거 */
    Method (_HOT, 0) { Return (3780) }  /* 105°C */
}
메서드설명필수 여부
_TMP현재 온도 (0.1K 단위)필수
_PSVPassive cooling 임계값선택
_PSLPassive cooling 대상 디바이스 리스트_PSV 시 필수
_TC1/_TC2Passive cooling PID 계수_PSV 시 필수
_TSP폴링 주기 (0.1초 단위)선택
_CRTCritical 온도 (즉시 셧다운)선택
_HOTHot 온도 (S4 전환)선택
_ACxActive cooling 임계값 (팬 단계)선택
_AL0~_AL9Active cooling 디바이스 리스트_ACx 시 필수

커널 thermal 프레임워크 연동

/* drivers/acpi/thermal.c — ACPI thermal zone 등록 */
static int acpi_thermal_add(struct acpi_device *device)
{
    struct acpi_thermal *tz;

    /* ACPI thermal zone 정보 수집 */
    acpi_thermal_get_temperature(tz);       /* _TMP */
    acpi_thermal_get_trip_points(tz);       /* _CRT, _HOT, _PSV, _ACx */

    /* 커널 thermal 프레임워크에 등록 */
    tz->thermal_zone = thermal_zone_device_register(
        "acpitz",               /* 이름 */
        trips,                   /* trip point 수 */
        tz,                      /* 드라이버 데이터 */
        &acpi_thermal_zone_ops,  /* get_temp, get_trip_type ... */
        NULL, passive_delay, polling_delay
    );
}

sysfs 인터페이스

# Thermal zone 정보 확인
$ ls /sys/class/thermal/thermal_zone0/
temp  type  trip_point_0_temp  trip_point_0_type  policy  mode

# 현재 온도 (밀리도, 45000 = 45°C)
$ cat /sys/class/thermal/thermal_zone0/temp
45000

# trip point 확인
$ cat /sys/class/thermal/thermal_zone0/trip_point_0_type
critical
$ cat /sys/class/thermal/thermal_zone0/trip_point_0_temp
110000

# 쿨링 디바이스 상태
$ cat /sys/class/thermal/cooling_device0/type
Processor
$ cat /sys/class/thermal/cooling_device0/cur_state
0
$ cat /sys/class/thermal/cooling_device0/max_state
10

GPE & SCI

ACPI 이벤트 모델의 핵심은 SCI(System Control Interrupt)와 GPE(General Purpose Event)입니다. 모든 ACPI 런타임 이벤트는 SCI를 통해 커널에 전달됩니다.

SCI (System Control Interrupt)

SCI는 ACPI 이벤트를 전달하는 공유 레벨 트리거 인터럽트입니다. FADT의 sci_interrupt 필드(보통 IRQ 9)로 지정됩니다. 하나의 SCI로 전원 버튼, 뚜껑 닫힘, GPE, EC 이벤트 등 모든 ACPI 이벤트가 멀티플렉싱됩니다.

GPE 블록 레지스터

GPE 블록은 Status/Enable 레지스터 쌍으로 구성됩니다. FADT에 GPE0/GPE1 블록 주소가 정의되며, 각 비트가 하나의 GPE 이벤트에 대응합니다.

GPE 유형접미사트리거처리 방식
Level-triggered_Lxx레벨 유지핸들러가 소스 클리어할 때까지 재발생
Edge-triggered_Exx에지 전환한 번 발생 후 자동 클리어
이벤트 그룹대표 예정의 위치의미
Fixed EventPower Button, Sleep Button, RTC, PCIEXP_WAKEFADT + PM1 status/enable 비트비트 위치가 고정된 이벤트로 SCI에서 바로 판별할 수 있습니다.
Runtime GPELid, Thermal, Hotplug, EC, DockGPE 블록 + _Lxx/_Exx 메서드런타임에 AML 메서드를 실행하거나 Notify를 발생시킵니다.
Wake GPE_PRW로 선언된 장치 wake장치별 _PRW 패키지suspend 중 특정 GPE를 armed 상태로 두어 시스템을 깨웁니다.

커널 GPE 처리

/* drivers/acpi/acpica/evgpe.c — GPE 처리 흐름 */

/* GPE 핸들러 등록 */
acpi_status acpi_install_gpe_handler(
    acpi_handle gpe_device,
    u32 gpe_number,
    u32 type,                          /* ACPI_GPE_LEVEL/EDGE_TRIGGERED */
    acpi_gpe_handler address,          /* 콜백 함수 */
    void *context)
{
    /* GPE 번호에 해당하는 레지스터 비트 계산 */
    gpe_event_info = acpi_ev_get_gpe_event_info(gpe_device, gpe_number);

    /* 핸들러를 GPE 이벤트에 연결 */
    gpe_event_info->dispatch.handler = handler;
    acpi_ev_enable_gpe(gpe_event_info);
}

/* SCI → GPE dispatch 경로:
 * acpi_ev_sci_xrupt_handler()
 *  → acpi_ev_gpe_detect()        (어떤 GPE 비트가 set되었는지 확인)
 *   → acpi_ev_gpe_dispatch()     (해당 GPE의 핸들러 또는 _Lxx/_Exx 실행)
 */
SCI 안에서 이벤트가 갈라지는 방식 Fixed Event 소스 전원 버튼, RTC, sleep 버튼 GPE 소스 리드, 도킹, 열, 핫플러그 wake GPE, GPIO 이벤트 Embedded Controller SCI_EVT 비트 set 이후 Query(0x84) 필요 SCI IRQ FADT.sci_interrupt로 연결 ACPICA SCI 핸들러 acpi_ev_sci_xrupt_handler() PM1 / GPE status 비트 검사 Fixed Event 경로 power button, RTC wake PM core / button driver로 전달 Runtime GPE 경로 acpi_ev_gpe_detect()acpi_ev_gpe_dispatch() _Lxx/_Exx 실행 또는 핸들러 호출 EC Query 경로 Query(0x84)_Qxx 실행 배터리, 팬, 밝기, thermal 갱신 결과: Notify, wakeup, thermal update, battery 갱신, lid switch, hotplug scan
SCI/GPE/EC 실행 경로 — 하나의 SCI가 들어와도 Fixed Event, Runtime GPE, EC Query 경로가 각각 다른 코드와 AML 메서드로 분기됩니다
Low Power S0 Idle와 GPE: wake 문제를 디버깅할 때는 "어떤 GPE가 S0ix 동안 armed 되었는가"와 "그 GPE가 runtime과 wake 양쪽에서 같은 번호를 쓰는가"를 분리해서 보아야 합니다. 펌웨어는 _PRW로 wake capability를 선언하지만, 실제 storm는 런타임 GPE 쪽에서 터지는 경우가 많습니다.
# GPE 인터럽트 통계 확인
$ cat /sys/firmware/acpi/interrupts/gpe_all
    1247

# 개별 GPE 확인 (GPE 0x6E = 리드 닫힘/열림 이벤트 등)
$ cat /sys/firmware/acpi/interrupts/gpe6E
      23   enabled   unmasked

# SCI 총 인터럽트 수
$ cat /sys/firmware/acpi/interrupts/sci
    1523

# 문제 있는 GPE 비활성화 (GPE storm 대응)
$ echo disable > /sys/firmware/acpi/interrupts/gpe6E

Embedded Controller (EC)

EC(Embedded Controller)는 노트북/랩톱에서 배터리, 팬, 키보드, 온도 센서 등을 제어하는 마이크로컨트롤러입니다. x86 I/O 포트(0x62/0x66)를 통해 호스트 CPU와 통신합니다.

ECDT와 early EC: 어떤 플랫폼은 DSDT를 충분히 해석하기 전부터 EC 접근이 필요합니다. 이 경우 ECDT(Embedded Controller Boot Resources Table)가 EC command/data 포트와 GPE 번호를 먼저 제공해, 커널이 부팅 초기에 EC 경로를 준비할 수 있게 합니다.

EC 프로토콜

/* EC I/O 포트 및 상태 비트 */
#define ACPI_EC_DATA_PORT     0x62   /* 데이터 레지스터 */
#define ACPI_EC_COMMAND_PORT  0x66   /* 커맨드/상태 레지스터 */

/* 상태 레지스터 비트 */
#define ACPI_EC_FLAG_OBF      0x01   /* Output Buffer Full */
#define ACPI_EC_FLAG_IBF      0x02   /* Input Buffer Full */
#define ACPI_EC_FLAG_SCI_EVT  0x20   /* SCI Event Pending */

/* EC 커맨드 */
#define ACPI_EC_COMMAND_READ  0x80   /* 바이트 읽기 */
#define ACPI_EC_COMMAND_WRITE 0x81   /* 바이트 쓰기 */
#define ACPI_EC_COMMAND_QUERY 0x84   /* SCI 이벤트 쿼리 */
#define ACPI_EC_BURST_ENABLE  0x82   /* 버스트 모드 활성화 */

/* drivers/acpi/ec.c — EC 트랜잭션 */
static int acpi_ec_transaction(struct acpi_ec *ec,
                               struct transaction *t)
{
    /* 1. IBF가 클리어될 때까지 대기 */
    ec_poll_guard(ec);

    /* 2. 커맨드 전송 (0x66 포트) */
    outb(t->command, ec->command_addr);

    /* 3. 주소 전송 (0x62 포트) */
    outb(t->wdata[0], ec->data_addr);

    /* 4. OBF 대기 후 데이터 읽기 */
    t->rdata[0] = inb(ec->data_addr);
}

EC 이벤트와 쿼리 메서드

EC가 SCI_EVT 비트를 설정하면 커널이 Query 커맨드(0x84)를 보내 이벤트 번호를 얻고, 해당 _Qxx AML 메서드를 실행합니다.

/* drivers/acpi/ec.c — EC 이벤트 처리 */
static void acpi_ec_event_handler(struct work_struct *work)
{
    /* Query 커맨드로 이벤트 번호 획득 */
    acpi_ec_query(ec, &value);  /* value = 0x00~0xFF */

    /* 해당 _Qxx 메서드 실행 (예: _Q0D = 밝기 변경) */
    snprintf(name, sizeof(name), "_Q%02X", value);
    acpi_evaluate_object(ec->handle, name, NULL, NULL);
}

EC 디버깅

# EC 상태 확인
$ cat /sys/kernel/debug/ec/ec0/io

# EC 이벤트 로그
$ dmesg | grep -i "ec:"
[    0.512] ACPI: EC: EC started
[    0.512] ACPI: EC: interrupt mode

# EC 디버깅 활성화
$ echo 0x10 > /sys/module/acpi/parameters/debug_level
EC 타이밍 문제:
  • 부팅 초기에 EC 트랜잭션 타임아웃이 발생하면 배터리 정보와 팬 제어가 실패할 수 있습니다.
  • 리소스 충돌 우회가 필요할 때는 acpi_enforce_resources=lax를 검토합니다.
  • suspend 중 EC 웨이크업을 막으려면 acpi.ec_no_wakeup=1을 사용합니다.

배터리, AC 어댑터, 뚜껑, 전원 버튼

노트북 계열 플랫폼에서 ACPI는 단순 부팅 테이블이 아니라 사용자 체감 기능의 핵심 제어 경로입니다. 배터리 잔량, AC 연결 여부, 뚜껑 개폐, 전원 버튼 입력은 대부분 ACPI 디바이스와 AML 메서드를 거쳐 커널 power_supply 또는 input 서브시스템으로 올라옵니다. 이 경로는 EC와 GPE에 강하게 의존하므로, 배터리 문제와 lid wake 문제는 거의 항상 함께 봐야 합니다.

장치대표 HID핵심 메서드커널 노출
배터리PNP0C0A_BIF/_BIX, _BST, _BTP/sys/class/power_supply/BAT*/
AC 어댑터ACPI0003_PSR/sys/class/power_supply/AC*/online
뚜껑PNP0C0D_LID, Notify 0x80SW_LID input event, 일부 시스템은 /proc/acpi/button/lid/*
전원/슬립 버튼PNP0C0C, PNP0C0EFixed Event 또는 NotifyKEY_POWER, KEY_SLEEP
/* 노트북 장치 예시 — DSDT/SSDT 디컴파일에서 자주 보는 형태 */
Device (AC) {
    Name (_HID, "ACPI0003")
    Method (_PSR, 0) { Return (\_SB.PCI0.LPCB.EC0.ACST) }
}

Device (BAT0) {
    Name (_HID, "PNP0C0A")
    Method (_BST, 0) { ... }  /* 상태: charging/discharging */
    Method (_BIX, 0) { ... }  /* 설계 용량, 제조사 정보 */
}

Device (LID0) {
    Name (_HID, "PNP0C0D")
    Method (_LID, 0) { Return (\_SB.PCI0.LPCB.EC0.LIDS) }
}
# 배터리/AC 상태 확인
$ ls /sys/class/power_supply/
AC  BAT0

$ cat /sys/class/power_supply/AC/online
1
$ cat /sys/class/power_supply/BAT0/status
Discharging
$ cat /sys/class/power_supply/BAT0/energy_now

# lid / 버튼 이벤트 확인
$ libinput debug-events | grep -i lid
$ cat /proc/acpi/button/lid/LID0/state
lid 장치의 함정: Linux 문서에서도 강조하듯이, 일부 펌웨어는 _LID의 초기 반환값이 신뢰할 수 없고, "닫힘" Notify는 보내면서 "열림" Notify는 빠뜨립니다. 이런 플랫폼에서는 현재 상태를 _LID 하나만으로 판단하지 말고 input 이벤트(SW_LID)와 suspend/wake 동작을 같이 봐야 합니다.

Operation Regions

Operation Region은 AML 코드가 시스템 자원(메모리, I/O 포트, PCI 설정, EC 등)에 접근하기 위한 주소 공간 추상화입니다. ASL에서 OperationRegion을 선언하고 Field로 개별 비트/바이트를 정의합니다.

Region 유형ID설명
SystemMemory0x00물리 메모리 직접 접근
SystemIO0x01I/O 포트 접근 (in/out 명령)
PCI_Config0x02PCI 설정 공간 접근
EmbeddedControl0x03EC 레지스터 접근
SMBus0x04SMBus 트랜잭션
CMOS0x05CMOS RAM 접근
PciBarTarget0x06PCI BAR 공간 접근
IPMI0x07IPMI 인터페이스
GPIO0x08GPIO 핀 제어
GenericSerialBus0x09I2C/SPI 버스 접근
PCC0x0APlatform Communication Channel
Field 옵션대표 값실무 의미
AccessTypeByteAcc, WordAcc, DWordAcc, QWordAcc, AnyAccAML이 레지스터를 어떤 폭으로 읽고 쓸지 결정합니다. 폭이 틀리면 부작용 레지스터를 건드릴 수 있습니다.
LockRuleLock, NoLockGlobal Lock을 잡고 접근할지 정합니다. 펌웨어와 OS가 같은 레지스터를 공유할 때 중요합니다.
UpdateRulePreserve, WriteAsZeros, WriteAsOnes비트필드 쓰기 시 주변 비트를 어떻게 합성할지 정합니다. 잘못 이해하면 "의도하지 않은 비트 clear/set" 버그가 생깁니다.
/* ASL OperationRegion 예시 — EC 기반 배터리 정보 */
OperationRegion (ERAM, EmbeddedControl, 0x00, 0xFF)
Field (ERAM, ByteAcc, NoLock, Preserve) {
    Offset (0x20),
    BTST, 8,       /* 배터리 상태 (충전/방전) */
    BTCR, 16,      /* 배터리 현재 충전률 */
    BTVL, 16,      /* 배터리 전압 (mV) */
    Offset (0x30),
    FANS, 8,       /* 팬 속도 단계 (0~7) */
    CPUT, 8,       /* CPU 온도 */
}

/* SystemMemory 예시 — MMIO 레지스터 */
OperationRegion (GNVS, SystemMemory, 0xBF7E9000, 0x100)
Field (GNVS, AnyAcc, NoLock, Preserve) {
    OSYS, 16,      /* OS 유형 */
    LIDS, 8,       /* 뚜껑 상태 */
    PWRS, 8,       /* AC 전원 상태 */
}
/* 커널 Operation Region 핸들러 등록 — 커스텀 주소 공간 */
/* drivers/acpi/acpica/evregion.c */

acpi_status acpi_install_address_space_handler(
    acpi_handle device,
    acpi_adr_space_type space_id,   /* GPIO, GenericSerialBus 등 */
    acpi_adr_space_handler handler, /* read/write 콜백 */
    acpi_adr_space_setup setup,     /* 초기화 콜백 */
    void *context);

/* 예: GPIO Operation Region 핸들러
 * drivers/gpio/gpiolib-acpi.c
 * AML의 GPIO 필드 접근 → 커널 gpiod_get_value()/gpiod_set_value() 호출
 */
Operation Region 실행 경로 AML Method Store(), Add(), If() Field 오브젝트 참조 ACPICA Field 해석기 bit offset, access width, update rule 계산 필요하면 read-modify-write 수행 Address Space Handler acpi_install_address_space_handler() space_id별 read/write 콜백 SystemMemory MMIO / 공유 메모리 SystemIO inb/outb 포트 접근 EmbeddedControl EC 0x62 / 0x66 GPIO gpiolib-acpi GSBus I2C / SPI 결과: AML 필드 참조가 실제 하드웨어 읽기/쓰기로 변환되고, 지역 폭과 잠금 규칙이 side effect를 좌우합니다
Operation Region 경로 — AML의 Field 참조는 ACPICA가 비트폭·잠금·갱신 규칙을 적용한 뒤, 주소 공간별 핸들러로 내려 실제 MMIO, I/O port, EC, GPIO, 직렬 버스 접근으로 바뀝니다
펌웨어 버그를 읽는 요령: BankFieldIndexField가 보이면 "실제 레지스터 하나"가 아니라 "인덱스 포트 + 데이터 포트" 또는 "은행 선택 레지스터" 조합일 가능성이 큽니다. 이런 장치는 read-modify-write 과정에서 side effect가 더 자주 발생합니다.

ACPI 핫플러그

ACPI 핫플러그는 시스템 실행 중 디바이스(PCI, CPU, 메모리)의 추가/제거를 지원합니다. 펌웨어가 Notify 이벤트를 발생시키면 커널이 디바이스 열거/해제를 수행합니다.

PCI 핫플러그

/* PCI 핫플러그 — drivers/pci/hotplug/acpiphp_glue.c */

/* 핫플러그 Notify 핸들러 */
static void handle_hotplug_event(acpi_handle handle, u32 type, ...)
{
    switch (type) {
    case ACPI_NOTIFY_BUS_CHECK:         /* 0x00 */
    case ACPI_NOTIFY_DEVICE_CHECK:      /* 0x01 */
        /* _STA 평가: 디바이스 존재 여부 확인 */
        acpiphp_check_bridge(bridge);
        /* 새 디바이스 발견 → pci_scan_slot() → 드라이버 바인딩 */
        break;
    case ACPI_NOTIFY_EJECT_REQUEST:      /* 0x03 */
        /* _EJ0 메서드 실행 → 디바이스 제거 */
        acpiphp_disable_slot(slot);
        acpi_evaluate_ej0(handle);
        break;
    }
}

/* ASL 핫플러그 디바이스 정의 예시 */
/*
 * Device (SLT0) {
 *     Name (_ADR, 0x001F0000)
 *     Method (_STA, 0) { Return (0x0F) }  // Present + Enabled
 *     Method (_EJ0, 1) { ... }            // Eject 실행
 *     Method (_RMV, 0) { Return (1) }     // Removable
 * }
 */
핫플러그 메서드역할디버깅 포인트
_STA슬롯/장치 존재와 기능 상태 반환Notify 후 _STA 결과가 바뀌지 않으면 열거가 일어나지 않습니다.
_RMV제거 가능 장치 여부고정 장치인데 _RMV=1로 잘못 기술된 펌웨어가 종종 있습니다.
_EJ0실제 eject 동작 수행OS가 제거 준비를 끝낸 뒤 호출하며, 실패 시 펌웨어 로그와 슬롯 전원 제어를 함께 봐야 합니다.
_OSTOS가 펌웨어에 성공/실패 상태 회신핫플러그 시도는 했지만 BIOS UI와 상태가 어긋날 때 중요합니다.
_SUN사용자에게 보이는 슬롯 번호랙 서버나 외부 확장함에서 물리 슬롯 매칭에 유용합니다.
_HPX핫플러그 슬롯용 구성 힌트브리지 초기화 파라미터가 꼬일 때 참고할 수 있습니다.

PCIe Native Hotplug, AER, PME와 _OSC

서버와 워크스테이션에서 "핫플러그는 되는데 AER가 안 보인다" 또는 "PCIe 포트 서비스 드라이버가 안 붙는다"는 문제의 상당수는 _OSC 협상에서 이미 결정됩니다. _OSC는 Root Port 또는 Host Bridge가 펌웨어와 OS 사이에 PCIe 서비스 제어권을 나누는 메커니즘이며, Native Hotplug, PME, AER, SHPC 같은 권한을 OS가 얻어야 Linux 쪽 포트 서비스가 완전히 활성화됩니다.

서비스 비트의미협상 실패 시 흔한 증상
Native Hotplug슬롯 삽입/제거 이벤트를 OS가 직접 관리pciehp가 기대대로 동작하지 않거나 펌웨어 이벤트에만 의존
AERPCIe Advanced Error Reporting을 OS가 처리dmesg에 AER 드라이버가 비활성처럼 보이거나 오류 해석이 제한됨
PMEPower Management Event를 OS가 제어wake 또는 runtime PM 이벤트가 불안정
SHPCStandard Hot Plug Controller 제어일부 구형 슬롯 컨트롤러에서 제거/삽입 흐름이 펌웨어 의존으로 남음
# _OSC / PCIe 서비스 관련 단서 찾기
$ dmesg | grep -Ei "_OSC|AER|pciehp|PME|hotplug"

# Root Port capability와 서비스 드라이버 확인
$ lspci -vv -s 00:1c.0
$ lsmod | grep -E "pciehp|aer|pcieport"

# 강제로 OS 제어를 선호하도록 시도할 때 쓰는 커널 파라미터 예
# pcie_ports=native
# pcie_ports=compat
판단 순서:
  1. 먼저 dmesg에서 _OSC 협상 성공/거절 흔적을 찾습니다.
  2. 그 다음 Root Port의 AER capability와 실제 포트 서비스 드라이버 바인딩 상태를 확인합니다.
  3. 핫플러그가 ACPI Notify는 오는데 슬롯 스캔이 이상하면 _OSC_HPX, 그리고 펌웨어 슬롯 전원 제어 순서를 같이 봅니다.

CPU / 메모리 핫플러그

/* CPU 핫플러그 — drivers/acpi/acpi_processor.c */
static void acpi_processor_hotplug_notify(struct acpi_device *adev, u32 type)
{
    switch (type) {
    case ACPI_NOTIFY_BUS_CHECK:
    case ACPI_NOTIFY_DEVICE_CHECK:
        /* CPU online: cpu_up() → sched_domain 재구성 */
        acpi_bus_scan(adev->handle);
        break;
    case ACPI_NOTIFY_EJECT_REQUEST:
        /* CPU offline: cpu_down() → 프로세스 마이그레이션 */
        acpi_scan_hot_remove(adev);
        break;
    }
}

/* 메모리 핫플러그 — drivers/acpi/acpi_memhotplug.c */
/* Notify 0x00 → acpi_memory_device_add()
 *             → add_memory_resource()
 *             → online_pages()
 *
 * sysfs로 메모리 블록 온라인:
 * echo online > /sys/devices/system/memory/memoryXX/state
 */
QEMU로 핫플러그 테스트:
  1. qemu-system-x86_64 -machine q35,acpi=on -smp 2,maxcpus=4로 VM을 시작합니다.
  2. QEMU 모니터에서 device_add qemu64-x86_64-cpu,core-id=2로 CPU 핫 추가를 확인합니다.
  3. 메모리는 -m 2G,slots=4,maxmem=8Gobject memory-backend-ram, device pc-dimm 조합으로 검증합니다.

커널 ACPI 서브시스템

drivers/acpi/ 디렉토리 구조

읽는 포인트:
  • 좌측은 ACPICA 코어, 중앙은 열거/전원 경로, 우측은 기능별 하위 모듈입니다.
  • 하단 흐름 1~5는 테이블 파싱부터 사용자 공간 노출까지의 실행 순서를 요약합니다.
  • 디버깅 시에는 scan.c, ec.c, apei/처럼 문제 영역에 맞는 블록을 먼저 추적하세요.
drivers/acpi/ 구조 요약 drivers/acpi/ ACPICA 코어 acpica/ (참조 구현) dsfield.c (Field/OpRegion) evgpe.c (GPE 이벤트) nsaccess.c / tbxfload.c 네임스페이스/테이블 로딩 플랫폼/열거/전원 scan.c (acpi_bus_scan) device_pm.c / sleep.c ec.c / thermal.c processor_idle.c / cppc_acpi.c C/P-state 및 절전 경로 서브시스템/디바이스 apei/ (bert/einj/erst/ghes/hest) numa/ (srat/hmat) battery.c / ac.c / button.c video.c / fan.c 동작 흐름 (요약) 1) ACPICA가 DSDT/SSDT를 파싱하여 네임스페이스 구성 2) scan.c가 acpi_device 생성 후 디바이스 모델에 연결 3) PM/열/APEI/NUMA 모듈이 이벤트를 처리하고 sysfs에 반영
영역핵심 파일/디렉터리역할
ACPICA 코어drivers/acpi/acpica/AML 실행, 네임스페이스/테이블 해석
플랫폼 경로scan.c, device_pm.c, sleep.c, ec.c, thermal.c열거, 전원 전환, EC/열 이벤트 처리
기능 모듈apei/, numa/, battery.c, button.c에러 보고, NUMA, 배터리/버튼 등 기능별 처리

acpi_device 구조체

/* include/acpi/acpi_bus.h */
struct acpi_device {
    acpi_handle handle;              /* 네임스페이스 핸들 */
    struct device dev;               /* 리눅스 디바이스 모델 */

    struct acpi_device_status status; /* _STA 결과 */
    struct acpi_device_flags flags;

    struct acpi_hardware_id *pnp;    /* _HID, _CID, _UID */
    struct acpi_device_power power;  /* D-state 정보 */
    struct acpi_device_wakeup wakeup; /* 웨이크업 능력 */

    struct acpi_driver *driver;      /* 바인딩된 드라이버 */
    struct acpi_device *parent;      /* 부모 디바이스 */
    struct list_head children;       /* 자식 리스트 */
};

플랫폼 디바이스 연결 (ACPI_COMPANION)

/* ACPI 디바이스와 platform_device 연결 */
/* drivers/acpi/glue.c */

/* ACPI_COMPANION 매크로로 디바이스에서 acpi_device 접근 */
struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);

/* 드라이버에서 ACPI 매칭 테이블 정의 */
static const struct acpi_device_id my_acpi_ids[] = {
    { "PNP0C09", 0 },   /* Embedded Controller */
    { "ACPI0003", 0 },  /* AC Adapter */
    { "PNP0C0A", 0 },   /* Battery */
    { },
};
MODULE_DEVICE_TABLE(acpi, my_acpi_ids);

/* platform_driver에서 ACPI 매칭 사용 */
static struct platform_driver my_driver = {
    .driver = {
        .name = "my-acpi-device",
        .acpi_match_table = my_acpi_ids,
    },
    .probe = my_probe,
};

ACPI와 Device Tree를 어떻게 구분해 봐야 하는가

둘 다 "하드웨어를 OS에 기술한다"는 점은 같지만, 표현력과 실행 모델은 다릅니다. Device Tree는 정적 데이터 중심이고, ACPI는 데이터 테이블에 더해 AML 메서드와 런타임 조건 분기를 포함합니다. 드라이버 관점에서는 ACPI 지원을 추가할 때 DT 경로를 대체하는 것이 아니라, property / GPIO / interrupt 해석 계층을 공통화하는 방식이 일반적입니다.

항목ACPIDevice Tree
기술 방식정적 테이블 + AML 메서드 실행정적 트리 데이터
장치 식별_HID, _CID, _UID, _ADRcompatible, reg, 노드 경로
추가 속성_DSD UUID property package표준 property 직접 선언
전원/열/깨우기_PRx, _PSx, _PRW, thermal methodspower-domain, wakeup-source, thermal bindings
런타임 분기펌웨어가 _OSI, _OSC, method 로직으로 조건 분기 가능대체로 없음. OS 쪽 코드가 조건 처리
드라이버 공통화 방법device_property_*, fwnode, gpiod, irq helpers를 사용같은 공용 helper를 사용
드라이버 작성 원칙:
  • ACPI 지원을 추가할 때 if (ACPI) ... else if (OF) ... 식으로 자원 해석 전체를 복제하기보다, 가능한 한 fwnodedevice_property_* 계층으로 수렴시키는 편이 낫습니다.
  • Arm64 계열에서는 부트 시 ACPI 또는 Device Tree 중 한 방식만 선택되는 경우가 일반적이므로, 플랫폼 문서를 함께 봐야 합니다.
  • PRP0001이 보인다면 펌웨어가 DT 스타일 속성을 ACPI 쪽으로 브리지하려는 설계라는 신호로 읽으면 됩니다.

디버깅 & 트러블슈팅

acpi.debug_layer / acpi.debug_level

# ACPI 디버그 레이어/레벨 활성화 (커널 CONFIG_ACPI_DEBUG=y 필요)

# 디버그 레이어 — 서브시스템별 필터링
$ echo 0xFFFFFFFF > /sys/module/acpi/parameters/debug_layer
# 주요 레이어:
# 0x00000001 = ACPI_UTILITIES     0x00000010 = ACPI_TABLES
# 0x00000020 = ACPI_EVENTS        0x00000040 = ACPI_CONTROL_METHODS
# 0x00000100 = ACPI_NAMESPACE     0x00000800 = ACPI_BUS_COMPONENT

# 디버그 레벨 — 상세도 조절
$ echo 0x0000001F > /sys/module/acpi/parameters/debug_level
# 0x01 = Error  0x02 = Warn  0x04 = Info  0x08 = Debug  0x10 = Verbose

# 부팅 시 커널 파라미터로 설정
# acpi.debug_layer=0x20 acpi.debug_level=0x1F

/sys/firmware/acpi/ 인터페이스

# ACPI 테이블 원본 덤프
$ ls /sys/firmware/acpi/tables/
APIC  DSDT  FACP  FACS  HMAT  MCFG  SRAT  SSDT*

# DSDT 디컴파일 (iasl 필요)
$ cat /sys/firmware/acpi/tables/DSDT > dsdt.aml
$ iasl -d dsdt.aml    # → dsdt.dsl (ASL 소스)

# ACPI 이벤트 인터럽트 카운터
$ cat /sys/firmware/acpi/interrupts/sci
$ cat /sys/firmware/acpi/interrupts/gpe_all

# PM 프로필
$ cat /sys/firmware/acpi/pm_profile
Desktop

acpidump / acpixtract / iasl 실전 절차

복잡한 플랫폼에서는 DSDT 하나만 디컴파일하면 참조가 끊기기 쉽습니다. SSDT가 여러 장 겹쳐 장치 정의를 덮어쓰는 경우가 흔하므로, 가능하면 전체 테이블을 덤프하고 외부 참조를 포함해 디컴파일하는 편이 정확합니다.

# 1) 전체 ACPI 테이블 덤프 (acpica-tools 필요)
$ acpidump -b

# 2) 바이너리 테이블 분해
$ acpixtract -a acpidump.out

# 3) SSDT들을 외부 참조로 포함하여 DSDT 디컴파일
$ iasl -e ssdt*.dat -d dsdt.dat

# 4) 관심 장치/메서드 찾기
$ grep -n "Device (EC0)" dsdt.dsl
$ grep -n "_PRW" dsdt.dsl
$ grep -n "OperationRegion" dsdt.dsl
$ grep -n "_OSC" dsdt.dsl
iasl -e가 중요한가: SSDT가 별도 테이블로 쪼개진 플랫폼에서는, 외부 참조 없이 DSDT만 디컴파일하면 정의되지 않은 심볼이 다수 발생합니다. 특히 GPU, 전원 리소스, Type-C, 플랫폼 전원 메서드는 SSDT로 이동하는 경우가 많습니다.

DSDT/SSDT를 읽는 실제 패턴

디컴파일된 ASL을 처음 보면 "문법은 읽히는데 어디서부터 봐야 하는지"가 가장 어렵습니다. 실전에서는 전체 파일을 위에서 아래로 읽기보다, 문제가 난 장치의 ACPI 경로를 잡고 그 장치를 둘러싼 식별, 자원, wake, power method를 역으로 추적하는 방식이 효율적입니다.

/* SSDT/DSDT에서 자주 보는 문제 장치 예시 */
Device (XHCI) {
    Name (_ADR, 0x00140000)
    Name (_STA, 0x0F)
    Method (_CRS, 0, Serialized) { ... }
    Method (_PRW, 0, NotSerialized) {
        Return (Package () { 0x6D, 0x03 })
    }
    Name (_PR0, Package () { PR2A })
    Name (_PR3, Package () { PR3A })
    Method (_PS0, 0) { ... }
    Method (_PS3, 0) { ... }
}
읽는 순서확인할 것질문
1. 경로 고정XHCI가 실제 어떤 PCI 함수인지이 장치가 우리가 문제 삼는 실물 장치가 맞는가?
2. 존재 여부_STA 값과 조건문펌웨어가 특정 조건에서 장치를 숨기고 있지는 않은가?
3. 자원_CRS, GPIO, IRQ, MMIO커널이 받은 자원과 펌웨어 선언이 일치하는가?
4. 전원_PR0, _PR3, _PS0, _PS3runtime suspend/resume 실패가 전원 순서 문제는 아닌가?
5. wake_PRW의 GPE 번호와 최소 wake state장치가 어느 절전 상태에서 깨울 수 있다고 선언하는가?
grep 우선순위: 특정 장치가 문제라면 보통 _STA_CRS_PRW_PR0/_PR3_DSM 순서로 보는 편이 빠릅니다. 이 다섯 축만 잡아도 "안 보임 / 자원 충돌 / 절전 실패 / wake 실패 / OEM 특수 메서드 의존"을 대부분 분류할 수 있습니다.

ACPI 커널 파라미터 총정리

파라미터설명
acpi=offACPI 완전 비활성화 (PCI, SMP 등에 영향)
acpi=noirqACPI 인터럽트 라우팅 비활성화
acpi=strictACPI 스펙 엄격 준수 (비표준 BIOS 거부)
acpi=force오래된 BIOS에서도 ACPI 강제 활성화
acpi_osi=!모든 _OSI 문자열 비활성화
acpi_osi="Windows 2020"특정 Windows 버전으로 위장
acpi_enforce_resources=laxI/O 리소스 충돌 무시 (hwmon 등)
acpi_backlight=vendor벤더 백라이트 드라이버 우선 사용
acpi_sleep=s3_biosS3 복원 시 BIOS 비디오 초기화 호출
acpi_sleep=s3_modeS3 복원 시 비디오 모드 복원
acpi.ec_no_wakeup=1Suspend 중 EC 웨이크업 비활성화
button.lid_init_state=ignore신뢰할 수 없는 초기 lid 상태 보고를 무시하고 이벤트 기반으로 처리
noapicI/O APIC 비활성화 (MADT 무시)
pci=noacpiPCI에 ACPI 라우팅 미사용
pcie_ports=native가능하면 PCIe 포트 서비스를 OS가 직접 제어하도록 요청
pcie_ports=compat펌웨어 제어를 유지하는 호환 경로 선호
acpi_no_auto_serializeAML 메서드 자동 직렬화 비활성화
acpi.debug_layer=0x20부팅 시 ACPI 디버그 레이어 설정
acpi.debug_level=0x1F부팅 시 ACPI 디버그 레벨 설정
ACPI 테이블 오버라이드: 버그 있는 DSDT를 수정하려면 UEFI 페이지의 DSDT 오버라이드 섹션을 참조하세요. CONFIG_ACPI_TABLE_OVERRIDE_VIA_BUILTIN_INITRD로 커스텀 DSDT를 initramfs에 포함하거나, acpi_override 커널 파라미터를 사용합니다.

ACPI 디버깅 플레이북

ACPI 문제는 BIOS/펌웨어 AML 품질의 영향을 크게 받기 때문에, 커널 코드만 보는 방식으로는 해결이 어렵습니다. 테이블 원본, AML 디컴파일 결과, 런타임 이벤트를 함께 비교해야 합니다.

분석 대상 확인 방법 목적
테이블 무결성 /sys/firmware/acpi/tables 펌웨어가 제공한 원본 확인
AML 로직 iasl -d dsdt.aml 메서드/장치 선언 분석
이벤트 경로 /sys/firmware/acpi/interrupts/* SCI/GPE 동작 확인
커널 로그 dmesg | grep -i acpi 파싱/초기화 실패 지점 확인
# DSDT 추출 및 디컴파일
cat /sys/firmware/acpi/tables/DSDT > dsdt.aml
iasl -d dsdt.aml

# ACPI 디버그 레벨 상승
echo 0xFFFFFFFF > /sys/module/acpi/parameters/debug_layer
echo 0x1F > /sys/module/acpi/parameters/debug_level

# 이벤트 카운터 점검
cat /sys/firmware/acpi/interrupts/gpe_all
cat /sys/firmware/acpi/interrupts/sci
주의: acpi=off는 진단을 쉽게 만들 수 있지만 PCI 라우팅, 전원 관리, CPU 토폴로지 인식에 큰 부작용을 줄 수 있습니다. 디버깅 시에도 단계적으로 옵션을 줄여가며 사용하세요.

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