| /* |
| * Container for vfio-user IOMMU type: rather than communicating with the kernel |
| * vfio driver, we communicate over a socket to a server using the vfio-user |
| * protocol. |
| * |
| * SPDX-License-Identifier: GPL-2.0-or-later |
| */ |
| |
| #include <sys/ioctl.h> |
| #include <linux/vfio.h> |
| #include "qemu/osdep.h" |
| |
| #include "hw/vfio-user/container.h" |
| #include "hw/vfio-user/device.h" |
| #include "hw/vfio-user/trace.h" |
| #include "hw/vfio/vfio-device.h" |
| #include "hw/vfio/vfio-listener.h" |
| #include "qapi/error.h" |
| |
| /* |
| * When DMA space is the physical address space, the region add/del listeners |
| * will fire during memory update transactions. These depend on BQL being held, |
| * so do any resulting map/demap ops async while keeping BQL. |
| */ |
| static void vfio_user_listener_begin(VFIOContainerBase *bcontainer) |
| { |
| VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer, |
| bcontainer); |
| |
| container->proxy->async_ops = true; |
| } |
| |
| static void vfio_user_listener_commit(VFIOContainerBase *bcontainer) |
| { |
| VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer, |
| bcontainer); |
| |
| /* wait here for any async requests sent during the transaction */ |
| container->proxy->async_ops = false; |
| vfio_user_wait_reqs(container->proxy); |
| } |
| |
| static int vfio_user_dma_unmap(const VFIOContainerBase *bcontainer, |
| hwaddr iova, ram_addr_t size, |
| IOMMUTLBEntry *iotlb, bool unmap_all) |
| { |
| VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer, |
| bcontainer); |
| Error *local_err = NULL; |
| int ret = 0; |
| |
| VFIOUserDMAUnmap *msgp = g_malloc(sizeof(*msgp)); |
| |
| vfio_user_request_msg(&msgp->hdr, VFIO_USER_DMA_UNMAP, sizeof(*msgp), 0); |
| msgp->argsz = sizeof(struct vfio_iommu_type1_dma_unmap); |
| msgp->flags = unmap_all ? VFIO_DMA_UNMAP_FLAG_ALL : 0; |
| msgp->iova = iova; |
| msgp->size = size; |
| trace_vfio_user_dma_unmap(msgp->iova, msgp->size, msgp->flags, |
| container->proxy->async_ops); |
| |
| if (container->proxy->async_ops) { |
| if (!vfio_user_send_nowait(container->proxy, &msgp->hdr, NULL, |
| 0, &local_err)) { |
| error_report_err(local_err); |
| ret = -EFAULT; |
| } |
| } else { |
| if (!vfio_user_send_wait(container->proxy, &msgp->hdr, NULL, |
| 0, &local_err)) { |
| error_report_err(local_err); |
| ret = -EFAULT; |
| } |
| |
| if (msgp->hdr.flags & VFIO_USER_ERROR) { |
| ret = -msgp->hdr.error_reply; |
| } |
| |
| g_free(msgp); |
| } |
| |
| return ret; |
| } |
| |
| static int vfio_user_dma_map(const VFIOContainerBase *bcontainer, hwaddr iova, |
| ram_addr_t size, void *vaddr, bool readonly, |
| MemoryRegion *mrp) |
| { |
| VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer, |
| bcontainer); |
| int fd = memory_region_get_fd(mrp); |
| Error *local_err = NULL; |
| int ret = 0; |
| |
| VFIOUserFDs *fds = NULL; |
| VFIOUserDMAMap *msgp = g_malloc0(sizeof(*msgp)); |
| |
| vfio_user_request_msg(&msgp->hdr, VFIO_USER_DMA_MAP, sizeof(*msgp), 0); |
| msgp->argsz = sizeof(struct vfio_iommu_type1_dma_map); |
| msgp->flags = VFIO_DMA_MAP_FLAG_READ; |
| msgp->offset = 0; |
| msgp->iova = iova; |
| msgp->size = size; |
| |
| /* |
| * vaddr enters as a QEMU process address; make it either a file offset |
| * for mapped areas or leave as 0. |
| */ |
| if (fd != -1) { |
| msgp->offset = qemu_ram_block_host_offset(mrp->ram_block, vaddr); |
| } |
| |
| if (!readonly) { |
| msgp->flags |= VFIO_DMA_MAP_FLAG_WRITE; |
| } |
| |
| trace_vfio_user_dma_map(msgp->iova, msgp->size, msgp->offset, msgp->flags, |
| container->proxy->async_ops); |
| |
| /* |
| * The async_ops case sends without blocking. They're later waited for in |
| * vfio_send_wait_reqs. |
| */ |
| if (container->proxy->async_ops) { |
| /* can't use auto variable since we don't block */ |
| if (fd != -1) { |
| fds = vfio_user_getfds(1); |
| fds->send_fds = 1; |
| fds->fds[0] = fd; |
| } |
| |
| if (!vfio_user_send_nowait(container->proxy, &msgp->hdr, fds, |
| 0, &local_err)) { |
| error_report_err(local_err); |
| ret = -EFAULT; |
| } |
| } else { |
| VFIOUserFDs local_fds = { 1, 0, &fd }; |
| |
| fds = fd != -1 ? &local_fds : NULL; |
| |
| if (!vfio_user_send_wait(container->proxy, &msgp->hdr, fds, |
| 0, &local_err)) { |
| error_report_err(local_err); |
| ret = -EFAULT; |
| } |
| |
| if (msgp->hdr.flags & VFIO_USER_ERROR) { |
| ret = -msgp->hdr.error_reply; |
| } |
| |
| g_free(msgp); |
| } |
| |
| return ret; |
| } |
| |
| static int |
| vfio_user_set_dirty_page_tracking(const VFIOContainerBase *bcontainer, |
| bool start, Error **errp) |
| { |
| error_setg_errno(errp, ENOTSUP, "Not supported"); |
| return -ENOTSUP; |
| } |
| |
| static int vfio_user_query_dirty_bitmap(const VFIOContainerBase *bcontainer, |
| VFIOBitmap *vbmap, hwaddr iova, |
| hwaddr size, Error **errp) |
| { |
| error_setg_errno(errp, ENOTSUP, "Not supported"); |
| return -ENOTSUP; |
| } |
| |
| static bool vfio_user_setup(VFIOContainerBase *bcontainer, Error **errp) |
| { |
| VFIOUserContainer *container = container_of(bcontainer, VFIOUserContainer, |
| bcontainer); |
| |
| assert(container->proxy->dma_pgsizes != 0); |
| bcontainer->pgsizes = container->proxy->dma_pgsizes; |
| bcontainer->dma_max_mappings = container->proxy->max_dma; |
| |
| /* No live migration support yet. */ |
| bcontainer->dirty_pages_supported = false; |
| bcontainer->max_dirty_bitmap_size = container->proxy->max_bitmap; |
| bcontainer->dirty_pgsizes = container->proxy->migr_pgsize; |
| |
| return true; |
| } |
| |
| static VFIOUserContainer *vfio_user_create_container(VFIODevice *vbasedev, |
| Error **errp) |
| { |
| VFIOUserContainer *container; |
| |
| container = VFIO_IOMMU_USER(object_new(TYPE_VFIO_IOMMU_USER)); |
| container->proxy = vbasedev->proxy; |
| return container; |
| } |
| |
| /* |
| * Try to mirror vfio_container_connect() as much as possible. |
| */ |
| static VFIOUserContainer * |
| vfio_user_container_connect(AddressSpace *as, VFIODevice *vbasedev, |
| Error **errp) |
| { |
| VFIOContainerBase *bcontainer; |
| VFIOUserContainer *container; |
| VFIOAddressSpace *space; |
| VFIOIOMMUClass *vioc; |
| int ret; |
| |
| space = vfio_address_space_get(as); |
| |
| container = vfio_user_create_container(vbasedev, errp); |
| if (!container) { |
| goto put_space_exit; |
| } |
| |
| bcontainer = &container->bcontainer; |
| |
| ret = ram_block_uncoordinated_discard_disable(true); |
| if (ret) { |
| error_setg_errno(errp, -ret, "Cannot set discarding of RAM broken"); |
| goto free_container_exit; |
| } |
| |
| vioc = VFIO_IOMMU_GET_CLASS(bcontainer); |
| assert(vioc->setup); |
| |
| if (!vioc->setup(bcontainer, errp)) { |
| goto enable_discards_exit; |
| } |
| |
| vfio_address_space_insert(space, bcontainer); |
| |
| if (!vfio_listener_register(bcontainer, errp)) { |
| goto listener_release_exit; |
| } |
| |
| bcontainer->initialized = true; |
| |
| return container; |
| |
| listener_release_exit: |
| vfio_listener_unregister(bcontainer); |
| if (vioc->release) { |
| vioc->release(bcontainer); |
| } |
| |
| enable_discards_exit: |
| ram_block_uncoordinated_discard_disable(false); |
| |
| free_container_exit: |
| object_unref(container); |
| |
| put_space_exit: |
| vfio_address_space_put(space); |
| |
| return NULL; |
| } |
| |
| static void vfio_user_container_disconnect(VFIOUserContainer *container) |
| { |
| VFIOContainerBase *bcontainer = &container->bcontainer; |
| VFIOIOMMUClass *vioc = VFIO_IOMMU_GET_CLASS(bcontainer); |
| VFIOAddressSpace *space = bcontainer->space; |
| |
| ram_block_uncoordinated_discard_disable(false); |
| |
| vfio_listener_unregister(bcontainer); |
| if (vioc->release) { |
| vioc->release(bcontainer); |
| } |
| |
| object_unref(container); |
| |
| vfio_address_space_put(space); |
| } |
| |
| static bool vfio_user_device_get(VFIOUserContainer *container, |
| VFIODevice *vbasedev, Error **errp) |
| { |
| struct vfio_device_info info = { .argsz = sizeof(info) }; |
| |
| |
| if (!vfio_user_get_device_info(vbasedev->proxy, &info, errp)) { |
| return false; |
| } |
| |
| vbasedev->fd = -1; |
| |
| vfio_device_prepare(vbasedev, &container->bcontainer, &info); |
| |
| return true; |
| } |
| |
| /* |
| * vfio_user_device_attach: attach a device to a new container. |
| */ |
| static bool vfio_user_device_attach(const char *name, VFIODevice *vbasedev, |
| AddressSpace *as, Error **errp) |
| { |
| VFIOUserContainer *container; |
| |
| container = vfio_user_container_connect(as, vbasedev, errp); |
| if (container == NULL) { |
| error_prepend(errp, "failed to connect proxy"); |
| return false; |
| } |
| |
| return vfio_user_device_get(container, vbasedev, errp); |
| } |
| |
| static void vfio_user_device_detach(VFIODevice *vbasedev) |
| { |
| VFIOUserContainer *container = container_of(vbasedev->bcontainer, |
| VFIOUserContainer, bcontainer); |
| |
| vfio_device_unprepare(vbasedev); |
| |
| vfio_user_container_disconnect(container); |
| } |
| |
| static int vfio_user_pci_hot_reset(VFIODevice *vbasedev, bool single) |
| { |
| /* ->needs_reset is always false for vfio-user. */ |
| return 0; |
| } |
| |
| static void vfio_iommu_user_class_init(ObjectClass *klass, const void *data) |
| { |
| VFIOIOMMUClass *vioc = VFIO_IOMMU_CLASS(klass); |
| |
| vioc->setup = vfio_user_setup; |
| vioc->listener_begin = vfio_user_listener_begin, |
| vioc->listener_commit = vfio_user_listener_commit, |
| vioc->dma_map = vfio_user_dma_map; |
| vioc->dma_unmap = vfio_user_dma_unmap; |
| vioc->attach_device = vfio_user_device_attach; |
| vioc->detach_device = vfio_user_device_detach; |
| vioc->set_dirty_page_tracking = vfio_user_set_dirty_page_tracking; |
| vioc->query_dirty_bitmap = vfio_user_query_dirty_bitmap; |
| vioc->pci_hot_reset = vfio_user_pci_hot_reset; |
| }; |
| |
| static const TypeInfo types[] = { |
| { |
| .name = TYPE_VFIO_IOMMU_USER, |
| .parent = TYPE_VFIO_IOMMU, |
| .instance_size = sizeof(VFIOUserContainer), |
| .class_init = vfio_iommu_user_class_init, |
| }, |
| }; |
| |
| DEFINE_TYPES(types) |