/* * virtio-iommu device * * Copyright (c) 2020 Red Hat, Inc. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2 or later, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . * */ #include "qemu/osdep.h" #include "qemu/iov.h" #include "qemu-common.h" #include "hw/qdev-properties.h" #include "hw/virtio/virtio.h" #include "sysemu/kvm.h" #include "trace.h" #include "standard-headers/linux/virtio_ids.h" #include "hw/virtio/virtio-bus.h" #include "hw/virtio/virtio-access.h" #include "hw/virtio/virtio-iommu.h" /* Max size */ #define VIOMMU_DEFAULT_QUEUE_SIZE 256 static int virtio_iommu_handle_attach(VirtIOIOMMU *s, struct iovec *iov, unsigned int iov_cnt) { return VIRTIO_IOMMU_S_UNSUPP; } static int virtio_iommu_handle_detach(VirtIOIOMMU *s, struct iovec *iov, unsigned int iov_cnt) { return VIRTIO_IOMMU_S_UNSUPP; } static int virtio_iommu_handle_map(VirtIOIOMMU *s, struct iovec *iov, unsigned int iov_cnt) { return VIRTIO_IOMMU_S_UNSUPP; } static int virtio_iommu_handle_unmap(VirtIOIOMMU *s, struct iovec *iov, unsigned int iov_cnt) { return VIRTIO_IOMMU_S_UNSUPP; } static void virtio_iommu_handle_command(VirtIODevice *vdev, VirtQueue *vq) { VirtIOIOMMU *s = VIRTIO_IOMMU(vdev); struct virtio_iommu_req_head head; struct virtio_iommu_req_tail tail = {}; VirtQueueElement *elem; unsigned int iov_cnt; struct iovec *iov; size_t sz; for (;;) { elem = virtqueue_pop(vq, sizeof(VirtQueueElement)); if (!elem) { return; } if (iov_size(elem->in_sg, elem->in_num) < sizeof(tail) || iov_size(elem->out_sg, elem->out_num) < sizeof(head)) { virtio_error(vdev, "virtio-iommu bad head/tail size"); virtqueue_detach_element(vq, elem, 0); g_free(elem); break; } iov_cnt = elem->out_num; iov = elem->out_sg; sz = iov_to_buf(iov, iov_cnt, 0, &head, sizeof(head)); if (unlikely(sz != sizeof(head))) { tail.status = VIRTIO_IOMMU_S_DEVERR; goto out; } qemu_mutex_lock(&s->mutex); switch (head.type) { case VIRTIO_IOMMU_T_ATTACH: tail.status = virtio_iommu_handle_attach(s, iov, iov_cnt); break; case VIRTIO_IOMMU_T_DETACH: tail.status = virtio_iommu_handle_detach(s, iov, iov_cnt); break; case VIRTIO_IOMMU_T_MAP: tail.status = virtio_iommu_handle_map(s, iov, iov_cnt); break; case VIRTIO_IOMMU_T_UNMAP: tail.status = virtio_iommu_handle_unmap(s, iov, iov_cnt); break; default: tail.status = VIRTIO_IOMMU_S_UNSUPP; } qemu_mutex_unlock(&s->mutex); out: sz = iov_from_buf(elem->in_sg, elem->in_num, 0, &tail, sizeof(tail)); assert(sz == sizeof(tail)); virtqueue_push(vq, elem, sizeof(tail)); virtio_notify(vdev, vq); g_free(elem); } } static void virtio_iommu_get_config(VirtIODevice *vdev, uint8_t *config_data) { VirtIOIOMMU *dev = VIRTIO_IOMMU(vdev); struct virtio_iommu_config *config = &dev->config; trace_virtio_iommu_get_config(config->page_size_mask, config->input_range.start, config->input_range.end, config->domain_range.end, config->probe_size); memcpy(config_data, &dev->config, sizeof(struct virtio_iommu_config)); } static void virtio_iommu_set_config(VirtIODevice *vdev, const uint8_t *config_data) { struct virtio_iommu_config config; memcpy(&config, config_data, sizeof(struct virtio_iommu_config)); trace_virtio_iommu_set_config(config.page_size_mask, config.input_range.start, config.input_range.end, config.domain_range.end, config.probe_size); } static uint64_t virtio_iommu_get_features(VirtIODevice *vdev, uint64_t f, Error **errp) { VirtIOIOMMU *dev = VIRTIO_IOMMU(vdev); f |= dev->features; trace_virtio_iommu_get_features(f); return f; } /* * Migration is not yet supported: most of the state consists * of balanced binary trees which are not yet ready for getting * migrated */ static const VMStateDescription vmstate_virtio_iommu_device = { .name = "virtio-iommu-device", .unmigratable = 1, }; static void virtio_iommu_device_realize(DeviceState *dev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); VirtIOIOMMU *s = VIRTIO_IOMMU(dev); virtio_init(vdev, "virtio-iommu", VIRTIO_ID_IOMMU, sizeof(struct virtio_iommu_config)); s->req_vq = virtio_add_queue(vdev, VIOMMU_DEFAULT_QUEUE_SIZE, virtio_iommu_handle_command); s->event_vq = virtio_add_queue(vdev, VIOMMU_DEFAULT_QUEUE_SIZE, NULL); s->config.page_size_mask = TARGET_PAGE_MASK; s->config.input_range.end = -1UL; s->config.domain_range.end = 32; virtio_add_feature(&s->features, VIRTIO_RING_F_EVENT_IDX); virtio_add_feature(&s->features, VIRTIO_RING_F_INDIRECT_DESC); virtio_add_feature(&s->features, VIRTIO_F_VERSION_1); virtio_add_feature(&s->features, VIRTIO_IOMMU_F_INPUT_RANGE); virtio_add_feature(&s->features, VIRTIO_IOMMU_F_DOMAIN_RANGE); virtio_add_feature(&s->features, VIRTIO_IOMMU_F_MAP_UNMAP); virtio_add_feature(&s->features, VIRTIO_IOMMU_F_BYPASS); virtio_add_feature(&s->features, VIRTIO_IOMMU_F_MMIO); qemu_mutex_init(&s->mutex); } static void virtio_iommu_device_unrealize(DeviceState *dev, Error **errp) { VirtIODevice *vdev = VIRTIO_DEVICE(dev); virtio_cleanup(vdev); } static void virtio_iommu_device_reset(VirtIODevice *vdev) { trace_virtio_iommu_device_reset(); } static void virtio_iommu_set_status(VirtIODevice *vdev, uint8_t status) { trace_virtio_iommu_device_status(status); } static void virtio_iommu_instance_init(Object *obj) { } static const VMStateDescription vmstate_virtio_iommu = { .name = "virtio-iommu", .minimum_version_id = 1, .version_id = 1, .fields = (VMStateField[]) { VMSTATE_VIRTIO_DEVICE, VMSTATE_END_OF_LIST() }, }; static Property virtio_iommu_properties[] = { DEFINE_PROP_LINK("primary-bus", VirtIOIOMMU, primary_bus, "PCI", PCIBus *), DEFINE_PROP_END_OF_LIST(), }; static void virtio_iommu_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); device_class_set_props(dc, virtio_iommu_properties); dc->vmsd = &vmstate_virtio_iommu; set_bit(DEVICE_CATEGORY_MISC, dc->categories); vdc->realize = virtio_iommu_device_realize; vdc->unrealize = virtio_iommu_device_unrealize; vdc->reset = virtio_iommu_device_reset; vdc->get_config = virtio_iommu_get_config; vdc->set_config = virtio_iommu_set_config; vdc->get_features = virtio_iommu_get_features; vdc->set_status = virtio_iommu_set_status; vdc->vmsd = &vmstate_virtio_iommu_device; } static const TypeInfo virtio_iommu_info = { .name = TYPE_VIRTIO_IOMMU, .parent = TYPE_VIRTIO_DEVICE, .instance_size = sizeof(VirtIOIOMMU), .instance_init = virtio_iommu_instance_init, .class_init = virtio_iommu_class_init, }; static void virtio_register_types(void) { type_register_static(&virtio_iommu_info); } type_init(virtio_register_types)