|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +/* |
| 3 | + * Copyright (C) 2022-2023, Advanced Micro Devices, Inc. |
| 4 | + */ |
| 5 | + |
| 6 | +#include <linux/vfio.h> |
| 7 | +#include <linux/cdx/cdx_bus.h> |
| 8 | + |
| 9 | +#include "private.h" |
| 10 | + |
| 11 | +static int vfio_cdx_open_device(struct vfio_device *core_vdev) |
| 12 | +{ |
| 13 | + struct vfio_cdx_device *vdev = |
| 14 | + container_of(core_vdev, struct vfio_cdx_device, vdev); |
| 15 | + struct cdx_device *cdx_dev = to_cdx_device(core_vdev->dev); |
| 16 | + int count = cdx_dev->res_count; |
| 17 | + int i; |
| 18 | + |
| 19 | + vdev->regions = kcalloc(count, sizeof(struct vfio_cdx_region), |
| 20 | + GFP_KERNEL_ACCOUNT); |
| 21 | + if (!vdev->regions) |
| 22 | + return -ENOMEM; |
| 23 | + |
| 24 | + for (i = 0; i < count; i++) { |
| 25 | + struct resource *res = &cdx_dev->res[i]; |
| 26 | + |
| 27 | + vdev->regions[i].addr = res->start; |
| 28 | + vdev->regions[i].size = resource_size(res); |
| 29 | + vdev->regions[i].type = res->flags; |
| 30 | + /* |
| 31 | + * Only regions addressed with PAGE granularity may be |
| 32 | + * MMAP'ed securely. |
| 33 | + */ |
| 34 | + if (!(vdev->regions[i].addr & ~PAGE_MASK) && |
| 35 | + !(vdev->regions[i].size & ~PAGE_MASK)) |
| 36 | + vdev->regions[i].flags |= |
| 37 | + VFIO_REGION_INFO_FLAG_MMAP; |
| 38 | + vdev->regions[i].flags |= VFIO_REGION_INFO_FLAG_READ; |
| 39 | + if (!(cdx_dev->res[i].flags & IORESOURCE_READONLY)) |
| 40 | + vdev->regions[i].flags |= VFIO_REGION_INFO_FLAG_WRITE; |
| 41 | + } |
| 42 | + |
| 43 | + return 0; |
| 44 | +} |
| 45 | + |
| 46 | +static void vfio_cdx_close_device(struct vfio_device *core_vdev) |
| 47 | +{ |
| 48 | + struct vfio_cdx_device *vdev = |
| 49 | + container_of(core_vdev, struct vfio_cdx_device, vdev); |
| 50 | + |
| 51 | + kfree(vdev->regions); |
| 52 | + cdx_dev_reset(core_vdev->dev); |
| 53 | +} |
| 54 | + |
| 55 | +static int vfio_cdx_ioctl_get_info(struct vfio_cdx_device *vdev, |
| 56 | + struct vfio_device_info __user *arg) |
| 57 | +{ |
| 58 | + unsigned long minsz = offsetofend(struct vfio_device_info, num_irqs); |
| 59 | + struct cdx_device *cdx_dev = to_cdx_device(vdev->vdev.dev); |
| 60 | + struct vfio_device_info info; |
| 61 | + |
| 62 | + if (copy_from_user(&info, arg, minsz)) |
| 63 | + return -EFAULT; |
| 64 | + |
| 65 | + if (info.argsz < minsz) |
| 66 | + return -EINVAL; |
| 67 | + |
| 68 | + info.flags = VFIO_DEVICE_FLAGS_CDX; |
| 69 | + info.flags |= VFIO_DEVICE_FLAGS_RESET; |
| 70 | + |
| 71 | + info.num_regions = cdx_dev->res_count; |
| 72 | + info.num_irqs = 0; |
| 73 | + |
| 74 | + return copy_to_user(arg, &info, minsz) ? -EFAULT : 0; |
| 75 | +} |
| 76 | + |
| 77 | +static int vfio_cdx_ioctl_get_region_info(struct vfio_cdx_device *vdev, |
| 78 | + struct vfio_region_info __user *arg) |
| 79 | +{ |
| 80 | + unsigned long minsz = offsetofend(struct vfio_region_info, offset); |
| 81 | + struct cdx_device *cdx_dev = to_cdx_device(vdev->vdev.dev); |
| 82 | + struct vfio_region_info info; |
| 83 | + |
| 84 | + if (copy_from_user(&info, arg, minsz)) |
| 85 | + return -EFAULT; |
| 86 | + |
| 87 | + if (info.argsz < minsz) |
| 88 | + return -EINVAL; |
| 89 | + |
| 90 | + if (info.index >= cdx_dev->res_count) |
| 91 | + return -EINVAL; |
| 92 | + |
| 93 | + /* map offset to the physical address */ |
| 94 | + info.offset = vfio_cdx_index_to_offset(info.index); |
| 95 | + info.size = vdev->regions[info.index].size; |
| 96 | + info.flags = vdev->regions[info.index].flags; |
| 97 | + |
| 98 | + return copy_to_user(arg, &info, minsz) ? -EFAULT : 0; |
| 99 | +} |
| 100 | + |
| 101 | +static long vfio_cdx_ioctl(struct vfio_device *core_vdev, |
| 102 | + unsigned int cmd, unsigned long arg) |
| 103 | +{ |
| 104 | + struct vfio_cdx_device *vdev = |
| 105 | + container_of(core_vdev, struct vfio_cdx_device, vdev); |
| 106 | + void __user *uarg = (void __user *)arg; |
| 107 | + |
| 108 | + switch (cmd) { |
| 109 | + case VFIO_DEVICE_GET_INFO: |
| 110 | + return vfio_cdx_ioctl_get_info(vdev, uarg); |
| 111 | + case VFIO_DEVICE_GET_REGION_INFO: |
| 112 | + return vfio_cdx_ioctl_get_region_info(vdev, uarg); |
| 113 | + case VFIO_DEVICE_RESET: |
| 114 | + return cdx_dev_reset(core_vdev->dev); |
| 115 | + default: |
| 116 | + return -ENOTTY; |
| 117 | + } |
| 118 | +} |
| 119 | + |
| 120 | +static int vfio_cdx_mmap_mmio(struct vfio_cdx_region region, |
| 121 | + struct vm_area_struct *vma) |
| 122 | +{ |
| 123 | + u64 size = vma->vm_end - vma->vm_start; |
| 124 | + u64 pgoff, base; |
| 125 | + |
| 126 | + pgoff = vma->vm_pgoff & |
| 127 | + ((1U << (VFIO_CDX_OFFSET_SHIFT - PAGE_SHIFT)) - 1); |
| 128 | + base = pgoff << PAGE_SHIFT; |
| 129 | + |
| 130 | + if (base + size > region.size) |
| 131 | + return -EINVAL; |
| 132 | + |
| 133 | + vma->vm_pgoff = (region.addr >> PAGE_SHIFT) + pgoff; |
| 134 | + vma->vm_page_prot = pgprot_device(vma->vm_page_prot); |
| 135 | + |
| 136 | + return io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, |
| 137 | + size, vma->vm_page_prot); |
| 138 | +} |
| 139 | + |
| 140 | +static int vfio_cdx_mmap(struct vfio_device *core_vdev, |
| 141 | + struct vm_area_struct *vma) |
| 142 | +{ |
| 143 | + struct vfio_cdx_device *vdev = |
| 144 | + container_of(core_vdev, struct vfio_cdx_device, vdev); |
| 145 | + struct cdx_device *cdx_dev = to_cdx_device(core_vdev->dev); |
| 146 | + unsigned int index; |
| 147 | + |
| 148 | + index = vma->vm_pgoff >> (VFIO_CDX_OFFSET_SHIFT - PAGE_SHIFT); |
| 149 | + |
| 150 | + if (index >= cdx_dev->res_count) |
| 151 | + return -EINVAL; |
| 152 | + |
| 153 | + if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_MMAP)) |
| 154 | + return -EINVAL; |
| 155 | + |
| 156 | + if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_READ) && |
| 157 | + (vma->vm_flags & VM_READ)) |
| 158 | + return -EPERM; |
| 159 | + |
| 160 | + if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_WRITE) && |
| 161 | + (vma->vm_flags & VM_WRITE)) |
| 162 | + return -EPERM; |
| 163 | + |
| 164 | + return vfio_cdx_mmap_mmio(vdev->regions[index], vma); |
| 165 | +} |
| 166 | + |
| 167 | +static const struct vfio_device_ops vfio_cdx_ops = { |
| 168 | + .name = "vfio-cdx", |
| 169 | + .open_device = vfio_cdx_open_device, |
| 170 | + .close_device = vfio_cdx_close_device, |
| 171 | + .ioctl = vfio_cdx_ioctl, |
| 172 | + .mmap = vfio_cdx_mmap, |
| 173 | + .bind_iommufd = vfio_iommufd_physical_bind, |
| 174 | + .unbind_iommufd = vfio_iommufd_physical_unbind, |
| 175 | + .attach_ioas = vfio_iommufd_physical_attach_ioas, |
| 176 | +}; |
| 177 | + |
| 178 | +static int vfio_cdx_probe(struct cdx_device *cdx_dev) |
| 179 | +{ |
| 180 | + struct vfio_cdx_device *vdev; |
| 181 | + struct device *dev = &cdx_dev->dev; |
| 182 | + int ret; |
| 183 | + |
| 184 | + vdev = vfio_alloc_device(vfio_cdx_device, vdev, dev, |
| 185 | + &vfio_cdx_ops); |
| 186 | + if (IS_ERR(vdev)) |
| 187 | + return PTR_ERR(vdev); |
| 188 | + |
| 189 | + ret = vfio_register_group_dev(&vdev->vdev); |
| 190 | + if (ret) |
| 191 | + goto out_uninit; |
| 192 | + |
| 193 | + dev_set_drvdata(dev, vdev); |
| 194 | + return 0; |
| 195 | + |
| 196 | +out_uninit: |
| 197 | + vfio_put_device(&vdev->vdev); |
| 198 | + return ret; |
| 199 | +} |
| 200 | + |
| 201 | +static int vfio_cdx_remove(struct cdx_device *cdx_dev) |
| 202 | +{ |
| 203 | + struct device *dev = &cdx_dev->dev; |
| 204 | + struct vfio_cdx_device *vdev = dev_get_drvdata(dev); |
| 205 | + |
| 206 | + vfio_unregister_group_dev(&vdev->vdev); |
| 207 | + vfio_put_device(&vdev->vdev); |
| 208 | + |
| 209 | + return 0; |
| 210 | +} |
| 211 | + |
| 212 | +static const struct cdx_device_id vfio_cdx_table[] = { |
| 213 | + { CDX_DEVICE_DRIVER_OVERRIDE(CDX_ANY_ID, CDX_ANY_ID, |
| 214 | + CDX_ID_F_VFIO_DRIVER_OVERRIDE) }, /* match all by default */ |
| 215 | + {} |
| 216 | +}; |
| 217 | + |
| 218 | +MODULE_DEVICE_TABLE(cdx, vfio_cdx_table); |
| 219 | + |
| 220 | +static struct cdx_driver vfio_cdx_driver = { |
| 221 | + .probe = vfio_cdx_probe, |
| 222 | + .remove = vfio_cdx_remove, |
| 223 | + .match_id_table = vfio_cdx_table, |
| 224 | + .driver = { |
| 225 | + .name = "vfio-cdx", |
| 226 | + .owner = THIS_MODULE, |
| 227 | + }, |
| 228 | + .driver_managed_dma = true, |
| 229 | +}; |
| 230 | + |
| 231 | +module_driver(vfio_cdx_driver, cdx_driver_register, cdx_driver_unregister); |
| 232 | + |
| 233 | +MODULE_LICENSE("GPL"); |
| 234 | +MODULE_DESCRIPTION("VFIO for CDX devices - User Level meta-driver"); |
0 commit comments