blob: 47f6309b48894a9603f8c0333edf1590d3896916 [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;
54 break;
55 case IORESOURCE_IO:
56 vdev->regions[i].type = VFIO_PLATFORM_REGION_TYPE_PIO;
57 break;
58 default:
59 goto err;
60 }
61 }
62
63 vdev->num_regions = cnt;
64
65 return 0;
66err:
67 kfree(vdev->regions);
68 return -EINVAL;
69}
70
71static void vfio_platform_regions_cleanup(struct vfio_platform_device *vdev)
72{
73 vdev->num_regions = 0;
74 kfree(vdev->regions);
75}
76
Antonios Motakisde49fc02015-03-16 14:08:42 -060077static void vfio_platform_release(void *device_data)
78{
Antonios Motakise8909e62015-03-16 14:08:46 -060079 struct vfio_platform_device *vdev = device_data;
80
81 mutex_lock(&driver_lock);
82
83 if (!(--vdev->refcnt)) {
84 vfio_platform_regions_cleanup(vdev);
85 }
86
87 mutex_unlock(&driver_lock);
88
Antonios Motakisde49fc02015-03-16 14:08:42 -060089 module_put(THIS_MODULE);
90}
91
92static int vfio_platform_open(void *device_data)
93{
Antonios Motakise8909e62015-03-16 14:08:46 -060094 struct vfio_platform_device *vdev = device_data;
95 int ret;
96
Antonios Motakisde49fc02015-03-16 14:08:42 -060097 if (!try_module_get(THIS_MODULE))
98 return -ENODEV;
99
Antonios Motakise8909e62015-03-16 14:08:46 -0600100 mutex_lock(&driver_lock);
101
102 if (!vdev->refcnt) {
103 ret = vfio_platform_regions_init(vdev);
104 if (ret)
105 goto err_reg;
106 }
107
108 vdev->refcnt++;
109
110 mutex_unlock(&driver_lock);
Antonios Motakisde49fc02015-03-16 14:08:42 -0600111 return 0;
Antonios Motakise8909e62015-03-16 14:08:46 -0600112
113err_reg:
114 mutex_unlock(&driver_lock);
115 module_put(THIS_MODULE);
116 return ret;
Antonios Motakisde49fc02015-03-16 14:08:42 -0600117}
118
119static long vfio_platform_ioctl(void *device_data,
120 unsigned int cmd, unsigned long arg)
121{
Antonios Motakis2e8567b2015-03-16 14:08:46 -0600122 struct vfio_platform_device *vdev = device_data;
123 unsigned long minsz;
Antonios Motakisde49fc02015-03-16 14:08:42 -0600124
Antonios Motakis2e8567b2015-03-16 14:08:46 -0600125 if (cmd == VFIO_DEVICE_GET_INFO) {
126 struct vfio_device_info info;
127
128 minsz = offsetofend(struct vfio_device_info, num_irqs);
129
130 if (copy_from_user(&info, (void __user *)arg, minsz))
131 return -EFAULT;
132
133 if (info.argsz < minsz)
134 return -EINVAL;
135
136 info.flags = vdev->flags;
Antonios Motakise8909e62015-03-16 14:08:46 -0600137 info.num_regions = vdev->num_regions;
Antonios Motakis2e8567b2015-03-16 14:08:46 -0600138 info.num_irqs = 0;
139
140 return copy_to_user((void __user *)arg, &info, minsz);
141
Antonios Motakise8909e62015-03-16 14:08:46 -0600142 } else if (cmd == VFIO_DEVICE_GET_REGION_INFO) {
143 struct vfio_region_info info;
Antonios Motakisde49fc02015-03-16 14:08:42 -0600144
Antonios Motakise8909e62015-03-16 14:08:46 -0600145 minsz = offsetofend(struct vfio_region_info, offset);
146
147 if (copy_from_user(&info, (void __user *)arg, minsz))
148 return -EFAULT;
149
150 if (info.argsz < minsz)
151 return -EINVAL;
152
153 if (info.index >= vdev->num_regions)
154 return -EINVAL;
155
156 /* map offset to the physical address */
157 info.offset = VFIO_PLATFORM_INDEX_TO_OFFSET(info.index);
158 info.size = vdev->regions[info.index].size;
159 info.flags = vdev->regions[info.index].flags;
160
161 return copy_to_user((void __user *)arg, &info, minsz);
162
163 } else if (cmd == VFIO_DEVICE_GET_IRQ_INFO)
Antonios Motakisde49fc02015-03-16 14:08:42 -0600164 return -EINVAL;
165
166 else if (cmd == VFIO_DEVICE_SET_IRQS)
167 return -EINVAL;
168
169 else if (cmd == VFIO_DEVICE_RESET)
170 return -EINVAL;
171
172 return -ENOTTY;
173}
174
175static ssize_t vfio_platform_read(void *device_data, char __user *buf,
176 size_t count, loff_t *ppos)
177{
178 return -EINVAL;
179}
180
181static ssize_t vfio_platform_write(void *device_data, const char __user *buf,
182 size_t count, loff_t *ppos)
183{
184 return -EINVAL;
185}
186
187static int vfio_platform_mmap(void *device_data, struct vm_area_struct *vma)
188{
189 return -EINVAL;
190}
191
192static const struct vfio_device_ops vfio_platform_ops = {
193 .name = "vfio-platform",
194 .open = vfio_platform_open,
195 .release = vfio_platform_release,
196 .ioctl = vfio_platform_ioctl,
197 .read = vfio_platform_read,
198 .write = vfio_platform_write,
199 .mmap = vfio_platform_mmap,
200};
201
202int vfio_platform_probe_common(struct vfio_platform_device *vdev,
203 struct device *dev)
204{
205 struct iommu_group *group;
206 int ret;
207
208 if (!vdev)
209 return -EINVAL;
210
211 group = iommu_group_get(dev);
212 if (!group) {
213 pr_err("VFIO: No IOMMU group for device %s\n", vdev->name);
214 return -EINVAL;
215 }
216
217 ret = vfio_add_group_dev(dev, &vfio_platform_ops, vdev);
218 if (ret) {
219 iommu_group_put(group);
220 return ret;
221 }
222
223 return 0;
224}
225EXPORT_SYMBOL_GPL(vfio_platform_probe_common);
226
227struct vfio_platform_device *vfio_platform_remove_common(struct device *dev)
228{
229 struct vfio_platform_device *vdev;
230
231 vdev = vfio_del_group_dev(dev);
232 if (vdev)
233 iommu_group_put(dev->iommu_group);
234
235 return vdev;
236}
237EXPORT_SYMBOL_GPL(vfio_platform_remove_common);