// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2022-2023, Advanced Micro Devices, Inc. */ #include #include #include "private.h" static int vfio_cdx_open_device(struct vfio_device *core_vdev) { struct vfio_cdx_device *vdev = container_of(core_vdev, struct vfio_cdx_device, vdev); struct cdx_device *cdx_dev = to_cdx_device(core_vdev->dev); int count = cdx_dev->res_count; int i, ret; vdev->regions = kcalloc(count, sizeof(struct vfio_cdx_region), GFP_KERNEL_ACCOUNT); if (!vdev->regions) return -ENOMEM; for (i = 0; i < count; i++) { struct resource *res = &cdx_dev->res[i]; vdev->regions[i].addr = res->start; vdev->regions[i].size = resource_size(res); vdev->regions[i].type = res->flags; /* * Only regions addressed with PAGE granularity may be * MMAP'ed securely. */ if (!(vdev->regions[i].addr & ~PAGE_MASK) && !(vdev->regions[i].size & ~PAGE_MASK)) vdev->regions[i].flags |= VFIO_REGION_INFO_FLAG_MMAP; vdev->regions[i].flags |= VFIO_REGION_INFO_FLAG_READ; if (!(cdx_dev->res[i].flags & IORESOURCE_READONLY)) vdev->regions[i].flags |= VFIO_REGION_INFO_FLAG_WRITE; } ret = cdx_dev_reset(core_vdev->dev); if (ret) { kfree(vdev->regions); vdev->regions = NULL; return ret; } ret = cdx_clear_master(cdx_dev); if (ret) vdev->flags &= ~BME_SUPPORT; else vdev->flags |= BME_SUPPORT; return 0; } static void vfio_cdx_close_device(struct vfio_device *core_vdev) { struct vfio_cdx_device *vdev = container_of(core_vdev, struct vfio_cdx_device, vdev); kfree(vdev->regions); cdx_dev_reset(core_vdev->dev); vfio_cdx_irqs_cleanup(vdev); } static int vfio_cdx_bm_ctrl(struct vfio_device *core_vdev, u32 flags, void __user *arg, size_t argsz) { size_t minsz = offsetofend(struct vfio_device_feature_bus_master, op); struct vfio_cdx_device *vdev = container_of(core_vdev, struct vfio_cdx_device, vdev); struct cdx_device *cdx_dev = to_cdx_device(core_vdev->dev); struct vfio_device_feature_bus_master ops; int ret; if (!(vdev->flags & BME_SUPPORT)) return -ENOTTY; ret = vfio_check_feature(flags, argsz, VFIO_DEVICE_FEATURE_SET, sizeof(ops)); if (ret != 1) return ret; if (copy_from_user(&ops, arg, minsz)) return -EFAULT; switch (ops.op) { case VFIO_DEVICE_FEATURE_CLEAR_MASTER: return cdx_clear_master(cdx_dev); case VFIO_DEVICE_FEATURE_SET_MASTER: return cdx_set_master(cdx_dev); default: return -EINVAL; } } static int vfio_cdx_ioctl_feature(struct vfio_device *device, u32 flags, void __user *arg, size_t argsz) { switch (flags & VFIO_DEVICE_FEATURE_MASK) { case VFIO_DEVICE_FEATURE_BUS_MASTER: return vfio_cdx_bm_ctrl(device, flags, arg, argsz); default: return -ENOTTY; } } static int vfio_cdx_ioctl_get_info(struct vfio_cdx_device *vdev, struct vfio_device_info __user *arg) { unsigned long minsz = offsetofend(struct vfio_device_info, num_irqs); struct cdx_device *cdx_dev = to_cdx_device(vdev->vdev.dev); struct vfio_device_info info; if (copy_from_user(&info, arg, minsz)) return -EFAULT; if (info.argsz < minsz) return -EINVAL; info.flags = VFIO_DEVICE_FLAGS_CDX; info.flags |= VFIO_DEVICE_FLAGS_RESET; info.num_regions = cdx_dev->res_count; info.num_irqs = cdx_dev->num_msi ? 1 : 0; return copy_to_user(arg, &info, minsz) ? -EFAULT : 0; } static int vfio_cdx_ioctl_get_region_info(struct vfio_cdx_device *vdev, struct vfio_region_info __user *arg) { unsigned long minsz = offsetofend(struct vfio_region_info, offset); struct cdx_device *cdx_dev = to_cdx_device(vdev->vdev.dev); struct vfio_region_info info; if (copy_from_user(&info, arg, minsz)) return -EFAULT; if (info.argsz < minsz) return -EINVAL; if (info.index >= cdx_dev->res_count) return -EINVAL; /* map offset to the physical address */ info.offset = vfio_cdx_index_to_offset(info.index); info.size = vdev->regions[info.index].size; info.flags = vdev->regions[info.index].flags; return copy_to_user(arg, &info, minsz) ? -EFAULT : 0; } static int vfio_cdx_ioctl_get_irq_info(struct vfio_cdx_device *vdev, struct vfio_irq_info __user *arg) { unsigned long minsz = offsetofend(struct vfio_irq_info, count); struct cdx_device *cdx_dev = to_cdx_device(vdev->vdev.dev); struct vfio_irq_info info; if (copy_from_user(&info, arg, minsz)) return -EFAULT; if (info.argsz < minsz) return -EINVAL; if (info.index >= 1) return -EINVAL; if (!cdx_dev->num_msi) return -EINVAL; info.flags = VFIO_IRQ_INFO_EVENTFD | VFIO_IRQ_INFO_NORESIZE; info.count = cdx_dev->num_msi; return copy_to_user(arg, &info, minsz) ? -EFAULT : 0; } static int vfio_cdx_ioctl_set_irqs(struct vfio_cdx_device *vdev, struct vfio_irq_set __user *arg) { unsigned long minsz = offsetofend(struct vfio_irq_set, count); struct cdx_device *cdx_dev = to_cdx_device(vdev->vdev.dev); struct vfio_irq_set hdr; size_t data_size = 0; u8 *data = NULL; int ret = 0; if (copy_from_user(&hdr, arg, minsz)) return -EFAULT; ret = vfio_set_irqs_validate_and_prepare(&hdr, cdx_dev->num_msi, 1, &data_size); if (ret) return ret; if (data_size) { data = memdup_user(arg->data, data_size); if (IS_ERR(data)) return PTR_ERR(data); } ret = vfio_cdx_set_irqs_ioctl(vdev, hdr.flags, hdr.index, hdr.start, hdr.count, data); kfree(data); return ret; } static long vfio_cdx_ioctl(struct vfio_device *core_vdev, unsigned int cmd, unsigned long arg) { struct vfio_cdx_device *vdev = container_of(core_vdev, struct vfio_cdx_device, vdev); void __user *uarg = (void __user *)arg; switch (cmd) { case VFIO_DEVICE_GET_INFO: return vfio_cdx_ioctl_get_info(vdev, uarg); case VFIO_DEVICE_GET_REGION_INFO: return vfio_cdx_ioctl_get_region_info(vdev, uarg); case VFIO_DEVICE_GET_IRQ_INFO: return vfio_cdx_ioctl_get_irq_info(vdev, uarg); case VFIO_DEVICE_SET_IRQS: return vfio_cdx_ioctl_set_irqs(vdev, uarg); case VFIO_DEVICE_RESET: return cdx_dev_reset(core_vdev->dev); default: return -ENOTTY; } } static int vfio_cdx_mmap_mmio(struct vfio_cdx_region region, struct vm_area_struct *vma) { u64 size = vma->vm_end - vma->vm_start; u64 pgoff, base; pgoff = vma->vm_pgoff & ((1U << (VFIO_CDX_OFFSET_SHIFT - PAGE_SHIFT)) - 1); base = pgoff << PAGE_SHIFT; if (base + size > region.size) return -EINVAL; vma->vm_pgoff = (region.addr >> PAGE_SHIFT) + pgoff; vma->vm_page_prot = pgprot_device(vma->vm_page_prot); return io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, size, vma->vm_page_prot); } static int vfio_cdx_mmap(struct vfio_device *core_vdev, struct vm_area_struct *vma) { struct vfio_cdx_device *vdev = container_of(core_vdev, struct vfio_cdx_device, vdev); struct cdx_device *cdx_dev = to_cdx_device(core_vdev->dev); unsigned int index; index = vma->vm_pgoff >> (VFIO_CDX_OFFSET_SHIFT - PAGE_SHIFT); if (index >= cdx_dev->res_count) return -EINVAL; if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_MMAP)) return -EINVAL; if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_READ) && (vma->vm_flags & VM_READ)) return -EPERM; if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_WRITE) && (vma->vm_flags & VM_WRITE)) return -EPERM; return vfio_cdx_mmap_mmio(vdev->regions[index], vma); } static const struct vfio_device_ops vfio_cdx_ops = { .name = "vfio-cdx", .open_device = vfio_cdx_open_device, .close_device = vfio_cdx_close_device, .ioctl = vfio_cdx_ioctl, .device_feature = vfio_cdx_ioctl_feature, .mmap = vfio_cdx_mmap, .bind_iommufd = vfio_iommufd_physical_bind, .unbind_iommufd = vfio_iommufd_physical_unbind, .attach_ioas = vfio_iommufd_physical_attach_ioas, }; static int vfio_cdx_probe(struct cdx_device *cdx_dev) { struct vfio_cdx_device *vdev; struct device *dev = &cdx_dev->dev; int ret; vdev = vfio_alloc_device(vfio_cdx_device, vdev, dev, &vfio_cdx_ops); if (IS_ERR(vdev)) return PTR_ERR(vdev); ret = vfio_register_group_dev(&vdev->vdev); if (ret) goto out_uninit; dev_set_drvdata(dev, vdev); return 0; out_uninit: vfio_put_device(&vdev->vdev); return ret; } static int vfio_cdx_remove(struct cdx_device *cdx_dev) { struct device *dev = &cdx_dev->dev; struct vfio_cdx_device *vdev = dev_get_drvdata(dev); vfio_unregister_group_dev(&vdev->vdev); vfio_put_device(&vdev->vdev); return 0; } static const struct cdx_device_id vfio_cdx_table[] = { { CDX_DEVICE_DRIVER_OVERRIDE(CDX_ANY_ID, CDX_ANY_ID, CDX_ID_F_VFIO_DRIVER_OVERRIDE) }, /* match all by default */ {} }; MODULE_DEVICE_TABLE(cdx, vfio_cdx_table); static struct cdx_driver vfio_cdx_driver = { .probe = vfio_cdx_probe, .remove = vfio_cdx_remove, .match_id_table = vfio_cdx_table, .driver = { .name = "vfio-cdx", }, .driver_managed_dma = true, }; module_driver(vfio_cdx_driver, cdx_driver_register, cdx_driver_unregister); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("VFIO for CDX devices - User Level meta-driver"); MODULE_IMPORT_NS("CDX_BUS");