ACPI 심화 (Advanced Configuration and Power Interface)

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

관련 표준: ACPI Specification 6.5 (테이블, 전원 상태, GPE), Intel SDM (MSR, 인터럽트), UEFI 2.10 (런타임 서비스) — 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.32019CXL 지원, CDAT, 에너지 성능 힌트 확장
6.52022CEDT(CXL Early Discovery), CPER v3, PRMT

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가 전 계층을 관통합니다

ACPI 네임스페이스

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

루트 스코프 트리

/* ACPI 네임스페이스 루트 스코프 구조 */
\                              /* 루트 */
├── _SB                        /* System Bus — 모든 디바이스 */
│   ├── PCI0                   /* PCI Host Bridge */
│   │   ├── SATA               /* SATA 컨트롤러 */
│   │   ├── GFX0               /* GPU */
│   │   ├── RP01               /* PCIe Root Port */
│   │   │   └── NVME           /* NVMe SSD */
│   │   └── LPCB               /* LPC Bridge */
│   │       ├── EC0            /* Embedded Controller */
│   │       └── SIO1           /* Super I/O */
│   ├── MEM0                   /* 메모리 디바이스 */
│   └── CPU0 ... CPUn          /* 프로세서 오브젝트 */
├── _PR                        /* Processor — 프로세서 성능/전원 */
│   ├── CPU0                   /* _CST, _CPC, _PCT, _PSS */
│   └── ...
├── _TZ                        /* Thermal Zone — 열 관리 */
│   └── TZ00                   /* _TMP, _PSL, _CRT, _HOT */
├── _GPE                       /* General Purpose Events */
│   ├── _L0D                   /* Level-triggered GPE 0x0D */
│   └── _E02                   /* Edge-triggered GPE 0x02 */
└── _SI                        /* System Indicators */
    └── _MSG                   /* 메시지 표시기 */

디바이스 열거 흐름

커널 부팅 시 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);
}

Notify 메커니즘

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

Notify 값의미용도
0x00Bus Check버스에 디바이스 변경 감지 (핫플러그)
0x01Device Check특정 디바이스 상태 변경
0x02Device Wake디바이스가 시스템을 깨움
0x03Eject Request디바이스 꺼내기 요청
0x80Status Change열 상태, 배터리 상태 등 변경
0x81Information Change디바이스 정보 업데이트
디버깅: echo 1 > /sys/firmware/acpi/interrupts/notify로 Notify 이벤트 수를 확인할 수 있습니다. 또한 acpi_dbg 모듈로 AML 실행을 실시간 추적할 수 있습니다.

주요 ACPI 테이블 심화

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

FADT (Fixed ACPI Description Table)

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

/* include/acpi/actbl.h — FADT 핵심 필드 (ACPI 6.5 기준) */
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에서 가장 중요한 에러 소스 유형입니다. GHES는 NMI/SCI/IRQ를 통해 에러를 수신하고, CPER(Common Platform Error Record) 형식으로 기록합니다. 펌웨어가 에러 정보를 공유 메모리에 기록하면 커널이 이를 읽어 처리합니다. CONFIG_ACPI_APEI_GHES로 활성화합니다.

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전원 차단전원 차단전원 차단전체 부팅

S0ix / Modern Standby

S0ix는 S0 상태에서 S3에 근접하는 저전력을 달성하는 Intel의 Modern Standby 기술입니다. FADT의 LOW_POWER_S0 플래그(bit 21)가 설정되면 활성화됩니다.

/* 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
 *
 * S0ix residency 확인:
 * cat /sys/kernel/debug/pmc_core/slp_s0_residency_usec */

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;
}
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 = 물리적 전원 차단 S0ix: S0 내에서 S3급 저전력 달성 (Modern Standby) | D-states: 개별 디바이스 전원, S-state와 독립적으로 전환 가능
G/S/D 상태 전환도 — G-states는 시스템 전체, S-states는 절전 수준, D-states는 디바이스별 전원을 제어합니다

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로 확인할 수 있습니다.

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에지 전환한 번 발생 후 자동 클리어

커널 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 실행)
 */
# 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와 통신합니다.

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 커널 파라미터로 리소스 충돌을 무시하거나, ec_no_wakeup으로 suspend 중 EC 웨이크업을 비활성화할 수 있습니다.

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
/* 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() 호출
 */

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
 * }
 */

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로 핫플러그 테스트: qemu-system-x86_64 -machine q35,acpi=on -smp 2,maxcpus=4로 VM을 시작한 후, QEMU 모니터에서 device_add qemu64-x86_64-cpu,core-id=2로 CPU를 핫 추가할 수 있습니다. 메모리는 -m 2G,slots=4,maxmem=8G 옵션과 object memory-backend-ram + device pc-dimm으로 테스트합니다.

커널 ACPI 서브시스템

drivers/acpi/ 디렉토리 구조

drivers/acpi/
├── acpica/              /* ACPICA 참조 코드 (Intel 제공, OS 독립적) */
│   ├── dsfield.c        /* Field 오브젝트 (OperationRegion) */
│   ├── evgpe.c          /* GPE 이벤트 처리 */
│   ├── exoparg*.c       /* AML 인터프리터 opcode 실행 */
│   ├── nsaccess.c       /* 네임스페이스 접근 */
│   └── tbxfload.c       /* 테이블 로딩 */
├── apei/                /* APEI 프레임워크 */
│   ├── bert.c, einj.c, erst.c, ghes.c, hest.c
├── numa/                /* NUMA 관련 ACPI */
│   ├── srat.c, hmat.c
├── scan.c               /* 디바이스 열거 (acpi_bus_scan) */
├── bus.c                /* ACPI 버스 드라이버 */
├── device_pm.c          /* 디바이스 전원 관리 */
├── sleep.c              /* 시스템 절전 (S3/S4/S0ix) */
├── ec.c                 /* Embedded Controller */
├── thermal.c            /* Thermal Zone */
├── processor_idle.c     /* C-states */
├── cppc_acpi.c          /* CPPC (P-states) */
├── battery.c            /* 배터리 드라이버 */
├── ac.c                 /* AC 어댑터 드라이버 */
├── button.c             /* 전원/뚜껑 버튼 */
├── video.c              /* ACPI 비디오 (백라이트) */
├── fan.c                /* ACPI 팬 드라이버 */
└── glue.c               /* 플랫폼 디바이스 연결 */

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.debug_layer / 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

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 웨이크업 비활성화
noapicI/O APIC 비활성화 (MADT 무시)
pci=noacpiPCI에 ACPI 라우팅 미사용
acpi_no_auto_serializeAML 메서드 자동 직렬화 비활성화
acpi_dbg_level=N부팅 시 ACPI 디버그 레벨 설정
ACPI 테이블 오버라이드: 버그 있는 DSDT를 수정하려면 UEFI 페이지의 DSDT 오버라이드 섹션을 참조하세요. CONFIG_ACPI_TABLE_OVERRIDE_VIA_BUILTIN_INITRD로 커스텀 DSDT를 initramfs에 포함하거나, acpi_override 커널 파라미터를 사용합니다.