HomeCXL CourseDay 13
DAY 13 · PHASE 3 — DEPLOYMENT

The Software Stack
OS/Kernel Memory Tiering & Page Migration

By EcrioniX · Updated July 2026

Day 11 introduced the shape of Linux memory tiering — CXL memory as a CPU-less NUMA node, DAMON migrating hot and cold pages. Day 13 goes underneath that summary and asks the harder question: how does a CXL device go from a piece of silicon on a PCIe-style link to a NUMA node the kernel can safely hand out to applications at all? The answer is a specific, layered discovery-and-programming pipeline — and it's worth understanding, because every pooling and tiering benefit from Days 11–12 depends on this pipeline working correctly.

The Kernel Driver Split

The Linux CXL driver isn't one monolithic module — it's deliberately split so that different concerns stay separated:

Core ModulePort/decoder topology, region construction, HDM decoder setup, mailbox interface, DVSEC parsing, and CDAT parsing — the shared logic every other module builds on
🔌
cxl_pciPCI-layer specifics: discovering CXL devices as PCIe-style endpoints and handling their PCI configuration space
📋
cxl_acpiACPI-layer specifics: reading platform firmware tables that describe where CXL memory windows live in the system's address map

Core objects — ports, decoders, and regions — are shared across these subsystem drivers and the higher-level drivers built on top of them (a port-driver, a region-driver, and nvdimm object-drivers, since CXL persistent memory reuses much of the existing Linux NVDIMM subsystem from Day 6). This layering means the PCI-discovery logic and the ACPI-firmware logic don't need to know about each other's internals — they both feed into the same core topology model.

Step 1: ACPI Tells the Kernel Where to Look

Before Linux can talk to a single CXL device, platform firmware has to tell it where CXL memory windows exist in the system's address space. This comes from two ACPI structures:

ACPI StructureWhat It Provides
CEDT (CXL Early Discovery Table)Describes the platform's CXL Host Bridges and Fixed Memory Window Structures (CFMWS)
CFMWS (CXL Fixed Memory Window Structure)Each entry describes one fixed memory window — a range of host physical address space reserved for CXL memory

During cxl_acpi_probe, the kernel creates one root decoder per CFMWS entry it finds in the CEDT. That root decoder's memory range becomes a memory region, and the hosts it's reachable from are presented in sysfs via a target_list parameter — meaning this ACPI data is what first tells the kernel "CXL memory could exist somewhere in this address range," well before any specific device has been identified.

Step 2: CDAT Tells the Kernel What It Actually Found

ACPI's CEDT/CFMWS describe the platform's side of the story — where memory windows are reserved. The device's own CDAT (Coherent Device Attribute Table) describes the other half: what the device itself reports about its own memory. CDAT parsing (handled by the core module) gives the kernel the device's memory ranges along with the latency and bandwidth of each range — real, hardware-reported numbers, not an assumption.

Why this two-table split matters: CEDT/CFMWS answer "where in the address map should I look for CXL memory," while CDAT answers "now that I've found a device there, how fast is it really?" Day 11's memory tiering framework needs exactly this second answer — the kernel places a CXL node into its tier based on the device's own reported abstract distance, derived from CDAT's real latency/bandwidth figures, not a fixed assumption baked into the kernel.

Step 3: Programming the HDM Decoders

Knowing where memory should be and how fast a device claims to be still isn't enough to actually route traffic. The kernel has to program HDM (Host-managed Device Memory) decoders — both host-side and device-side — so that a contiguous Host Physical Address (HPA) range actually routes through the CXL fabric to the correct physical device. For interleaved regions (memory striped across multiple devices for bandwidth, similar in spirit to RAID striping), the decoders also have to agree on the correct interleave granularity so that consecutive address ranges land on the correct device in the correct order.

This is the step where the abstractions from earlier in this course become concrete register programming: Day 4's HDM concept, Day 7's Logical Devices, and Day 8's GFAM addressing all ultimately resolve down to an HDM decoder configuration that the kernel must get exactly right, on both ends of the link, before a single byte can be safely read or written.

