QEMU CXL 模拟环境搭建

使用QEMU模拟CXL设备

本文只介绍环境搭建过程,CXL设备的使用,之后再说

总览

研发的目标平台是CXL3.0,目前物理设备尚未生产出来,使用QEMU来模拟CXL设备

Linux内核对于CXL的支持情况

功能最低内核版本说明
CXL 总线与 Type 3 内存设备基本支持v5.17初步引入 cxl_core 子系统,但功能有限
CXL 内存作为 System RAM 注册v6.3可通过 add_memory() 将 CXL 内存加入 buddy allocator
完整 DAX 支持(/dev/daxX.Y)v6.4–6.6需配合 cxl_pmemdevice-dax
ACPI CDAT/HMAT 解析(用于 QEMU 模拟)v6.6+QEMU 通过 ACPI 表描述 CXL 拓扑
CXL 2.0/3.0 交换机与多端口模拟支持v6.8+更好支持 QEMU 的 CXL 拓扑建模
稳定性和调试工具(cxl-cli, sysfs 接口)v6.9+用户态工具链成熟

QEMU对于CXL的支持情况

QEMU 版本发布时间CXL 支持亮点
7.22022 年底初步支持 CXL 2.0 Type 3 内存设备(基础仿真)
8.02023 年中重大扩展:HDM 解码器、CXL 交换机、内存交织
8.1–8.22023–2024稳定性提升,ACPI 表支持(CDAT/HMAT)
9.02024 年中支持 Type 1/2 设备、MMIO 缓存、NUMA 拓扑
9.12024 年底CXL 3.0 Type 3 设备、动态容量设备(DCD)初步支持
10.0–10.12025 年CXL 3.0 全面支持、热插拔、嵌套虚拟化、多层交换

选用Linux Kernel 6.11 和 QEMU 10.1.3

依赖安装

1
2
3
4
5
6
7
8
sudo apt-get install libglib2.0-dev libgcrypt20-dev zlib1g-dev \
autoconf automake libtool bison flex libpixman-1-dev bc \
make ninja-build libncurses-dev libelf-dev libssl-dev debootstrap \
libcap-ng-dev libattr1-dev libslirp-dev libslirp0

sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison

pip3 install tomli

QEMU编译安装

QEMU环境好配,设置目标平台为x86_64,并且启用debug模式,方便断点调试Kernel
1
2
3
4
5
6
7
8
9
10
11
12
13
# 完整拉取
git clone -b v10.1.3 https://github.com/qemu/qemu.git

# 只拉取最浅层(推荐,我们只需要编译源码)
git clone --depth 1 --single-branch --no-tags -b v10.1.3 https://github.com/qemu/qemu.git

# 编译安装
cd qemu
./configure --target-list=x86_64-softmmu --enable-debug
make -j$(nproc)

# 修改~/.bashrc或~/.zshrc
alias qemu='~/tool/qemu/build/qemu-system-x86_64' # 设置为你自己的编译路径

Linux Kernel

Kernel的配置项较为复杂,本来想直接使用CXL-Emulator-QEMU这个仓库中的.config文件,但是作者太粗糙了,打包了所有的驱动,众所周知,驱动是内核最大的一坨屎山,编译时间巨长,只能自己配置。

推荐使用menuconfig界面,启用PCIE,CXL,DAX三个选项

1
2
3
git clone --depth 1 --single-branch --no-tags -b v6.11 https://github.com/torvalds/linux.git
# 生成配置项
make menuconfig # 启用完整的 PCIE支持 CXL支持 DAX支持

如果想直接修改.config,先make localmodconfig根据主机信息来配置驱动和模块,然后复制下面的选项,或者使用scripts/config --enable 选项名的形式逐个添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!-- 修改.config文件 -->
CONFIG_PCIEAER_CXL=y
CONFIG_CXL_BUS=y
CONFIG_CXL_PCI=y
CONFIG_CXL_MEM_RAW_COMMANDS=y
CONFIG_CXL_ACPI=y
CONFIG_CXL_PMEM=y
CONFIG_CXL_MEM=y
CONFIG_CXL_PORT=y
CONFIG_CXL_SUSPEND=y
CONFIG_CXL_REGION=y
CONFIG_CXL_REGION_INVALIDATION_TEST=y

