ACPI 심화 (Advanced Configuration and Power Interface)
ACPI 아키텍처 4계층, 네임스페이스, 주요 테이블 심화, 전원 상태 계층(G/S/D), 열 관리, GPE/SCI, Embedded Controller, Operation Region, 핫플러그, 커널 ACPI 서브시스템까지 — 리눅스 커널의 ACPI 구현을 소스 코드 수준에서 분석합니다.
ACPI 아키텍처 개요
ACPI(Advanced Configuration and Power Interface)는 OS가 하드웨어 구성 탐색, 전원 관리, 열 관리를 수행하기 위한 플랫폼 독립적 인터페이스 규격입니다. 1996년 Intel/Microsoft/Toshiba가 공동 개발했으며, 2013년부터 UEFI Forum이 관리합니다.
스펙 버전 역사
| 버전 | 연도 | 주요 변경 |
|---|---|---|
| 1.0 | 1996 | 최초 공개 — APM 대체, AML/DSDT 도입 |
| 2.0 | 2000 | 64-bit XSDT, Generic Address Structure, Processor Aggregator |
| 3.0 | 2004 | NUMA(SRAT/SLIT), PCI Express, x2APIC 지원 |
| 4.0 | 2009 | HW-Reduced ACPI(ARM 서버), USB3, SD 카드 |
| 5.0 | 2011 | CPPC(Collaborative Processor Performance Control), LPIT |
| 6.0 | 2015 | PPTT(CPU 토폴로지), HMAT(이기종 메모리), IORT(ARM SMMU) |
| 6.3 | 2019 | CXL 지원, CDAT, 에너지 성능 힌트 확장 |
| 6.5 | 2022 | CEDT(CXL Early Discovery), CPER v3, PRMT |
4대 구성요소
ACPI 규격은 네 가지 핵심 구성요소로 이루어집니다:
- ACPI Tables — RSDP → XSDT → {FADT, MADT, DMAR, HMAT, ...} 계층 구조의 정적 데이터 테이블. 펌웨어가 메모리에 배치합니다.
- ACPI Namespace — 디바이스, 메서드, 오브젝트를 트리 구조로 조직하는 계층적 이름 공간. AML 코드가 정의합니다.
- AML Interpreter — DSDT/SSDT의 AML 바이트코드를 실행하는 커널 내 인터프리터. 리눅스는 ACPICA(ACPI Component Architecture) 코드를 사용합니다.
- ACPI Event Model — SCI(System Control Interrupt)와 GPE(General Purpose Event)를 통한 비동기 하드웨어 이벤트 처리.
drivers/acpi/acpica/)를 공유합니다. 별도의 릴리스 주기를 갖습니다.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 값 | 의미 | 용도 |
|---|---|---|
| 0x00 | Bus Check | 버스에 디바이스 변경 감지 (핫플러그) |
| 0x01 | Device Check | 특정 디바이스 상태 변경 |
| 0x02 | Device Wake | 디바이스가 시스템을 깨움 |
| 0x03 | Eject Request | 디바이스 꺼내기 요청 |
| 0x80 | Status Change | 열 상태, 배터리 상태 등 변경 |
| 0x81 | Information 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;
/* ... */
};
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/ */
}
memory_tier 프레임워크(mm/memory-tiers.c)가 HMAT 대역폭/지연 정보를 기반으로 핫/콜드 페이지 마이그레이션 정책을 자동 설정합니다.BERT / HEST / ERST / EINJ (하드웨어 에러 처리)
APEI(ACPI Platform Error Interface) 프레임워크는 4개의 테이블로 구성되며, 머신체크(MCE)를 넘어선 플랫폼 수준 에러 처리를 지원합니다.
| 테이블 | 용도 | 커널 파일 |
|---|---|---|
| BERT | Boot Error Record — 이전 부팅에서 발생한 에러 로그 | drivers/acpi/apei/bert.c |
| HEST | Hardware Error Source — 에러 소스(MCE, PCIe AER, GHES) 기술 | drivers/acpi/apei/hest.c |
| ERST | Error Record Serialization — 에러 로그 영속 저장(NVRAM) | drivers/acpi/apei/erst.c |
| EINJ | Error Injection — 에러 주입 테스트 인터페이스 | drivers/acpi/apei/einj.c |
CONFIG_ACPI_APEI_GHES로 활성화합니다.ACPI 전원 관리 심화
ACPI는 시스템 전체(Global/Sleep)부터 개별 디바이스(Device)까지 계층적 전원 상태를 정의합니다.
G-states (Global States)
G-states는 시스템 전체의 전원 상태를 나타냅니다:
- G0 (Working) — 시스템 정상 동작. CPU가 S0에서 명령 실행 중.
- G1 (Sleeping) — 시스템 절전. S1~S4 중 하나의 Sleep state.
- G2 (Soft Off) — S5, OS가 종료되었으나 PSU 대기 전원 공급. 전원 버튼으로 재시작 가능.
- G3 (Mechanical Off) — 전원 완전 차단. PSU 스위치 또는 전원 코드 분리 상태.
S-states (Sleep States)
| S-state | 이름 | CPU | 메모리 | 디바이스 | 복원 시간 |
|---|---|---|---|---|---|
| S0 | Working | 실행 중 | 활성 | 활성 | — |
| S1 | Power On Suspend | 정지, 캐시 유지 | 유지 | D1 이상 | 매우 빠름 |
| S2 | — | 전원 차단 | 유지 | D2 이상 | 빠름 |
| S3 | Suspend to RAM | 전원 차단 | 자체 리프레시 | D3 | 수 초 |
| S4 | Hibernate | 전원 차단 | 디스크 저장 | 전원 차단 | 수십 초 |
| S5 | Soft 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 전원 상태를 가집니다:
- D0 (Fully On) — 디바이스 완전 동작 상태
- D1, D2 — 중간 절전 (디바이스 클래스별 정의)
- D3hot — 소프트웨어적 전원 차단, PCI 설정 공간 접근 가능, Vaux 유지
- 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;
}
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 */
}
}
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 단위) | 필수 |
| _PSV | Passive cooling 임계값 | 선택 |
| _PSL | Passive cooling 대상 디바이스 리스트 | _PSV 시 필수 |
| _TC1/_TC2 | Passive cooling PID 계수 | _PSV 시 필수 |
| _TSP | 폴링 주기 (0.1초 단위) | 선택 |
| _CRT | Critical 온도 (즉시 셧다운) | 선택 |
| _HOT | Hot 온도 (S4 전환) | 선택 |
| _ACx | Active cooling 임계값 (팬 단계) | 선택 |
| _AL0~_AL9 | Active 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
acpi_enforce_resources=lax 커널 파라미터로 리소스 충돌을 무시하거나, ec_no_wakeup으로 suspend 중 EC 웨이크업을 비활성화할 수 있습니다.Operation Regions
Operation Region은 AML 코드가 시스템 자원(메모리, I/O 포트, PCI 설정, EC 등)에 접근하기 위한 주소 공간 추상화입니다. ASL에서 OperationRegion을 선언하고 Field로 개별 비트/바이트를 정의합니다.
| Region 유형 | ID | 설명 |
|---|---|---|
| SystemMemory | 0x00 | 물리 메모리 직접 접근 |
| SystemIO | 0x01 | I/O 포트 접근 (in/out 명령) |
| PCI_Config | 0x02 | PCI 설정 공간 접근 |
| EmbeddedControl | 0x03 | EC 레지스터 접근 |
| SMBus | 0x04 | SMBus 트랜잭션 |
| CMOS | 0x05 | CMOS RAM 접근 |
| PciBarTarget | 0x06 | PCI BAR 공간 접근 |
| IPMI | 0x07 | IPMI 인터페이스 |
| GPIO | 0x08 | GPIO 핀 제어 |
| GenericSerialBus | 0x09 | I2C/SPI 버스 접근 |
| PCC | 0x0A | Platform 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-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=off | ACPI 완전 비활성화 (PCI, SMP 등에 영향) |
acpi=noirq | ACPI 인터럽트 라우팅 비활성화 |
acpi=strict | ACPI 스펙 엄격 준수 (비표준 BIOS 거부) |
acpi=force | 오래된 BIOS에서도 ACPI 강제 활성화 |
acpi_osi=! | 모든 _OSI 문자열 비활성화 |
acpi_osi="Windows 2020" | 특정 Windows 버전으로 위장 |
acpi_enforce_resources=lax | I/O 리소스 충돌 무시 (hwmon 등) |
acpi_backlight=vendor | 벤더 백라이트 드라이버 우선 사용 |
acpi_sleep=s3_bios | S3 복원 시 BIOS 비디오 초기화 호출 |
acpi_sleep=s3_mode | S3 복원 시 비디오 모드 복원 |
acpi.ec_no_wakeup=1 | Suspend 중 EC 웨이크업 비활성화 |
noapic | I/O APIC 비활성화 (MADT 무시) |
pci=noacpi | PCI에 ACPI 라우팅 미사용 |
acpi_no_auto_serialize | AML 메서드 자동 직렬화 비활성화 |
acpi_dbg_level=N | 부팅 시 ACPI 디버그 레벨 설정 |
CONFIG_ACPI_TABLE_OVERRIDE_VIA_BUILTIN_INITRD로 커스텀 DSDT를 initramfs에 포함하거나, acpi_override 커널 파라미터를 사용합니다.