Step 4: Becoming a Usable NUMA Node

Once HDM decoders are correctly programmed, the resulting CXL region is exposed as a hot-pluggable NUMA node. It typically first appears as a dax device (Direct Access) — a mode that gives applications direct, byte-addressable access to the memory without going through the page cache. From there, an administrator has a choice:

💾
Convert to System RAMUse daxctl to reconfigure the dax device into ordinary System RAM, which the memory tiering framework (Day 11) then manages transparently
🎯
Leave as DAX-mappedKeep the region in DAX mode for applications that specifically want direct, page-cache-bypassing access to the CXL memory

This is precisely the point where all the ACPI/CDAT/HDM machinery above becomes invisible to an application: whether it's a Day 11 DAMON-managed tiered NUMA node or a Day 12 GPU-reachable memory pool, the application (or in Day 12's case, the accelerator's runtime) just sees memory it can allocate from — the entire discovery-and-decode pipeline above is what made that simplicity possible.

The Userspace Toolchain

A parallel set of userspace tools exists to configure, validate, and debug every layer above:

cxl # CXL-specific CLI: topology, regions, decoders ndctl # nvdimm/persistent-memory subsystem CXL builds on daxctl # convert dax devices to System RAM, or configure DAX mode numactl # allocate/bind memory relative to NUMA nodes, including CXL tiers lspci # inspect PCIe/CXL device configuration space setpci # low-level PCI configuration space read/write, used in bring-up debug

Together, these tools validate the entire boot-to-usable-memory path this page just walked through: power-on → DRAM training → DVSEC and HDM reporting → decoder programming → CDAT delivery → ACPI table handoff. When a CXL memory expander doesn't show up as expected, this is the exact sequence an engineer steps through with these tools to find which stage failed — is the CEDT/CFMWS entry missing, did CDAT parsing fail to return sane latency numbers, or did HDM decoder programming leave the HPA range unrouted?

🎯 Day 13 Key Takeaways

Frequently Asked Questions

How is the Linux CXL driver organized?
The Linux CXL driver code has a core module handling port/decoder topology, region construction, HDM decoder setup, mailbox interface, DVSEC parsing, and CDAT parsing, plus separate cxl_pci, cxl_mem, and cxl_acpi modules. Core objects like ports, decoders, and regions are shared between these subsystem drivers and the region-driver and nvdimm object-drivers built on top of them.
What is a CXL root decoder and where does it come from?
A root decoder is created during cxl_acpi_probe, with one root decoder created per CFMWS (CXL Fixed Memory Window Structure) entry found in the ACPI CEDT (CXL Early Discovery Table). The memory range described in the root decoder becomes a memory region, and its target hosts are presented in sysfs via the target_list parameter.
What is CDAT and why does it matter for CXL?
CDAT (Coherent Device Attribute Table) is a table reported by the CXL device itself, describing its own memory ranges along with the latency and bandwidth of each range. While ACPI's CEDT/CFMWS tell the kernel about the host's memory windows, CDAT is what lets the kernel's memory tiering framework place a specific CXL device into the correct performance tier automatically, based on real reported numbers rather than a guess.
How does a CXL memory region become usable RAM in Linux?
Once the kernel programs the host-side and device-side HDM decoders so a Host Physical Address range correctly routes to the right device, the resulting CXL region is exposed as a hot-pluggable NUMA node, typically surfaced first as a dax device. From there it can be reconfigured into ordinary System RAM using daxctl, or left as a DAX-mapped region for applications that want direct, byte-addressable access.
What userspace tools manage CXL memory on Linux?
The core userspace toolchain is cxl (the CXL-specific CLI for topology, regions, and decoders), ndctl (for the underlying nvdimm/persistent-memory subsystem CXL builds on), daxctl (for converting dax devices into System RAM or configuring DAX mode), plus general tools like numactl, lspci, and setpci used to validate the boot path from power-on through DRAM training, DVSEC/HDM reporting, decoder programming, CDAT delivery, and ACPI table handoff.