CONFIG_DWC_PCIE_PMU=y
CONFIG_CXL_PMU=y

CONFIG_DAX=y
CONFIG_DEV_DAX=y
CONFIG_DEV_DAX_PMEM=y
CONFIG_DEV_DAX_HMEM=y
CONFIG_DEV_DAX_CXL=y
CONFIG_DEV_DAX_HMEM_DEVICES=y
CONFIG_DEV_DAX_KMEM=y
CONFIG_NVMEM=y
CONFIG_NVMEM_SYSFS=y

编译安装

1
2
3
make -j$(nproc)
ls arch/x86_64/boot/bzImage -lh # 检测是否编译成果
sudo make modules_install

QEMU启动

在本章节一共需要使用三个目录,QEMU_PATH,KERNEL_PATH,DISK_PATH

为客户机创建根文件系统:

1
2
3
4
5
6
7
8
9
10
11
cd $(QEMU_PATH)
# 创建磁盘镜像文件
./build/qemu-img create qemu-cxl-img 40G
# 格式化文件系统
sudo mkfs.ext4 qemu-cxl-img
# 创建挂载目录
mkdir ~/qemu-cxl-mnt
# 挂载磁盘镜像
sudo mount -o loop ./qemu-cxl-img ~/qemu-cxl-mnt
# 安装ubuntu发行版
sudo debootstrap --arch amd64 noble ~/qemu-cxl-mnt

设置虚拟机和主机互通:
修改用户名为你的用户名

1
2
3
4
5
6
7
8
echo "#! /bin/bash
mount -t 9p -o trans=virtio homeshare /home/zmy
mount -t 9p -o trans=virtio modshare /lib/modules
" > ./rc.local
chmod a+x ./rc.local
sudo mv ./rc.local ~/qemu-cxl-mnt/etc/
sudo mkdir -p ~/qemu-cxl-mnt/home/zmy
sudo mkdir -p ~/qemu-cxl-mnt/lib/modules/

设置网络:

1
2
3
sudo mkdir -p ~/qemu-cxl-mnt/etc/netplan
vim -p ~/qemu-cxl-mnt/etc/netplan/config.yaml
ll ~/qemu-cxl-mnt/etc/netplan/config.yaml

其中config.yaml文件内容如下:

1
2
3
4
5
6
network:
version: 2
renderer: networkd
ethernets:
enp0s2:
dhcp4: true

设置虚拟机用户密码: 之后就使用root用户登录虚拟机镜像

1
2
3
sudo chroot ~/qemu-cxl-mnt 
passwd
exit

启动虚拟机,并配置512MiB的持久化CXL内存: start_qemu-pmem.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/bash

# 替换路径为你的路径
QEMU_BIN="$HOME/tool/qemu/build/qemu-system-x86_64"
KERNEL_IMG="$HOME/work/mempool/linux/arch/x86_64/boot/bzImage"
DISK_IMG="$HOME/tool/qemu/qemu-cxl-img"

