/* * iommufd container backend * * Copyright (C) 2023 Intel Corporation. * Copyright Red Hat, Inc. 2023 * * Authors: Yi Liu * Eric Auger * * 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 #include 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_old("/dev/iommu", O_RDWR); if (fd < 0) { error_setg_errno(errp, errno, "/dev/iommu opening failed"); 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; } 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, .abstract = true, } }; DEFINE_TYPES(types)