| /* | 
 |  * iommufd container backend | 
 |  * | 
 |  * Copyright (C) 2023 Intel Corporation. | 
 |  * Copyright Red Hat, Inc. 2023 | 
 |  * | 
 |  * Authors: Yi Liu <yi.l.liu@intel.com> | 
 |  *          Eric Auger <eric.auger@redhat.com> | 
 |  * | 
 |  * SPDX-License-Identifier: GPL-2.0-or-later | 
 |  */ | 
 |  | 
 | #include "qemu/osdep.h" | 
 | #include "sysemu/iommufd.h" | 
 | #include "qapi/error.h" | 
 | #include "qemu/module.h" | 
 | #include "qom/object_interfaces.h" | 
 | #include "qemu/error-report.h" | 
 | #include "monitor/monitor.h" | 
 | #include "trace.h" | 
 | #include "hw/vfio/vfio-common.h" | 
 | #include <sys/ioctl.h> | 
 | #include <linux/iommufd.h> | 
 |  | 
 | static void iommufd_backend_init(Object *obj) | 
 | { | 
 |     IOMMUFDBackend *be = IOMMUFD_BACKEND(obj); | 
 |  | 
 |     be->fd = -1; | 
 |     be->users = 0; | 
 |     be->owned = true; | 
 | } | 
 |  | 
 | static void iommufd_backend_finalize(Object *obj) | 
 | { | 
 |     IOMMUFDBackend *be = IOMMUFD_BACKEND(obj); | 
 |  | 
 |     if (be->owned) { | 
 |         close(be->fd); | 
 |         be->fd = -1; | 
 |     } | 
 | } | 
 |  | 
 | static void iommufd_backend_set_fd(Object *obj, const char *str, Error **errp) | 
 | { | 
 |     ERRP_GUARD(); | 
 |     IOMMUFDBackend *be = IOMMUFD_BACKEND(obj); | 
 |     int fd = -1; | 
 |  | 
 |     fd = monitor_fd_param(monitor_cur(), str, errp); | 
 |     if (fd == -1) { | 
 |         error_prepend(errp, "Could not parse remote object fd %s:", str); | 
 |         return; | 
 |     } | 
 |     be->fd = fd; | 
 |     be->owned = false; | 
 |     trace_iommu_backend_set_fd(be->fd); | 
 | } | 
 |  | 
 | static bool iommufd_backend_can_be_deleted(UserCreatable *uc) | 
 | { | 
 |     IOMMUFDBackend *be = IOMMUFD_BACKEND(uc); | 
 |  | 
 |     return !be->users; | 
 | } | 
 |  | 
 | static void iommufd_backend_class_init(ObjectClass *oc, void *data) | 
 | { | 
 |     UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); | 
 |  | 
 |     ucc->can_be_deleted = iommufd_backend_can_be_deleted; | 
 |  | 
 |     object_class_property_add_str(oc, "fd", NULL, iommufd_backend_set_fd); | 
 | } | 
 |  | 
 | bool iommufd_backend_connect(IOMMUFDBackend *be, Error **errp) | 
 | { | 
 |     int fd; | 
 |  | 
 |     if (be->owned && !be->users) { | 
 |         fd = qemu_open("/dev/iommu", O_RDWR, errp); | 
 |         if (fd < 0) { | 
 |             return false; | 
 |         } | 
 |         be->fd = fd; | 
 |     } | 
 |     be->users++; | 
 |  | 
 |     trace_iommufd_backend_connect(be->fd, be->owned, be->users); | 
 |     return true; | 
 | } | 
 |  | 
 | void iommufd_backend_disconnect(IOMMUFDBackend *be) | 
 | { | 
 |     if (!be->users) { | 
 |         goto out; | 
 |     } | 
 |     be->users--; | 
 |     if (!be->users && be->owned) { | 
 |         close(be->fd); | 
 |         be->fd = -1; | 
 |     } | 
 | out: | 
 |     trace_iommufd_backend_disconnect(be->fd, be->users); | 
 | } | 
 |  | 
 | bool iommufd_backend_alloc_ioas(IOMMUFDBackend *be, uint32_t *ioas_id, | 
 |                                 Error **errp) | 
 | { | 
 |     int fd = be->fd; | 
 |     struct iommu_ioas_alloc alloc_data  = { | 
 |         .size = sizeof(alloc_data), | 
 |         .flags = 0, | 
 |     }; | 
 |  | 
 |     if (ioctl(fd, IOMMU_IOAS_ALLOC, &alloc_data)) { | 
 |         error_setg_errno(errp, errno, "Failed to allocate ioas"); | 
 |         return false; | 
 |     } | 
 |  | 
 |     *ioas_id = alloc_data.out_ioas_id; | 
 |     trace_iommufd_backend_alloc_ioas(fd, *ioas_id); | 
 |  | 
 |     return true; | 
 | } | 
 |  | 
 | void iommufd_backend_free_id(IOMMUFDBackend *be, uint32_t id) | 
 | { | 
 |     int ret, fd = be->fd; | 
 |     struct iommu_destroy des = { | 
 |         .size = sizeof(des), | 
 |         .id = id, | 
 |     }; | 
 |  | 
 |     ret = ioctl(fd, IOMMU_DESTROY, &des); | 
 |     trace_iommufd_backend_free_id(fd, id, ret); | 
 |     if (ret) { | 
 |         error_report("Failed to free id: %u %m", id); | 
 |     } | 
 | } | 
 |  | 
 | int iommufd_backend_map_dma(IOMMUFDBackend *be, uint32_t ioas_id, hwaddr iova, | 
 |                             ram_addr_t size, void *vaddr, bool readonly) | 
 | { | 
 |     int ret, fd = be->fd; | 
 |     struct iommu_ioas_map map = { | 
 |         .size = sizeof(map), | 
 |         .flags = IOMMU_IOAS_MAP_READABLE | | 
 |                  IOMMU_IOAS_MAP_FIXED_IOVA, | 
 |         .ioas_id = ioas_id, | 
 |         .__reserved = 0, | 
 |         .user_va = (uintptr_t)vaddr, | 
 |         .iova = iova, | 
 |         .length = size, | 
 |     }; | 
 |  | 
 |     if (!readonly) { | 
 |         map.flags |= IOMMU_IOAS_MAP_WRITEABLE; | 
 |     } | 
 |  | 
 |     ret = ioctl(fd, IOMMU_IOAS_MAP, &map); | 
 |     trace_iommufd_backend_map_dma(fd, ioas_id, iova, size, | 
 |                                   vaddr, readonly, ret); | 
 |     if (ret) { | 
 |         ret = -errno; | 
 |  | 
 |         /* TODO: Not support mapping hardware PCI BAR region for now. */ | 
 |         if (errno == EFAULT) { | 
 |             warn_report("IOMMU_IOAS_MAP failed: %m, PCI BAR?"); | 
 |         } else { | 
 |             error_report("IOMMU_IOAS_MAP failed: %m"); | 
 |         } | 
 |     } | 
 |     return ret; | 
 | } | 
 |  | 
 | int iommufd_backend_unmap_dma(IOMMUFDBackend *be, uint32_t ioas_id, | 
 |                               hwaddr iova, ram_addr_t size) | 
 | { | 
 |     int ret, fd = be->fd; | 
 |     struct iommu_ioas_unmap unmap = { | 
 |         .size = sizeof(unmap), | 
 |         .ioas_id = ioas_id, | 
 |         .iova = iova, | 
 |         .length = size, | 
 |     }; | 
 |  | 
 |     ret = ioctl(fd, IOMMU_IOAS_UNMAP, &unmap); | 
 |     /* | 
 |      * IOMMUFD takes mapping as some kind of object, unmapping | 
 |      * nonexistent mapping is treated as deleting a nonexistent | 
 |      * object and return ENOENT. This is different from legacy | 
 |      * backend which allows it. vIOMMU may trigger a lot of | 
 |      * redundant unmapping, to avoid flush the log, treat them | 
 |      * as succeess for IOMMUFD just like legacy backend. | 
 |      */ | 
 |     if (ret && errno == ENOENT) { | 
 |         trace_iommufd_backend_unmap_dma_non_exist(fd, ioas_id, iova, size, ret); | 
 |         ret = 0; | 
 |     } else { | 
 |         trace_iommufd_backend_unmap_dma(fd, ioas_id, iova, size, ret); | 
 |     } | 
 |  | 
 |     if (ret) { | 
 |         ret = -errno; | 
 |         error_report("IOMMU_IOAS_UNMAP failed: %m"); | 
 |     } | 
 |     return ret; | 
 | } | 
 |  | 
 | bool iommufd_backend_alloc_hwpt(IOMMUFDBackend *be, uint32_t dev_id, | 
 |                                 uint32_t pt_id, uint32_t flags, | 
 |                                 uint32_t data_type, uint32_t data_len, | 
 |                                 void *data_ptr, uint32_t *out_hwpt, | 
 |                                 Error **errp) | 
 | { | 
 |     int ret, fd = be->fd; | 
 |     struct iommu_hwpt_alloc alloc_hwpt = { | 
 |         .size = sizeof(struct iommu_hwpt_alloc), | 
 |         .flags = flags, | 
 |         .dev_id = dev_id, | 
 |         .pt_id = pt_id, | 
 |         .data_type = data_type, | 
 |         .data_len = data_len, | 
 |         .data_uptr = (uintptr_t)data_ptr, | 
 |     }; | 
 |  | 
 |     ret = ioctl(fd, IOMMU_HWPT_ALLOC, &alloc_hwpt); | 
 |     trace_iommufd_backend_alloc_hwpt(fd, dev_id, pt_id, flags, data_type, | 
 |                                      data_len, (uintptr_t)data_ptr, | 
 |                                      alloc_hwpt.out_hwpt_id, ret); | 
 |     if (ret) { | 
 |         error_setg_errno(errp, errno, "Failed to allocate hwpt"); | 
 |         return false; | 
 |     } | 
 |  | 
 |     *out_hwpt = alloc_hwpt.out_hwpt_id; | 
 |     return true; | 
 | } | 
 |  | 
 | bool iommufd_backend_set_dirty_tracking(IOMMUFDBackend *be, | 
 |                                         uint32_t hwpt_id, bool start, | 
 |                                         Error **errp) | 
 | { | 
 |     int ret; | 
 |     struct iommu_hwpt_set_dirty_tracking set_dirty = { | 
 |             .size = sizeof(set_dirty), | 
 |             .hwpt_id = hwpt_id, | 
 |             .flags = start ? IOMMU_HWPT_DIRTY_TRACKING_ENABLE : 0, | 
 |     }; | 
 |  | 
 |     ret = ioctl(be->fd, IOMMU_HWPT_SET_DIRTY_TRACKING, &set_dirty); | 
 |     trace_iommufd_backend_set_dirty(be->fd, hwpt_id, start, ret ? errno : 0); | 
 |     if (ret) { | 
 |         error_setg_errno(errp, errno, | 
 |                          "IOMMU_HWPT_SET_DIRTY_TRACKING(hwpt_id %u) failed", | 
 |                          hwpt_id); | 
 |         return false; | 
 |     } | 
 |  | 
 |     return true; | 
 | } | 
 |  | 
 | bool iommufd_backend_get_dirty_bitmap(IOMMUFDBackend *be, | 
 |                                       uint32_t hwpt_id, | 
 |                                       uint64_t iova, ram_addr_t size, | 
 |                                       uint64_t page_size, uint64_t *data, | 
 |                                       Error **errp) | 
 | { | 
 |     int ret; | 
 |     struct iommu_hwpt_get_dirty_bitmap get_dirty_bitmap = { | 
 |         .size = sizeof(get_dirty_bitmap), | 
 |         .hwpt_id = hwpt_id, | 
 |         .iova = iova, | 
 |         .length = size, | 
 |         .page_size = page_size, | 
 |         .data = (uintptr_t)data, | 
 |     }; | 
 |  | 
 |     ret = ioctl(be->fd, IOMMU_HWPT_GET_DIRTY_BITMAP, &get_dirty_bitmap); | 
 |     trace_iommufd_backend_get_dirty_bitmap(be->fd, hwpt_id, iova, size, | 
 |                                            page_size, ret ? errno : 0); | 
 |     if (ret) { | 
 |         error_setg_errno(errp, errno, | 
 |                          "IOMMU_HWPT_GET_DIRTY_BITMAP (iova: 0x%"HWADDR_PRIx | 
 |                          " size: 0x"RAM_ADDR_FMT") failed", iova, size); | 
 |         return false; | 
 |     } | 
 |  | 
 |     return true; | 
 | } | 
 |  | 
 | bool iommufd_backend_get_device_info(IOMMUFDBackend *be, uint32_t devid, | 
 |                                      uint32_t *type, void *data, uint32_t len, | 
 |                                      uint64_t *caps, Error **errp) | 
 | { | 
 |     struct iommu_hw_info info = { | 
 |         .size = sizeof(info), | 
 |         .dev_id = devid, | 
 |         .data_len = len, | 
 |         .data_uptr = (uintptr_t)data, | 
 |     }; | 
 |  | 
 |     if (ioctl(be->fd, IOMMU_GET_HW_INFO, &info)) { | 
 |         error_setg_errno(errp, errno, "Failed to get hardware info"); | 
 |         return false; | 
 |     } | 
 |  | 
 |     g_assert(type); | 
 |     *type = info.out_data_type; | 
 |     g_assert(caps); | 
 |     *caps = info.out_capabilities; | 
 |  | 
 |     return true; | 
 | } | 
 |  | 
 | static int hiod_iommufd_get_cap(HostIOMMUDevice *hiod, int cap, Error **errp) | 
 | { | 
 |     HostIOMMUDeviceCaps *caps = &hiod->caps; | 
 |  | 
 |     switch (cap) { | 
 |     case HOST_IOMMU_DEVICE_CAP_IOMMU_TYPE: | 
 |         return caps->type; | 
 |     case HOST_IOMMU_DEVICE_CAP_AW_BITS: | 
 |         return vfio_device_get_aw_bits(hiod->agent); | 
 |     default: | 
 |         error_setg(errp, "%s: unsupported capability %x", hiod->name, cap); | 
 |         return -EINVAL; | 
 |     } | 
 | } | 
 |  | 
 | static void hiod_iommufd_class_init(ObjectClass *oc, void *data) | 
 | { | 
 |     HostIOMMUDeviceClass *hioc = HOST_IOMMU_DEVICE_CLASS(oc); | 
 |  | 
 |     hioc->get_cap = hiod_iommufd_get_cap; | 
 | }; | 
 |  | 
 | static const TypeInfo types[] = { | 
 |     { | 
 |         .name = TYPE_IOMMUFD_BACKEND, | 
 |         .parent = TYPE_OBJECT, | 
 |         .instance_size = sizeof(IOMMUFDBackend), | 
 |         .instance_init = iommufd_backend_init, | 
 |         .instance_finalize = iommufd_backend_finalize, | 
 |         .class_size = sizeof(IOMMUFDBackendClass), | 
 |         .class_init = iommufd_backend_class_init, | 
 |         .interfaces = (InterfaceInfo[]) { | 
 |             { TYPE_USER_CREATABLE }, | 
 |             { } | 
 |         } | 
 |     }, { | 
 |         .name = TYPE_HOST_IOMMU_DEVICE_IOMMUFD, | 
 |         .parent = TYPE_HOST_IOMMU_DEVICE, | 
 |         .class_init = hiod_iommufd_class_init, | 
 |         .abstract = true, | 
 |     } | 
 | }; | 
 |  | 
 | DEFINE_TYPES(types) |