$QEMU_BIN \
-s \
-kernel $KERNEL_IMG \
-append "root=/dev/sda rw console=ttyS0,115200 ignore_loglevel nokaslr \
cxl_acpi.dyndbg=+fplm cxl_pci.dyndbg=+fplm cxl_core.dyndbg=+fplm \
cxl_mem.dyndbg=+fplm cxl_pmem.dyndbg=+fplm cxl_port.dyndbg=+fplm \
cxl_region.dyndbg=+fplm cxl_test.dyndbg=+fplm cxl_mock.dyndbg=+fplm \
cxl_mock_mem.dyndbg=+fplm dax.dyndbg=+fplm dax_cxl.dyndbg=+fplm \
device_dax.dyndbg=+fplm" \
-smp 1 \
-accel kvm \
-serial mon:stdio \
-nographic \
-qmp tcp:localhost:4444,server,wait=off \
-netdev user,id=network0,hostfwd=tcp::2024-:22 \
-device e1000,netdev=network0 \
-monitor telnet:127.0.0.1:12345,server,nowait \
-drive file=$DISK_IMG,index=0,media=disk,format=raw \
-machine q35,cxl=on -m 8G,maxmem=32G,slots=8 \
-virtfs local,path=/lib/modules,mount_tag=modshare,security_model=mapped \
-virtfs local,path=/home/zmy,mount_tag=homeshare,security_model=mapped # 替换用户名为你设置的用户名 \
-object memory-backend-file,id=cxl-mem1,share=on,mem-path=/tmp/cxltest.raw,size=512M \
-object memory-backend-file,id=cxl-lsa1,share=on,mem-path=/tmp/lsa.raw,size=512M \
-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,memdev=cxl-mem1,lsa=cxl-lsa1,id=cxl-pmem0 \
-M cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=4G,cxl-fmw.0.interleave-granularity=8k

启动虚拟机,并配置CXL动态容量设备: 2个区域,每个2GiB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/bin/bash

QEMU_BIN="$HOME/tool/qemu/build/qemu-system-x86_64"
KERNEL_IMG="$HOME/work/mempool/linux/arch/x86_64/boot/bzImage"
DISK_IMG="$HOME/tool/qemu/qemu-cxl-img"

$QEMU_BIN \
-s \
-kernel $KERNEL_IMG \
-append "root=/dev/sda rw console=ttyS0,115200 ignore_loglevel nokaslr \
cxl_acpi.dyndbg=+fplm cxl_pci.dyndbg=+fplm cxl_core.dyndbg=+fplm \
cxl_mem.dyndbg=+fplm cxl_pmem.dyndbg=+fplm cxl_port.dyndbg=+fplm \
cxl_region.dyndbg=+fplm cxl_test.dyndbg=+fplm cxl_mock.dyndbg=+fplm \
cxl_mock_mem.dyndbg=+fplm dax.dyndbg=+fplm dax_cxl.dyndbg=+fplm \
device_dax.dyndbg=+fplm" \
-smp 1 \
-accel kvm \
-serial mon:stdio \
-nographic \
-qmp tcp:localhost:4444,server,wait=off \
-netdev user,id=network0,hostfwd=tcp::2024-:22 \
-device e1000,netdev=network0 \
-monitor telnet:127.0.0.1:12345,server,nowait \
-drive file=$DISK_IMG,index=0,media=disk,format=raw \
-machine q35,cxl=on -m 8G,maxmem=32G,slots=8 \
-virtfs local,path=/lib/modules,mount_tag=modshare,security_model=mapped \
-virtfs local,path=/home/zmy,mount_tag=homeshare,security_model=mapped #替换用户名为你设置的用户名 \
-device pxb-cxl,bus_nr=12,bus=pcie.0,id=cxl.1 \
-device cxl-rp,port=13,bus=cxl.1,id=root_port13,chassis=0,slot=2 \
-object memory-backend-file,id=dhmem0,share=on,mem-path=/tmp/dhmem0.raw,size=4G \
-object memory-backend-file,id=lsa0,share=on,mem-path=/tmp/lsa0.raw,size=512M \
-device cxl-type3,bus=root_port13,volatile-dc-memdev=dhmem0,num-dc-regions=2,id=cxl-memdev0 \
-M cxl-fmw.0.targets.0=cxl.1,cxl-fmw.0.size=4G,cxl-fmw.0.interleave-granularity=8K

启动虚拟机网络:

1
2
ip link set dev enp0s2 up
dhclient enp0s2

查询CXL设备:

1
2
3
4
5
apt install pciutils
lspci
lspci | grep CXL
lspci -vvv
lspci -vvv -s 0d:00.0

小结

按照上述流程,可以成功编译QEMU和Kernel,并使用虚拟机模拟CXL设备,美中不足的是参考文献的作者使用的是2.X的CXL设备,如何启动3.X的设备之后再探索

参考文献

  1. CXL-Emulator-QEMU