본문으로 건너뛰기
CXL 4.0 Internals · 12/15

Ch 12: QEMU CXL 에뮬레이션 — 노트북에서 CXL 개발

· Hawk · 5분 읽기

#한 줄 요약

“QEMU는 CXL host bridge·root port·switch·Type 3 메모리 디바이스를 에뮬레이션해, 실 디바이스 없이 Linux guest의 CXL 드라이버 경로를 돌려 볼 수 있게 합니다.” — -M q35,cxl=on(또는 arm virt,cxl=on)에 pxb-cxl·cxl-rp·cxl-type3와 cxl-fmw를 조합합니다. 링크·PHY는 없으니 성능 측정에는 쓰지 않습니다. 이 장은 QEMU master(11.1 개발판, 2026-10) 문서와 소스 기준입니다.

Ch 11에서 Linux drivers/cxl/ 코드를 봤습니다. 이 코드를 돌려 보려면 실 디바이스나 에뮬레이션이 필요합니다. QEMU가 그 자리를 채웁니다.

#QEMU가 제공하는 것

QEMU 문서(docs/system/devices/cxl.rst)와 소스에서 확인되는 구성 요소:

구성 요소QEMU 이름
CXL host bridgepxb-cxl
CXL root portcxl-rp
CXL switchcxl-upstream + cxl-downstream (단일 virtual hierarchy)
Type 3 메모리 디바이스cxl-type3 — volatile, persistent, Dynamic Capacity(num-dc-regions)
Fixed Memory Window (CEDT CFMWS)머신 옵션 cxl-fmw.N.*
HDM-DBcxl-type3의 hdm-db, 256B flit(x-256b-flit), window의 back-invalidate=on
머신x86 q35, arm virt

Type 1·Type 2 가속기 디바이스 모델은 QEMU 트리에 없습니다. 오류·poison·이벤트 주입은 QMP 명령으로 됩니다: cxl-inject-poison, cxl-inject-uncorrectable-errors, cxl-inject-correctable-error, cxl-inject-general-media-event, cxl-inject-dram-event, cxl-inject-memory-module-event, 그리고 Dynamic Capacity용 cxl-add-dynamic-capacity·cxl-release-dynamic-capacity.

#머신 옵션

Terminal window
qemu-system-x86_64 \
-M q35,cxl=on \
-m 4G,maxmem=8G,slots=8 \
-smp 4 \
...
옵션의미
cxl=on머신의 CXL 지원 켜기. CEDT 생성, pxb-cxl 레지스터 연결
maxmem·slotsQEMU 문서의 모든 CXL 예시가 함께 씀
q35pxb-cxl은 PCIe root bus(pcie.0)에 달아야 함

cxl=off인데 pxb-cxl이 있으면 QEMU는 CXL host bridges present, but cxl=off로 멈춥니다.

#Type 3 디바이스 추가

QEMU 문서의 volatile 메모리 예시:

Terminal window
qemu-system-x86_64 -M q35,cxl=on -m 4G,maxmem=8G,slots=8 -smp 4 \
... \
-object memory-backend-ram,id=vmem0,share=on,size=256M \
-device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1 \
-device cxl-rp,port=0,bus=cxl.1,id=root_port13,chassis=0,slot=2 \
-device cxl-type3,bus=root_port13,volatile-memdev=vmem0,id=cxl-vmem0 \
-M cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=4G
옵션역할
memory-backend-ram·memory-backend-file디바이스 메모리의 backing store
pxb-cxlCXL host bridge (PCI Expander Bridge의 CXL 판)
cxl-rpCXL root port
cxl-type3Type 3 디바이스. volatile-memdev= 또는 persistent-memdev=(+ lsa=)
cxl-fmw.NCFMWS 하나. targets는 host bridge, size는 256 MiB 배수

cxl-type3의 옛 memdev= 속성은 deprecated이고, 하위 호환을 위해 persistent 메모리로 취급됩니다. volatile region을 만들려면 volatile-memdev=를 써야 합니다.

#Linux guest에서 확인

QEMU 문서는 Linux 5.18 기준 필요한 커널 옵션으로 CONFIG_CXL_BUS·CXL_PCI·CXL_ACPI·CXL_PMEM·CXL_MEM·CXL_PORT·CXL_REGION을 듭니다.

Terminal window
# PCI 디바이스: vendor 8086, device 0d93, class 0502 (CXL memory)
guest$ lspci -nn -d 8086:0d93
# CXL bus 객체: root0, portN, endpointN, decoderX.Y, mem0 등
guest$ ls /sys/bus/cxl/devices/
# 토폴로지·decoder·memdev
guest$ cxl list -M -D -T
# volatile region 생성 (크기를 빼면 가능한 최대)
guest$ cxl create-region -m -d decoder0.0 -t ram mem0
# dax 장치를 System RAM으로 (이미 online이면 생략)
guest$ daxctl reconfigure-device dax0.0 -m system-ram
# 새 NUMA 노드 확인
guest$ numactl --hardware

