On Wed, Jul 9, 2025 at 4:11 PM Andrew Keesler <ankees...@google.com> wrote: > > Thanks to 72d277a7, 1ed2cb32, and others, EDID (Extended Display > Identification Data) is propagated by QEMU such that a virtual display > presents legitimate metadata (e.g., name, serial number, preferred > resolutions, etc.) to its connected guest. > > This change adds the ability to specify the EDID name for a particular > virtio-vga display. Previously, every virtual display would have the same > name: "QEMU Monitor". Now, we can inject names of displays in order to test > guest behavior that is specific to display names. We provide the ability to > inject the display name from the frontend since this is guest visible > data. Furthermore, this makes it clear where N potential display outputs > would get their name from (which will be added in a future change). > > Note that we have elected to use a struct here for output data for > extensibility - we intend to add per-output fields like resolution in a > future change. > > It should be noted that EDID names longer than 12 bytes will be truncated > per spec (I think?). > > Testing: verified that when I specified 2 outputs for a virtio-gpu with > edid_name set, the names matched those that I configured with my vnc > display. > > -display vnc=localhost:0,id=aaa,display=vga,head=0 \ > -display vnc=localhost:1,id=bbb,display=vga,head=1 \ > -device '{"driver":"virtio-vga", > "max_outputs":2, > "id":"vga", > "outputs":[ > { > "name":"AAA" > }, > { > "name":"BBB" > } > ]}' > > Signed-off-by: Andrew Keesler <ankees...@google.com>
Reviewed-by: Marc-André Lureau <marcandre.lur...@redhat.com> > --- > hw/core/qdev-properties-system.c | 44 +++++++++++++++++++++++++++++ > hw/display/virtio-gpu-base.c | 27 ++++++++++++++++++ > include/hw/display/edid.h | 2 ++ > include/hw/qdev-properties-system.h | 5 ++++ > include/hw/virtio/virtio-gpu.h | 3 ++ > qapi/virtio.json | 18 ++++++++++-- > 6 files changed, 97 insertions(+), 2 deletions(-) > > diff --git a/hw/core/qdev-properties-system.c > b/hw/core/qdev-properties-system.c > index 24e145d870..1f810b7ddf 100644 > --- a/hw/core/qdev-properties-system.c > +++ b/hw/core/qdev-properties-system.c > @@ -1299,3 +1299,47 @@ const PropertyInfo > qdev_prop_vmapple_virtio_blk_variant = { > .set = qdev_propinfo_set_enum, > .set_default_value = qdev_propinfo_set_default_value_enum, > }; > + > +/* --- VirtIOGPUOutputList --- */ > + > +static void get_virtio_gpu_output_list(Object *obj, Visitor *v, > + const char *name, void *opaque, Error **errp) > +{ > + VirtIOGPUOutputList **prop_ptr = > + object_field_prop_ptr(obj, opaque); > + > + visit_type_VirtIOGPUOutputList(v, name, prop_ptr, errp); > +} > + > +static void set_virtio_gpu_output_list(Object *obj, Visitor *v, > + const char *name, void *opaque, Error **errp) > +{ > + VirtIOGPUOutputList **prop_ptr = > + object_field_prop_ptr(obj, opaque); > + VirtIOGPUOutputList *list; > + > + if (!visit_type_VirtIOGPUOutputList(v, name, &list, errp)) { > + return; > + } > + > + qapi_free_VirtIOGPUOutputList(*prop_ptr); > + *prop_ptr = list; > +} > + > +static void release_virtio_gpu_output_list(Object *obj, > + const char *name, void *opaque) > +{ > + VirtIOGPUOutputList **prop_ptr = > + object_field_prop_ptr(obj, opaque); > + > + qapi_free_VirtIOGPUOutputList(*prop_ptr); > + *prop_ptr = NULL; > +} > + > +const PropertyInfo qdev_prop_virtio_gpu_output_list = { > + .type = "VirtIOGPUOutputList", > + .description = "VirtIO GPU output list [{\"name\":\"<name>\"},...]", > + .get = get_virtio_gpu_output_list, > + .set = set_virtio_gpu_output_list, > + .release = release_virtio_gpu_output_list, > +}; > diff --git a/hw/display/virtio-gpu-base.c b/hw/display/virtio-gpu-base.c > index 9eb806b71f..7269477a1c 100644 > --- a/hw/display/virtio-gpu-base.c > +++ b/hw/display/virtio-gpu-base.c > @@ -19,6 +19,7 @@ > #include "qemu/error-report.h" > #include "hw/display/edid.h" > #include "trace.h" > +#include "qapi/qapi-types-virtio.h" > > void > virtio_gpu_base_reset(VirtIOGPUBase *g) > @@ -56,6 +57,8 @@ void > virtio_gpu_base_generate_edid(VirtIOGPUBase *g, int scanout, > struct virtio_gpu_resp_edid *edid) > { > + size_t output_idx; > + VirtIOGPUOutputList *node; > qemu_edid_info info = { > .width_mm = g->req_state[scanout].width_mm, > .height_mm = g->req_state[scanout].height_mm, > @@ -64,6 +67,14 @@ virtio_gpu_base_generate_edid(VirtIOGPUBase *g, int > scanout, > .refresh_rate = g->req_state[scanout].refresh_rate, > }; > > + for (output_idx = 0, node = g->conf.outputs; > + output_idx <= scanout && node; output_idx++, node = node->next) { > + if (output_idx == scanout && node->value && node->value->name) { > + info.name = node->value->name; > + break; > + } > + } > + > edid->size = cpu_to_le32(sizeof(edid->edid)); > qemu_edid_generate(edid->edid, sizeof(edid->edid), &info); > } > @@ -172,6 +183,8 @@ virtio_gpu_base_device_realize(DeviceState *qdev, > VirtIOHandleOutput cursor_cb, > Error **errp) > { > + size_t output_idx; > + VirtIOGPUOutputList *node; > VirtIODevice *vdev = VIRTIO_DEVICE(qdev); > VirtIOGPUBase *g = VIRTIO_GPU_BASE(qdev); > int i; > @@ -181,6 +194,20 @@ virtio_gpu_base_device_realize(DeviceState *qdev, > return false; > } > > + for (output_idx = 0, node = g->conf.outputs; > + node; output_idx++, node = node->next) { > + if (output_idx == g->conf.max_outputs) { > + error_setg(errp, "invalid outputs > %d", g->conf.max_outputs); > + return false; > + } > + if (node->value && node->value->name && > + strlen(node->value->name) > EDID_NAME_MAX_LENGTH) { > + error_setg(errp, "invalid output name '%s' > %d", > + node->value->name, EDID_NAME_MAX_LENGTH); > + return false; > + } > + } > + > if (virtio_gpu_virgl_enabled(g->conf)) { > error_setg(&g->migration_blocker, "virgl is not yet migratable"); > if (migrate_add_blocker(&g->migration_blocker, errp) < 0) { > diff --git a/include/hw/display/edid.h b/include/hw/display/edid.h > index 520f8ec202..91c0a428af 100644 > --- a/include/hw/display/edid.h > +++ b/include/hw/display/edid.h > @@ -1,6 +1,8 @@ > #ifndef EDID_H > #define EDID_H > > +#define EDID_NAME_MAX_LENGTH 12 > + > typedef struct qemu_edid_info { > const char *vendor; /* http://www.uefi.org/pnp_id_list */ > const char *name; > diff --git a/include/hw/qdev-properties-system.h > b/include/hw/qdev-properties-system.h > index b921392c52..9601a11a09 100644 > --- a/include/hw/qdev-properties-system.h > +++ b/include/hw/qdev-properties-system.h > @@ -32,6 +32,7 @@ extern const PropertyInfo qdev_prop_cpus390entitlement; > extern const PropertyInfo qdev_prop_iothread_vq_mapping_list; > extern const PropertyInfo qdev_prop_endian_mode; > extern const PropertyInfo qdev_prop_vmapple_virtio_blk_variant; > +extern const PropertyInfo qdev_prop_virtio_gpu_output_list; > > #define DEFINE_PROP_PCI_DEVFN(_n, _s, _f, _d) \ > DEFINE_PROP_SIGNED(_n, _s, _f, _d, qdev_prop_pci_devfn, int32_t) > @@ -110,4 +111,8 @@ extern const PropertyInfo > qdev_prop_vmapple_virtio_blk_variant; > qdev_prop_vmapple_virtio_blk_variant, \ > VMAppleVirtioBlkVariant) > > +#define DEFINE_PROP_VIRTIO_GPU_OUTPUT_LIST(_name, _state, _field) \ > + DEFINE_PROP(_name, _state, _field, qdev_prop_virtio_gpu_output_list, \ > + VirtIOGPUOutputList *) > + > #endif > diff --git a/include/hw/virtio/virtio-gpu.h b/include/hw/virtio/virtio-gpu.h > index a42957c4e2..9f16f89a36 100644 > --- a/include/hw/virtio/virtio-gpu.h > +++ b/include/hw/virtio/virtio-gpu.h > @@ -20,6 +20,7 @@ > #include "hw/virtio/virtio.h" > #include "qemu/log.h" > #include "system/vhost-user-backend.h" > +#include "qapi/qapi-types-virtio.h" > > #include "standard-headers/linux/virtio_gpu.h" > #include "standard-headers/linux/virtio_ids.h" > @@ -128,6 +129,7 @@ struct virtio_gpu_base_conf { > uint32_t xres; > uint32_t yres; > uint64_t hostmem; > + VirtIOGPUOutputList *outputs; > }; > > struct virtio_gpu_ctrl_command { > @@ -167,6 +169,7 @@ struct VirtIOGPUBaseClass { > > #define VIRTIO_GPU_BASE_PROPERTIES(_state, _conf) \ > DEFINE_PROP_UINT32("max_outputs", _state, _conf.max_outputs, 1), \ > + DEFINE_PROP_VIRTIO_GPU_OUTPUT_LIST("outputs", _state, _conf.outputs), \ > DEFINE_PROP_BIT("edid", _state, _conf.flags, \ > VIRTIO_GPU_FLAG_EDID_ENABLED, true), \ > DEFINE_PROP_UINT32("xres", _state, _conf.xres, 1280), \ > diff --git a/qapi/virtio.json b/qapi/virtio.json > index 73df718a26..5e658a7033 100644 > --- a/qapi/virtio.json > +++ b/qapi/virtio.json > @@ -963,17 +963,31 @@ > { 'struct': 'IOThreadVirtQueueMapping', > 'data': { 'iothread': 'str', '*vqs': ['uint16'] } } > > +## > +# @VirtIOGPUOutput: > +# > +# Describes configuration of a VirtIO GPU output. > +# > +# @name: the name of the output > +# > +# Since: 10.1 > +## > + > +{ 'struct': 'VirtIOGPUOutput', > + 'data': { 'name': 'str' } } > + > ## > # @DummyVirtioForceArrays: > # > # Not used by QMP; hack to let us use IOThreadVirtQueueMappingList > -# internally > +# and VirtIOGPUOutputList internally > # > # Since: 9.0 > ## > > { 'struct': 'DummyVirtioForceArrays', > - 'data': { 'unused-iothread-vq-mapping': ['IOThreadVirtQueueMapping'] } } > + 'data': { 'unused-iothread-vq-mapping': ['IOThreadVirtQueueMapping'], > + 'unused-virtio-gpu-output': ['VirtIOGPUOutput'] } } > > ## > # @GranuleMode: > -- > 2.50.0.727.gbf7dc18ff4-goog > -- Marc-André Lureau