Add qemu CLI support for EGM memory device model:
- Specify EGM device path to memory-backend-file object
- Support acpi-egm-memory object with id, pci-dev, and
node attributes
- Consolidate all acpi-egm-memory objects' memory into
a single memory-backend-file per EGM chardev
specified.
Signed-off-by: Ian May <ianm@nvidia.com>
Signed-off-by: Nathan Chen <nathanc@nvidia.com>
---
src/qemu/qemu_alias.c | 7 +-
src/qemu/qemu_capabilities.c | 2 +
src/qemu/qemu_capabilities.h | 1 +
src/qemu/qemu_command.c | 158 ++++++++++++++++++++++++++++++---
src/qemu/qemu_domain.c | 13 ++-
src/qemu/qemu_domain_address.c | 3 +
src/qemu/qemu_driver.c | 1 +
src/qemu/qemu_hotplug.c | 1 +
src/qemu/qemu_monitor_json.c | 1 +
src/qemu/qemu_postparse.c | 1 +
src/qemu/qemu_process.c | 2 +
src/qemu/qemu_validate.c | 6 ++
12 files changed, 180 insertions(+), 16 deletions(-)
diff --git a/src/qemu/qemu_alias.c b/src/qemu/qemu_alias.c
index 400ce73283..719224e1ba 100644
--- a/src/qemu/qemu_alias.c
+++ b/src/qemu/qemu_alias.c
@@ -504,7 +504,8 @@ qemuDeviceMemoryGetAliasID(virDomainDef *def,
* valid */
if (mem->model != VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM &&
mem->model != VIR_DOMAIN_MEMORY_MODEL_VIRTIO_MEM &&
- mem->model != VIR_DOMAIN_MEMORY_MODEL_SGX_EPC)
+ mem->model != VIR_DOMAIN_MEMORY_MODEL_SGX_EPC &&
+ mem->model != VIR_DOMAIN_MEMORY_MODEL_EGM)
return mem->info.addr.dimm.slot;
for (i = 0; i < def->nmems; i++) {
@@ -553,6 +554,10 @@ qemuAssignDeviceMemoryAlias(virDomainDef *def,
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
prefix = "epc";
break;
+ case VIR_DOMAIN_MEMORY_MODEL_EGM: {
+ prefix = "egm";
+ break;
+ }
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
default:
diff --git a/src/qemu/qemu_capabilities.c b/src/qemu/qemu_capabilities.c
index 92b863a826..3fc8fee4b3 100644
--- a/src/qemu/qemu_capabilities.c
+++ b/src/qemu/qemu_capabilities.c
@@ -755,6 +755,7 @@ VIR_ENUM_IMPL(virQEMUCaps,
"disk-timed-stats", /* QEMU_CAPS_DISK_TIMED_STATS */
"query-accelerators", /* QEMU_CAPS_QUERY_ACCELERATORS */
"mshv", /* QEMU_CAPS_MSHV */
+ "acpi-egm-memory", /* QEMU_CAPS_DEVICE_ACPI_EGM_MEMORY */
);
@@ -1462,6 +1463,7 @@ struct virQEMUCapsStringFlags virQEMUCapsObjectTypes[] = {
{ "tpm-emulator", QEMU_CAPS_DEVICE_TPM_EMULATOR },
{ "tpm-passthrough", QEMU_CAPS_DEVICE_TPM_PASSTHROUGH },
{ "acpi-generic-initiator", QEMU_CAPS_ACPI_GENERIC_INITIATOR },
+ { "acpi-egm-memory", QEMU_CAPS_DEVICE_ACPI_EGM_MEMORY },
};
diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h
index f180844e66..3eb12235f4 100644
--- a/src/qemu/qemu_capabilities.h
+++ b/src/qemu/qemu_capabilities.h
@@ -730,6 +730,7 @@ typedef enum { /* virQEMUCapsFlags grouping marker for syntax-check */
QEMU_CAPS_DISK_TIMED_STATS, /* timed stats support ('stats-intervals' property of disk frontends) */
QEMU_CAPS_QUERY_ACCELERATORS, /* query-accelerators command */
QEMU_CAPS_MSHV, /* -accel mshv */
+ QEMU_CAPS_DEVICE_ACPI_EGM_MEMORY, /* For using extended GPU memory */
QEMU_CAPS_LAST /* this must always be the last item */
} virQEMUCapsFlags;
diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
index b69fe23236..33848aa781 100644
--- a/src/qemu/qemu_command.c
+++ b/src/qemu/qemu_command.c
@@ -135,6 +135,21 @@ VIR_ENUM_IMPL(qemuACPITableSIG,
"SLIC",
"MSDM");
+typedef struct _qemuEGMBackendInfo {
+ char *alias;
+ unsigned long long totalSize;
+ bool created;
+ virDomainMemoryDef *firstMem; /* Pointer to first device for this path */
+} qemuEGMBackendInfo;
+
+static void
+qemuEGMBackendInfoFree(qemuEGMBackendInfo *info)
+{
+ if (!info)
+ return;
+ g_free(info->alias);
+ g_free(info);
+}
const char *
qemuAudioDriverTypeToString(virDomainAudioType type)
@@ -992,6 +1007,7 @@ qemuBuildVirtioDevGetConfigDev(const virDomainDeviceDef *device,
case VIR_DOMAIN_MEMORY_MODEL_DIMM:
case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
break;
@@ -3162,6 +3178,7 @@ qemuBuildMemoryGetPagesize(virQEMUDriverConfig *cfg,
nvdimmPath = mem->source.virtio_pmem.path;
break;
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
break;
@@ -3362,6 +3379,9 @@ qemuBuildMemoryBackendProps(virJSONValue **backendProps,
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
nvdimmPath = mem->source.virtio_pmem.path;
break;
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
+ nvdimmPath = mem->source.egm.path;
+ break;
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
break;
@@ -3573,12 +3593,17 @@ qemuBuildMemoryDimmBackendStr(virCommand *cmd,
virDomainMemoryDef *mem,
virDomainDef *def,
virQEMUDriverConfig *cfg,
- qemuDomainObjPrivate *priv)
+ qemuDomainObjPrivate *priv,
+ GHashTable *egmBackends)
{
g_autoptr(virJSONValue) props = NULL;
g_autoptr(virJSONValue) tcProps = NULL;
virBitmap *nodemask = NULL;
g_autofree char *alias = NULL;
+ unsigned long long originalSize = 0;
+ bool isEGM = (mem->model == VIR_DOMAIN_MEMORY_MODEL_EGM);
+ bool shouldCreateBackend = true;
+ qemuEGMBackendInfo *egmInfo = NULL;
if (!mem->info.alias) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
@@ -3588,19 +3613,65 @@ qemuBuildMemoryDimmBackendStr(virCommand *cmd,
alias = g_strdup_printf("mem%s", mem->info.alias);
- if (qemuBuildMemoryBackendProps(&props, alias, cfg, priv,
- def, mem, true, false, &nodemask) < 0)
- return -1;
+ /* Handle EGM shared backend logic */
+ if (isEGM && egmBackends) {
+ const char *egmPath = mem->source.egm.path;
+ egmInfo = g_hash_table_lookup(egmBackends, egmPath);
- if (qemuBuildThreadContextProps(&tcProps, &props, def, priv, nodemask) < 0)
- return -1;
+ if (egmInfo) {
+ alias = g_strdup(egmInfo->alias);
+ if (egmInfo->created) {
+ /* Backend already created, skip backend creation */
+ shouldCreateBackend = false;
+ } else {
+ /* First device for this path - temporarily use accumulated size */
+ originalSize = mem->size;
+ mem->size = egmInfo->totalSize;
+ egmInfo->created = true;
+ }
+ }
+ }
- if (tcProps &&
- qemuBuildObjectCommandlineFromJSON(cmd, tcProps) < 0)
- return -1;
+ if (shouldCreateBackend) {
+ /* Use existing function unchanged */
+ if (qemuBuildMemoryBackendProps(&props, alias, cfg, priv,
+ def, mem, true, false, &nodemask) < 0) {
+ if (originalSize > 0)
+ mem->size = originalSize; /* Restore on error */
+ return -1;
+ }
- if (qemuBuildObjectCommandlineFromJSON(cmd, props) < 0)
- return -1;
+ /* Restore original size after backend props are built */
+ if (originalSize > 0)
+ mem->size = originalSize;
+
+ if (qemuBuildThreadContextProps(&tcProps, &props, def, priv, nodemask) < 0)
+ return -1;
+
+ if (tcProps &&
+ qemuBuildObjectCommandlineFromJSON(cmd, tcProps) < 0)
+ return -1;
+
+ if (qemuBuildObjectCommandlineFromJSON(cmd, props) < 0)
+ return -1;
+ }
+
+ if (isEGM) {
+ g_autofree char *egmObjStr = NULL;
+ g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
+
+ virBufferAsprintf(&buf, "acpi-egm-memory,id=%s", mem->info.alias);
+
+ if (mem->target.egm.pciDev)
+ virBufferAsprintf(&buf, ",pci-dev=%s", mem->target.egm.pciDev);
+
+ if (mem->targetNode >= 0)
+ virBufferAsprintf(&buf, ",node=%d", mem->targetNode);
+
+ egmObjStr = virBufferContentAndReset(&buf);
+
+ virCommandAddArgList(cmd, "-object", egmObjStr, NULL);
+ }
return 0;
}
@@ -3671,6 +3742,7 @@ qemuBuildMemoryDeviceProps(virQEMUDriverConfig *cfg,
dynamicMemslots = mem->target.virtio_mem.dynamicMemslots;
break;
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
@@ -7104,6 +7176,7 @@ qemuAppendDomainMemoryMachineParams(virBuffer *buf,
case VIR_DOMAIN_MEMORY_MODEL_DIMM:
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_MEM:
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
break;
@@ -7821,6 +7894,8 @@ qemuBuildNumaCommandLine(virQEMUDriverConfig *cfg,
size_t ncells = virDomainNumaGetNodeCount(def->numa);
ssize_t masterInitiator = -1;
int rc;
+ g_autoptr(GHashTable) egmBackends = NULL;
+ size_t egmBackendCount = 0;
if (!virDomainNumatuneNodesetIsAvailable(def->numa, priv->autoNodeset))
goto cleanup;
@@ -7835,6 +7910,37 @@ qemuBuildNumaCommandLine(virQEMUDriverConfig *cfg,
hmat = true;
}
+ /* Pre-scan EGM devices to group by path and calculate total sizes */
+ egmBackends = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify)qemuEGMBackendInfoFree);
+
+ for (i = 0; i < def->nmems; i++) {
+ if (def->mems[i]->model == VIR_DOMAIN_MEMORY_MODEL_EGM) {
+ const char *egmPath = def->mems[i]->source.egm.path;
+ qemuEGMBackendInfo *info = g_hash_table_lookup(egmBackends, egmPath);
+
+ if (!info) {
+ info = g_new0(qemuEGMBackendInfo, 1);
+ info->alias = g_strdup_printf("memegm%zu", egmBackendCount);
+ egmBackendCount++;
+ info->totalSize = def->mems[i]->size;
+ info->created = false;
+ info->firstMem = def->mems[i];
+ g_hash_table_insert(egmBackends, g_strdup(egmPath), info);
+ } else {
+ info->totalSize += def->mems[i]->size;
+ }
+ }
+ }
+
+ /* Build the actual backend and device objects */
+ for (i = 0; i < def->nmems; i++) {
+ if (def->mems[i]->model == VIR_DOMAIN_MEMORY_MODEL_EGM) {
+ if (qemuBuildMemoryDimmBackendStr(cmd, def->mems[i], def, cfg, priv, egmBackends) < 0)
+ goto cleanup;
+ }
+ }
+
nodeBackends = g_new0(virJSONValue *, ncells);
nodemask = g_new0(virBitmap *, ncells);
@@ -7870,8 +7976,18 @@ qemuBuildNumaCommandLine(virQEMUDriverConfig *cfg,
for (i = 0; i < ncells; i++) {
ssize_t initiator = virDomainNumaGetNodeInitiator(def->numa, i);
unsigned long long memSize = virDomainNumaGetNodeMemorySize(def->numa, i);
+ bool egmBacked = false;
+ size_t k;
+
+ for (k = 0; k < def->nmems; k++) {
+ if (def->mems[k]->model == VIR_DOMAIN_MEMORY_MODEL_EGM &&
+ def->mems[k]->targetNode == (int)i) {
+ egmBacked = true;
+ break;
+ }
+ }
- if (needBackend && memSize > 0) {
+ if (needBackend && memSize > 0 && !egmBacked) {
g_autoptr(virJSONValue) tcProps = NULL;
if (qemuBuildThreadContextProps(&tcProps, &nodeBackends[i],
@@ -7901,7 +8017,15 @@ qemuBuildNumaCommandLine(virQEMUDriverConfig *cfg,
if (memSize > 0) {
if (needBackend) {
- virBufferAsprintf(&buf, ",memdev=ram-node%zu", i);
+ if (egmBacked) {
+ /* Look up the actual backend alias for EGM */
+ const char *egmPath = def->mems[k]->source.egm.path;
+ qemuEGMBackendInfo *egmInfo = g_hash_table_lookup(egmBackends, egmPath);
+ const char *backendAlias = egmInfo ? egmInfo->alias : def->mems[k]->info.alias;
+ virBufferAsprintf(&buf, ",memdev=%s", backendAlias);
+ } else {
+ virBufferAsprintf(&buf, ",memdev=ram-node%zu", i);
+ }
} else {
virBufferAsprintf(&buf, ",mem=%llu", memSize / 1024);
}
@@ -7965,7 +8089,10 @@ qemuBuildMemoryDeviceCommandLine(virCommand *cmd,
for (i = 0; i < def->nmems; i++) {
g_autoptr(virJSONValue) props = NULL;
- if (qemuBuildMemoryDimmBackendStr(cmd, def->mems[i], def, cfg, priv) < 0)
+ if (def->mems[i]->model == VIR_DOMAIN_MEMORY_MODEL_EGM)
+ continue;
+
+ if (qemuBuildMemoryDimmBackendStr(cmd, def->mems[i], def, cfg, priv, NULL) < 0)
return -1;
switch (def->mems[i]->model) {
@@ -7985,6 +8112,9 @@ qemuBuildMemoryDeviceCommandLine(virCommand *cmd,
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
break;
+ /* EGM memory backing is via memory-backend-file object */
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
+ break;
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
break;
diff --git a/src/qemu/qemu_domain.c b/src/qemu/qemu_domain.c
index ac56fc7cb4..14f2b3ec5d 100644
--- a/src/qemu/qemu_domain.c
+++ b/src/qemu/qemu_domain.c
@@ -7219,6 +7219,7 @@ qemuDomainUpdateMemoryDeviceInfo(virDomainObj *vm,
break;
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
break;
@@ -7453,7 +7454,8 @@ qemuDomainAlignMemorySizes(virDomainDef *def)
def->mems[i]->size = VIR_ROUND_UP(def->mems[i]->size, align);
}
- hotplugmem += def->mems[i]->size;
+ if (def->mems[i]->model != VIR_DOMAIN_MEMORY_MODEL_EGM)
+ hotplugmem += def->mems[i]->size;
if (def->mems[i]->size > maxmemkb) {
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
@@ -7941,6 +7943,12 @@ qemuDomainDefValidateMemoryHotplugDevice(const virDomainMemoryDef *mem,
virDomainMemoryModelTypeToString(mem->model));
return -1;
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
+ _("hotplug is not supported for the %1$s device"),
+ virDomainMemoryModelTypeToString(mem->model));
+ return -1;
+
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
return -1;
@@ -7999,6 +8007,7 @@ qemuDomainDefValidateMemoryHotplug(const virDomainDef *def,
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_MEM:
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
case VIR_DOMAIN_MEMORY_MODEL_NONE:
break;
@@ -8046,6 +8055,8 @@ qemuDomainDefValidateMemoryHotplug(const virDomainDef *def,
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
/* sgx epc memory does not support hotplug, skip this check */
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
+ /* egm memory does not support hotplug, skip this check */
case VIR_DOMAIN_MEMORY_MODEL_LAST:
case VIR_DOMAIN_MEMORY_MODEL_NONE:
break;
diff --git a/src/qemu/qemu_domain_address.c b/src/qemu/qemu_domain_address.c
index 7233df888c..97e533bf9a 100644
--- a/src/qemu/qemu_domain_address.c
+++ b/src/qemu/qemu_domain_address.c
@@ -3124,6 +3124,7 @@ qemuDomainAssignMemoryDeviceSlot(virDomainObj *vm,
return qemuDomainEnsureVirtioAddress(&releaseaddr, vm, &dev);
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
break;
@@ -3151,6 +3152,7 @@ qemuDomainReleaseMemoryDeviceSlot(virDomainObj *vm,
break;
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
break;
@@ -3185,6 +3187,7 @@ qemuDomainAssignMemorySlots(virDomainDef *def)
/* handled in qemuDomainAssignPCIAddresses() */
break;
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
break;
diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c
index f2e024dae3..e0ee056b92 100644
--- a/src/qemu/qemu_driver.c
+++ b/src/qemu/qemu_driver.c
@@ -6712,6 +6712,7 @@ qemuDomainAttachMemoryConfig(virDomainDef *vmdef,
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_MEM:
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
break;
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
diff --git a/src/qemu/qemu_hotplug.c b/src/qemu/qemu_hotplug.c
index fb426deb1a..890bb052b6 100644
--- a/src/qemu/qemu_hotplug.c
+++ b/src/qemu/qemu_hotplug.c
@@ -7348,6 +7348,7 @@ qemuDomainChangeMemoryLiveValidateChange(const virDomainMemoryDef *oldDef,
case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
_("cannot modify memory of model '%1$s'"),
diff --git a/src/qemu/qemu_monitor_json.c b/src/qemu/qemu_monitor_json.c
index 494d7ef515..081f0cedfd 100644
--- a/src/qemu/qemu_monitor_json.c
+++ b/src/qemu/qemu_monitor_json.c
@@ -7213,6 +7213,7 @@ qemuMonitorJSONGetMemoryDeviceInfo(qemuMonitor *mon,
switch ((virDomainMemoryModel) model) {
case VIR_DOMAIN_MEMORY_MODEL_DIMM:
case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_MEM:
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
/* While 'id' attribute is marked as optional in QEMU's QAPI
diff --git a/src/qemu/qemu_postparse.c b/src/qemu/qemu_postparse.c
index dc5ade829a..3c2867edb3 100644
--- a/src/qemu/qemu_postparse.c
+++ b/src/qemu/qemu_postparse.c
@@ -1839,6 +1839,7 @@ qemuDomainDefNumaAutoAdd(virDomainDef *def,
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
break;
}
diff --git a/src/qemu/qemu_process.c b/src/qemu/qemu_process.c
index 0e50cd1ccc..d451c12dd0 100644
--- a/src/qemu/qemu_process.c
+++ b/src/qemu/qemu_process.c
@@ -4156,6 +4156,7 @@ qemuProcessDomainMemoryDefNeedHugepagesPath(const virDomainMemoryDef *mem,
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_MEM:
pagesize = mem->source.virtio_mem.pagesize;
break;
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
@@ -4245,6 +4246,7 @@ qemuProcessNeedMemoryBackingPath(virDomainDef *def,
case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
case VIR_DOMAIN_MEMORY_MODEL_VIRTIO_PMEM:
case VIR_DOMAIN_MEMORY_MODEL_SGX_EPC:
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
/* Backed by user provided path. Not stored in memory
* backing dir anyway. */
diff --git a/src/qemu/qemu_validate.c b/src/qemu/qemu_validate.c
index da08fd17cd..e026e2fdd0 100644
--- a/src/qemu/qemu_validate.c
+++ b/src/qemu/qemu_validate.c
@@ -5861,6 +5861,12 @@ qemuValidateDomainDeviceDefMemory(const virDomainMemoryDef *mem,
break;
+ case VIR_DOMAIN_MEMORY_MODEL_EGM:
+ if (!virQEMUCapsGet(qemuCaps, QEMU_CAPS_DEVICE_ACPI_EGM_MEMORY)) {
+ virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
+ _("ACPI EGM memory device is not supported with this QEMU binary"));
+ return -1;
+ }
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
break;
--
2.43.0
On Tue, Nov 25, 2025 at 11:17:03 -0800, Nathan Chen via Devel wrote:
> Add qemu CLI support for EGM memory device model:
> - Specify EGM device path to memory-backend-file object
> - Support acpi-egm-memory object with id, pci-dev, and
> node attributes
> - Consolidate all acpi-egm-memory objects' memory into
> a single memory-backend-file per EGM chardev
> specified.
>
> Signed-off-by: Ian May <ianm@nvidia.com>
> Signed-off-by: Nathan Chen <nathanc@nvidia.com>
> ---
> src/qemu/qemu_alias.c | 7 +-
> src/qemu/qemu_capabilities.c | 2 +
> src/qemu/qemu_capabilities.h | 1 +
> src/qemu/qemu_command.c | 158 ++++++++++++++++++++++++++++++---
> src/qemu/qemu_domain.c | 13 ++-
> src/qemu/qemu_domain_address.c | 3 +
> src/qemu/qemu_driver.c | 1 +
> src/qemu/qemu_hotplug.c | 1 +
> src/qemu/qemu_monitor_json.c | 1 +
> src/qemu/qemu_postparse.c | 1 +
> src/qemu/qemu_process.c | 2 +
> src/qemu/qemu_validate.c | 6 ++
> 12 files changed, 180 insertions(+), 16 deletions(-)
Note that I'm replying to this patch just due to the issue with qemu
capabilities. This is not a full review.
[...]
> diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h
> index f180844e66..3eb12235f4 100644
> --- a/src/qemu/qemu_capabilities.h
> +++ b/src/qemu/qemu_capabilities.h
> @@ -730,6 +730,7 @@ typedef enum { /* virQEMUCapsFlags grouping marker for syntax-check */
> QEMU_CAPS_DISK_TIMED_STATS, /* timed stats support ('stats-intervals' property of disk frontends) */
> QEMU_CAPS_QUERY_ACCELERATORS, /* query-accelerators command */
> QEMU_CAPS_MSHV, /* -accel mshv */
> + QEMU_CAPS_DEVICE_ACPI_EGM_MEMORY, /* For using extended GPU memory */
>
> QEMU_CAPS_LAST /* this must always be the last item */
> } virQEMUCapsFlags;
For any further postin please separate the addition of the capability
along with the detection and any change to the detected capabilities
into a separate patch.
> diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
> index b69fe23236..33848aa781 100644
> --- a/src/qemu/qemu_command.c
> +++ b/src/qemu/qemu_command.c
> @@ -3573,12 +3593,17 @@ qemuBuildMemoryDimmBackendStr(virCommand *cmd,
> virDomainMemoryDef *mem,
> virDomainDef *def,
> virQEMUDriverConfig *cfg,
> - qemuDomainObjPrivate *priv)
> + qemuDomainObjPrivate *priv,
> + GHashTable *egmBackends)
> {
> g_autoptr(virJSONValue) props = NULL;
> g_autoptr(virJSONValue) tcProps = NULL;
> virBitmap *nodemask = NULL;
> g_autofree char *alias = NULL;
> + unsigned long long originalSize = 0;
> + bool isEGM = (mem->model == VIR_DOMAIN_MEMORY_MODEL_EGM);
> + bool shouldCreateBackend = true;
> + qemuEGMBackendInfo *egmInfo = NULL;
>
> if (!mem->info.alias) {
> virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> @@ -3588,19 +3613,65 @@ qemuBuildMemoryDimmBackendStr(virCommand *cmd,
>
> alias = g_strdup_printf("mem%s", mem->info.alias);
>
> - if (qemuBuildMemoryBackendProps(&props, alias, cfg, priv,
> - def, mem, true, false, &nodemask) < 0)
> - return -1;
> + /* Handle EGM shared backend logic */
> + if (isEGM && egmBackends) {
> + const char *egmPath = mem->source.egm.path;
> + egmInfo = g_hash_table_lookup(egmBackends, egmPath);
>
> - if (qemuBuildThreadContextProps(&tcProps, &props, def, priv, nodemask) < 0)
> - return -1;
> + if (egmInfo) {
> + alias = g_strdup(egmInfo->alias);
> + if (egmInfo->created) {
> + /* Backend already created, skip backend creation */
> + shouldCreateBackend = false;
> + } else {
> + /* First device for this path - temporarily use accumulated size */
> + originalSize = mem->size;
> + mem->size = egmInfo->totalSize;
> + egmInfo->created = true;
> + }
> + }
> + }
>
> - if (tcProps &&
> - qemuBuildObjectCommandlineFromJSON(cmd, tcProps) < 0)
> - return -1;
> + if (shouldCreateBackend) {
> + /* Use existing function unchanged */
> + if (qemuBuildMemoryBackendProps(&props, alias, cfg, priv,
> + def, mem, true, false, &nodemask) < 0) {
> + if (originalSize > 0)
> + mem->size = originalSize; /* Restore on error */
> + return -1;
> + }
>
> - if (qemuBuildObjectCommandlineFromJSON(cmd, props) < 0)
> - return -1;
> + /* Restore original size after backend props are built */
> + if (originalSize > 0)
> + mem->size = originalSize;
> +
> + if (qemuBuildThreadContextProps(&tcProps, &props, def, priv, nodemask) < 0)
> + return -1;
> +
> + if (tcProps &&
> + qemuBuildObjectCommandlineFromJSON(cmd, tcProps) < 0)
> + return -1;
> +
> + if (qemuBuildObjectCommandlineFromJSON(cmd, props) < 0)
> + return -1;
> + }
> +
> + if (isEGM) {
> + g_autofree char *egmObjStr = NULL;
> + g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
> +
> + virBufferAsprintf(&buf, "acpi-egm-memory,id=%s", mem->info.alias);
> +
> + if (mem->target.egm.pciDev)
> + virBufferAsprintf(&buf, ",pci-dev=%s", mem->target.egm.pciDev);
> +
> + if (mem->targetNode >= 0)
> + virBufferAsprintf(&buf, ",node=%d", mem->targetNode);
> +
> + egmObjStr = virBufferContentAndReset(&buf);
> +
> + virCommandAddArgList(cmd, "-object", egmObjStr, NULL);
> + }
>
> return 0;
> }
[...]
> @@ -7835,6 +7910,37 @@ qemuBuildNumaCommandLine(virQEMUDriverConfig *cfg,
> hmat = true;
> }
>
> + /* Pre-scan EGM devices to group by path and calculate total sizes */
> + egmBackends = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
> + (GDestroyNotify)qemuEGMBackendInfoFree);
For creating hash tables use virHashNew, which uses our hashing
function.
> +
> + for (i = 0; i < def->nmems; i++) {
> + if (def->mems[i]->model == VIR_DOMAIN_MEMORY_MODEL_EGM) {
> + const char *egmPath = def->mems[i]->source.egm.path;
> + qemuEGMBackendInfo *info = g_hash_table_lookup(egmBackends, egmPath);
> +
> + if (!info) {
> + info = g_new0(qemuEGMBackendInfo, 1);
> + info->alias = g_strdup_printf("memegm%zu", egmBackendCount);
> + egmBackendCount++;
> + info->totalSize = def->mems[i]->size;
> + info->created = false;
> + info->firstMem = def->mems[i];
> + g_hash_table_insert(egmBackends, g_strdup(egmPath), info);
> + } else {
> + info->totalSize += def->mems[i]->size;
> + }
> + }
> + }
> +
> + /* Build the actual backend and device objects */
> + for (i = 0; i < def->nmems; i++) {
> + if (def->mems[i]->model == VIR_DOMAIN_MEMORY_MODEL_EGM) {
> + if (qemuBuildMemoryDimmBackendStr(cmd, def->mems[i], def, cfg, priv, egmBackends) < 0)
> + goto cleanup;
> + }
> + }
> +
> nodeBackends = g_new0(virJSONValue *, ncells);
> nodemask = g_new0(virBitmap *, ncells);
>
> @@ -7870,8 +7976,18 @@ qemuBuildNumaCommandLine(virQEMUDriverConfig *cfg,
> for (i = 0; i < ncells; i++) {
> ssize_t initiator = virDomainNumaGetNodeInitiator(def->numa, i);
> unsigned long long memSize = virDomainNumaGetNodeMemorySize(def->numa, i);
> + bool egmBacked = false;
> + size_t k;
> +
> + for (k = 0; k < def->nmems; k++) {
> + if (def->mems[k]->model == VIR_DOMAIN_MEMORY_MODEL_EGM &&
> + def->mems[k]->targetNode == (int)i) {
> + egmBacked = true;
> + break;
> + }
> + }
>
> - if (needBackend && memSize > 0) {
> + if (needBackend && memSize > 0 && !egmBacked) {
> g_autoptr(virJSONValue) tcProps = NULL;
>
> if (qemuBuildThreadContextProps(&tcProps, &nodeBackends[i],
> @@ -7901,7 +8017,15 @@ qemuBuildNumaCommandLine(virQEMUDriverConfig *cfg,
>
> if (memSize > 0) {
> if (needBackend) {
> - virBufferAsprintf(&buf, ",memdev=ram-node%zu", i);
> + if (egmBacked) {
> + /* Look up the actual backend alias for EGM */
> + const char *egmPath = def->mems[k]->source.egm.path;
> + qemuEGMBackendInfo *egmInfo = g_hash_table_lookup(egmBackends, egmPath);
> + const char *backendAlias = egmInfo ? egmInfo->alias : def->mems[k]->info.alias;
> + virBufferAsprintf(&buf, ",memdev=%s", backendAlias);
> + } else {
> + virBufferAsprintf(&buf, ",memdev=ram-node%zu", i);
> + }
> } else {
> virBufferAsprintf(&buf, ",mem=%llu", memSize / 1024);
> }
Hmm 'qemuBuildMemoryDimmBackendStr' and 'qemuBuildNumaCommandLine' are
getting quite out of hand these patches (they're kind of a mess already
in the current state) and all of that to support some niche hardware.
Please consider refactoring the code first to simplify it. This will
be a nightmare to review otherwise.
On 11/25/2025 1:12 PM, Peter Krempa wrote:
> On Tue, Nov 25, 2025 at 11:17:03 -0800, Nathan Chen via Devel wrote:
>> Add qemu CLI support for EGM memory device model:
>> - Specify EGM device path to memory-backend-file object
>> - Support acpi-egm-memory object with id, pci-dev, and
>> node attributes
>> - Consolidate all acpi-egm-memory objects' memory into
>> a single memory-backend-file per EGM chardev
>> specified.
>>
>> Signed-off-by: Ian May<ianm@nvidia.com>
>> Signed-off-by: Nathan Chen<nathanc@nvidia.com>
>> ---
>> src/qemu/qemu_alias.c | 7 +-
>> src/qemu/qemu_capabilities.c | 2 +
>> src/qemu/qemu_capabilities.h | 1 +
>> src/qemu/qemu_command.c | 158 ++++++++++++++++++++++++++++++---
>> src/qemu/qemu_domain.c | 13 ++-
>> src/qemu/qemu_domain_address.c | 3 +
>> src/qemu/qemu_driver.c | 1 +
>> src/qemu/qemu_hotplug.c | 1 +
>> src/qemu/qemu_monitor_json.c | 1 +
>> src/qemu/qemu_postparse.c | 1 +
>> src/qemu/qemu_process.c | 2 +
>> src/qemu/qemu_validate.c | 6 ++
>> 12 files changed, 180 insertions(+), 16 deletions(-)
> Note that I'm replying to this patch just due to the issue with qemu
> capabilities. This is not a full review.
>
> [...]
>
>> diff --git a/src/qemu/qemu_capabilities.h b/src/qemu/qemu_capabilities.h
>> index f180844e66..3eb12235f4 100644
>> --- a/src/qemu/qemu_capabilities.h
>> +++ b/src/qemu/qemu_capabilities.h
>> @@ -730,6 +730,7 @@ typedef enum { /* virQEMUCapsFlags grouping marker for syntax-check */
>> QEMU_CAPS_DISK_TIMED_STATS, /* timed stats support ('stats-intervals' property of disk frontends) */
>> QEMU_CAPS_QUERY_ACCELERATORS, /* query-accelerators command */
>> QEMU_CAPS_MSHV, /* -accel mshv */
>> + QEMU_CAPS_DEVICE_ACPI_EGM_MEMORY, /* For using extended GPU memory */
>>
>> QEMU_CAPS_LAST /* this must always be the last item */
>> } virQEMUCapsFlags;
> For any further postin please separate the addition of the capability
> along with the detection and any change to the detected capabilities
> into a separate patch.
>
>
Ok, I will separate it out in the next revision.
>> diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
>> index b69fe23236..33848aa781 100644
>> --- a/src/qemu/qemu_command.c
>> +++ b/src/qemu/qemu_command.c
>> @@ -3573,12 +3593,17 @@ qemuBuildMemoryDimmBackendStr(virCommand *cmd,
>> virDomainMemoryDef *mem,
>> virDomainDef *def,
>> virQEMUDriverConfig *cfg,
>> - qemuDomainObjPrivate *priv)
>> + qemuDomainObjPrivate *priv,
>> + GHashTable *egmBackends)
>> {
>> g_autoptr(virJSONValue) props = NULL;
>> g_autoptr(virJSONValue) tcProps = NULL;
>> virBitmap *nodemask = NULL;
>> g_autofree char *alias = NULL;
>> + unsigned long long originalSize = 0;
>> + bool isEGM = (mem->model == VIR_DOMAIN_MEMORY_MODEL_EGM);
>> + bool shouldCreateBackend = true;
>> + qemuEGMBackendInfo *egmInfo = NULL;
>>
>> if (!mem->info.alias) {
>> virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
>> @@ -3588,19 +3613,65 @@ qemuBuildMemoryDimmBackendStr(virCommand *cmd,
>>
>> alias = g_strdup_printf("mem%s", mem->info.alias);
>>
>> - if (qemuBuildMemoryBackendProps(&props, alias, cfg, priv,
>> - def, mem, true, false, &nodemask) < 0)
>> - return -1;
>> + /* Handle EGM shared backend logic */
>> + if (isEGM && egmBackends) {
>> + const char *egmPath = mem->source.egm.path;
>> + egmInfo = g_hash_table_lookup(egmBackends, egmPath);
>>
>> - if (qemuBuildThreadContextProps(&tcProps, &props, def, priv, nodemask) < 0)
>> - return -1;
>> + if (egmInfo) {
>> + alias = g_strdup(egmInfo->alias);
>> + if (egmInfo->created) {
>> + /* Backend already created, skip backend creation */
>> + shouldCreateBackend = false;
>> + } else {
>> + /* First device for this path - temporarily use accumulated size */
>> + originalSize = mem->size;
>> + mem->size = egmInfo->totalSize;
>> + egmInfo->created = true;
>> + }
>> + }
>> + }
>>
>> - if (tcProps &&
>> - qemuBuildObjectCommandlineFromJSON(cmd, tcProps) < 0)
>> - return -1;
>> + if (shouldCreateBackend) {
>> + /* Use existing function unchanged */
>> + if (qemuBuildMemoryBackendProps(&props, alias, cfg, priv,
>> + def, mem, true, false, &nodemask) < 0) {
>> + if (originalSize > 0)
>> + mem->size = originalSize; /* Restore on error */
>> + return -1;
>> + }
>>
>> - if (qemuBuildObjectCommandlineFromJSON(cmd, props) < 0)
>> - return -1;
>> + /* Restore original size after backend props are built */
>> + if (originalSize > 0)
>> + mem->size = originalSize;
>> +
>> + if (qemuBuildThreadContextProps(&tcProps, &props, def, priv, nodemask) < 0)
>> + return -1;
>> +
>> + if (tcProps &&
>> + qemuBuildObjectCommandlineFromJSON(cmd, tcProps) < 0)
>> + return -1;
>> +
>> + if (qemuBuildObjectCommandlineFromJSON(cmd, props) < 0)
>> + return -1;
>> + }
>> +
>> + if (isEGM) {
>> + g_autofree char *egmObjStr = NULL;
>> + g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
>> +
>> + virBufferAsprintf(&buf, "acpi-egm-memory,id=%s", mem->info.alias);
>> +
>> + if (mem->target.egm.pciDev)
>> + virBufferAsprintf(&buf, ",pci-dev=%s", mem->target.egm.pciDev);
>> +
>> + if (mem->targetNode >= 0)
>> + virBufferAsprintf(&buf, ",node=%d", mem->targetNode);
>> +
>> + egmObjStr = virBufferContentAndReset(&buf);
>> +
>> + virCommandAddArgList(cmd, "-object", egmObjStr, NULL);
>> + }
>>
>> return 0;
>> }
> [...]
>
>> @@ -7835,6 +7910,37 @@ qemuBuildNumaCommandLine(virQEMUDriverConfig *cfg,
>> hmat = true;
>> }
>>
>> + /* Pre-scan EGM devices to group by path and calculate total sizes */
>> + egmBackends = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
>> + (GDestroyNotify)qemuEGMBackendInfoFree);
> For creating hash tables use virHashNew, which uses our hashing
> function.
>
Will do, thanks.
>> +
>> + for (i = 0; i < def->nmems; i++) {
>> + if (def->mems[i]->model == VIR_DOMAIN_MEMORY_MODEL_EGM) {
>> + const char *egmPath = def->mems[i]->source.egm.path;
>> + qemuEGMBackendInfo *info = g_hash_table_lookup(egmBackends, egmPath);
>> +
>> + if (!info) {
>> + info = g_new0(qemuEGMBackendInfo, 1);
>> + info->alias = g_strdup_printf("memegm%zu", egmBackendCount);
>> + egmBackendCount++;
>> + info->totalSize = def->mems[i]->size;
>> + info->created = false;
>> + info->firstMem = def->mems[i];
>> + g_hash_table_insert(egmBackends, g_strdup(egmPath), info);
>> + } else {
>> + info->totalSize += def->mems[i]->size;
>> + }
>> + }
>> + }
>> +
>> + /* Build the actual backend and device objects */
>> + for (i = 0; i < def->nmems; i++) {
>> + if (def->mems[i]->model == VIR_DOMAIN_MEMORY_MODEL_EGM) {
>> + if (qemuBuildMemoryDimmBackendStr(cmd, def->mems[i], def, cfg, priv, egmBackends) < 0)
>> + goto cleanup;
>> + }
>> + }
>> +
>> nodeBackends = g_new0(virJSONValue *, ncells);
>> nodemask = g_new0(virBitmap *, ncells);
>>
>> @@ -7870,8 +7976,18 @@ qemuBuildNumaCommandLine(virQEMUDriverConfig *cfg,
>> for (i = 0; i < ncells; i++) {
>> ssize_t initiator = virDomainNumaGetNodeInitiator(def->numa, i);
>> unsigned long long memSize = virDomainNumaGetNodeMemorySize(def->numa, i);
>> + bool egmBacked = false;
>> + size_t k;
>> +
>> + for (k = 0; k < def->nmems; k++) {
>> + if (def->mems[k]->model == VIR_DOMAIN_MEMORY_MODEL_EGM &&
>> + def->mems[k]->targetNode == (int)i) {
>> + egmBacked = true;
>> + break;
>> + }
>> + }
>>
>> - if (needBackend && memSize > 0) {
>> + if (needBackend && memSize > 0 && !egmBacked) {
>> g_autoptr(virJSONValue) tcProps = NULL;
>>
>> if (qemuBuildThreadContextProps(&tcProps, &nodeBackends[i],
>> @@ -7901,7 +8017,15 @@ qemuBuildNumaCommandLine(virQEMUDriverConfig *cfg,
>>
>> if (memSize > 0) {
>> if (needBackend) {
>> - virBufferAsprintf(&buf, ",memdev=ram-node%zu", i);
>> + if (egmBacked) {
>> + /* Look up the actual backend alias for EGM */
>> + const char *egmPath = def->mems[k]->source.egm.path;
>> + qemuEGMBackendInfo *egmInfo = g_hash_table_lookup(egmBackends, egmPath);
>> + const char *backendAlias = egmInfo ? egmInfo->alias : def->mems[k]->info.alias;
>> + virBufferAsprintf(&buf, ",memdev=%s", backendAlias);
>> + } else {
>> + virBufferAsprintf(&buf, ",memdev=ram-node%zu", i);
>> + }
>> } else {
>> virBufferAsprintf(&buf, ",mem=%llu", memSize / 1024);
>> }
> Hmm 'qemuBuildMemoryDimmBackendStr' and 'qemuBuildNumaCommandLine' are
> getting quite out of hand these patches (they're kind of a mess already
> in the current state) and all of that to support some niche hardware.
>
> Please consider refactoring the code first to simplify it. This will
> be a nightmare to review otherwise.
Ok, I will take some time to simplify the structure here in the next
revision, either separating out the logic from
qemuBuildMemoryDimmBackendStr and qemuBuildNumaCommandLine or making the
implementation simpler within these functions. Thanks for taking a
preliminary look at these!
-Nathan
© 2016 - 2025 Red Hat, Inc.