virtio: add vhost-user-base and a generic vhost-user-device
In theory we shouldn't need to repeat so much boilerplate to support vhost-user backends. This provides a generic vhost-user-base QOM object and a derived vhost-user-device for which the user needs to provide the few bits of information that aren't currently provided by the vhost-user protocol. This should provide a baseline implementation from which the other vhost-user stub can specialise. Signed-off-by: Alex Bennée <alex.bennee@linaro.org> Message-Id: <20230710153522.3469097-8-alex.bennee@linaro.org> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
This commit is contained in:
		
							parent
							
								
									f05356f84d
								
							
						
					
					
						commit
						eee7780973
					
				| @ -15,7 +15,10 @@ if have_vhost | ||||
|   system_virtio_ss.add(files('vhost.c')) | ||||
|   specific_virtio_ss.add(files('vhost-backend.c', 'vhost-iova-tree.c')) | ||||
|   if have_vhost_user | ||||
|     # fixme - this really should be generic | ||||
|     specific_virtio_ss.add(files('vhost-user.c')) | ||||
|     system_virtio_ss.add(files('vhost-user-device.c')) | ||||
|     system_virtio_ss.add(when: 'CONFIG_VIRTIO_PCI', if_true: files('vhost-user-device-pci.c')) | ||||
|   endif | ||||
|   if have_vhost_vdpa | ||||
|     system_virtio_ss.add(files('vhost-vdpa.c')) | ||||
|  | ||||
							
								
								
									
										71
									
								
								hw/virtio/vhost-user-device-pci.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								hw/virtio/vhost-user-device-pci.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,71 @@ | ||||
| /*
 | ||||
|  * Vhost-user generic virtio device PCI glue | ||||
|  * | ||||
|  * Copyright (c) 2023 Linaro Ltd | ||||
|  * Author: Alex Bennée <alex.bennee@linaro.org> | ||||
|  * | ||||
|  * SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  */ | ||||
| 
 | ||||
| #include "qemu/osdep.h" | ||||
| #include "hw/qdev-properties.h" | ||||
| #include "hw/virtio/vhost-user-device.h" | ||||
| #include "hw/virtio/virtio-pci.h" | ||||
| 
 | ||||
| struct VHostUserDevicePCI { | ||||
|     VirtIOPCIProxy parent_obj; | ||||
|     VHostUserBase vub; | ||||
| }; | ||||
| 
 | ||||
| typedef struct VHostUserDevicePCI VHostUserDevicePCI; | ||||
| 
 | ||||
| #define TYPE_VHOST_USER_DEVICE_PCI "vhost-user-device-pci-base" | ||||
| 
 | ||||
| DECLARE_INSTANCE_CHECKER(VHostUserDevicePCI, | ||||
|                          VHOST_USER_DEVICE_PCI, | ||||
|                          TYPE_VHOST_USER_DEVICE_PCI) | ||||
| 
 | ||||
| static void vhost_user_device_pci_realize(VirtIOPCIProxy *vpci_dev, Error **errp) | ||||
| { | ||||
|     VHostUserDevicePCI *dev = VHOST_USER_DEVICE_PCI(vpci_dev); | ||||
|     DeviceState *vdev = DEVICE(&dev->vub); | ||||
| 
 | ||||
|     vpci_dev->nvectors = 1; | ||||
|     qdev_realize(vdev, BUS(&vpci_dev->bus), errp); | ||||
| } | ||||
| 
 | ||||
| static void vhost_user_device_pci_class_init(ObjectClass *klass, void *data) | ||||
| { | ||||
|     DeviceClass *dc = DEVICE_CLASS(klass); | ||||
|     VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass); | ||||
|     PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass); | ||||
|     k->realize = vhost_user_device_pci_realize; | ||||
|     set_bit(DEVICE_CATEGORY_INPUT, dc->categories); | ||||
|     pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET; | ||||
|     pcidev_k->device_id = 0; /* Set by virtio-pci based on virtio id */ | ||||
|     pcidev_k->revision = 0x00; | ||||
|     pcidev_k->class_id = PCI_CLASS_COMMUNICATION_OTHER; | ||||
| } | ||||
| 
 | ||||
