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");