| /* |
| * 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) |