Hi all,
This the RFC I'm preparing to send to upstream QEMU for initial RFC review. I didn't see much precedence for Signed-off-by lines in cover letter so instead I'm thinking to explicitely CC Bill and Alex and anyone else who would like to be on copy, just let me know.
Changes from v1: * Move VIRTIO_MSG_VENDOR_ID to virtio-msg.c * Update to match recent spec changes (token + set/get_vqueue padding) * Add endian conversion of dev_num and msg_size * Add instructions for running on GPEX PCI x86 microVM and ARM virt * Add missing spsc_queue.h
-------------------
This adds virtio-msg, a new virtio transport. Virtio-msg works by exchanging messages over a bus and doesn't rely on trapping and emulating making it a good fit for a number of applications such as AMP, real-time and safety applications.
Together with the new transport, this series adds a PCI device that implements an AMP setup much like it would look if two SoC's would use virtio-msg across a PCI link.
Current limitations: We only support a single device per bus (dev_num = 0). Shared memory queue layout likely to change in the future. Temporarily uses PCI Vendor Xilinx / Device 0x9039. Missing documentation.
The virtio-msg spec: https://github.com/Linaro/virtio-msg-spec/
QEMU with these patches: https://github.com/edgarigl/qemu/tree/edgar/virtio-msg-rfc
Linux with virtio-msg suppport: https://github.com/edgarigl/linux/tree/edgari/virtio-msg-6.17
To try it, first build Linux with the following enabled: CONFIG_VIRTIO_MSG=y CONFIG_VIRTIO_MSG_AMP=y CONFIG_VIRTIO_MSG_AMP_PCI=y
Boot linux in QEMU with a virtio-msg-amp-pci device, in this example with a virtio-net device attached to it:
x86/q35 machine: -device virtio-msg-amp-pci -device virtio-net-device,netdev=n1,bus=/q35-pcihost/pcie.0/virtio-msg-amp-pci/vmsg.0 -netdev user,id=n1
x86/microvm or ARM virt machines: -device virtio-msg-amp-pci -device virtio-net-device,netdev=n1,bus=/gpex-pcihost/pcie.0/virtio-msg-amp-pci/vmsg.0/virtio-msg/virtio-msg-proxy-bus.0 -netdev user,id=n1
Cheers, Edgar
Edgar E. Iglesias (4): virtio: Introduce notify_queue virtio: Add virtio_queue_get_rings virtio: Add the virtio-msg transport virtio-msg-bus: amp-pci: Add generic AMP PCI device
hw/misc/Kconfig | 7 + hw/misc/meson.build | 1 + hw/misc/virtio-msg-amp-pci.c | 324 ++++++++++++ hw/virtio/Kconfig | 4 + hw/virtio/meson.build | 5 + hw/virtio/virtio-msg-bus.c | 89 ++++ hw/virtio/virtio-msg.c | 598 ++++++++++++++++++++++ hw/virtio/virtio.c | 23 + include/hw/virtio/spsc_queue.h | 213 ++++++++ include/hw/virtio/virtio-bus.h | 1 + include/hw/virtio/virtio-msg-bus.h | 95 ++++ include/hw/virtio/virtio-msg-prot.h | 749 ++++++++++++++++++++++++++++ include/hw/virtio/virtio-msg.h | 45 ++ include/hw/virtio/virtio.h | 2 + 14 files changed, 2156 insertions(+) create mode 100644 hw/misc/virtio-msg-amp-pci.c create mode 100644 hw/virtio/virtio-msg-bus.c create mode 100644 hw/virtio/virtio-msg.c create mode 100644 include/hw/virtio/spsc_queue.h create mode 100644 include/hw/virtio/virtio-msg-bus.h create mode 100644 include/hw/virtio/virtio-msg-prot.h create mode 100644 include/hw/virtio/virtio-msg.h
Signed-off-by: Edgar E. Iglesias edgar.iglesias@amd.com --- hw/virtio/virtio.c | 7 +++++++ include/hw/virtio/virtio-bus.h | 1 + 2 files changed, 8 insertions(+)
diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c index 153ee0a0cf..8a53fb5f93 100644 --- a/hw/virtio/virtio.c +++ b/hw/virtio/virtio.c @@ -2700,12 +2700,19 @@ static void virtio_irq(VirtQueue *vq)
void virtio_notify(VirtIODevice *vdev, VirtQueue *vq) { + BusState *qbus = qdev_get_parent_bus(DEVICE(vdev)); + VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); + WITH_RCU_READ_LOCK_GUARD() { if (!virtio_should_notify(vdev, vq)) { return; } }
+ if (k->notify_queue) { + k->notify_queue(qbus->parent, virtio_get_queue_index(vq)); + } + trace_virtio_notify(vdev, vq); virtio_irq(vq); } diff --git a/include/hw/virtio/virtio-bus.h b/include/hw/virtio/virtio-bus.h index 7ab8c9dab0..043dbeb4cf 100644 --- a/include/hw/virtio/virtio-bus.h +++ b/include/hw/virtio/virtio-bus.h @@ -39,6 +39,7 @@ DECLARE_OBJ_CHECKERS(VirtioBusState, VirtioBusClass, struct VirtioBusClass { /* This is what a VirtioBus must implement */ BusClass parent; + void (*notify_queue)(DeviceState *d, uint16_t index); void (*notify)(DeviceState *d, uint16_t vector); void (*save_config)(DeviceState *d, QEMUFile *f); void (*save_queue)(DeviceState *d, int n, QEMUFile *f);
Signed-off-by: Edgar E. Iglesias edgar.iglesias@amd.com --- hw/virtio/virtio.c | 16 ++++++++++++++++ include/hw/virtio/virtio.h | 2 ++ 2 files changed, 18 insertions(+)
diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c index 8a53fb5f93..fe7c635390 100644 --- a/hw/virtio/virtio.c +++ b/hw/virtio/virtio.c @@ -2379,6 +2379,22 @@ void virtio_queue_set_rings(VirtIODevice *vdev, int n, hwaddr desc, virtio_init_region_cache(vdev, n); }
+void virtio_queue_get_rings(VirtIODevice *vdev, int n, hwaddr *desc, + hwaddr *avail, hwaddr *used) +{ + assert(vdev->vq[n].vring.num); + + if (desc) { + *desc = vdev->vq[n].vring.desc; + } + if (avail) { + *avail = vdev->vq[n].vring.avail; + } + if (used) { + *used = vdev->vq[n].vring.used; + } +} + void virtio_queue_set_num(VirtIODevice *vdev, int n, int num) { /* Don't allow guest to flip queue between existent and diff --git a/include/hw/virtio/virtio.h b/include/hw/virtio/virtio.h index d97529c3f1..8bceb115a3 100644 --- a/include/hw/virtio/virtio.h +++ b/include/hw/virtio/virtio.h @@ -361,6 +361,8 @@ int virtio_queue_get_max_num(VirtIODevice *vdev, int n); int virtio_get_num_queues(VirtIODevice *vdev); void virtio_queue_set_rings(VirtIODevice *vdev, int n, hwaddr desc, hwaddr avail, hwaddr used); +void virtio_queue_get_rings(VirtIODevice *vdev, int n, hwaddr *desc, + hwaddr *avail, hwaddr *used); void virtio_queue_update_rings(VirtIODevice *vdev, int n); void virtio_init_region_cache(VirtIODevice *vdev, int n); void virtio_queue_set_align(VirtIODevice *vdev, int n, int align);
Add the new virtio-msg transport together with a framework for virtio-msg buses.
Signed-off-by: Edgar E. Iglesias edgar.iglesias@amd.com --- hw/virtio/Kconfig | 4 + hw/virtio/meson.build | 5 + hw/virtio/virtio-msg-bus.c | 89 ++++ hw/virtio/virtio-msg.c | 598 ++++++++++++++++++++++ include/hw/virtio/virtio-msg-bus.h | 95 ++++ include/hw/virtio/virtio-msg-prot.h | 749 ++++++++++++++++++++++++++++ include/hw/virtio/virtio-msg.h | 45 ++ 7 files changed, 1585 insertions(+) create mode 100644 hw/virtio/virtio-msg-bus.c create mode 100644 hw/virtio/virtio-msg.c create mode 100644 include/hw/virtio/virtio-msg-bus.h create mode 100644 include/hw/virtio/virtio-msg-prot.h create mode 100644 include/hw/virtio/virtio-msg.h
diff --git a/hw/virtio/Kconfig b/hw/virtio/Kconfig index 10f5c53ac0..d2d8c475b2 100644 --- a/hw/virtio/Kconfig +++ b/hw/virtio/Kconfig @@ -26,6 +26,10 @@ config VIRTIO_MMIO bool select VIRTIO
+config VIRTIO_MSG + bool + select VIRTIO + config VIRTIO_CCW bool select VIRTIO diff --git a/hw/virtio/meson.build b/hw/virtio/meson.build index affd66887d..7944bbb652 100644 --- a/hw/virtio/meson.build +++ b/hw/virtio/meson.build @@ -13,6 +13,11 @@ specific_virtio_ss = ss.source_set() specific_virtio_ss.add(files('virtio.c')) specific_virtio_ss.add(files('virtio-qmp.c'))
+specific_virtio_ss.add(when: 'CONFIG_VIRTIO_MSG', if_true: files( + 'virtio-msg.c', + 'virtio-msg-bus.c', +)) + if have_vhost system_virtio_ss.add(files('vhost.c')) system_virtio_ss.add(files('vhost-backend.c', 'vhost-iova-tree.c')) diff --git a/hw/virtio/virtio-msg-bus.c b/hw/virtio/virtio-msg-bus.c new file mode 100644 index 0000000000..e907fd64ec --- /dev/null +++ b/hw/virtio/virtio-msg-bus.c @@ -0,0 +1,89 @@ +/* + * VirtIO MSG bus. + * + * Copyright (c) 2024 Advanced Micro Devices, Inc. + * Written by Edgar E. Iglesias edgar.iglesias@amd.com + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "hw/virtio/virtio-msg-bus.h" + +bool virtio_msg_bus_connect(BusState *bus, + const VirtIOMSGBusPort *port, + void *opaque) +{ + VirtIOMSGBusDevice *bd = virtio_msg_bus_get_device(bus); + + if (!bd) { + /* Nothing connected to this virtio-msg device. Ignore. */ + return false; + } + + bd->peer = port; + bd->opaque = opaque; + return true; +} + +void virtio_msg_bus_process(VirtIOMSGBusDevice *bd) +{ + VirtIOMSGBusDeviceClass *bdc; + bdc = VIRTIO_MSG_BUS_DEVICE_CLASS(object_get_class(OBJECT(bd))); + + bdc->process(bd); +} + +int virtio_msg_bus_send(BusState *bus, VirtIOMSG *msg_req) +{ + VirtIOMSGBusDevice *bd = virtio_msg_bus_get_device(bus); + VirtIOMSGBusDeviceClass *bdc; + int r = VIRTIO_MSG_NO_ERROR; + + bdc = VIRTIO_MSG_BUS_DEVICE_CLASS(object_get_class(OBJECT(bd))); + + if (bdc->send) { + r = bdc->send(bd, msg_req); + } + return r; +} + +static void virtio_msg_bus_class_init(ObjectClass *klass, const void *data) +{ + BusClass *bc = BUS_CLASS(klass); + + bc->max_dev = 1; +} + +static const TypeInfo virtio_msg_bus_info = { + .name = TYPE_VIRTIO_MSG_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(BusState), + .class_init = virtio_msg_bus_class_init, +}; + +static void virtio_msg_bus_device_class_init(ObjectClass *klass, + const void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + + k->bus_type = TYPE_VIRTIO_MSG_BUS; +} + +static const TypeInfo virtio_msg_bus_device_type_info = { + .name = TYPE_VIRTIO_MSG_BUS_DEVICE, + .parent = TYPE_DEVICE, + .instance_size = sizeof(VirtIOMSGBusDevice), + .abstract = true, + .class_size = sizeof(VirtIOMSGBusDeviceClass), + .class_init = virtio_msg_bus_device_class_init, +}; + +static void virtio_msg_bus_register_types(void) +{ + type_register_static(&virtio_msg_bus_info); + type_register_static(&virtio_msg_bus_device_type_info); +} + +type_init(virtio_msg_bus_register_types) diff --git a/hw/virtio/virtio-msg.c b/hw/virtio/virtio-msg.c new file mode 100644 index 0000000000..33a37fd7f7 --- /dev/null +++ b/hw/virtio/virtio-msg.c @@ -0,0 +1,598 @@ +/* + * Virtio MSG bindings + * + * Copyright (c) 2024 Advanced Micro Devices, Inc. + * Written by Edgar E. Iglesias edgar.iglesias@amd.com. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "hw/irq.h" +#include "hw/qdev-properties.h" +#include "hw/virtio/virtio.h" +#include "hw/virtio/virtio-msg-bus.h" +#include "hw/virtio/virtio-msg.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "trace.h" + +#define VIRTIO_MSG_VENDOR_ID 0x554D4551 /* 'QEMU' */ + +static bool virtio_msg_bad(VirtIOMSGProxy *s, VirtIOMSG *msg) +{ + bool drop = false; + size_t min_size; + unsigned int n; + + min_size = virtio_msg_header_size(); + switch (msg->msg_id) { + case VIRTIO_MSG_GET_DEVICE_STATUS: + case VIRTIO_MSG_DEVICE_INFO: + break; + case VIRTIO_MSG_GET_FEATURES: + min_size += sizeof msg->get_features; + break; + case VIRTIO_MSG_SET_FEATURES: + n = msg->set_features.num; + + /* We expect at least one feature block. */ + if (n == 0 || n > VIRTIO_MSG_MAX_FEATURE_NUM) { + drop = true; + break; + } + + min_size += sizeof msg->set_features + n * 4; + break; + case VIRTIO_MSG_GET_CONFIG: + min_size += sizeof msg->get_config; + break; + case VIRTIO_MSG_SET_CONFIG: + if (msg->set_config.size > VIRTIO_MSG_MAX_CONFIG_BYTES) { + drop = true; + break; + } + + min_size += sizeof msg->set_config + msg->set_config.size; + break; + case VIRTIO_MSG_SET_DEVICE_STATUS: + min_size += sizeof msg->set_device_status; + break; + case VIRTIO_MSG_GET_VQUEUE: + min_size += sizeof msg->get_vqueue; + break; + case VIRTIO_MSG_SET_VQUEUE: + min_size += sizeof msg->set_vqueue; + break; + case VIRTIO_MSG_RESET_VQUEUE: + min_size += sizeof msg->reset_vqueue; + break; + case VIRTIO_MSG_EVENT_AVAIL: + min_size += sizeof msg->event_avail; + break; + default: + /* Unexpected message. */ + drop = true; + break; + } + + /* Accept large messages allowing future backwards compatible extensions. */ + if (drop || + msg->msg_size < min_size || msg->msg_size > VIRTIO_MSG_MAX_SIZE) { + return true; + } + + return false; +} + +static void virtio_msg_device_info(VirtIOMSGProxy *s, + VirtIOMSG *msg) +{ + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + uint32_t device_id = 0; + VirtIOMSG msg_resp; + + if (vdev) { + device_id = vdev->device_id; + } else { + error_report("%s: No virtio device on bus %s!", + __func__, BUS(&s->bus)->name); + } + + virtio_msg_pack_get_device_info_resp(&msg_resp, device_id, + VIRTIO_MSG_VENDOR_ID, + /* Feature bits */ + 64, + vdev->config_len, + VIRTIO_QUEUE_MAX, + 0, 0); + virtio_msg_bus_send(&s->msg_bus, &msg_resp); +} + +static void virtio_msg_get_features(VirtIOMSGProxy *s, + VirtIOMSG *msg) +{ + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev); + VirtIOMSG msg_resp; + uint32_t index = msg->get_features.index; + uint32_t f[VIRTIO_MSG_MAX_FEATURE_NUM] = { 0 }; + uint32_t num = msg->get_features.num; + uint64_t features; + + features = vdev->host_features & ~vdc->legacy_features; + + /* We only have 64 feature bits. If driver asks for more, return zeros */ + if (index < 2) { + features >>= index * 32; + f[0] = features; + f[1] = features >> 32; + } + + /* If index is out of bounds, we respond with num=0, f=0. */ + virtio_msg_pack_get_features_resp(&msg_resp, index, num, f); + virtio_msg_bus_send(&s->msg_bus, &msg_resp); +} + +static void virtio_msg_set_features(VirtIOMSGProxy *s, + VirtIOMSG *msg) +{ + VirtIOMSG msg_resp; + unsigned int i; + + for (i = 0; i < msg->set_features.num; i++) { + unsigned int feature_index = i + msg->set_features.index; + + /* We only support up to 64bits */ + if (feature_index >= 2) { + break; + } + + s->guest_features = deposit64(s->guest_features, + feature_index * 32, 32, + msg->set_features.b32[i]); + } + + virtio_msg_pack_set_features_resp(&msg_resp); + virtio_msg_bus_send(&s->msg_bus, &msg_resp); +} + +static void virtio_msg_soft_reset(VirtIOMSGProxy *s) +{ + virtio_bus_reset(&s->bus); + s->guest_features = 0; +} + +static void virtio_msg_set_device_status(VirtIOMSGProxy *s, + VirtIOMSG *msg) +{ + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + uint32_t status = msg->set_device_status.status; + VirtIOMSG msg_resp; + + if (!(status & VIRTIO_CONFIG_S_DRIVER_OK)) { + virtio_bus_stop_ioeventfd(&s->bus); + } + + if (status & VIRTIO_CONFIG_S_FEATURES_OK) { + virtio_set_features(vdev, s->guest_features); + } + + virtio_set_status(vdev, status); + assert(vdev->status == status); + + if (status & VIRTIO_CONFIG_S_DRIVER_OK) { + virtio_bus_start_ioeventfd(&s->bus); + } + + if (status == 0) { + virtio_msg_soft_reset(s); + } + + virtio_msg_pack_set_device_status_resp(&msg_resp, vdev->status); + virtio_msg_bus_send(&s->msg_bus, &msg_resp); +} + +static void virtio_msg_get_device_status(VirtIOMSGProxy *s, + VirtIOMSG *msg) +{ + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + VirtIOMSG msg_resp; + + virtio_msg_pack_get_device_status_resp(&msg_resp, vdev->status); + virtio_msg_bus_send(&s->msg_bus, &msg_resp); +} + +static void virtio_msg_get_config(VirtIOMSGProxy *s, + VirtIOMSG *msg) +{ + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + uint32_t size = msg->get_config.size; + uint32_t offset = msg->get_config.offset; + uint8_t data[VIRTIO_MSG_MAX_CONFIG_BYTES]; + VirtIOMSG msg_resp; + unsigned int i; + + if (size > VIRTIO_MSG_MAX_CONFIG_BYTES) { + return; + } + + for (i = 0; i < size; i++) { + data[i] = virtio_config_modern_readb(vdev, offset + i); + } + + virtio_msg_pack_get_config_resp(&msg_resp, size, offset, + vdev->generation, data); + virtio_msg_bus_send(&s->msg_bus, &msg_resp); +} + +static void virtio_msg_set_config(VirtIOMSGProxy *s, + VirtIOMSG *msg) +{ + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + uint32_t offset = msg->set_config.offset; + uint32_t size = msg->set_config.size; + uint8_t *data = msg->set_config.data; + VirtIOMSG msg_resp; + unsigned int i; + + for (i = 0; i < size; i++) { + virtio_config_modern_writeb(vdev, offset + i, data[i]); + } + + virtio_msg_pack_set_config_resp(&msg_resp, size, offset, + vdev->generation, data); + virtio_msg_bus_send(&s->msg_bus, &msg_resp); +} + +static void virtio_msg_get_vqueue(VirtIOMSGProxy *s, + VirtIOMSG *msg) +{ + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + uint32_t max_size = VIRTQUEUE_MAX_SIZE; + uint32_t index = msg->get_vqueue.index; + hwaddr desc, avail, used; + VirtIOMSG msg_resp; + uint32_t size; + + if (index < VIRTIO_QUEUE_MAX) { + size = virtio_queue_get_num(vdev, index); + if (!size) { + max_size = 0; + } + + virtio_queue_get_rings(vdev, index, &desc, &avail, &used); + virtio_msg_pack_get_vqueue_resp(&msg_resp, index, max_size, size, + desc, avail, used); + } else { + /* OOB index, respond with all zeroes. */ + virtio_msg_pack_get_vqueue_resp(&msg_resp, index, 0, 0, 0, 0, 0); + } + + virtio_msg_bus_send(&s->msg_bus, &msg_resp); +} + +static void virtio_msg_set_vqueue(VirtIOMSGProxy *s, VirtIOMSG *msg) +{ + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + uint32_t index = msg->set_vqueue.index; + VirtIOMSG msg_resp; + + if (index >= VIRTIO_QUEUE_MAX) { + /* OOB index, ignore. */ + return; + } + + virtio_queue_set_num(vdev, index, msg->set_vqueue.size); + virtio_queue_set_rings(vdev, index, + msg->set_vqueue.descriptor_addr, + msg->set_vqueue.driver_addr, + msg->set_vqueue.device_addr); + virtio_queue_enable(vdev, index); + + virtio_msg_pack_set_vqueue_resp(&msg_resp); + virtio_msg_bus_send(&s->msg_bus, &msg_resp); +} + +static void virtio_msg_reset_vqueue(VirtIOMSGProxy *s, VirtIOMSG *msg) +{ + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + VirtIOMSG msg_resp; + + virtio_queue_reset(vdev, msg->reset_vqueue.index); + + virtio_msg_pack_reset_vqueue_resp(&msg_resp); + virtio_msg_bus_send(&s->msg_bus, &msg_resp); +} + +static void virtio_msg_event_avail(VirtIOMSGProxy *s, + VirtIOMSG *msg) +{ + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + uint16_t vq_idx = msg->event_avail.index; + + if (!(vdev->status & VIRTIO_CONFIG_S_DRIVER_OK)) { + VirtIOMSG msg_ev; + + virtio_error(vdev, "Notification while driver not OK?"); + virtio_msg_pack_event_config(&msg_ev, vdev->status, vdev->generation, + 0, 0, NULL); + virtio_msg_bus_send(&s->msg_bus, &msg_ev); + return; + } + + if (vq_idx >= VIRTIO_QUEUE_MAX) { + virtio_error(vdev, "Notification to bad VQ!"); + return; + } + + if (!virtio_queue_get_num(vdev, vq_idx)) { + virtio_error(vdev, "Notification to unconfigured VQ!"); + return; + } + + /* NOTIFICATION_DATA doesn't exist in QEMU 8.2.7. if (0) it */ + if (virtio_vdev_has_feature(vdev, VIRTIO_F_NOTIFICATION_DATA) && 0) { + VirtQueue *vq = virtio_get_queue(vdev, vq_idx); + uint32_t next_offset_wrap = msg->event_avail.next_offset_wrap; + uint16_t qsize = virtio_queue_get_num(vdev, vq_idx); + uint32_t offset = next_offset_wrap & 0x7fffffff; + bool wrap = next_offset_wrap & 0x80000000; + uint16_t data; + + if (offset > 0x7fff || offset >= qsize) { + virtio_error(vdev, "Next offset to large!"); + /* Bail out without notification??? */ + return; + } + + data = wrap << 15; + data |= offset & 0x7fff; + + virtio_queue_set_shadow_avail_idx(vq, data); + } + virtio_queue_notify(vdev, msg->event_avail.index); +} + +typedef void (*VirtIOMSGHandler)(VirtIOMSGProxy *s, + VirtIOMSG *msg); + +static const VirtIOMSGHandler msg_handlers[] = { + [VIRTIO_MSG_DEVICE_INFO] = virtio_msg_device_info, + [VIRTIO_MSG_GET_FEATURES] = virtio_msg_get_features, + [VIRTIO_MSG_SET_FEATURES] = virtio_msg_set_features, + [VIRTIO_MSG_GET_DEVICE_STATUS] = virtio_msg_get_device_status, + [VIRTIO_MSG_SET_DEVICE_STATUS] = virtio_msg_set_device_status, + [VIRTIO_MSG_GET_CONFIG] = virtio_msg_get_config, + [VIRTIO_MSG_SET_CONFIG] = virtio_msg_set_config, + [VIRTIO_MSG_GET_VQUEUE] = virtio_msg_get_vqueue, + [VIRTIO_MSG_SET_VQUEUE] = virtio_msg_set_vqueue, + [VIRTIO_MSG_RESET_VQUEUE] = virtio_msg_reset_vqueue, + [VIRTIO_MSG_EVENT_AVAIL] = virtio_msg_event_avail, +}; + +static int virtio_msg_receive_msg(VirtIOMSGBusDevice *bd, VirtIOMSG *msg) +{ + VirtIOMSGProxy *s = VIRTIO_MSG(bd->opaque); + VirtIOMSGHandler handler; + + /* virtio_msg_print(msg); */ + if (msg->msg_id > ARRAY_SIZE(msg_handlers)) { + return VIRTIO_MSG_ERROR_UNSUPPORTED_MESSAGE_ID; + } + + handler = msg_handlers[msg->msg_id]; + assert((msg->type & VIRTIO_MSG_TYPE_RESPONSE) == 0); + + if (virtio_msg_bad(s, msg)) { + /* Drop bad messages. */ + return VIRTIO_MSG_ERROR_BAD_MESSAGE; + } + + if (handler) { + handler(s, msg); + } + + return VIRTIO_MSG_NO_ERROR; +} + +static const VirtIOMSGBusPort virtio_msg_port = { + .receive = virtio_msg_receive_msg, + .is_driver = false +}; + +static void virtio_msg_notify_queue(DeviceState *opaque, uint16_t index) +{ + VirtIOMSGProxy *s = VIRTIO_MSG(opaque); + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + VirtIOMSG msg; + + if (!vdev || !virtio_msg_bus_connected(&s->msg_bus)) { + return; + } + + virtio_msg_pack_event_used(&msg, index); + virtio_msg_bus_send(&s->msg_bus, &msg); +} + +static void virtio_msg_notify(DeviceState *opaque, uint16_t vector) +{ + VirtIOMSGProxy *s = VIRTIO_MSG(opaque); + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + VirtIOMSG msg; + + if (!virtio_msg_bus_connected(&s->msg_bus)) { + return; + } + + /* Check if we're notifying for VQ or CONFIG updates. */ + if (vdev->isr & 2) { + virtio_msg_pack_event_config(&msg, vdev->status, vdev->generation, + 0, 0, NULL); + virtio_msg_bus_send(&s->msg_bus, &msg); + } +} + +static const VMStateDescription vmstate_virtio_msg_state_sub = { + .name = "virtio_msg_device", + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT64(guest_features, VirtIOMSGProxy), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_virtio_msg = { + .name = "virtio_msg_proxy_backend", + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_END_OF_LIST() + }, + .subsections = (const VMStateDescription * []) { + &vmstate_virtio_msg_state_sub, + NULL + } +}; + +static void virtio_msg_save_extra_state(DeviceState *opaque, QEMUFile *f) +{ + VirtIOMSGProxy *s = VIRTIO_MSG(opaque); + + vmstate_save_state(f, &vmstate_virtio_msg, s, NULL, &error_fatal); +} + +static int virtio_msg_load_extra_state(DeviceState *opaque, QEMUFile *f) +{ + VirtIOMSGProxy *s = VIRTIO_MSG(opaque); + + return vmstate_load_state(f, &vmstate_virtio_msg, s, 1, &error_fatal); +} + +static bool virtio_msg_has_extra_state(DeviceState *opaque) +{ + return true; +} + +static void virtio_msg_reset_hold(Object *obj, ResetType type) +{ + VirtIOMSGProxy *s = VIRTIO_MSG(obj); + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + + virtio_msg_soft_reset(s); + + /* Only connect transports with virtio-devs. */ + if (vdev) { + bool r; + + r = virtio_msg_bus_connect(&s->msg_bus, &virtio_msg_port, s); + if (!r) { + error_report("%s: No bus connected!", + object_get_canonical_path(obj)); + exit(EXIT_FAILURE); + } + } +} + +static void virtio_msg_pre_plugged(DeviceState *d, Error **errp) +{ + VirtIOMSGProxy *s = VIRTIO_MSG(d); + VirtIODevice *vdev = virtio_bus_get_device(&s->bus); + + virtio_add_feature(&vdev->host_features, VIRTIO_F_VERSION_1); +} + +static AddressSpace *virtio_msg_get_dma_as(DeviceState *d) +{ + VirtIOMSGProxy *s = VIRTIO_MSG(d); + AddressSpace *as; + + as = virtio_msg_bus_get_remote_as(&s->msg_bus); + return as; +} + +static void virtio_msg_realize(DeviceState *d, Error **errp) +{ + VirtIOMSGProxy *s = VIRTIO_MSG(d); + + qbus_init(&s->bus, sizeof(s->bus), + TYPE_VIRTIO_MSG_PROXY_BUS, d, NULL); + qbus_init(&s->msg_bus, sizeof(s->msg_bus), + TYPE_VIRTIO_MSG_BUS, d, NULL); +} + +static void virtio_msg_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + + dc->realize = virtio_msg_realize; + dc->bus_type = TYPE_BUS; + dc->user_creatable = true; + rc->phases.hold = virtio_msg_reset_hold; + + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo virtio_msg_types[] = { + { + .name = TYPE_VIRTIO_MSG, + .parent = TYPE_DEVICE, + .instance_size = sizeof(VirtIOMSGProxy), + .class_init = virtio_msg_class_init, + }, +}; +DEFINE_TYPES(virtio_msg_types); + +static char *virtio_msg_bus_get_dev_path(DeviceState *dev) +{ + BusState *virtio_msg_bus; + VirtIOMSGProxy *virtio_msg_proxy; + char *proxy_path; + + virtio_msg_bus = qdev_get_parent_bus(dev); + virtio_msg_proxy = VIRTIO_MSG(virtio_msg_bus->parent); + proxy_path = qdev_get_dev_path(DEVICE(virtio_msg_proxy)); + + if (!proxy_path) { + const char *path = object_get_canonical_path(OBJECT(virtio_msg_proxy)); + proxy_path = strdup(path); + } + return proxy_path; +} + +static void virtio_msg_bus_class_init(ObjectClass *klass, const void *data) +{ + BusClass *bus_class = BUS_CLASS(klass); + VirtioBusClass *k = VIRTIO_BUS_CLASS(klass); + + k->notify_queue = virtio_msg_notify_queue; + k->notify = virtio_msg_notify; + k->save_extra_state = virtio_msg_save_extra_state; + k->load_extra_state = virtio_msg_load_extra_state; + k->has_extra_state = virtio_msg_has_extra_state; + k->pre_plugged = virtio_msg_pre_plugged; + k->has_variable_vring_alignment = true; + k->get_dma_as = virtio_msg_get_dma_as; + bus_class->max_dev = 1; + bus_class->get_dev_path = virtio_msg_bus_get_dev_path; +} + +static const TypeInfo virtio_msg_bus_types[] = { + { + .name = TYPE_VIRTIO_MSG_PROXY_BUS, + .parent = TYPE_VIRTIO_BUS, + .instance_size = sizeof(VirtioBusState), + .class_init = virtio_msg_bus_class_init, + }, + { + .name = TYPE_VIRTIO_MSG_TP_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(BusState), + }, +}; + +DEFINE_TYPES(virtio_msg_bus_types); diff --git a/include/hw/virtio/virtio-msg-bus.h b/include/hw/virtio/virtio-msg-bus.h new file mode 100644 index 0000000000..12ef8030f4 --- /dev/null +++ b/include/hw/virtio/virtio-msg-bus.h @@ -0,0 +1,95 @@ +/* + * VirtIO MSG bus. + * + * Copyright (c) 2024 Advanced Micro Devices, Inc. + * Written by Edgar E. Iglesias edgar.iglesias@amd.com + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef QEMU_VIRTIO_MSG_BUS_H +#define QEMU_VIRTIO_MSG_BUS_H + +#include "qom/object.h" +#include "system/dma.h" +#include "hw/qdev-core.h" +#include "hw/virtio/virtio-msg-prot.h" + +#define TYPE_VIRTIO_MSG_BUS "virtio-msg-bus" +DECLARE_INSTANCE_CHECKER(BusState, VIRTIO_MSG_BUS, + TYPE_VIRTIO_MSG_BUS) + + +#define TYPE_VIRTIO_MSG_BUS_DEVICE "virtio-msg-bus-device" +OBJECT_DECLARE_TYPE(VirtIOMSGBusDevice, VirtIOMSGBusDeviceClass, + VIRTIO_MSG_BUS_DEVICE) + +typedef struct VirtIOMSGBusPort { + int (*receive)(VirtIOMSGBusDevice *bd, VirtIOMSG *msg); + bool is_driver; +} VirtIOMSGBusPort; + +struct VirtIOMSGBusDeviceClass { + /*< private >*/ + DeviceClass parent_class; + + DeviceRealize parent_realize; + DeviceUnrealize parent_unrealize; + + /* + * Ask the bus to receive and process all messages that + * are readily available. The bus will call the registered + * VirtIOMSGBusPort.receive() function for each message. + * + * Will return immediately if no messages are available. + */ + void (*process)(VirtIOMSGBusDevice *bd); + + /* + * Called by the transport to send a message. + */ + int (*send)(VirtIOMSGBusDevice *bd, VirtIOMSG *msg_req); +}; + +typedef struct VirtIOMSGBusDevice { + DeviceState parent; + + const VirtIOMSGBusPort *peer; + void *opaque; +} VirtIOMSGBusDevice; + +static inline VirtIOMSGBusDevice *virtio_msg_bus_get_device(BusState *qbus) +{ + BusChild *kid = QTAILQ_FIRST(&qbus->children); + DeviceState *qdev = kid ? kid->child : NULL; + + return (VirtIOMSGBusDevice *)qdev; +} + +static inline bool virtio_msg_bus_connected(BusState *bus) +{ + VirtIOMSGBusDevice *bd = virtio_msg_bus_get_device(bus); + + return bd && bd->peer != NULL; +} + +void virtio_msg_bus_process(VirtIOMSGBusDevice *bd); + +bool virtio_msg_bus_connect(BusState *bus, + const VirtIOMSGBusPort *port, + void *opaque); + +static inline void +virtio_msg_bus_receive(VirtIOMSGBusDevice *bd, VirtIOMSG *msg) +{ + virtio_msg_unpack(msg); + bd->peer->receive(bd, msg); +} + +int virtio_msg_bus_send(BusState *bus, VirtIOMSG *msg_req); + +static inline AddressSpace *virtio_msg_bus_get_remote_as(BusState *bus) +{ + return &address_space_memory; +} +#endif diff --git a/include/hw/virtio/virtio-msg-prot.h b/include/hw/virtio/virtio-msg-prot.h new file mode 100644 index 0000000000..a200716374 --- /dev/null +++ b/include/hw/virtio/virtio-msg-prot.h @@ -0,0 +1,749 @@ +/* + * Virtio MSG - Message packing/unpacking functions. + * + * Copyright (c) 2024 Advanced Micro Devices, Inc. + * Written by Edgar E. Iglesias edgar.iglesias@amd.com. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef QEMU_VIRTIO_MSG_H +#define QEMU_VIRTIO_MSG_H + +#include <stdint.h> +#include "standard-headers/linux/virtio_config.h" + +enum { + VIRTIO_MSG_NO_ERROR = 0, + VIRTIO_MSG_ERROR_RETRY, + VIRTIO_MSG_ERROR_TIMEOUT, + VIRTIO_MSG_ERROR_UNSUPPORTED_MESSAGE_ID, + VIRTIO_MSG_ERROR_BAD_MESSAGE, + VIRTIO_MSG_ERROR_MEMORY, /* General memory error. */ +}; + +enum { + VIRTIO_MSG_DEVICE_INFO = 0x02, + VIRTIO_MSG_GET_FEATURES = 0x03, + VIRTIO_MSG_SET_FEATURES = 0x04, + VIRTIO_MSG_GET_CONFIG = 0x05, + VIRTIO_MSG_SET_CONFIG = 0x06, + VIRTIO_MSG_GET_DEVICE_STATUS = 0x07, + VIRTIO_MSG_SET_DEVICE_STATUS = 0x08, + VIRTIO_MSG_GET_VQUEUE = 0x09, + VIRTIO_MSG_SET_VQUEUE = 0x0a, + VIRTIO_MSG_RESET_VQUEUE = 0x0b, + VIRTIO_MSG_GET_SHM = 0x0c, /* Not yet supported */ + VIRTIO_MSG_EVENT_CONFIG = 0x40, + VIRTIO_MSG_EVENT_AVAIL = 0x41, + VIRTIO_MSG_EVENT_USED = 0x42, + + VIRTIO_MSG_MAX = VIRTIO_MSG_EVENT_USED, +}; + +#define VIRTIO_MSG_MAX_SIZE 50 + +#define VIRTIO_MSG_TYPE_RESPONSE (1 << 0) +#define VIRTIO_MSG_TYPE_BUS (1 << 1) + +typedef struct VirtIOMSG { + uint8_t type; + uint8_t msg_id; + uint16_t dev_num; + uint16_t token; + uint16_t msg_size; + + union { + uint8_t payload_u8[38]; + + struct { + uint32_t device_id; + uint32_t vendor_id; + uint32_t num_feature_bits; + uint32_t config_size; + uint32_t max_vqs; + uint16_t admin_vq_idx; + uint16_t admin_vq_count; + } QEMU_PACKED get_device_info_resp; + struct { + uint32_t index; + uint32_t num; + } QEMU_PACKED get_features; + struct { + uint32_t index; + uint32_t num; + uint32_t b32[]; + } QEMU_PACKED get_features_resp; + struct { + uint32_t index; + uint32_t num; + uint32_t b32[]; + } QEMU_PACKED set_features; + struct { + uint32_t offset; + uint32_t size; + } QEMU_PACKED get_config; + struct { + uint32_t generation; + uint32_t offset; + uint32_t size; + uint8_t data[]; + } QEMU_PACKED get_config_resp; + struct { + uint32_t generation; + uint32_t offset; + uint32_t size; + uint8_t data[]; + } QEMU_PACKED set_config; + struct { + uint32_t generation; + uint32_t offset; + uint32_t size; + uint8_t data[]; + } QEMU_PACKED set_config_resp; + struct { + uint32_t status; + } QEMU_PACKED get_device_status_resp; + struct { + uint32_t status; + } QEMU_PACKED set_device_status; + struct { + uint32_t status; + } QEMU_PACKED set_device_status_resp; + struct { + uint32_t index; + } QEMU_PACKED get_vqueue; + struct { + uint32_t index; + uint32_t max_size; + uint32_t size; + uint32_t reserved; + uint64_t descriptor_addr; + uint64_t driver_addr; + uint64_t device_addr; + } QEMU_PACKED get_vqueue_resp; + struct { + uint32_t index; + uint32_t unused; + uint32_t size; + uint32_t reserved; + uint64_t descriptor_addr; + uint64_t driver_addr; + uint64_t device_addr; + } QEMU_PACKED set_vqueue; + struct { + uint32_t index; + } QEMU_PACKED reset_vqueue; + struct { + uint32_t status; + uint32_t generation; + uint32_t offset; + uint32_t size; + uint8_t config_value[]; + } QEMU_PACKED event_config; + struct { + uint32_t index; + uint32_t next_offset_wrap; + } QEMU_PACKED event_avail; + struct { + uint32_t index; + } QEMU_PACKED event_used; + }; +} QEMU_PACKED VirtIOMSG; + +/* Maximum number of 32b feature-blocks in a single message. */ +#define VIRTIO_MSG_MAX_FEATURE_NUM \ + ((VIRTIO_MSG_MAX_SIZE - offsetof(VirtIOMSG, get_features_resp.b32)) / 4) + +/* Maximum amount of config-data in a single message, in bytes. */ +#define VIRTIO_MSG_MAX_CONFIG_BYTES \ + (VIRTIO_MSG_MAX_SIZE - offsetof(VirtIOMSG, set_config.data)) + +#define LE_TO_CPU(v) \ +{ \ + if (sizeof(v) == 2) { \ + v = le16_to_cpu(v); \ + } else if (sizeof(v) == 4) { \ + v = le32_to_cpu(v); \ + } else if (sizeof(v) == 8) { \ + v = le64_to_cpu(v); \ + } else { \ + g_assert_not_reached(); \ + } \ +} + +/** + * virtio_msg_unpack_resp: Unpacks a wire virtio message responses into + * a host version + * @msg: the virtio message to unpack + * + * See virtio_msg_unpack(). + */ +static inline void virtio_msg_unpack_resp(VirtIOMSG *msg) +{ + int i; + + switch (msg->msg_id) { + case VIRTIO_MSG_DEVICE_INFO: + LE_TO_CPU(msg->get_device_info_resp.device_id); + LE_TO_CPU(msg->get_device_info_resp.vendor_id); + LE_TO_CPU(msg->get_device_info_resp.num_feature_bits); + LE_TO_CPU(msg->get_device_info_resp.config_size); + LE_TO_CPU(msg->get_device_info_resp.max_vqs); + LE_TO_CPU(msg->get_device_info_resp.admin_vq_idx); + LE_TO_CPU(msg->get_device_info_resp.admin_vq_count); + break; + case VIRTIO_MSG_GET_FEATURES: + LE_TO_CPU(msg->get_features_resp.index); + LE_TO_CPU(msg->get_features_resp.num); + for (i = 0; i < VIRTIO_MSG_MAX_FEATURE_NUM && + i < msg->get_features_resp.num; i++) { + LE_TO_CPU(msg->get_features_resp.b32[i]); + } + break; + case VIRTIO_MSG_GET_DEVICE_STATUS: + LE_TO_CPU(msg->get_device_status_resp.status); + break; + case VIRTIO_MSG_GET_CONFIG: + LE_TO_CPU(msg->get_config_resp.generation); + LE_TO_CPU(msg->get_config_resp.offset); + LE_TO_CPU(msg->get_config_resp.size); + break; + case VIRTIO_MSG_SET_CONFIG: + LE_TO_CPU(msg->set_config_resp.generation); + LE_TO_CPU(msg->set_config_resp.offset); + LE_TO_CPU(msg->set_config_resp.size); + break; + case VIRTIO_MSG_GET_VQUEUE: + LE_TO_CPU(msg->get_vqueue_resp.index); + LE_TO_CPU(msg->get_vqueue_resp.max_size); + LE_TO_CPU(msg->get_vqueue_resp.size); + LE_TO_CPU(msg->get_vqueue_resp.descriptor_addr); + LE_TO_CPU(msg->get_vqueue_resp.driver_addr); + LE_TO_CPU(msg->get_vqueue_resp.device_addr); + break; + default: + break; + } +} + +/** + * virtio_msg_unpack: Unpacks a wire virtio message into a host version + * @msg: the virtio message to unpack + * + * Virtio messages arriving on the virtio message bus have a specific + * format (little-endian, packet encoding, etc). To simplify for the + * the rest of the implementation, we have packers and unpackers that + * convert the wire messages into host versions. This includes endianess + * conversion and potentially future decoding and expansion. + * + * At the moment, we only do endian conversion, virtio_msg_unpack() should + * get completely eliminated by the compiler when targetting little-endian + * hosts. + */ +static inline void virtio_msg_unpack(VirtIOMSG *msg) +{ + int i; + + LE_TO_CPU(msg->dev_num); + LE_TO_CPU(msg->token); + LE_TO_CPU(msg->msg_size); + + if (msg->type & VIRTIO_MSG_TYPE_RESPONSE) { + virtio_msg_unpack_resp(msg); + return; + } + + switch (msg->msg_id) { + case VIRTIO_MSG_GET_FEATURES: + LE_TO_CPU(msg->get_features.index); + LE_TO_CPU(msg->get_features.num); + break; + case VIRTIO_MSG_SET_FEATURES: + LE_TO_CPU(msg->set_features.index); + LE_TO_CPU(msg->set_features.num); + for (i = 0; i < VIRTIO_MSG_MAX_FEATURE_NUM && + i < msg->set_features.num; i++) { + LE_TO_CPU(msg->set_features.b32[i]); + } + break; + case VIRTIO_MSG_SET_DEVICE_STATUS: + LE_TO_CPU(msg->set_device_status.status); + break; + case VIRTIO_MSG_GET_CONFIG: + LE_TO_CPU(msg->get_config.offset); + LE_TO_CPU(msg->get_config.size); + break; + case VIRTIO_MSG_SET_CONFIG: + LE_TO_CPU(msg->set_config.generation); + LE_TO_CPU(msg->set_config.offset); + LE_TO_CPU(msg->set_config.size); + break; + case VIRTIO_MSG_GET_VQUEUE: + LE_TO_CPU(msg->get_vqueue.index); + break; + case VIRTIO_MSG_SET_VQUEUE: + LE_TO_CPU(msg->set_vqueue.index); + LE_TO_CPU(msg->set_vqueue.size); + LE_TO_CPU(msg->set_vqueue.descriptor_addr); + LE_TO_CPU(msg->set_vqueue.driver_addr); + LE_TO_CPU(msg->set_vqueue.device_addr); + break; + case VIRTIO_MSG_RESET_VQUEUE: + LE_TO_CPU(msg->reset_vqueue.index); + break; + case VIRTIO_MSG_EVENT_CONFIG: + LE_TO_CPU(msg->event_config.status); + LE_TO_CPU(msg->event_config.generation); + LE_TO_CPU(msg->event_config.offset); + LE_TO_CPU(msg->event_config.size); + break; + case VIRTIO_MSG_EVENT_AVAIL: + LE_TO_CPU(msg->event_avail.index); + LE_TO_CPU(msg->event_avail.next_offset_wrap); + break; + case VIRTIO_MSG_EVENT_USED: + LE_TO_CPU(msg->event_used.index); + break; + default: + break; + } +} + +static inline size_t virtio_msg_header_size(void) +{ + return offsetof(VirtIOMSG, payload_u8); +} + +static inline void virtio_msg_pack_header(VirtIOMSG *msg, + uint8_t msg_id, + uint8_t type, + uint16_t dev_num, + uint16_t payload_size) +{ + uint16_t msg_size = virtio_msg_header_size() + payload_size; + + msg->type = type; + msg->msg_id = msg_id; + msg->dev_num = cpu_to_le16(dev_num); + msg->msg_size = cpu_to_le16(msg_size); + + /* Keep things predictable. */ + memset(msg->payload_u8, 0, sizeof msg->payload_u8); +} + +static inline void virtio_msg_pack_get_device_info(VirtIOMSG *msg) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_DEVICE_INFO, 0, 0, 0); +} + +static inline void virtio_msg_pack_get_device_info_resp(VirtIOMSG *msg, + uint32_t dev_num, + uint32_t vendor_id, + uint32_t num_feature_bits, + uint32_t config_size, + uint32_t max_vqs, + uint16_t admin_vq_idx, + uint16_t admin_vq_count) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_DEVICE_INFO, + VIRTIO_MSG_TYPE_RESPONSE, 0, + sizeof msg->get_device_info_resp); + + msg->get_device_info_resp.device_id = cpu_to_le32(dev_num); + msg->get_device_info_resp.vendor_id = cpu_to_le32(vendor_id); + msg->get_device_info_resp.num_feature_bits = cpu_to_le32(num_feature_bits); + msg->get_device_info_resp.config_size = cpu_to_le32(config_size); + msg->get_device_info_resp.max_vqs = cpu_to_le32(max_vqs); + msg->get_device_info_resp.admin_vq_idx = cpu_to_le16(admin_vq_idx); + msg->get_device_info_resp.admin_vq_count = cpu_to_le16(admin_vq_count); +} + +static inline void virtio_msg_pack_get_features(VirtIOMSG *msg, + uint32_t index, + uint32_t num) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_GET_FEATURES, 0, 0, + sizeof msg->get_features); + + msg->get_features.index = cpu_to_le32(index); + msg->get_features.num = cpu_to_le32(num); +} + +static inline void virtio_msg_pack_get_features_resp(VirtIOMSG *msg, + uint32_t index, + uint32_t num, + uint32_t *f) +{ + unsigned int i; + + virtio_msg_pack_header(msg, VIRTIO_MSG_GET_FEATURES, + VIRTIO_MSG_TYPE_RESPONSE, 0, + sizeof msg->get_features_resp + num * sizeof(*f)); + + msg->get_features_resp.index = cpu_to_le32(index); + msg->get_features_resp.num = cpu_to_le32(num); + + assert(num <= VIRTIO_MSG_MAX_FEATURE_NUM); + + for (i = 0; i < num && i < VIRTIO_MSG_MAX_FEATURE_NUM; i++) { + msg->get_features_resp.b32[i] = cpu_to_le32(f[i]); + } +} + +static inline void virtio_msg_pack_set_features(VirtIOMSG *msg, + uint32_t index, + uint32_t num, + uint32_t *f) +{ + unsigned int i; + + virtio_msg_pack_header(msg, VIRTIO_MSG_SET_FEATURES, 0, 0, + sizeof msg->set_features + num * sizeof(*f)); + + msg->set_features.index = cpu_to_le32(index); + msg->set_features.num = cpu_to_le32(num); + + assert(num <= VIRTIO_MSG_MAX_FEATURE_NUM); + + for (i = 0; i < num && i < VIRTIO_MSG_MAX_FEATURE_NUM; i++) { + msg->set_features.b32[i] = cpu_to_le32(f[i]); + } +} + +static inline void virtio_msg_pack_set_features_resp(VirtIOMSG *msg) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_SET_FEATURES, + VIRTIO_MSG_TYPE_RESPONSE, 0, 0); +} + +static inline void virtio_msg_pack_set_device_status(VirtIOMSG *msg, + uint32_t status) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_SET_DEVICE_STATUS, 0, 0, + sizeof msg->set_device_status); + + msg->set_device_status.status = cpu_to_le32(status); +} + +static inline void virtio_msg_pack_set_device_status_resp(VirtIOMSG *msg, + uint32_t status) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_SET_DEVICE_STATUS, + VIRTIO_MSG_TYPE_RESPONSE, 0, + sizeof msg->set_device_status_resp); + + msg->set_device_status_resp.status = cpu_to_le32(status); +} + +static inline void virtio_msg_pack_get_device_status(VirtIOMSG *msg) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_GET_DEVICE_STATUS, 0, 0, 0); +} + +static inline void virtio_msg_pack_get_device_status_resp(VirtIOMSG *msg, + uint32_t status) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_GET_DEVICE_STATUS, + VIRTIO_MSG_TYPE_RESPONSE, 0, + sizeof msg->get_device_status_resp); + + msg->get_device_status_resp.status = cpu_to_le32(status); +} + +static inline void virtio_msg_pack_get_config(VirtIOMSG *msg, + uint32_t size, + uint32_t offset) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_GET_CONFIG, 0, 0, + sizeof msg->get_config); + + msg->get_config.offset = cpu_to_le32(offset); + msg->get_config.size = cpu_to_le32(size); +} + +static inline void virtio_msg_pack_get_config_resp(VirtIOMSG *msg, + uint32_t size, + uint32_t offset, + uint32_t generation, + uint8_t data[]) +{ + assert(size <= VIRTIO_MSG_MAX_CONFIG_BYTES); + + virtio_msg_pack_header(msg, VIRTIO_MSG_GET_CONFIG, + VIRTIO_MSG_TYPE_RESPONSE, 0, + sizeof msg->get_config_resp + size); + + msg->get_config_resp.generation = cpu_to_le32(generation); + msg->get_config_resp.offset = cpu_to_le32(offset); + msg->get_config_resp.size = cpu_to_le32(size); + + memcpy(&msg->get_config_resp.data, data, size); +} + +static inline void virtio_msg_pack_set_config(VirtIOMSG *msg, + uint32_t size, + uint32_t offset, + uint32_t generation, + uint8_t data[]) +{ + assert(size <= VIRTIO_MSG_MAX_CONFIG_BYTES); + + virtio_msg_pack_header(msg, VIRTIO_MSG_SET_CONFIG, 0, 0, + sizeof msg->set_config + size); + + msg->set_config.offset = cpu_to_le32(offset); + msg->set_config.size = cpu_to_le32(size); + msg->set_config.generation = cpu_to_le32(generation); + + memcpy(&msg->set_config.data, data, size); +} + +static inline void virtio_msg_pack_set_config_resp(VirtIOMSG *msg, + uint32_t size, + uint32_t offset, + uint32_t generation, + uint8_t data[]) +{ + assert(size <= VIRTIO_MSG_MAX_CONFIG_BYTES); + + virtio_msg_pack_header(msg, VIRTIO_MSG_SET_CONFIG, + VIRTIO_MSG_TYPE_RESPONSE, 0, + sizeof msg->set_config_resp + size); + + msg->set_config_resp.offset = cpu_to_le32(offset); + msg->set_config_resp.size = cpu_to_le32(size); + msg->set_config_resp.generation = cpu_to_le32(generation); + + memcpy(&msg->set_config_resp.data, data, size); +} + +static inline void virtio_msg_pack_get_vqueue(VirtIOMSG *msg, + uint32_t index) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_GET_VQUEUE, 0, 0, + sizeof msg->get_vqueue); + + msg->get_vqueue.index = cpu_to_le32(index); +} + +static inline void virtio_msg_pack_get_vqueue_resp(VirtIOMSG *msg, + uint32_t index, + uint32_t max_size, + uint32_t size, + uint64_t descriptor_addr, + uint64_t driver_addr, + uint64_t device_addr) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_GET_VQUEUE, + VIRTIO_MSG_TYPE_RESPONSE, 0, + sizeof msg->get_vqueue_resp); + + msg->get_vqueue_resp.index = cpu_to_le32(index); + msg->get_vqueue_resp.max_size = cpu_to_le32(max_size); + msg->get_vqueue_resp.size = cpu_to_le32(size); + msg->get_vqueue_resp.descriptor_addr = cpu_to_le64(descriptor_addr); + msg->get_vqueue_resp.driver_addr = cpu_to_le64(driver_addr); + msg->get_vqueue_resp.device_addr = cpu_to_le64(device_addr); +} + +static inline void virtio_msg_pack_reset_vqueue(VirtIOMSG *msg, uint32_t index) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_RESET_VQUEUE, 0, 0, + sizeof msg->reset_vqueue); + + msg->reset_vqueue.index = cpu_to_le32(index); +} + +static inline void virtio_msg_pack_reset_vqueue_resp(VirtIOMSG *msg) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_RESET_VQUEUE, + VIRTIO_MSG_TYPE_RESPONSE, 0, 0); +} + +static inline void virtio_msg_pack_set_vqueue(VirtIOMSG *msg, + uint32_t index, + uint32_t size, + uint64_t descriptor_addr, + uint64_t driver_addr, + uint64_t device_addr) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_SET_VQUEUE, 0, 0, + sizeof msg->set_vqueue); + + msg->set_vqueue.index = cpu_to_le32(index); + msg->set_vqueue.unused = 0; + msg->set_vqueue.size = cpu_to_le32(size); + msg->set_vqueue.descriptor_addr = cpu_to_le64(descriptor_addr); + msg->set_vqueue.driver_addr = cpu_to_le64(driver_addr); + msg->set_vqueue.device_addr = cpu_to_le64(device_addr); +} + +static inline void virtio_msg_pack_set_vqueue_resp(VirtIOMSG *msg) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_SET_VQUEUE, + VIRTIO_MSG_TYPE_RESPONSE, 0, 0); +} + +static inline void virtio_msg_pack_event_avail(VirtIOMSG *msg, + uint32_t index, + uint32_t next_offset, + bool next_wrap) +{ + uint32_t next_ow; + + virtio_msg_pack_header(msg, VIRTIO_MSG_EVENT_AVAIL, 0, 0, + sizeof msg->event_avail); + + /* next_offset is 31b wide. */ + assert((next_offset & 0x80000000U) == 0); + + /* Pack the next_offset_wrap field. */ + next_ow = next_wrap << 31; + next_ow |= next_offset; + + msg->event_avail.index = cpu_to_le32(index); + msg->event_avail.next_offset_wrap = cpu_to_le32(next_ow); +} + +static inline void virtio_msg_pack_event_used(VirtIOMSG *msg, uint32_t index) +{ + virtio_msg_pack_header(msg, VIRTIO_MSG_EVENT_USED, 0, 0, + sizeof msg->event_used); + + msg->event_used.index = cpu_to_le32(index); +} + +static inline void virtio_msg_pack_event_config(VirtIOMSG *msg, + uint32_t status, + uint32_t generation, + uint32_t offset, + uint32_t size, + uint8_t *value) +{ + unsigned int max_size; + + virtio_msg_pack_header(msg, VIRTIO_MSG_EVENT_CONFIG, 0, 0, + sizeof msg->event_config); + + msg->event_config.status = cpu_to_le32(status); + msg->event_config.generation = cpu_to_le32(generation); + msg->event_config.offset = cpu_to_le32(offset); + msg->event_config.size = cpu_to_le32(size); + + max_size = VIRTIO_MSG_MAX_SIZE; + max_size -= offsetof(VirtIOMSG, event_config.config_value); + assert(size <= max_size); + + if (size > 0 && size <= max_size) { + memcpy(&msg->event_config.config_value[0], value, size); + } +} + +/* + * Return true if msg_resp is a response for msg_req. + */ +static inline bool virtio_msg_is_resp(VirtIOMSG *msg_req, VirtIOMSG *msg_resp) +{ + if (msg_resp->dev_num == msg_req->dev_num && + msg_resp->msg_id == msg_req->msg_id && + msg_resp->type & VIRTIO_MSG_TYPE_RESPONSE) { + return true; + } + return false; +} + +static inline const char *virtio_msg_id_to_str(unsigned int type) +{ +#define VIRTIO_MSG_TYPE2STR(x) [VIRTIO_MSG_ ## x] = stringify(x) + static const char *type2str[VIRTIO_MSG_MAX + 1] = { + VIRTIO_MSG_TYPE2STR(DEVICE_INFO), + VIRTIO_MSG_TYPE2STR(GET_FEATURES), + VIRTIO_MSG_TYPE2STR(SET_FEATURES), + VIRTIO_MSG_TYPE2STR(GET_CONFIG), + VIRTIO_MSG_TYPE2STR(SET_CONFIG), + VIRTIO_MSG_TYPE2STR(GET_DEVICE_STATUS), + VIRTIO_MSG_TYPE2STR(SET_DEVICE_STATUS), + VIRTIO_MSG_TYPE2STR(GET_VQUEUE), + VIRTIO_MSG_TYPE2STR(SET_VQUEUE), + VIRTIO_MSG_TYPE2STR(RESET_VQUEUE), + VIRTIO_MSG_TYPE2STR(EVENT_CONFIG), + VIRTIO_MSG_TYPE2STR(EVENT_AVAIL), + VIRTIO_MSG_TYPE2STR(EVENT_USED), + }; + + return type2str[type]; +} + +static inline void virtio_msg_print_status(uint32_t status) +{ + printf("status %x", status); + + if (status & VIRTIO_CONFIG_S_ACKNOWLEDGE) { + printf(" ACKNOWLEDGE"); + } + if (status & VIRTIO_CONFIG_S_DRIVER) { + printf(" DRIVER"); + } + if (status & VIRTIO_CONFIG_S_DRIVER_OK) { + printf(" DRIVER_OK"); + } + if (status & VIRTIO_CONFIG_S_FEATURES_OK) { + printf(" FEATURES_OK"); + } + if (status & VIRTIO_CONFIG_S_NEEDS_RESET) { + printf(" NEEDS_RESET"); + } + if (status & VIRTIO_CONFIG_S_FAILED) { + printf(" FAILED"); + } + + printf("\n"); +} + +static inline void virtio_msg_print(VirtIOMSG *msg) +{ + bool resp = msg->type & VIRTIO_MSG_TYPE_RESPONSE; + size_t payload_size; + int i; + + assert(msg); + printf("virtio-msg: id %s 0x%x type 0x%x dev_num 0x%x msg_size 0x%x\n", + virtio_msg_id_to_str(msg->msg_id), msg->msg_id, + msg->type, msg->dev_num, msg->msg_size); + + payload_size = msg->msg_size - offsetof(VirtIOMSG, payload_u8); + if (payload_size > ARRAY_SIZE(msg->payload_u8)) { + printf("Size overflow! %zu > %zu\n", + payload_size, ARRAY_SIZE(msg->payload_u8)); + payload_size = ARRAY_SIZE(msg->payload_u8); + } + + for (i = 0; i < payload_size; i++) { + printf("%2.2x ", msg->payload_u8[i]); + if (((i + 1) % 16) == 0) { + printf("\n"); + } + } + + switch (msg->msg_id) { + case VIRTIO_MSG_GET_DEVICE_STATUS: + if (resp) { + virtio_msg_print_status(msg->get_device_status_resp.status); + } + break; + case VIRTIO_MSG_SET_DEVICE_STATUS: + virtio_msg_print_status(msg->set_device_status.status); + break; + case VIRTIO_MSG_SET_VQUEUE: + printf("set-vqueue: index=%d size=%d desc-addr=%lx " + "driver-addr=%lx device-addr=%lx\n", + msg->set_vqueue.index, msg->set_vqueue.size, + msg->set_vqueue.descriptor_addr, + msg->set_vqueue.driver_addr, + msg->set_vqueue.device_addr); + break; + } + printf("\n"); +} +#endif diff --git a/include/hw/virtio/virtio-msg.h b/include/hw/virtio/virtio-msg.h new file mode 100644 index 0000000000..99a72e93f4 --- /dev/null +++ b/include/hw/virtio/virtio-msg.h @@ -0,0 +1,45 @@ +/* + * Virtio MSG bindings + * + * Copyright (c) 2024 Advanced Micro Devices, Inc. + * Written by Edgar E. Iglesias edgar.iglesias@amd.com. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_VIRTIO_MSG_PROXY_BACKEND_H +#define HW_VIRTIO_MSG_PROXY_BACKEND_H + +#include "hw/sysbus.h" +#include "hw/virtio/virtio-bus.h" + +#define TYPE_VIRTIO_MSG_PROXY_BUS "virtio-msg-proxy-bus" +/* This is reusing the VirtioBusState typedef from TYPE_VIRTIO_BUS */ +DECLARE_OBJ_CHECKERS(VirtioBusState, VirtioBusClass, + VIRTIO_MSG_PROXY_BUS, TYPE_VIRTIO_MSG_PROXY_BUS) + +#define TYPE_VIRTIO_MSG "virtio-msg" +OBJECT_DECLARE_SIMPLE_TYPE(VirtIOMSGProxy, VIRTIO_MSG) + +/* This is a BUS to hold VirtIOMSG transports */ +#define TYPE_VIRTIO_MSG_TP_BUS "virtio-msg-tp-bus" + +struct VirtIOMSGProxy { + SysBusDevice parent_obj; + + AddressSpace dma_as; + AddressSpace *bus_as; + IOMMUMemoryRegion mr_iommu; + MemoryRegion *mr_bus; + + /* virtio-bus */ + VirtioBusState bus; + /* virtio-msg-bus. */ + BusState msg_bus; + + bool iommu_enabled; + + /* Fields only used for non-legacy (v2) devices */ + uint64_t guest_features; +}; +#endif
Signed-off-by: Edgar E. Iglesias edgar.iglesias@amd.com --- hw/misc/Kconfig | 7 + hw/misc/meson.build | 1 + hw/misc/virtio-msg-amp-pci.c | 324 +++++++++++++++++++++++++++++++++ include/hw/virtio/spsc_queue.h | 213 ++++++++++++++++++++++ 4 files changed, 545 insertions(+) create mode 100644 hw/misc/virtio-msg-amp-pci.c create mode 100644 include/hw/virtio/spsc_queue.h
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 4e35657468..53124f5c9b 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -25,6 +25,13 @@ config PCI_TESTDEV default y if TEST_DEVICES depends on PCI
+config VIRTIO_MSG_AMP_PCI + bool + default y if PCI_DEVICES + depends on PCI + select VIRTIO + select VIRTIO_MSG + config EDU bool default y if TEST_DEVICES diff --git a/hw/misc/meson.build b/hw/misc/meson.build index b1d8d8e5d2..80d4886808 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -8,6 +8,7 @@ system_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c')) system_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c')) system_ss.add(when: 'CONFIG_LED', if_true: files('led.c')) system_ss.add(when: 'CONFIG_PVPANIC_COMMON', if_true: files('pvpanic.c')) +system_ss.add(when: 'CONFIG_VIRTIO_MSG_AMP_PCI', if_true: files('virtio-msg-amp-pci.c'))
# ARM devices system_ss.add(when: 'CONFIG_PL310', if_true: files('arm_l2x0.c')) diff --git a/hw/misc/virtio-msg-amp-pci.c b/hw/misc/virtio-msg-amp-pci.c new file mode 100644 index 0000000000..6926da848c --- /dev/null +++ b/hw/misc/virtio-msg-amp-pci.c @@ -0,0 +1,324 @@ +/* + * Model of a virtio-msg AMP capable PCI device. + * + * Copyright (C) 2025 Advanced Micro Devices, Inc. + * Written by Edgar E. Iglesias edgar.iglesias@amd.com + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qemu/log.h" + +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "hw/pci/pci_device.h" +#include "hw/pci/msix.h" +#include "hw/sysbus.h" +#include "hw/register.h" + +#include "hw/virtio/virtio-msg.h" +#include "hw/virtio/virtio-msg-bus.h" +#include "hw/virtio/spsc_queue.h" + +#define TYPE_VMSG_AMP_PCI "virtio-msg-amp-pci" +OBJECT_DECLARE_SIMPLE_TYPE(VmsgAmpPciState, VMSG_AMP_PCI) + +#define TYPE_VMSG_BUS_AMP_PCI "virtio-msg-bus-amp-pci" +OBJECT_DECLARE_SIMPLE_TYPE(VmsgBusAmpPciState, VMSG_BUS_AMP_PCI) +#define VMSG_BUS_AMP_PCI_GET_PARENT_CLASS(obj) \ + OBJECT_GET_PARENT_CLASS(obj, TYPE_VMSG_BUS_AMP_PCI) + +REG32(VERSION, 0x00) +REG32(FEATURES, 0x04) +REG32(NOTIFY, 0x20) + +#define MAX_FIFOS 1 + +typedef struct VmsgBusAmpPciState { + VirtIOMSGBusDevice parent; + PCIDevice *pcidev; + unsigned int queue_index; + + struct { + void *va; + spsc_queue driver; + spsc_queue device; + unsigned int mapcount; + } shm; +} VmsgBusAmpPciState; + +typedef struct VmsgAmpPciState { + PCIDevice dev; + MemoryRegion mr_mmio; + MemoryRegion mr_ram; + + struct fifo_bus { + VmsgBusAmpPciState dev; + VirtIOMSGProxy proxy; + BusState bus; + } fifo[MAX_FIFOS]; + + struct { + uint32_t num_fifos; + } cfg; +} VmsgAmpPciState; + +static void vmsg_bus_amp_pci_process(VirtIOMSGBusDevice *bd); + +static uint64_t vmsg_read(void *opaque, hwaddr addr, unsigned int size) +{ + uint64_t r = 0; + + assert(size == 4); + + switch (addr) { + case A_VERSION: + /* v0.1 */ + r = 0x0001; + break; + case A_FEATURES: + /* No features bit yet. */ + break; + default: + break; + } + + return r; +} + +static void vmsg_write(void *opaque, hwaddr addr, uint64_t val, + unsigned int size) +{ + VmsgAmpPciState *s = VMSG_AMP_PCI(opaque); + VmsgBusAmpPciState *dev; + unsigned int q; + + assert(size == 4); + + if (addr >= A_NOTIFY) { + q = (addr - A_NOTIFY) / 4; + dev = &s->fifo[q].dev; + + vmsg_bus_amp_pci_process(VIRTIO_MSG_BUS_DEVICE(dev)); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to read-only reg 0x%" HWADDR_PRIx "\n", + __func__, addr); + } +} + +static const MemoryRegionOps vmsg_pci_ops = { + .read = vmsg_read, + .write = vmsg_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void vmsg_create_bus(VmsgAmpPciState *s, unsigned int i) +{ + DeviceState *dev = DEVICE(s); + Object *o = OBJECT(s); + struct fifo_bus *fifo = &s->fifo[i]; + g_autofree char *name = g_strdup_printf("vmsg.%d", i); + + qbus_init(&fifo->bus, sizeof(fifo->bus), TYPE_SYSTEM_BUS, dev, name); + + /* Create the proxy. */ + object_initialize_child(o, "proxy[*]", &fifo->proxy, TYPE_VIRTIO_MSG); + qdev_realize(DEVICE(&fifo->proxy), &fifo->bus, &error_fatal); + + object_initialize_child(o, "dev[*]", &fifo->dev, + TYPE_VMSG_BUS_AMP_PCI); + qdev_realize(DEVICE(&fifo->dev), &fifo->proxy.msg_bus, &error_fatal); + + msix_vector_use(PCI_DEVICE(s), i); + + /* Caches for quick lookup. */ + fifo->dev.queue_index = i; + fifo->dev.pcidev = PCI_DEVICE(s); +} + +static void vmsg_amp_pci_realizefn(PCIDevice *dev, Error **errp) +{ + VmsgAmpPciState *s = VMSG_AMP_PCI(dev); + int i; + + if (!s->cfg.num_fifos || s->cfg.num_fifos > MAX_FIFOS) { + error_setg(errp, "Unsupported number of FIFOs (%u)", s->cfg.num_fifos); + } + + memory_region_init_io(&s->mr_mmio, OBJECT(s), &vmsg_pci_ops, s, + TYPE_VMSG_AMP_PCI, 16 * KiB); + + /* 16KB per FIFO. */ + memory_region_init_ram(&s->mr_ram, OBJECT(s), "ram", + s->cfg.num_fifos * 16 * KiB, &error_fatal); + + pci_register_bar(dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mr_mmio); + pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY | + PCI_BASE_ADDRESS_MEM_PREFETCH, + &s->mr_ram); + + msix_init_exclusive_bar(PCI_DEVICE(s), s->cfg.num_fifos, 2, &error_fatal); + for (i = 0; i < s->cfg.num_fifos; i++) { + vmsg_create_bus(s, i); + } +} + +static const Property vmsg_properties[] = { + DEFINE_PROP_UINT32("num-fifos", VmsgAmpPciState, cfg.num_fifos, 1), +}; + +static const VMStateDescription vmstate_vmsg_pci = { + .name = TYPE_VMSG_AMP_PCI, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_PCI_DEVICE(dev, VmsgAmpPciState), + /* TODO: Add all the sub-devs. */ + VMSTATE_END_OF_LIST() + } +}; + +static void vmsg_amp_pci_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + PCIDeviceClass *pc = PCI_DEVICE_CLASS(klass); + + device_class_set_props(dc, vmsg_properties); + + pc->realize = vmsg_amp_pci_realizefn; + pc->vendor_id = PCI_VENDOR_ID_XILINX; + pc->device_id = 0x9039; + pc->revision = 1; + pc->class_id = PCI_CLASS_SYSTEM_OTHER; + dc->vmsd = &vmstate_vmsg_pci; + + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static bool vmsg_bus_amp_pci_map_fifo(VmsgBusAmpPciState *s) +{ + VmsgAmpPciState *pci_s = VMSG_AMP_PCI(s->pcidev); + void *va; + + if (s->shm.mapcount) { + s->shm.mapcount++; + return true; + } + + va = memory_region_get_ram_ptr(&pci_s->mr_ram); + if (!va) { + return false; + } + + if (!s->shm.driver.shm) { + int capacity = spsc_capacity(4 * KiB); + + /* + * Layout: + * 0 - 4KB Reserved + * 4KB - 8KB Driver queue + * 8KB - 12KB Device queue + */ + spsc_init(&s->shm.driver, "driver", capacity, va + 4 * KiB); + spsc_init(&s->shm.device, "device", capacity, va + 8 * KiB); + } + + /* Map queues. */ + s->shm.va = va; + s->shm.mapcount++; + return true; +} + +static void vmsg_bus_amp_pci_unmap_fifo(VmsgBusAmpPciState *s) +{ + assert(s->shm.mapcount); + if (--s->shm.mapcount) { + return; + } + + /* TODO: Actually unmap. */ +} + +static void vmsg_bus_amp_pci_process(VirtIOMSGBusDevice *bd) +{ + VmsgBusAmpPciState *s = VMSG_BUS_AMP_PCI(bd); + spsc_queue *q; + VirtIOMSG msg; + bool r; + + if (!vmsg_bus_amp_pci_map_fifo(s)) { + return; + } + + /* + * We process the opposite queue, i.e, a driver will want to receive + * messages on the backend queue (and send messages on the driver queue). + */ + q = bd->peer->is_driver ? &s->shm.device : &s->shm.driver; + do { + r = spsc_recv(q, &msg, sizeof msg); + if (r) { + virtio_msg_bus_receive(bd, &msg); + } + } while (r); + vmsg_bus_amp_pci_unmap_fifo(s); +} + +static int vmsg_bus_amp_pci_send(VirtIOMSGBusDevice *bd, VirtIOMSG *msg_req) +{ + VmsgAmpPciState *pci_s = VMSG_AMP_PCI(OBJECT(bd)->parent); + VmsgBusAmpPciState *s = VMSG_BUS_AMP_PCI(bd); + + if (!vmsg_bus_amp_pci_map_fifo(s)) { + return VIRTIO_MSG_ERROR_MEMORY; + } + + spsc_send(&s->shm.device, msg_req, sizeof *msg_req); + + /* Notify. */ + msix_notify(PCI_DEVICE(pci_s), s->queue_index); + + vmsg_bus_amp_pci_unmap_fifo(s); + return VIRTIO_MSG_NO_ERROR; +} + +static void vmsg_bus_amp_pci_class_init(ObjectClass *klass, + const void *data) +{ + VirtIOMSGBusDeviceClass *bdc = VIRTIO_MSG_BUS_DEVICE_CLASS(klass); + + bdc->process = vmsg_bus_amp_pci_process; + bdc->send = vmsg_bus_amp_pci_send; +} + +static const TypeInfo vmsg_pci_info[] = { + { + .name = TYPE_VMSG_AMP_PCI, + .parent = TYPE_PCI_DEVICE, + .instance_size = sizeof(VmsgAmpPciState), + .class_init = vmsg_amp_pci_class_init, + .interfaces = (const InterfaceInfo[]) { + { INTERFACE_CONVENTIONAL_PCI_DEVICE }, + { } + }, + }, { + .name = TYPE_VMSG_BUS_AMP_PCI, + .parent = TYPE_VIRTIO_MSG_BUS_DEVICE, + .instance_size = sizeof(VmsgBusAmpPciState), + .class_init = vmsg_bus_amp_pci_class_init, + }, +}; + +static void vmsg_pci_register_types(void) +{ + type_register_static_array(vmsg_pci_info, ARRAY_SIZE(vmsg_pci_info)); +} + +type_init(vmsg_pci_register_types); diff --git a/include/hw/virtio/spsc_queue.h b/include/hw/virtio/spsc_queue.h new file mode 100644 index 0000000000..3d88baab55 --- /dev/null +++ b/include/hw/virtio/spsc_queue.h @@ -0,0 +1,213 @@ +/* + * Hardened and lockless Single Producer Single Consumer Queue implemented + * over shared-memory. + * + * The queue implementation does not look at packet contents, it's up to upper + * layers to make sure data is produced and parsed safely. All data is copied + * in/out from/to local private buffers so the peer cannot mess with them while + * upper layers parse. + * + * The queue is split into a private and a shared part. + * The private part contains cached and sanitized versions of the indexes that + * indicate our position in the ring-buffer. Peers can corrupt the shared area + * but have no access to the private area. So whenever we copy from the shared + * area into the private one, we need to sanitize indexes and make sure they + * are within bounds. + * + * A malicious peer can send corrupt data, it can stop receiving or flood the + * queue causing a sort of denial of service but it can NOT cause our side + * to copy data in or out of buffers outside of the shared memory area. + * + * This implementation expects the SHM area to be cache-coherent or uncached. + * The shared area can be mapped in different ways and our peer may be anything + * from another thread on our same OS to an FPGA implementation on a PCI card. + * So local CPU cache-lines sizes, or spin-locks and things that work on a + * single CPU cluster are not used. Instead the implementation sticks to atomic + * load/stores of 32b values and to using memory-barriers to guarantee ordering. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef SPSC_QUEUE_H__ +#define SPSC_QUEUE_H__ + +#include <assert.h> +#include "qemu/atomic.h" + +#define BUG_ON(x) assert(!(x)) + +#define SPSC_QUEUE_MAX_PACKET_SIZE 64 +/* + * This cache-line size is used to align fields in the hope of + * avoiding cache-line ping-pong:ing. Since the queue layout is + * used across heterogenous CPU clusters and across FPGA/HW implementations, + * a fixed size must be used, i.e not the local CPU's cache-line size. + */ +#define SPSC_QUEUE_CACHE_LINE_SIZE 64 + +typedef struct spsc_queue_shared { + uint32_t head __attribute__((__aligned__(SPSC_QUEUE_CACHE_LINE_SIZE))); + uint32_t tail __attribute__((__aligned__(SPSC_QUEUE_CACHE_LINE_SIZE))); + uint32_t packets[][SPSC_QUEUE_MAX_PACKET_SIZE / 4] + __attribute__((__aligned__(SPSC_QUEUE_CACHE_LINE_SIZE))); +} spsc_queue_shared; + +typedef struct spsc_queue { + uint32_t cached_tail; + uint32_t cached_head; + spsc_queue_shared *shm; + const char *name; + unsigned int capacity; +} spsc_queue; + +/* Atomically load and sanitize an index from the SHM area. */ +static inline uint32_t spsc_atomic_load(spsc_queue *q, uint32_t *ptr) +{ + uint32_t val; + + val = qatomic_read(ptr); + /* Make sure packet reads are done after reading the index. */ + smp_mb_acquire(); + + /* Bounds check that index is within queue size. */ + if (val >= q->capacity) { + val = val % q->capacity; + } + + return val; +} + +static inline void spsc_atomic_store(spsc_queue *q, uint32_t *ptr, uint32_t v) +{ + /* Make sure packet-data gets written before updating the index. */ + smp_mb_release(); + qatomic_set(ptr, v); +} + +/* Returns the capacity of a queue given a specific mapsize. */ +static inline unsigned int spsc_capacity(size_t mapsize) +{ + unsigned int capacity; + spsc_queue *q = NULL; + + if (mapsize < sizeof(*q->shm)) { + return 0; + } + + /* Start with the size of the shared area. */ + mapsize -= sizeof(*q->shm); + capacity = mapsize / sizeof(q->shm->packets[0]); + + if (capacity < 2) { + /* Capacities of less than 2 are invalid. */ + return 0; + } + + return capacity; +} + +static inline size_t spsc_mapsize(unsigned int capacity) +{ + spsc_queue *q = NULL; + size_t mapsize; + + BUG_ON(capacity < 2); + + mapsize = sizeof(*q->shm); + mapsize += sizeof(q->shm->packets[0]) * capacity; + + return mapsize; +} + +static inline void spsc_init(spsc_queue *q, const char *name, size_t capacity, + void *mem) +{ + BUG_ON(!mem); + + /* Initialize private queue area to all zeroes */ + memset(q, 0, sizeof *q); + + q->shm = (spsc_queue_shared *) mem; + q->name = name; + q->capacity = capacity; + + /* In case we're opening a pre-existing queue, pick up where we left off. */ + q->cached_tail = spsc_atomic_load(q, &q->shm->tail); + q->cached_head = spsc_atomic_load(q, &q->shm->head); +} + +static inline bool spsc_queue_is_full(spsc_queue *q) +{ + uint32_t next_head; + uint32_t head; + + head = spsc_atomic_load(q, &q->shm->head); + + next_head = head + 1; + if (next_head >= q->capacity) { + next_head = 0; + } + + if (next_head == q->cached_tail) { + q->cached_tail = spsc_atomic_load(q, &q->shm->tail); + if (next_head == q->cached_tail) { + return true; + } + } + return false; +} + +static inline bool spsc_send(spsc_queue *q, void *buf, size_t size) +{ + uint32_t next_head; + uint32_t head; + + BUG_ON(size > sizeof q->shm->packets[0]); + BUG_ON(size == 0); + + /* Is the queue full? */ + if (spsc_queue_is_full(q)) { + return false; + } + + head = spsc_atomic_load(q, &q->shm->head); + next_head = head + 1; + if (next_head >= q->capacity) { + next_head = 0; + } + + memcpy(q->shm->packets[head], buf, size); + + spsc_atomic_store(q, &q->shm->head, next_head); + return true; +} + +static inline bool spsc_recv(spsc_queue *q, void *buf, size_t size) +{ + uint32_t tail; + + BUG_ON(size > sizeof q->shm->packets[0]); + BUG_ON(size == 0); + + tail = spsc_atomic_load(q, &q->shm->tail); + + /* Is the queue empty? */ + if (tail == q->cached_head) { + q->cached_head = spsc_atomic_load(q, &q->shm->head); + if (tail == q->cached_head) { + return false; + } + } + + memcpy(buf, q->shm->packets[tail], size); + + /* Update the read pointer. */ + tail++; + if (tail >= q->capacity) { + tail = 0; + } + + spsc_atomic_store(q, &q->shm->tail, tail); + return true; +} +#endif /* SPSC_QUEUE_H__ */
"Edgar E. Iglesias" edgar.iglesias@amd.com writes:
Hi all,
This the RFC I'm preparing to send to upstream QEMU for initial RFC review. I didn't see much precedence for Signed-off-by lines in cover letter so instead I'm thinking to explicitely CC Bill and Alex and anyone else who would like to be on copy, just let me know.
Changes from v1:
- Move VIRTIO_MSG_VENDOR_ID to virtio-msg.c
- Update to match recent spec changes (token + set/get_vqueue padding)
- Add endian conversion of dev_num and msg_size
- Add instructions for running on GPEX PCI x86 microVM and ARM virt
- Add missing spsc_queue.h
This adds virtio-msg, a new virtio transport. Virtio-msg works by exchanging messages over a bus and doesn't rely on trapping and emulating making it a good fit for a number of applications such as AMP, real-time and safety applications.
Together with the new transport, this series adds a PCI device that implements an AMP setup much like it would look if two SoC's would use virtio-msg across a PCI link.
Current limitations: We only support a single device per bus (dev_num = 0). Shared memory queue layout likely to change in the future. Temporarily uses PCI Vendor Xilinx / Device 0x9039. Missing documentation.
Why can't the module be built-in?
It hangs in this case:
ACPI: button: Power Button [PWRB] virtio_msg_amp_pci 0000:00:03.0: enabling device (0000 -> 0002) virtio_msg_amp_pci 0000:00:03.0: device_name=0000:00:03.0 virtio_msg_amp_pci 0000:00:03.0: mmr (BAR0) at 0x0000000010040000, size 0x0000000000004000 virtio_msg_amp_pci 0000:00:03.0: mmr (BAR1) at 0x0000000010044000, size 0x0000000000004000 ram=(____ptrval____) virtio_msg_amp_pci 0000:00:03.0: SHMEM @ 0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 virtio_msg_amp_pci 0000:00:03.0: get bus name for dev_id=0
tracing through we get to:
transport_msg_prepare (vmdev=0xffffff80c0a9c8c0, msg_id=2 '\002', payload_size=0) at /home/alex/lsrc/linux.git/drivers/virtio/virtio_msg.c:54 54 msg_prepare(vmdev->request, false, msg_id, vmdev->dev_id, payload_size); (gdb) n 106 transport_msg_prepare(vmdev, VIRTIO_MSG_DEVICE_INFO, 0); (gdb) ^C Thread 4 received signal SIGINT, Interrupt. [Switching to Thread 1.4] cpu_do_idle () at /home/alex/lsrc/linux.git/arch/arm64/kernel/idle.c:32 32 arm_cpuidle_restore_irq_context(&context); (gdb) bt #0 cpu_do_idle () at /home/alex/lsrc/linux.git/arch/arm64/kernel/idle.c:32 #1 0xffffffc0813a1a40 in arch_cpu_idle () at /home/alex/lsrc/linux.git/arch/arm64/kernel/idle.c:44 #2 0xffffffc0813a1ba8 in default_idle_call () at /home/alex/lsrc/linux.git/kernel/sched/idle.c:122 #3 0xffffffc08012baa8 in cpuidle_idle_call () at /home/alex/lsrc/linux.git/kernel/sched/idle.c:190 #4 do_idle () at /home/alex/lsrc/linux.git/kernel/sched/idle.c:330 #5 0xffffffc08012bd74 in cpu_startup_entry (state=state@entry=CPUHP_AP_ONLINE_IDLE) at /home/alex/lsrc/linux.git/kernel/sched/idle.c:428 #6 0xffffffc080048798 in secondary_start_kernel () at /home/alex/lsrc/linux.git/arch/arm64/kernel/smp.c:271 #7 0xffffffc080056078 in __secondary_switched () at /home/alex/lsrc/linux.git/arch/arm64/kernel/head.S:404 Backtrace stopped: previous frame identical to this frame (corrupt stack?)
but I can't see where the kernel would trigger a schedule....
The virtio-msg spec: https://github.com/Linaro/virtio-msg-spec/
QEMU with these patches: https://github.com/edgarigl/qemu/tree/edgar/virtio-msg-rfc
Linux with virtio-msg suppport: https://github.com/edgarigl/linux/tree/edgari/virtio-msg-6.17
To try it, first build Linux with the following enabled: CONFIG_VIRTIO_MSG=y CONFIG_VIRTIO_MSG_AMP=y CONFIG_VIRTIO_MSG_AMP_PCI=y
Boot linux in QEMU with a virtio-msg-amp-pci device, in this example with a virtio-net device attached to it:
x86/q35 machine: -device virtio-msg-amp-pci -device virtio-net-device,netdev=n1,bus=/q35-pcihost/pcie.0/virtio-msg-amp-pci/vmsg.0 -netdev user,id=n1
x86/microvm or ARM virt machines: -device virtio-msg-amp-pci -device virtio-net-device,netdev=n1,bus=/gpex-pcihost/pcie.0/virtio-msg-amp-pci/vmsg.0/virtio-msg/virtio-msg-proxy-bus.0 -netdev user,id=n1
Cheers, Edgar
Edgar E. Iglesias (4): virtio: Introduce notify_queue virtio: Add virtio_queue_get_rings virtio: Add the virtio-msg transport virtio-msg-bus: amp-pci: Add generic AMP PCI device
hw/misc/Kconfig | 7 + hw/misc/meson.build | 1 + hw/misc/virtio-msg-amp-pci.c | 324 ++++++++++++ hw/virtio/Kconfig | 4 + hw/virtio/meson.build | 5 + hw/virtio/virtio-msg-bus.c | 89 ++++ hw/virtio/virtio-msg.c | 598 ++++++++++++++++++++++ hw/virtio/virtio.c | 23 + include/hw/virtio/spsc_queue.h | 213 ++++++++ include/hw/virtio/virtio-bus.h | 1 + include/hw/virtio/virtio-msg-bus.h | 95 ++++ include/hw/virtio/virtio-msg-prot.h | 749 ++++++++++++++++++++++++++++ include/hw/virtio/virtio-msg.h | 45 ++ include/hw/virtio/virtio.h | 2 + 14 files changed, 2156 insertions(+) create mode 100644 hw/misc/virtio-msg-amp-pci.c create mode 100644 hw/virtio/virtio-msg-bus.c create mode 100644 hw/virtio/virtio-msg.c create mode 100644 include/hw/virtio/spsc_queue.h create mode 100644 include/hw/virtio/virtio-msg-bus.h create mode 100644 include/hw/virtio/virtio-msg-prot.h create mode 100644 include/hw/virtio/virtio-msg.h
On Mon, Oct 27, 2025 at 11:42 AM Alex Bennée alex.bennee@linaro.org wrote:
"Edgar E. Iglesias" edgar.iglesias@amd.com writes:
Hi all,
This the RFC I'm preparing to send to upstream QEMU for initial RFC
review.
I didn't see much precedence for Signed-off-by lines in cover letter so
instead
I'm thinking to explicitely CC Bill and Alex and anyone else who would
like to be
on copy, just let me know.
Changes from v1:
- Move VIRTIO_MSG_VENDOR_ID to virtio-msg.c
- Update to match recent spec changes (token + set/get_vqueue padding)
- Add endian conversion of dev_num and msg_size
- Add instructions for running on GPEX PCI x86 microVM and ARM virt
- Add missing spsc_queue.h
This adds virtio-msg, a new virtio transport. Virtio-msg works by exchanging messages over a bus and doesn't rely on trapping and emulating making it a good fit for a number of applications such as AMP, real-time and safety applications.
Together with the new transport, this series adds a PCI device that implements an AMP setup much like it would look if two SoC's would use virtio-msg across a PCI link.
Current limitations: We only support a single device per bus (dev_num = 0). Shared memory queue layout likely to change in the future. Temporarily uses PCI Vendor Xilinx / Device 0x9039. Missing documentation.
Why can't the module be built-in?
It was due to a bug in the kernel driver, I fixed it last week.
There's been a ton of updates during the weekend, I've pushed them to the repos:
QEMU with these patches: https://github.com/edgarigl/qemu/tree/edgar/virtio-msg-rfc
Linux with virtio-msg suppport: https://github.com/edgarigl/linux/tree/edgari/virtio-msg-6.17
Changes: * Multi-device support (dev_num) * Actual echo of token * Tweak qom bus structure, avoids bug in 'info qtree' and needed for multi-device
With the latest commits, command-lines have changed slightly:
x86/q35: -device virtio-msg-amp-pci -device virtio-net-device,netdev=n1,bus=/q35-pcihost/pcie.0/virtio-msg-amp-pci/fifo0/virtio-msg/bus0/virtio-msg-dev -netdev user,id=n1
ARM virt: -device virtio-msg-amp-pci \ -device virtio-net-device,netdev=n1,bus=/gpex-pcihost/pcie.0/virtio-msg-amp-pci/fifo0/virtio-msg/bus0/virtio-msg-dev -netdev user,id=n1
We can now also add multiple devs, for example to add an additional virtio-net on dev_num = 1: -device virtio-net-device,netdev=n2,bus=/gpex-pcihost/pcie.0/virtio-msg-amp-pci/fifo0/virtio-msg/bus1/virtio-msg-dev -netdev user,id=n2
Cheers, Edgar
It hangs in this case:
ACPI: button: Power Button [PWRB] virtio_msg_amp_pci 0000:00:03.0: enabling device (0000 -> 0002) virtio_msg_amp_pci 0000:00:03.0: device_name=0000:00:03.0 virtio_msg_amp_pci 0000:00:03.0: mmr (BAR0) at 0x0000000010040000, size 0x0000000000004000 virtio_msg_amp_pci 0000:00:03.0: mmr (BAR1) at 0x0000000010044000, size 0x0000000000004000 ram=(____ptrval____) virtio_msg_amp_pci 0000:00:03.0: SHMEM @ 0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 virtio_msg_amp_pci 0000:00:03.0: get bus name for dev_id=0
tracing through we get to:
transport_msg_prepare (vmdev=0xffffff80c0a9c8c0, msg_id=2 '\002', payload_size=0) at /home/alex/lsrc/linux.git/drivers/virtio/virtio_msg.c:54 54 msg_prepare(vmdev->request, false, msg_id, vmdev->dev_id, payload_size); (gdb) n 106 transport_msg_prepare(vmdev, VIRTIO_MSG_DEVICE_INFO, 0); (gdb) ^C Thread 4 received signal SIGINT, Interrupt. [Switching to Thread 1.4] cpu_do_idle () at /home/alex/lsrc/linux.git/arch/arm64/kernel/idle.c:32 32 arm_cpuidle_restore_irq_context(&context); (gdb) bt #0 cpu_do_idle () at /home/alex/lsrc/linux.git/arch/arm64/kernel/idle.c:32 #1 0xffffffc0813a1a40 in arch_cpu_idle () at /home/alex/lsrc/linux.git/arch/arm64/kernel/idle.c:44 #2 0xffffffc0813a1ba8 in default_idle_call () at /home/alex/lsrc/linux.git/kernel/sched/idle.c:122 #3 0xffffffc08012baa8 in cpuidle_idle_call () at /home/alex/lsrc/linux.git/kernel/sched/idle.c:190 #4 do_idle () at /home/alex/lsrc/linux.git/kernel/sched/idle.c:330 #5 0xffffffc08012bd74 in cpu_startup_entry (state=state@entry=CPUHP_AP_ONLINE_IDLE) at /home/alex/lsrc/linux.git/kernel/sched/idle.c:428 #6 0xffffffc080048798 in secondary_start_kernel () at /home/alex/lsrc/linux.git/arch/arm64/kernel/smp.c:271 #7 0xffffffc080056078 in __secondary_switched () at /home/alex/lsrc/linux.git/arch/arm64/kernel/head.S:404 Backtrace stopped: previous frame identical to this frame (corrupt stack?)
but I can't see where the kernel would trigger a schedule....
The virtio-msg spec: https://github.com/Linaro/virtio-msg-spec/
QEMU with these patches: https://github.com/edgarigl/qemu/tree/edgar/virtio-msg-rfc
Linux with virtio-msg suppport: https://github.com/edgarigl/linux/tree/edgari/virtio-msg-6.17
To try it, first build Linux with the following enabled: CONFIG_VIRTIO_MSG=y CONFIG_VIRTIO_MSG_AMP=y CONFIG_VIRTIO_MSG_AMP_PCI=y
Boot linux in QEMU with a virtio-msg-amp-pci device, in this example with a virtio-net device attached to it:
x86/q35 machine: -device virtio-msg-amp-pci -device
virtio-net-device,netdev=n1,bus=/q35-pcihost/pcie.0/virtio-msg-amp-pci/vmsg.0
-netdev user,id=n1
x86/microvm or ARM virt machines: -device virtio-msg-amp-pci -device
virtio-net-device,netdev=n1,bus=/gpex-pcihost/pcie.0/virtio-msg-amp-pci/vmsg.0/virtio-msg/virtio-msg-proxy-bus.0
-netdev user,id=n1
Cheers, Edgar
Edgar E. Iglesias (4): virtio: Introduce notify_queue virtio: Add virtio_queue_get_rings virtio: Add the virtio-msg transport virtio-msg-bus: amp-pci: Add generic AMP PCI device
hw/misc/Kconfig | 7 + hw/misc/meson.build | 1 + hw/misc/virtio-msg-amp-pci.c | 324 ++++++++++++ hw/virtio/Kconfig | 4 + hw/virtio/meson.build | 5 + hw/virtio/virtio-msg-bus.c | 89 ++++ hw/virtio/virtio-msg.c | 598 ++++++++++++++++++++++ hw/virtio/virtio.c | 23 + include/hw/virtio/spsc_queue.h | 213 ++++++++ include/hw/virtio/virtio-bus.h | 1 + include/hw/virtio/virtio-msg-bus.h | 95 ++++ include/hw/virtio/virtio-msg-prot.h | 749 ++++++++++++++++++++++++++++ include/hw/virtio/virtio-msg.h | 45 ++ include/hw/virtio/virtio.h | 2 + 14 files changed, 2156 insertions(+) create mode 100644 hw/misc/virtio-msg-amp-pci.c create mode 100644 hw/virtio/virtio-msg-bus.c create mode 100644 hw/virtio/virtio-msg.c create mode 100644 include/hw/virtio/spsc_queue.h create mode 100644 include/hw/virtio/virtio-msg-bus.h create mode 100644 include/hw/virtio/virtio-msg-prot.h create mode 100644 include/hw/virtio/virtio-msg.h
-- Alex Bennée Virtualisation Tech Lead @ Linaro