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
11static 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, ret;
18
19 vdev->regions = kcalloc(n: count, size: 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 ret = cdx_dev_reset(dev: core_vdev->dev);
43 if (ret) {
44 kfree(objp: vdev->regions);
45 vdev->regions = NULL;
46 return ret;
47 }
48 ret = cdx_clear_master(cdx_dev);
49 if (ret)
50 vdev->flags &= ~BME_SUPPORT;
51 else
52 vdev->flags |= BME_SUPPORT;
53
54 return 0;
55}
56
57static void vfio_cdx_close_device(struct vfio_device *core_vdev)
58{
59 struct vfio_cdx_device *vdev =
60 container_of(core_vdev, struct vfio_cdx_device, vdev);
61
62 kfree(objp: vdev->regions);
63 cdx_dev_reset(dev: core_vdev->dev);
64}
65
66static int vfio_cdx_bm_ctrl(struct vfio_device *core_vdev, u32 flags,
67 void __user *arg, size_t argsz)
68{
69 size_t minsz =
70 offsetofend(struct vfio_device_feature_bus_master, op);
71 struct vfio_cdx_device *vdev =
72 container_of(core_vdev, struct vfio_cdx_device, vdev);
73 struct cdx_device *cdx_dev = to_cdx_device(core_vdev->dev);
74 struct vfio_device_feature_bus_master ops;
75 int ret;
76
77 if (!(vdev->flags & BME_SUPPORT))
78 return -ENOTTY;
79
80 ret = vfio_check_feature(flags, argsz, VFIO_DEVICE_FEATURE_SET,
81 minsz: sizeof(ops));
82 if (ret != 1)
83 return ret;
84
85 if (copy_from_user(to: &ops, from: arg, n: minsz))
86 return -EFAULT;
87
88 switch (ops.op) {
89 case VFIO_DEVICE_FEATURE_CLEAR_MASTER:
90 return cdx_clear_master(cdx_dev);
91 case VFIO_DEVICE_FEATURE_SET_MASTER:
92 return cdx_set_master(cdx_dev);
93 default:
94 return -EINVAL;
95 }
96}
97
98static int vfio_cdx_ioctl_feature(struct vfio_device *device, u32 flags,
99 void __user *arg, size_t argsz)
100{
101 switch (flags & VFIO_DEVICE_FEATURE_MASK) {
102 case VFIO_DEVICE_FEATURE_BUS_MASTER:
103 return vfio_cdx_bm_ctrl(core_vdev: device, flags, arg, argsz);
104 default:
105 return -ENOTTY;
106 }
107}
108
109static int vfio_cdx_ioctl_get_info(struct vfio_cdx_device *vdev,
110 struct vfio_device_info __user *arg)
111{
112 unsigned long minsz = offsetofend(struct vfio_device_info, num_irqs);
113 struct cdx_device *cdx_dev = to_cdx_device(vdev->vdev.dev);
114 struct vfio_device_info info;
115
116 if (copy_from_user(to: &info, from: arg, n: minsz))
117 return -EFAULT;
118
119 if (info.argsz < minsz)
120 return -EINVAL;
121
122 info.flags = VFIO_DEVICE_FLAGS_CDX;
123 info.flags |= VFIO_DEVICE_FLAGS_RESET;
124
125 info.num_regions = cdx_dev->res_count;
126 info.num_irqs = 0;
127
128 return copy_to_user(to: arg, from: &info, n: minsz) ? -EFAULT : 0;
129}
130
131static int vfio_cdx_ioctl_get_region_info(struct vfio_cdx_device *vdev,
132 struct vfio_region_info __user *arg)
133{
134 unsigned long minsz = offsetofend(struct vfio_region_info, offset);
135 struct cdx_device *cdx_dev = to_cdx_device(vdev->vdev.dev);
136 struct vfio_region_info info;
137
138 if (copy_from_user(to: &info, from: arg, n: minsz))
139 return -EFAULT;
140
141 if (info.argsz < minsz)
142 return -EINVAL;
143
144 if (info.index >= cdx_dev->res_count)
145 return -EINVAL;
146
147 /* map offset to the physical address */
148 info.offset = vfio_cdx_index_to_offset(index: info.index);
149 info.size = vdev->regions[info.index].size;
150 info.flags = vdev->regions[info.index].flags;
151
152 return copy_to_user(to: arg, from: &info, n: minsz) ? -EFAULT : 0;
153}
154
155static long vfio_cdx_ioctl(struct vfio_device *core_vdev,
156 unsigned int cmd, unsigned long arg)
157{
158 struct vfio_cdx_device *vdev =
159 container_of(core_vdev, struct vfio_cdx_device, vdev);
160 void __user *uarg = (void __user *)arg;
161
162 switch (cmd) {
163 case VFIO_DEVICE_GET_INFO:
164 return vfio_cdx_ioctl_get_info(vdev, arg: uarg);
165 case VFIO_DEVICE_GET_REGION_INFO:
166 return vfio_cdx_ioctl_get_region_info(vdev, arg: uarg);
167 case VFIO_DEVICE_RESET:
168 return cdx_dev_reset(dev: core_vdev->dev);
169 default:
170 return -ENOTTY;
171 }
172}
173
174static int vfio_cdx_mmap_mmio(struct vfio_cdx_region region,
175 struct vm_area_struct *vma)
176{
177 u64 size = vma->vm_end - vma->vm_start;
178 u64 pgoff, base;
179
180 pgoff = vma->vm_pgoff &
181 ((1U << (VFIO_CDX_OFFSET_SHIFT - PAGE_SHIFT)) - 1);
182 base = pgoff << PAGE_SHIFT;
183
184 if (base + size > region.size)
185 return -EINVAL;
186
187 vma->vm_pgoff = (region.addr >> PAGE_SHIFT) + pgoff;
188 vma->vm_page_prot = pgprot_device(vma->vm_page_prot);
189
190 return io_remap_pfn_range(vma, addr: vma->vm_start, pfn: vma->vm_pgoff,
191 size, prot: vma->vm_page_prot);
192}
193
194static int vfio_cdx_mmap(struct vfio_device *core_vdev,
195 struct vm_area_struct *vma)
196{
197 struct vfio_cdx_device *vdev =
198 container_of(core_vdev, struct vfio_cdx_device, vdev);
199 struct cdx_device *cdx_dev = to_cdx_device(core_vdev->dev);
200 unsigned int index;
201
202 index = vma->vm_pgoff >> (VFIO_CDX_OFFSET_SHIFT - PAGE_SHIFT);
203
204 if (index >= cdx_dev->res_count)
205 return -EINVAL;
206
207 if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_MMAP))
208 return -EINVAL;
209
210 if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_READ) &&
211 (vma->vm_flags & VM_READ))
212 return -EPERM;
213
214 if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_WRITE) &&
215 (vma->vm_flags & VM_WRITE))
216 return -EPERM;
217
218 return vfio_cdx_mmap_mmio(region: vdev->regions[index], vma);
219}
220
221static const struct vfio_device_ops vfio_cdx_ops = {
222 .name = "vfio-cdx",
223 .open_device = vfio_cdx_open_device,
224 .close_device = vfio_cdx_close_device,
225 .ioctl = vfio_cdx_ioctl,
226 .device_feature = vfio_cdx_ioctl_feature,
227 .mmap = vfio_cdx_mmap,
228 .bind_iommufd = vfio_iommufd_physical_bind,
229 .unbind_iommufd = vfio_iommufd_physical_unbind,
230 .attach_ioas = vfio_iommufd_physical_attach_ioas,
231};
232
233static int vfio_cdx_probe(struct cdx_device *cdx_dev)
234{
235 struct vfio_cdx_device *vdev;
236 struct device *dev = &cdx_dev->dev;
237 int ret;
238
239 vdev = vfio_alloc_device(vfio_cdx_device, vdev, dev,
240 &vfio_cdx_ops);
241 if (IS_ERR(ptr: vdev))
242 return PTR_ERR(ptr: vdev);
243
244 ret = vfio_register_group_dev(device: &vdev->vdev);
245 if (ret)
246 goto out_uninit;
247
248 dev_set_drvdata(dev, data: vdev);
249 return 0;
250
251out_uninit:
252 vfio_put_device(device: &vdev->vdev);
253 return ret;
254}
255
256static int vfio_cdx_remove(struct cdx_device *cdx_dev)
257{
258 struct device *dev = &cdx_dev->dev;
259 struct vfio_cdx_device *vdev = dev_get_drvdata(dev);
260
261 vfio_unregister_group_dev(device: &vdev->vdev);
262 vfio_put_device(device: &vdev->vdev);
263
264 return 0;
265}
266
267static const struct cdx_device_id vfio_cdx_table[] = {
268 { CDX_DEVICE_DRIVER_OVERRIDE(CDX_ANY_ID, CDX_ANY_ID,
269 CDX_ID_F_VFIO_DRIVER_OVERRIDE) }, /* match all by default */
270 {}
271};
272
273MODULE_DEVICE_TABLE(cdx, vfio_cdx_table);
274
275static struct cdx_driver vfio_cdx_driver = {
276 .probe = vfio_cdx_probe,
277 .remove = vfio_cdx_remove,
278 .match_id_table = vfio_cdx_table,
279 .driver = {
280 .name = "vfio-cdx",
281 },
282 .driver_managed_dma = true,
283};
284
285module_driver(vfio_cdx_driver, cdx_driver_register, cdx_driver_unregister);
286
287MODULE_LICENSE("GPL");
288MODULE_DESCRIPTION("VFIO for CDX devices - User Level meta-driver");
289MODULE_IMPORT_NS(CDX_BUS);
290

source code of linux/drivers/vfio/cdx/main.c