0d93 디바이스 ID와 Intel vendor ID는 QEMU hw/mem/cxl_type3.c가 그대로 박아 둔 값입니다. 출력 JSON 형식은 ndctl 문서(cxl-list, cxl-create-region)에 예시가 있습니다.

#CEDT 확인

QEMU의 hw/acpi/cxl.c가 CEDT를 만듭니다.

Subtable만드는 단위주요 값
CHBS (Type 0)pxb-cxl 하나당Record Length 32, UID = bus_nr, CXL Version 1, Base·Length = host bridge 레지스터 영역
CFMWScxl-fmw.N 하나당window base·size, target host bridge, interleave
Terminal window
guest$ acpidump -n CEDT -b
guest$ iasl -d cedt.dat

위 예시처럼 bus_nr=12면 CHBS UID는 12(0x0C)입니다. 드라이버(cxl_acpi)는 실 BIOS가 만든 CEDT와 같은 경로로 이 표를 읽습니다.

#cxl_test — QEMU 없이 도는 mock 토폴로지

커널 트리의 tools/testing/cxl/은 QEMU 디바이스와 별개입니다. CXL 모듈을 mock 함수와 함께 다시 빌드하고, cxl_test 모듈이 가짜 CXL 토폴로지를 만듭니다. ndctl의 CXL 테스트가 이걸 씁니다.

Terminal window
# ndctl README의 절차
$ sudo make M=tools/testing/cxl modules_install
$ sudo make modules_install
# 테스트 토폴로지 올리기·내리기 (ndctl test 스크립트와 같음)
$ sudo modprobe cxl_test
$ sudo modprobe -r cxl_test
모듈소스
cxl_testtest/cxl.c, test/hmem_test.c
cxl_mocktest/mock.c
cxl_mock_memtest/mem.c
cxl_mock_acceltest/accel.c

드라이버 로직을 빠르게 반복 테스트하기엔 cxl_test, 실제 PCI 열거·CEDT·레지스터 경로를 보기엔 QEMU가 맞습니다.

#QEMU CXL로 못 하는 것

한계이유
성능·지연 측정링크와 디바이스 지연을 모델링하지 않음
PHY·LTSSM·신호 무결성물리 링크가 없음
Type 1·Type 2 가속기 경로디바이스 모델 없음

#자주 하는 실수

#memdev=로 붙이고 ram region을 만들려 함

memdev=는 persistent로 취급됩니다. volatile은 volatile-memdev=를 써야 합니다.

#pc(i440fx) 머신에 pxb-cxl

pxb-cxl devices cannot reside on a PCI bus

pxb-cxl은 PCIe root bus가 필요합니다. q35를 씁니다.

#FMW 크기를 256 MiB 배수가 아닌 값으로

Size of a CXL fixed memory window must be a multiple of 256MiB

region 전체가 window 안에 들어가야 하므로, interleave할 디바이스 용량의 합보다 작게 잡으면 원하는 region을 만들 수 없습니다.

#같은 bus 번호로 pxb-cxl 두 개

Bus 12 is already in use

host bridge마다 bus_nr를 다르게 줍니다. QEMU 문서 예시는 12와 222를 씁니다.

#정리

  • QEMU는 pxb-cxl·cxl-rp·cxl-upstream/downstream·cxl-type3와 cxl-fmw로 CXL 토폴로지를 만듭니다. Type 1·2 모델은 없습니다.
  • -M q35,cxl=on(x86) 또는 virt,cxl=on(arm). volatile은 volatile-memdev=.
  • guest에서는 cxl list·cxl create-region·daxctl로 실 디바이스와 같은 경로를 탑니다.
  • 오류·poison·Dynamic Capacity는 QMP cxl-* 명령으로 주입합니다.
  • 드라이버 로직만 볼 땐 커널의 cxl_test mock이 더 가볍습니다.
  • 링크가 없으니 성능·PHY 검증은 실 하드웨어 몫입니다.

#다음 편

Ch 13: Switching·Fabric Manager — 2.0 pooling에서 3.x fabric까지에서 CXL switch의 진화와 Fabric Manager의 역할을 본격적으로 분해합니다.

#관련 항목

#시리즈 자료 출처 안내

이 글은 QEMU master 문서·소스(GPL), Linux tools/testing/cxl/, ndctl 문서·테스트를 근거로 합니다. 시리즈 전체의 자료 정책은 Ch 1에 있습니다.