blob: 4df66f5fb313f9116a735cfc61b7923ca24cfd36 [file] [log] [blame]
Antonios Motakisde49fc02015-03-16 14:08:42 -06001/*
2 * Copyright (C) 2013 - Virtual Open Systems
3 * Author: Antonios Motakis <a.motakis@virtualopensystems.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License, version 2, as
7 * published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 */
14
15#include <linux/device.h>
16#include <linux/iommu.h>
17#include <linux/module.h>
18#include <linux/mutex.h>
19#include <linux/slab.h>
20#include <linux/types.h>
Antonios Motakis2e8567b2015-03-16 14:08:46 -060021#include <linux/uaccess.h>
Antonios Motakisde49fc02015-03-16 14:08:42 -060022#include <linux/vfio.h>
23
24#include "vfio_platform_private.h"
25
Antonios Motakise8909e62015-03-16 14:08:46 -060026static DEFINE_MUTEX(driver_lock);
27
28static int vfio_platform_regions_init(struct vfio_platform_device *vdev)
29{
30 int cnt = 0, i;
31
32 while (vdev->get_resource(vdev, cnt))
33 cnt++;
34
35 vdev->regions = kcalloc(cnt, sizeof(struct vfio_platform_region),
36 GFP_KERNEL);
37 if (!vdev->regions)
38 return -ENOMEM;
39
40 for (i = 0; i < cnt; i++) {
41 struct resource *res =
42 vdev->get_resource(vdev, i);
43
44 if (!res)
45 goto err;
46
47 vdev->regions[i].addr = res->start;
48 vdev->regions[i].size = resource_size(res);
49 vdev->regions[i].flags = 0;
50
51 switch (resource_type(res)) {
52 case IORESOURCE_MEM:
53 vdev->regions[i].type = VFIO_PLATFORM_REGION_TYPE_MMIO;
Antonios Motakis6e3f2642015-03-16 14:08:47 -060054 vdev->regions[i].flags |= VFIO_REGION_INFO_FLAG_READ;
55 if (!(res->flags & IORESOURCE_READONLY))
56 vdev->regions[i].flags |=
57 VFIO_REGION_INFO_FLAG_WRITE;
Antonios Motakise8909e62015-03-16 14:08:46 -060058 break;
59 case IORESOURCE_IO:
60 vdev->regions[i].type = VFIO_PLATFORM_REGION_TYPE_PIO;
61 break;
62 default:
63 goto err;
64 }
65 }
66
67 vdev->num_regions = cnt;
68
69 return 0;
70err:
71 kfree(vdev->regions);
72 return -EINVAL;
73}
74
75static void vfio_platform_regions_cleanup(struct vfio_platform_device *vdev)
76{
Antonios Motakis6e3f2642015-03-16 14:08:47 -060077 int i;
78
79 for (i = 0; i < vdev->num_regions; i++)
80 iounmap(vdev->regions[i].ioaddr);
81
Antonios Motakise8909e62015-03-16 14:08:46 -060082 vdev->num_regions = 0;
83 kfree(vdev->regions);
84}
85
Antonios Motakisde49fc02015-03-16 14:08:42 -060086static void vfio_platform_release(void *device_data)
87{
Antonios Motakise8909e62015-03-16 14:08:46 -060088 struct vfio_platform_device *vdev = device_data;
89
90 mutex_lock(&driver_lock);
91
92 if (!(--vdev->refcnt)) {
93 vfio_platform_regions_cleanup(vdev);
94 }
95
96 mutex_unlock(&driver_lock);
97
Antonios Motakisde49fc02015-03-16 14:08:42 -060098 module_put(THIS_MODULE);
99}
100
101static int vfio_platform_open(void *device_data)
102{
Antonios Motakise8909e62015-03-16 14:08:46 -0600103 struct vfio_platform_device *vdev = device_data;
104 int ret;
105
Antonios Motakisde49fc02015-03-16 14:08:42 -0600106 if (!try_module_get(THIS_MODULE))
107 return -ENODEV;
108
Antonios Motakise8909e62015-03-16 14:08:46 -0600109 mutex_lock(&driver_lock);
110
111 if (!vdev->refcnt) {
112 ret = vfio_platform_regions_init(vdev);
113 if (ret)
114 goto err_reg;
115 }
116
117 vdev->refcnt++;
118
119 mutex_unlock(&driver_lock);
Antonios Motakisde49fc02015-03-16 14:08:42 -0600120 return 0;
Antonios Motakise8909e62015-03-16 14:08:46 -0600121
122err_reg:
123 mutex_unlock(&driver_lock);
124 module_put(THIS_MODULE);
125 return ret;
Antonios Motakisde49fc02015-03-16 14:08:42 -0600126}
127
128static long vfio_platform_ioctl(void *device_data,
129 unsigned int cmd, unsigned long arg)
130{
Antonios Motakis2e8567b2015-03-16 14:08:46 -0600131 struct vfio_platform_device *vdev = device_data;
132 unsigned long minsz;
Antonios Motakisde49fc02015-03-16 14:08:42 -0600133
Antonios Motakis2e8567b2015-03-16 14:08:46 -0600134 if (cmd == VFIO_DEVICE_GET_INFO) {
135 struct vfio_device_info info;
136
137 minsz = offsetofend(struct vfio_device_info, num_irqs);
138
139 if (copy_from_user(&info, (void __user *)arg, minsz))
140 return -EFAULT;
141
142 if (info.argsz < minsz)
143 return -EINVAL;
144
145 info.flags = vdev->flags;
Antonios Motakise8909e62015-03-16 14:08:46 -0600146 info.num_regions = vdev->num_regions;
Antonios Motakis2e8567b2015-03-16 14:08:46 -0600147 info.num_irqs = 0;
148
149 return copy_to_user((void __user *)arg, &info, minsz);
150
Antonios Motakise8909e62015-03-16 14:08:46 -0600151 } else if (cmd == VFIO_DEVICE_GET_REGION_INFO) {
152 struct vfio_region_info info;
Antonios Motakisde49fc02015-03-16 14:08:42 -0600153
Antonios Motakise8909e62015-03-16 14:08:46 -0600154 minsz = offsetofend(struct vfio_region_info, offset);
155
156 if (copy_from_user(&info, (void __user *)arg, minsz))
157 return -EFAULT;
158
159 if (info.argsz < minsz)
160 return -EINVAL;
161
162 if (info.index >= vdev->num_regions)
163 return -EINVAL;
164
165 /* map offset to the physical address */
166 info.offset = VFIO_PLATFORM_INDEX_TO_OFFSET(info.index);
167 info.size = vdev->regions[info.index].size;
168 info.flags = vdev->regions[info.index].flags;
169
170 return copy_to_user((void __user *)arg, &info, minsz);
171
172 } else if (cmd == VFIO_DEVICE_GET_IRQ_INFO)
Antonios Motakisde49fc02015-03-16 14:08:42 -0600173 return -EINVAL;
174
175 else if (cmd == VFIO_DEVICE_SET_IRQS)
176 return -EINVAL;
177
178 else if (cmd == VFIO_DEVICE_RESET)
179 return -EINVAL;
180
181 return -ENOTTY;
182}
183
Antonios Motakis6e3f2642015-03-16 14:08:47 -0600184static ssize_t vfio_platform_read_mmio(struct vfio_platform_region reg,
185 char __user *buf, size_t count,
186 loff_t off)
187{
188 unsigned int done = 0;
189
190 if (!reg.ioaddr) {
191 reg.ioaddr =
192 ioremap_nocache(reg.addr, reg.size);
193
194 if (!reg.ioaddr)
195 return -ENOMEM;
196 }
197
198 while (count) {
199 size_t filled;
200
201 if (count >= 4 && !(off % 4)) {
202 u32 val;
203
204 val = ioread32(reg.ioaddr + off);
205 if (copy_to_user(buf, &val, 4))
206 goto err;
207
208 filled = 4;
209 } else if (count >= 2 && !(off % 2)) {
210 u16 val;
211
212 val = ioread16(reg.ioaddr + off);
213 if (copy_to_user(buf, &val, 2))
214 goto err;
215
216 filled = 2;
217 } else {
218 u8 val;
219
220 val = ioread8(reg.ioaddr + off);
221 if (copy_to_user(buf, &val, 1))
222 goto err;
223
224 filled = 1;
225 }
226
227
228 count -= filled;
229 done += filled;
230 off += filled;
231 buf += filled;
232 }
233
234 return done;
235err:
236 return -EFAULT;
237}
238
Antonios Motakisde49fc02015-03-16 14:08:42 -0600239static ssize_t vfio_platform_read(void *device_data, char __user *buf,
240 size_t count, loff_t *ppos)
241{
Antonios Motakis6e3f2642015-03-16 14:08:47 -0600242 struct vfio_platform_device *vdev = device_data;
243 unsigned int index = VFIO_PLATFORM_OFFSET_TO_INDEX(*ppos);
244 loff_t off = *ppos & VFIO_PLATFORM_OFFSET_MASK;
245
246 if (index >= vdev->num_regions)
247 return -EINVAL;
248
249 if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_READ))
250 return -EINVAL;
251
252 if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_MMIO)
253 return vfio_platform_read_mmio(vdev->regions[index],
254 buf, count, off);
255 else if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_PIO)
256 return -EINVAL; /* not implemented */
257
Antonios Motakisde49fc02015-03-16 14:08:42 -0600258 return -EINVAL;
259}
260
Antonios Motakis6e3f2642015-03-16 14:08:47 -0600261static ssize_t vfio_platform_write_mmio(struct vfio_platform_region reg,
262 const char __user *buf, size_t count,
263 loff_t off)
264{
265 unsigned int done = 0;
266
267 if (!reg.ioaddr) {
268 reg.ioaddr =
269 ioremap_nocache(reg.addr, reg.size);
270
271 if (!reg.ioaddr)
272 return -ENOMEM;
273 }
274
275 while (count) {
276 size_t filled;
277
278 if (count >= 4 && !(off % 4)) {
279 u32 val;
280
281 if (copy_from_user(&val, buf, 4))
282 goto err;
283 iowrite32(val, reg.ioaddr + off);
284
285 filled = 4;
286 } else if (count >= 2 && !(off % 2)) {
287 u16 val;
288
289 if (copy_from_user(&val, buf, 2))
290 goto err;
291 iowrite16(val, reg.ioaddr + off);
292
293 filled = 2;
294 } else {
295 u8 val;
296
297 if (copy_from_user(&val, buf, 1))
298 goto err;
299 iowrite8(val, reg.ioaddr + off);
300
301 filled = 1;
302 }
303
304 count -= filled;
305 done += filled;
306 off += filled;
307 buf += filled;
308 }
309
310 return done;
311err:
312 return -EFAULT;
313}
314
Antonios Motakisde49fc02015-03-16 14:08:42 -0600315static ssize_t vfio_platform_write(void *device_data, const char __user *buf,
316 size_t count, loff_t *ppos)
317{
Antonios Motakis6e3f2642015-03-16 14:08:47 -0600318 struct vfio_platform_device *vdev = device_data;
319 unsigned int index = VFIO_PLATFORM_OFFSET_TO_INDEX(*ppos);
320 loff_t off = *ppos & VFIO_PLATFORM_OFFSET_MASK;
321
322 if (index >= vdev->num_regions)
323 return -EINVAL;
324
325 if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_WRITE))
326 return -EINVAL;
327
328 if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_MMIO)
329 return vfio_platform_write_mmio(vdev->regions[index],
330 buf, count, off);
331 else if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_PIO)
332 return -EINVAL; /* not implemented */
333
Antonios Motakisde49fc02015-03-16 14:08:42 -0600334 return -EINVAL;
335}
336
337static int vfio_platform_mmap(void *device_data, struct vm_area_struct *vma)
338{
339 return -EINVAL;
340}
341
342static const struct vfio_device_ops vfio_platform_ops = {
343 .name = "vfio-platform",
344 .open = vfio_platform_open,
345 .release = vfio_platform_release,
346 .ioctl = vfio_platform_ioctl,
347 .read = vfio_platform_read,
348 .write = vfio_platform_write,
349 .mmap = vfio_platform_mmap,
350};
351
352int vfio_platform_probe_common(struct vfio_platform_device *vdev,
353 struct device *dev)
354{
355 struct iommu_group *group;
356 int ret;
357
358 if (!vdev)
359 return -EINVAL;
360
361 group = iommu_group_get(dev);
362 if (!group) {
363 pr_err("VFIO: No IOMMU group for device %s\n", vdev->name);
364 return -EINVAL;
365 }
366
367 ret = vfio_add_group_dev(dev, &vfio_platform_ops, vdev);
368 if (ret) {
369 iommu_group_put(group);
370 return ret;
371 }
372
373 return 0;
374}
375EXPORT_SYMBOL_GPL(vfio_platform_probe_common);
376
377struct vfio_platform_device *vfio_platform_remove_common(struct device *dev)
378{
379 struct vfio_platform_device *vdev;
380
381 vdev = vfio_del_group_dev(dev);
382 if (vdev)
383 iommu_group_put(dev->iommu_group);
384
385 return vdev;
386}
387EXPORT_SYMBOL_GPL(vfio_platform_remove_common);