| static void vhost_user_device_pci_instance_init(Object *obj) | ||||
| { | ||||
|     VHostUserDevicePCI *dev = VHOST_USER_DEVICE_PCI(obj); | ||||
| 
 | ||||
|     virtio_instance_init_common(obj, &dev->vub, sizeof(dev->vub), | ||||
|                                 TYPE_VHOST_USER_DEVICE); | ||||
| } | ||||
| 
 | ||||
| static const VirtioPCIDeviceTypeInfo vhost_user_device_pci_info = { | ||||
|     .base_name = TYPE_VHOST_USER_DEVICE_PCI, | ||||
|     .non_transitional_name = "vhost-user-device-pci", | ||||
|     .instance_size = sizeof(VHostUserDevicePCI), | ||||
|     .instance_init = vhost_user_device_pci_instance_init, | ||||
|     .class_init = vhost_user_device_pci_class_init, | ||||
| }; | ||||
| 
 | ||||
| static void vhost_user_device_pci_register(void) | ||||
| { | ||||
|     virtio_pci_types_register(&vhost_user_device_pci_info); | ||||
| } | ||||
| 
 | ||||
| type_init(vhost_user_device_pci_register); | ||||
							
								
								
									
										324
									
								
								hw/virtio/vhost-user-device.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										324
									
								
								hw/virtio/vhost-user-device.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,324 @@ | ||||
| /*
 | ||||
|  * Generic vhost-user stub. This can be used to connect to any | ||||
|  * vhost-user backend. All configuration details must be handled by | ||||
|  * the vhost-user daemon itself | ||||
|  * | ||||
|  * Copyright (c) 2023 Linaro Ltd | ||||
|  * Author: Alex Bennée <alex.bennee@linaro.org> | ||||
|  * | ||||
|  * SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  */ | ||||
| 
 | ||||
| #include "qemu/osdep.h" | ||||
| #include "qapi/error.h" | ||||
| #include "hw/qdev-properties.h" | ||||
| #include "hw/virtio/virtio-bus.h" | ||||
| #include "hw/virtio/vhost-user-device.h" | ||||
| #include "qemu/error-report.h" | ||||
| 
 | ||||
| static void vub_start(VirtIODevice *vdev) | ||||
| { | ||||
|     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); | ||||
|     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); | ||||
|     VHostUserBase *vub = VHOST_USER_BASE(vdev); | ||||
|     int ret, i; | ||||
| 
 | ||||
