| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350 |
- // SPDX-License-Identifier: GPL-2.0
- /*
- * Copyright (C) 2022-2023, Advanced Micro Devices, Inc.
- */
- #include <linux/vfio.h>
- #include <linux/cdx/cdx_bus.h>
- #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);
|