/* * Vhost Vdpa Device * * Copyright (c) Huawei Technologies Co., Ltd. 2022. All Rights Reserved. * * Authors: * Longpeng * * Largely based on the "vhost-user-blk-pci.c" and "vhost-user-blk.c" * implemented by: * Changpeng Liu * * This work is licensed under the terms of the GNU LGPL, version 2 or later. * See the COPYING.LIB file in the top-level directory. */ #include "qemu/osdep.h" #include #include #include "qapi/error.h" #include "qemu/error-report.h" #include "qemu/cutils.h" #include "hw/qdev-core.h" #include "hw/qdev-properties.h" #include "hw/qdev-properties-system.h" #include "hw/virtio/vhost.h" #include "hw/virtio/virtio.h" #include "hw/virtio/virtio-bus.h" #include "hw/virtio/vdpa-dev.h" #include "sysemu/sysemu.h" #include "sysemu/runstate.h" static void vhost_vdpa_device_dummy_handle_output(VirtIODevice *vdev, VirtQueue *vq) { /* Nothing to do */ } static uint32_t vhost_vdpa_device_get_u32(int fd, unsigned long int cmd, Error **errp) { uint32_t val = (uint32_t)-1; if (ioctl(fd, cmd, &val) < 0) { error_setg(errp, "vhost-vdpa-device: cmd 0x%lx failed: %s", cmd, strerror(errno)); } return val; } static void vhost_vdpa_device_realize(DeviceState *dev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VhostVdpaDevice *v = VHOST_VDPA_DEVICE(vdev); struct vhost_vdpa_iova_range iova_range; uint16_t max_queue_size; struct vhost_virtqueue *vqs; int i, ret; if (!v->vhostdev) { error_setg(errp, "vhost-vdpa-device: vhostdev are missing"); return; } v->vhostfd = qemu_open(v->vhostdev, O_RDWR, errp); if (*errp) { return; } v->vdpa.device_fd = v->vhostfd; v->vdev_id = vhost_vdpa_device_get_u32(v->vhostfd, VHOST_VDPA_GET_DEVICE_ID, errp); if (*errp) { goto out; } max_queue_size = vhost_vdpa_device_get_u32(v->vhostfd, VHOST_VDPA_GET_VRING_NUM, errp); if (*errp) { goto out; } if (v->queue_size > max_queue_size) { error_setg(errp, "vhost-vdpa-device: invalid queue_size: %u (max:%u)", v->queue_size, max_queue_size); goto out; } else if (!v->queue_size) { v->queue_size = max_queue_size; } v->num_queues = vhost_vdpa_device_get_u32(v->vhostfd, VHOST_VDPA_GET_VQS_COUNT, errp); if (*errp) { goto out; } if (!v->num_queues || v->num_queues > VIRTIO_QUEUE_MAX) { error_setg(errp, "invalid number of virtqueues: %u (max:%u)", v->num_queues, VIRTIO_QUEUE_MAX); goto out; } v->dev.nvqs = v->num_queues; vqs = g_new0(struct vhost_virtqueue, v->dev.nvqs); v->dev.vqs = vqs; v->dev.vq_index = 0; v->dev.vq_index_end = v->dev.nvqs; v->dev.backend_features = 0; v->started = false; ret = vhost_vdpa_get_iova_range(v->vhostfd, &iova_range); if (ret < 0) { error_setg(errp, "vhost-vdpa-device: get iova range failed: %s", strerror(-ret)); goto free_vqs; } v->vdpa.shared = g_new0(VhostVDPAShared, 1); v->vdpa.shared->iova_range = iova_range; ret = vhost_dev_init(&v->dev, &v->vdpa, VHOST_BACKEND_TYPE_VDPA, 0, NULL); if (ret < 0) { error_setg(errp, "vhost-vdpa-device: vhost initialization failed: %s", strerror(-ret)); goto free_vqs; } v->config_size = vhost_vdpa_device_get_u32(v->vhostfd, VHOST_VDPA_GET_CONFIG_SIZE, errp); if (*errp) { goto vhost_cleanup; } /* * Invoke .post_init() to initialize the transport-specific fields * before calling virtio_init(). */ if (v->post_init && v->post_init(v, errp) < 0) { goto vhost_cleanup; } v->config = g_malloc0(v->config_size); ret = vhost_dev_get_config(&v->dev, v->config, v->config_size, NULL); if (ret < 0) { error_setg(errp, "vhost-vdpa-device: get config failed"); goto free_config; } virtio_init(vdev, v->vdev_id, v->config_size); v->virtqs = g_new0(VirtQueue *, v->dev.nvqs); for (i = 0; i < v->dev.nvqs; i++) { v->virtqs[i] = virtio_add_queue(vdev, v->queue_size, vhost_vdpa_device_dummy_handle_output); } return; free_config: g_free(v->config); vhost_cleanup: vhost_dev_cleanup(&v->dev); free_vqs: g_free(vqs); g_free(v->vdpa.shared); out: qemu_close(v->vhostfd); v->vhostfd = -1; } static void vhost_vdpa_device_unrealize(DeviceState *dev) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VhostVdpaDevice *s = VHOST_VDPA_DEVICE(vdev); int i; virtio_set_status(vdev, 0); for (i = 0; i < s->num_queues; i++) { virtio_delete_queue(s->virtqs[i]); } g_free(s->virtqs); virtio_cleanup(vdev); g_free(s->config); g_free(s->dev.vqs); vhost_dev_cleanup(&s->dev); g_free(s->vdpa.shared); qemu_close(s->vhostfd); s->vhostfd = -1; } static void vhost_vdpa_device_get_config(VirtIODevice *vdev, uint8_t *config) { VhostVdpaDevice *s = VHOST_VDPA_DEVICE(vdev); memcpy(config, s->config, s->config_size); } static void vhost_vdpa_device_set_config(VirtIODevice *vdev, const uint8_t *config) { VhostVdpaDevice *s = VHOST_VDPA_DEVICE(vdev); int ret; ret = vhost_dev_set_config(&s->dev, s->config, 0, s->config_size, VHOST_SET_CONFIG_TYPE_FRONTEND); if (ret) { error_report("set device config space failed"); return; } } static uint64_t vhost_vdpa_device_get_features(VirtIODevice *vdev, uint64_t features, Error **errp) { VhostVdpaDevice *s = VHOST_VDPA_DEVICE(vdev); uint64_t backend_features = s->dev.features; if (!virtio_has_feature(features, VIRTIO_F_IOMMU_PLATFORM)) { virtio_clear_feature(&backend_features, VIRTIO_F_IOMMU_PLATFORM); } return backend_features; } static int vhost_vdpa_device_start(VirtIODevice *vdev, Error **errp) { VhostVdpaDevice *s = VHOST_VDPA_DEVICE(vdev); BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); int i, ret; if (!k->set_guest_notifiers) { error_setg(errp, "binding does not support guest notifiers"); return -ENOSYS; } ret = vhost_dev_enable_notifiers(&s->dev, vdev); if (ret < 0) { error_setg_errno(errp, -ret, "Error enabling host notifiers"); return ret; } ret = k->set_guest_notifiers(qbus->parent, s->dev.nvqs, true); if (ret < 0) { error_setg_errno(errp, -ret, "Error binding guest notifier"); goto err_host_notifiers; } s->dev.acked_features = vdev->guest_features; ret = vhost_dev_start(&s->dev, vdev, false); if (ret < 0) { error_setg_errno(errp, -ret, "Error starting vhost"); goto err_guest_notifiers; } for (i = 0; i < s->dev.nvqs; ++i) { vhost_vdpa_set_vring_ready(&s->vdpa, i); } s->started = true; /* * 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 < s->dev.nvqs; i++) { vhost_virtqueue_mask(&s->dev, vdev, i, false); } return ret; err_guest_notifiers: k->set_guest_notifiers(qbus->parent, s->dev.nvqs, false); err_host_notifiers: vhost_dev_disable_notifiers(&s->dev, vdev); return ret; } static void vhost_vdpa_device_stop(VirtIODevice *vdev) { VhostVdpaDevice *s = VHOST_VDPA_DEVICE(vdev); BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); int ret; if (!s->started) { return; } s->started = false; if (!k->set_guest_notifiers) { return; } vhost_dev_stop(&s->dev, vdev, false); ret = k->set_guest_notifiers(qbus->parent, s->dev.nvqs, false); if (ret < 0) { error_report("vhost guest notifier cleanup failed: %d", ret); return; } vhost_dev_disable_notifiers(&s->dev, vdev); } static void vhost_vdpa_device_set_status(VirtIODevice *vdev, uint8_t status) { VhostVdpaDevice *s = VHOST_VDPA_DEVICE(vdev); bool should_start = virtio_device_started(vdev, status); Error *local_err = NULL; int ret; if (!vdev->vm_running) { should_start = false; } if (s->started == should_start) { return; } if (should_start) { ret = vhost_vdpa_device_start(vdev, &local_err); if (ret < 0) { error_reportf_err(local_err, "vhost-vdpa-device: start failed: "); } } else { vhost_vdpa_device_stop(vdev); } } static Property vhost_vdpa_device_properties[] = { DEFINE_PROP_STRING("vhostdev", VhostVdpaDevice, vhostdev), DEFINE_PROP_UINT16("queue-size", VhostVdpaDevice, queue_size, 0), DEFINE_PROP_END_OF_LIST(), }; static const VMStateDescription vmstate_vhost_vdpa_device = { .name = "vhost-vdpa-device", .unmigratable = 1, .minimum_version_id = 1, .version_id = 1, .fields = (VMStateField[]) { VMSTATE_VIRTIO_DEVICE, VMSTATE_END_OF_LIST() }, }; static void vhost_vdpa_device_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); device_class_set_props(dc, vhost_vdpa_device_properties); dc->desc = "VDPA-based generic device assignment"; dc->vmsd = &vmstate_vhost_vdpa_device; set_bit(DEVICE_CATEGORY_MISC, dc->categories); vdc->realize = vhost_vdpa_device_realize; vdc->unrealize = vhost_vdpa_device_unrealize; vdc->get_config = vhost_vdpa_device_get_config; vdc->set_config = vhost_vdpa_device_set_config; vdc->get_features = vhost_vdpa_device_get_features; vdc->set_status = vhost_vdpa_device_set_status; } static void vhost_vdpa_device_instance_init(Object *obj) { VhostVdpaDevice *s = VHOST_VDPA_DEVICE(obj); device_add_bootindex_property(obj, &s->bootindex, "bootindex", NULL, DEVICE(obj)); } static const TypeInfo vhost_vdpa_device_info = { .name = TYPE_VHOST_VDPA_DEVICE, .parent = TYPE_VIRTIO_DEVICE, .instance_size = sizeof(VhostVdpaDevice), .class_init = vhost_vdpa_device_class_init, .instance_init = vhost_vdpa_device_instance_init, }; static void register_vhost_vdpa_device_type(void) { type_register_static(&vhost_vdpa_device_info); } type_init(register_vhost_vdpa_device_type);