|     if (!k->set_guest_notifiers) { | ||||
|         error_report("binding does not support guest notifiers"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ret = vhost_dev_enable_notifiers(&vub->vhost_dev, vdev); | ||||
|     if (ret < 0) { | ||||
|         error_report("Error enabling host notifiers: %d", -ret); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     ret = k->set_guest_notifiers(qbus->parent, vub->vhost_dev.nvqs, true); | ||||
|     if (ret < 0) { | ||||
|         error_report("Error binding guest notifier: %d", -ret); | ||||
|         goto err_host_notifiers; | ||||
|     } | ||||
| 
 | ||||
|     vub->vhost_dev.acked_features = vdev->guest_features; | ||||
| 
 | ||||
|     ret = vhost_dev_start(&vub->vhost_dev, vdev, true); | ||||
|     if (ret < 0) { | ||||
|         error_report("Error starting vhost-user-device: %d", -ret); | ||||
|         goto err_guest_notifiers; | ||||
|     } | ||||
| 
 | ||||
|     /*
 | ||||
|      * guest_notifier_mask/pending not used yet, so just unmask | ||||
|      * everything here. virtio-pci will do the right thing by | ||||
|      * enabling/disabling irqfd. | ||||
|      */ | ||||
|     for (i = 0; i < vub->vhost_dev.nvqs; i++) { | ||||
|         vhost_virtqueue_mask(&vub->vhost_dev, vdev, i, false); | ||||
|     } | ||||
| 
 | ||||
|     return; | ||||
| 
 | ||||
| err_guest_notifiers: | ||||
|     k->set_guest_notifiers(qbus->parent, vub->vhost_dev.nvqs, false); | ||||
| err_host_notifiers: | ||||
|     vhost_dev_disable_notifiers(&vub->vhost_dev, vdev); | ||||
| } | ||||
| 
 | ||||
| static void vub_stop(VirtIODevice *vdev) | ||||
| { | ||||
|     VHostUserBase *vub = VHOST_USER_BASE(vdev); | ||||
|     BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); | ||||
|     VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); | ||||
|     int ret; | ||||
| 
 | ||||
|     if (!k->set_guest_notifiers) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     vhost_dev_stop(&vub->vhost_dev, vdev, true); | ||||
| 
 | ||||
|     ret = k->set_guest_notifiers(qbus->parent, vub->vhost_dev.nvqs, false); | ||||
|     if (ret < 0) { | ||||
|         error_report("vhost guest notifier cleanup failed: %d", ret); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     vhost_dev_disable_notifiers(&vub->vhost_dev, vdev); | ||||
| } | ||||
| 
 | ||||
| static void vub_set_status(VirtIODevice *vdev, uint8_t status) | ||||
| { | ||||
|     VHostUserBase *vub = VHOST_USER_BASE(vdev); | ||||
|     bool should_start = virtio_device_should_start(vdev, status); | ||||
| 
 | ||||
|     if (vhost_dev_is_started(&vub->vhost_dev) == should_start) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (should_start) { | ||||
|         vub_start(vdev); | ||||
|     } else { | ||||
|         vub_stop(vdev); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /*
 | ||||
|  * For an implementation where everything is delegated to the backend | ||||
|  * we don't do anything other than return the full feature set offered | ||||
|  * by the daemon (module the reserved feature bit). | ||||
|  */ | ||||
| static uint64_t vub_get_features(VirtIODevice *vdev, | ||||
|                                  uint64_t requested_features, Error **errp) | ||||
| { | ||||
|     VHostUserBase *vub = VHOST_USER_BASE(vdev); | ||||
|     /* This should be set when the vhost connection initialises */ | ||||
|     g_assert(vub->vhost_dev.features); | ||||
|     return vub->vhost_dev.features & ~(1ULL << VHOST_USER_F_PROTOCOL_FEATURES); | ||||
| } | ||||
| 
 | ||||
| static void vub_handle_output(VirtIODevice *vdev, VirtQueue *vq) | ||||
| { | ||||
|     /*
 | ||||
|      * Not normally called; it's the daemon that handles the queue; | ||||
|      * however virtio's cleanup path can call this. | ||||
|      */ | ||||
| } | ||||
| 
 | ||||
| static void do_vhost_user_cleanup(VirtIODevice *vdev, VHostUserBase *vub) | ||||
| { | ||||
|     vhost_user_cleanup(&vub->vhost_user); | ||||
| 
 | ||||
|     for (int i = 0; i < vub->num_vqs; i++) { | ||||
|         VirtQueue *vq = g_ptr_array_index(vub->vqs, i); | ||||
|         virtio_delete_queue(vq); | ||||
|     } | ||||
| 
 | ||||
|     virtio_cleanup(vdev); | ||||
| } | ||||
| 
 | ||||
| static int vub_connect(DeviceState *dev) | ||||
| { | ||||
|     VirtIODevice *vdev = VIRTIO_DEVICE(dev); | ||||
|     VHostUserBase *vub = VHOST_USER_BASE(vdev); | ||||
| 
 | ||||
|     if (vub->connected) { | ||||
|         return 0; | ||||
|     } | ||||
|     vub->connected = true; | ||||
| 
 | ||||
|     /* restore vhost state */ | ||||
|     if (virtio_device_started(vdev, vdev->status)) { | ||||
|         vub_start(vdev); | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| } | ||||
| 
 | ||||
| static void vub_disconnect(DeviceState *dev) | ||||
| { | ||||
|     VirtIODevice *vdev = VIRTIO_DEVICE(dev); | ||||
|     VHostUserBase *vub = VHOST_USER_BASE(vdev); | ||||
| 
 | ||||
|     if (!vub->connected) { | ||||
|         return; | ||||
|     } | ||||
|     vub->connected = false; | ||||
| 
 | ||||
|     if (vhost_dev_is_started(&vub->vhost_dev)) { | ||||
|         vub_stop(vdev); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void vub_event(void *opaque, QEMUChrEvent event) | ||||
| { | ||||
|     DeviceState *dev = opaque; | ||||
|     VirtIODevice *vdev = VIRTIO_DEVICE(dev); | ||||
|     VHostUserBase *vub = VHOST_USER_BASE(vdev); | ||||
| 
 | ||||
|     switch (event) { | ||||
|     case CHR_EVENT_OPENED: | ||||
|         if (vub_connect(dev) < 0) { | ||||
|             qemu_chr_fe_disconnect(&vub->chardev); | ||||
|             return; | ||||
|         } | ||||
|         break; | ||||
|     case CHR_EVENT_CLOSED: | ||||
|         vub_disconnect(dev); | ||||
|         break; | ||||
|     case CHR_EVENT_BREAK: | ||||
|     case CHR_EVENT_MUX_IN: | ||||
|     case CHR_EVENT_MUX_OUT: | ||||
|         /* Ignore */ | ||||
|         break; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| static void vub_device_realize(DeviceState *dev, Error **errp) | ||||
| { | ||||
|     VirtIODevice *vdev = VIRTIO_DEVICE(dev); | ||||
|     VHostUserBase *vub = VHOST_USER_BASE(dev); | ||||
|     int ret; | ||||
| 
 | ||||
|     if (!vub->chardev.chr) { | ||||
|         error_setg(errp, "vhost-user-device: missing chardev"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (!vub->virtio_id) { | ||||
|         error_setg(errp, "vhost-user-device: need to define device id"); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (!vub->num_vqs) { | ||||
|         vub->num_vqs = 1; /* reasonable default? */ | ||||
|     } | ||||
| 
 | ||||
|     if (!vhost_user_init(&vub->vhost_user, &vub->chardev, errp)) { | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     virtio_init(vdev, vub->virtio_id, 0); | ||||
| 
 | ||||
|     /*
 | ||||
|      * Disable guest notifiers, by default all notifications will be via the | ||||
|      * asynchronous vhost-user socket. | ||||
|      */ | ||||
|     vdev->use_guest_notifier_mask = false; | ||||
| 
 | ||||
|     /* Allocate queues */ | ||||
|     vub->vqs = g_ptr_array_sized_new(vub->num_vqs); | ||||
|     for (int i = 0; i < vub->num_vqs; i++) { | ||||
|         g_ptr_array_add(vub->vqs, | ||||
|                         virtio_add_queue(vdev, 4, vub_handle_output)); | ||||
|     } | ||||
| 
 | ||||
|     vub->vhost_dev.nvqs = vub->num_vqs; | ||||
|     vub->vhost_dev.vqs = g_new0(struct vhost_virtqueue, vub->vhost_dev.nvqs); | ||||
| 
 | ||||
|     /* connect to backend */ | ||||
|     ret = vhost_dev_init(&vub->vhost_dev, &vub->vhost_user, | ||||
|                          VHOST_BACKEND_TYPE_USER, 0, errp); | ||||
| 
 | ||||
|     if (ret < 0) { | ||||
|         do_vhost_user_cleanup(vdev, vub); | ||||
|     } | ||||
| 
 | ||||
|     qemu_chr_fe_set_handlers(&vub->chardev, NULL, NULL, vub_event, NULL, | ||||
|                              dev, NULL, true); | ||||
| } | ||||
| 
 | ||||
| static void vub_device_unrealize(DeviceState *dev) | ||||
| { | ||||
|     VirtIODevice *vdev = VIRTIO_DEVICE(dev); | ||||
|     VHostUserBase *vub = VHOST_USER_BASE(dev); | ||||
|     struct vhost_virtqueue *vhost_vqs = vub->vhost_dev.vqs; | ||||
| 
 | ||||
|     /* This will stop vhost backend if appropriate. */ | ||||
|     vub_set_status(vdev, 0); | ||||
|     vhost_dev_cleanup(&vub->vhost_dev); | ||||
|     g_free(vhost_vqs); | ||||
|     do_vhost_user_cleanup(vdev, vub); | ||||
| } | ||||
| 
 | ||||
| static void vub_class_init(ObjectClass *klass, void *data) | ||||
| { | ||||
|     VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); | ||||
| 
 | ||||
|     vdc->realize = vub_device_realize; | ||||
|     vdc->unrealize = vub_device_unrealize; | ||||
|     vdc->get_features = vub_get_features; | ||||
|     vdc->set_status = vub_set_status; | ||||
| } | ||||
| 
 | ||||
| static const TypeInfo vub_info = { | ||||
|     .name = TYPE_VHOST_USER_BASE, | ||||
|     .parent = TYPE_VIRTIO_DEVICE, | ||||
|     .instance_size = sizeof(VHostUserBase), | ||||
|     .class_init = vub_class_init, | ||||
|     .class_size = sizeof(VHostUserBaseClass), | ||||
|     .abstract = true | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| /*
 | ||||
|  * The following is a concrete implementation of the base class which | ||||
|  * allows the user to define the key parameters via the command line. | ||||
|  */ | ||||
| 
 | ||||
| static const VMStateDescription vud_vmstate = { | ||||
|     .name = "vhost-user-device", | ||||
|     .unmigratable = 1, | ||||
| }; | ||||
| 
 | ||||
| static Property vud_properties[] = { | ||||
|     DEFINE_PROP_CHR("chardev", VHostUserBase, chardev), | ||||
|     DEFINE_PROP_UINT16("virtio-id", VHostUserBase, virtio_id, 0), | ||||
|     DEFINE_PROP_UINT32("num_vqs", VHostUserBase, num_vqs, 1), | ||||
|     DEFINE_PROP_END_OF_LIST(), | ||||
| }; | ||||
| 
 | ||||
| static void vud_class_init(ObjectClass *klass, void *data) | ||||
| { | ||||
|     DeviceClass *dc = DEVICE_CLASS(klass); | ||||
| 
 | ||||
|     device_class_set_props(dc, vud_properties); | ||||
|     dc->vmsd = &vud_vmstate; | ||||
|     set_bit(DEVICE_CATEGORY_INPUT, dc->categories); | ||||
| } | ||||
| 
 | ||||
| static const TypeInfo vud_info = { | ||||
|     .name = TYPE_VHOST_USER_DEVICE, | ||||
|     .parent = TYPE_VHOST_USER_BASE, | ||||
|     .instance_size = sizeof(VHostUserBase), | ||||
|     .class_init = vud_class_init, | ||||
|     .class_size = sizeof(VHostUserBaseClass), | ||||
| }; | ||||
| 
 | ||||
| static void vu_register_types(void) | ||||
| { | ||||
|     type_register_static(&vub_info); | ||||
|     type_register_static(&vud_info); | ||||
| } | ||||
| 
 | ||||
| type_init(vu_register_types) | ||||
							
								
								
									
										45
									
								
								include/hw/virtio/vhost-user-device.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								include/hw/virtio/vhost-user-device.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | ||||
| /*
 | ||||
|  * Vhost-user generic virtio device | ||||
|  * | ||||
|  * Copyright (c) 2023 Linaro Ltd | ||||
|  * | ||||
|  * SPDX-License-Identifier: GPL-2.0-or-later | ||||
|  */ | ||||
| 
 | ||||
| #ifndef QEMU_VHOST_USER_DEVICE_H | ||||
| #define QEMU_VHOST_USER_DEVICE_H | ||||
| 
 | ||||
| #include "hw/virtio/vhost.h" | ||||
| #include "hw/virtio/vhost-user.h" | ||||
| 
 | ||||
| #define TYPE_VHOST_USER_BASE "vhost-user-base" | ||||
| 
 | ||||
| OBJECT_DECLARE_TYPE(VHostUserBase, VHostUserBaseClass, VHOST_USER_BASE) | ||||
| 
 | ||||
| struct VHostUserBase { | ||||
|     VirtIODevice parent; | ||||
|     /* Properties */ | ||||
|     CharBackend chardev; | ||||
|     uint16_t virtio_id; | ||||
|     uint32_t num_vqs; | ||||
|     /* State tracking */ | ||||
|     VhostUserState vhost_user; | ||||
|     struct vhost_virtqueue *vhost_vq; | ||||
|     struct vhost_dev vhost_dev; | ||||
|     GPtrArray *vqs; | ||||
|     bool connected; | ||||
| }; | ||||
| 
 | ||||
|     /* needed so we can use the base realize after specialisation
 | ||||
|        tweaks */ | ||||
| struct VHostUserBaseClass { | ||||
|     /*< private >*/ | ||||
|     VirtioDeviceClass parent_class; | ||||
|     /*< public >*/ | ||||
|     DeviceRealize parent_realize; | ||||
| }; | ||||
| 
 | ||||
| /* shared for the benefit of the derived pci class */ | ||||
| #define TYPE_VHOST_USER_DEVICE "vhost-user-device" | ||||
| 
 | ||||
| #endif /* QEMU_VHOST_USER_DEVICE_H */ | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Alex Bennée
						Alex Bennée