diff --git a/man/systemd-vmspawn.xml b/man/systemd-vmspawn.xml
index 9feb7407ca..d65bd965a3 100644
--- a/man/systemd-vmspawn.xml
+++ b/man/systemd-vmspawn.xml
@@ -167,10 +167,12 @@
-
+
- The amount of memory to start the virtual machine with.
- Defaults to 2G.
+ The amount of memory to start the virtual machine with. Defaults to 2G.
+ If a maximum size is specified after a colon, memory hotplug is enabled with the given
+ upper limit. The number of hotplug slots can optionally be specified after a second colon
+ and defaults to 1.
@@ -185,6 +187,18 @@
+
+
+
+ Controls whether to enable CXL (Compute Express Link) support in the virtual
+ machine. CXL is a high-speed interconnect standard that allows CPUs to access memory attached to
+ devices such as accelerators and memory expanders, enabling flexible memory pooling and expansion
+ beyond what is physically installed on the motherboard. Only supported on x86_64 and aarch64
+ architectures.
+
+
+
+
diff --git a/shell-completion/bash/systemd-vmspawn b/shell-completion/bash/systemd-vmspawn
index b035a42a65..995aeb1271 100644
--- a/shell-completion/bash/systemd-vmspawn
+++ b/shell-completion/bash/systemd-vmspawn
@@ -31,7 +31,7 @@ _systemd_vmspawn() {
local -A OPTS=(
[STANDALONE]='-h --help --version -q --quiet --no-pager -n --network-tap --network-user-mode --user --system -x --ephemeral'
[PATH]='-D --directory -i --image --linux --initrd --extra-drive --forward-journal --efi-nvram-template'
- [BOOL]='--kvm --vsock --tpm --discard-disk --register --pass-ssh-key'
+ [BOOL]='--kvm --cxl --vsock --tpm --discard-disk --register --pass-ssh-key'
[SECURE_BOOT]='--secure-boot'
[FIRMWARE]='--firmware'
[FIRMWARE_FEATURES]='--firmware-features'
diff --git a/src/vmspawn/vmspawn-util.h b/src/vmspawn/vmspawn-util.h
index 9fec6641aa..d9272b4900 100644
--- a/src/vmspawn/vmspawn-util.h
+++ b/src/vmspawn/vmspawn-util.h
@@ -33,6 +33,12 @@
# define ARCHITECTURE_SUPPORTS_HPET 0
#endif
+#if defined(__x86_64__) || defined(__aarch64__)
+# define ARCHITECTURE_SUPPORTS_CXL 1
+#else
+# define ARCHITECTURE_SUPPORTS_CXL 0
+#endif
+
#if defined(__x86_64__) || defined(__i386__)
# define QEMU_MACHINE_TYPE "q35"
#elif defined(__arm__) || defined(__aarch64__) || defined(__riscv) || defined(__loongarch64) || defined(__m68k__)
diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c
index 1b1e31bb5b..92a5555f64 100644
--- a/src/vmspawn/vmspawn.c
+++ b/src/vmspawn/vmspawn.c
@@ -130,7 +130,10 @@ static char *arg_slice = NULL;
static char **arg_property = NULL;
static char *arg_cpus = NULL;
static uint64_t arg_ram = UINT64_C(2) * U64_GB;
+static uint64_t arg_ram_max = 0;
+static unsigned arg_ram_slots = 0;
static int arg_kvm = -1;
+static bool arg_cxl = false;
static int arg_vsock = -1;
static unsigned arg_vsock_cid = VMADDR_CID_ANY;
static int arg_tpm = -1;
@@ -229,8 +232,11 @@ static int help(void) {
" scsi-cd; default: virtio-blk)\n"
"\n%3$sHost Configuration:%4$s\n"
" --cpus=CPUS Configure number of CPUs in guest\n"
- " --ram=BYTES Configure guest's RAM size\n"
+ " --ram=BYTES[:MAXBYTES[:SLOTS]]\n"
+ " Configure guest's RAM size (and max/slots for\n"
+ " hotplug)\n"
" --kvm=BOOL Enable use of KVM\n"
+ " --cxl=BOOL Enable use of CXL\n"
" --vsock=BOOL Override autodetection of VSOCK support\n"
" --vsock-cid=CID Specify the CID to use for the guest's VSOCK support\n"
" --tpm=BOOL Enable use of a virtual TPM\n"
@@ -326,6 +332,42 @@ static int parse_environment(void) {
return 0;
}
+static int parse_ram(const char *s) {
+ _cleanup_free_ char *ram = NULL, *ram_max = NULL, *ram_slots = NULL;
+ int r;
+
+ assert(s);
+
+ const char *p = s;
+ r = extract_many_words(&p, ":", EXTRACT_DONT_COALESCE_SEPARATORS, &ram, &ram_max, &ram_slots);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --ram=%s: %m", s);
+ if (r == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse --ram=%s", s);
+ if (!isempty(p))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unexpected trailing data in --ram=%s", s);
+
+ r = parse_size(ram, 1024, &arg_ram);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --ram=%s: %m", s);
+
+ if (!isempty(ram_max)) {
+ r = parse_size(ram_max, 1024, &arg_ram_max);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --ram=%s: %m", s);
+ } else
+ arg_ram_max = 0;
+
+ if (!isempty(ram_slots)) {
+ r = safe_atou(ram_slots, &arg_ram_slots);
+ if (r < 0)
+ return log_error_errno(r, "Failed to parse --ram=%s: %m", s);
+ } else
+ arg_ram_slots = 0;
+
+ return 0;
+}
+
static int parse_argv(int argc, char *argv[]) {
int r;
@@ -340,6 +382,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_CPUS,
ARG_RAM,
ARG_KVM,
+ ARG_CXL,
ARG_VSOCK,
ARG_VSOCK_CID,
ARG_TPM,
@@ -398,6 +441,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "ram", required_argument, NULL, ARG_RAM },
{ "qemu-mem", required_argument, NULL, ARG_RAM }, /* Compat alias */
{ "kvm", required_argument, NULL, ARG_KVM },
+ { "cxl", required_argument, NULL, ARG_CXL },
{ "qemu-kvm", required_argument, NULL, ARG_KVM }, /* Compat alias */
{ "vsock", required_argument, NULL, ARG_VSOCK },
{ "qemu-vsock", required_argument, NULL, ARG_VSOCK }, /* Compat alias */
@@ -518,9 +562,9 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_RAM:
- r = parse_size(optarg, 1024, &arg_ram);
+ r = parse_ram(optarg);
if (r < 0)
- return log_error_errno(r, "Failed to parse --ram=%s: %m", optarg);
+ return r;
break;
case ARG_KVM:
@@ -529,6 +573,15 @@ static int parse_argv(int argc, char *argv[]) {
return r;
break;
+ case ARG_CXL:
+ r = parse_boolean_argument("--cxl=", optarg, &arg_cxl);
+ if (r < 0)
+ return r;
+ if (arg_cxl && !ARCHITECTURE_SUPPORTS_CXL)
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "CXL not supported on %s.", architecture_to_string(native_architecture()));
+ break;
+
case ARG_VSOCK:
r = parse_tristate_argument_with_auto("--vsock=", optarg, &arg_vsock);
if (r < 0)
@@ -993,6 +1046,12 @@ static int parse_argv(int argc, char *argv[]) {
if (!strv_isempty(arg_bind_user_groups) && strv_isempty(arg_bind_user))
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --bind-user-group= without --bind-user=");
+ if (arg_ram_max > 0 && arg_ram_max < arg_ram)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Maximum RAM size must be greater than or equal to initial RAM size");
+
+ if (arg_ram_slots > 0 && arg_ram_max == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Memory hotplug slots require a maximum RAM size");
+
if (arg_ephemeral && arg_extra_drives.n_drives > 0)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot use --ephemeral with --extra-drive=");
@@ -2311,6 +2370,12 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
return r;
}
+ if (arg_cxl) {
+ r = qemu_config_key(config_file, "cxl", "on");
+ if (r < 0)
+ return r;
+ }
+
if (arg_directory || arg_runtime_mounts.n_mounts != 0) {
r = qemu_config_key(config_file, "memory-backend", "mem");
if (r < 0)
@@ -2333,6 +2398,16 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
if (r < 0)
return r;
+ if (arg_ram_max > 0) {
+ r = qemu_config_keyf(config_file, "maxmem", "%" PRIu64 "M", DIV_ROUND_UP(arg_ram_max, U64_MB));
+ if (r < 0)
+ return r;
+
+ r = qemu_config_keyf(config_file, "slots", "%u", arg_ram_slots > 0 ? arg_ram_slots : 1u);
+ if (r < 0)
+ return r;
+ }
+
r = qemu_config_section(config_file, "object", "rng0",
"qom-type", "rng-random",
"filename", "/dev/urandom");