Commit | Line | Data |
---|---|---|
1802d0be | 1 | // SPDX-License-Identifier: GPL-2.0-only |
de49fc0d AM |
2 | /* |
3 | * Copyright (C) 2013 - Virtual Open Systems | |
4 | * Author: Antonios Motakis <a.motakis@virtualopensystems.com> | |
de49fc0d AM |
5 | */ |
6 | ||
a88a7b3e BH |
7 | #define dev_fmt(fmt) "VFIO: " fmt |
8 | ||
de49fc0d | 9 | #include <linux/device.h> |
a12a9368 | 10 | #include <linux/acpi.h> |
de49fc0d AM |
11 | #include <linux/iommu.h> |
12 | #include <linux/module.h> | |
13 | #include <linux/mutex.h> | |
415eb9fc | 14 | #include <linux/pm_runtime.h> |
de49fc0d AM |
15 | #include <linux/slab.h> |
16 | #include <linux/types.h> | |
2e8567bb | 17 | #include <linux/uaccess.h> |
de49fc0d AM |
18 | #include <linux/vfio.h> |
19 | ||
20 | #include "vfio_platform_private.h" | |
21 | ||
32a2d71c EA |
22 | #define DRIVER_VERSION "0.10" |
23 | #define DRIVER_AUTHOR "Antonios Motakis <a.motakis@virtualopensystems.com>" | |
24 | #define DRIVER_DESC "VFIO platform base module" | |
25 | ||
d30daa33 SK |
26 | #define VFIO_PLATFORM_IS_ACPI(vdev) ((vdev)->acpihid != NULL) |
27 | ||
e086497d | 28 | static LIST_HEAD(reset_list); |
e8909e67 AM |
29 | static DEFINE_MUTEX(driver_lock); |
30 | ||
e9e0506e EA |
31 | static vfio_platform_reset_fn_t vfio_platform_lookup_reset(const char *compat, |
32 | struct module **module) | |
3eeb0d51 | 33 | { |
e9e0506e EA |
34 | struct vfio_platform_reset_node *iter; |
35 | vfio_platform_reset_fn_t reset_fn = NULL; | |
3eeb0d51 | 36 | |
e9e0506e EA |
37 | mutex_lock(&driver_lock); |
38 | list_for_each_entry(iter, &reset_list, link) { | |
39 | if (!strcmp(iter->compat, compat) && | |
40 | try_module_get(iter->owner)) { | |
41 | *module = iter->owner; | |
7aef80cf | 42 | reset_fn = iter->of_reset; |
e9e0506e | 43 | break; |
3eeb0d51 EA |
44 | } |
45 | } | |
e9e0506e EA |
46 | mutex_unlock(&driver_lock); |
47 | return reset_fn; | |
48 | } | |
49 | ||
a12a9368 SK |
50 | static int vfio_platform_acpi_probe(struct vfio_platform_device *vdev, |
51 | struct device *dev) | |
52 | { | |
53 | struct acpi_device *adev; | |
54 | ||
55 | if (acpi_disabled) | |
56 | return -ENOENT; | |
57 | ||
58 | adev = ACPI_COMPANION(dev); | |
59 | if (!adev) { | |
a88a7b3e | 60 | dev_err(dev, "ACPI companion device not found for %s\n", |
a12a9368 SK |
61 | vdev->name); |
62 | return -ENODEV; | |
63 | } | |
64 | ||
65 | #ifdef CONFIG_ACPI | |
66 | vdev->acpihid = acpi_device_hid(adev); | |
67 | #endif | |
68 | return WARN_ON(!vdev->acpihid) ? -EINVAL : 0; | |
69 | } | |
70 | ||
2e062856 | 71 | static int vfio_platform_acpi_call_reset(struct vfio_platform_device *vdev, |
d30daa33 SK |
72 | const char **extra_dbg) |
73 | { | |
74 | #ifdef CONFIG_ACPI | |
75 | struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; | |
76 | struct device *dev = vdev->device; | |
77 | acpi_handle handle = ACPI_HANDLE(dev); | |
78 | acpi_status acpi_ret; | |
79 | ||
80 | acpi_ret = acpi_evaluate_object(handle, "_RST", NULL, &buffer); | |
81 | if (ACPI_FAILURE(acpi_ret)) { | |
82 | if (extra_dbg) | |
83 | *extra_dbg = acpi_format_exception(acpi_ret); | |
84 | return -EINVAL; | |
85 | } | |
86 | ||
87 | return 0; | |
88 | #else | |
89 | return -ENOENT; | |
90 | #endif | |
91 | } | |
92 | ||
2e062856 | 93 | static bool vfio_platform_acpi_has_reset(struct vfio_platform_device *vdev) |
d30daa33 SK |
94 | { |
95 | #ifdef CONFIG_ACPI | |
96 | struct device *dev = vdev->device; | |
97 | acpi_handle handle = ACPI_HANDLE(dev); | |
98 | ||
99 | return acpi_has_method(handle, "_RST"); | |
100 | #else | |
101 | return false; | |
102 | #endif | |
103 | } | |
104 | ||
dc5542fb SK |
105 | static bool vfio_platform_has_reset(struct vfio_platform_device *vdev) |
106 | { | |
d30daa33 SK |
107 | if (VFIO_PLATFORM_IS_ACPI(vdev)) |
108 | return vfio_platform_acpi_has_reset(vdev); | |
109 | ||
dc5542fb SK |
110 | return vdev->of_reset ? true : false; |
111 | } | |
112 | ||
b5add544 | 113 | static int vfio_platform_get_reset(struct vfio_platform_device *vdev) |
e9e0506e | 114 | { |
d30daa33 | 115 | if (VFIO_PLATFORM_IS_ACPI(vdev)) |
b5add544 | 116 | return vfio_platform_acpi_has_reset(vdev) ? 0 : -ENOENT; |
d30daa33 | 117 | |
7aef80cf SK |
118 | vdev->of_reset = vfio_platform_lookup_reset(vdev->compat, |
119 | &vdev->reset_module); | |
120 | if (!vdev->of_reset) { | |
7200be7c | 121 | request_module("vfio-reset:%s", vdev->compat); |
7aef80cf SK |
122 | vdev->of_reset = vfio_platform_lookup_reset(vdev->compat, |
123 | &vdev->reset_module); | |
e9e0506e | 124 | } |
b5add544 SK |
125 | |
126 | return vdev->of_reset ? 0 : -ENOENT; | |
3eeb0d51 EA |
127 | } |
128 | ||
129 | static void vfio_platform_put_reset(struct vfio_platform_device *vdev) | |
130 | { | |
d30daa33 SK |
131 | if (VFIO_PLATFORM_IS_ACPI(vdev)) |
132 | return; | |
133 | ||
7aef80cf | 134 | if (vdev->of_reset) |
e9e0506e | 135 | module_put(vdev->reset_module); |
3eeb0d51 EA |
136 | } |
137 | ||
e8909e67 AM |
138 | static int vfio_platform_regions_init(struct vfio_platform_device *vdev) |
139 | { | |
140 | int cnt = 0, i; | |
141 | ||
142 | while (vdev->get_resource(vdev, cnt)) | |
143 | cnt++; | |
144 | ||
145 | vdev->regions = kcalloc(cnt, sizeof(struct vfio_platform_region), | |
146 | GFP_KERNEL); | |
147 | if (!vdev->regions) | |
148 | return -ENOMEM; | |
149 | ||
150 | for (i = 0; i < cnt; i++) { | |
151 | struct resource *res = | |
152 | vdev->get_resource(vdev, i); | |
153 | ||
154 | if (!res) | |
155 | goto err; | |
156 | ||
157 | vdev->regions[i].addr = res->start; | |
158 | vdev->regions[i].size = resource_size(res); | |
159 | vdev->regions[i].flags = 0; | |
160 | ||
161 | switch (resource_type(res)) { | |
162 | case IORESOURCE_MEM: | |
163 | vdev->regions[i].type = VFIO_PLATFORM_REGION_TYPE_MMIO; | |
6e3f2645 AM |
164 | vdev->regions[i].flags |= VFIO_REGION_INFO_FLAG_READ; |
165 | if (!(res->flags & IORESOURCE_READONLY)) | |
166 | vdev->regions[i].flags |= | |
167 | VFIO_REGION_INFO_FLAG_WRITE; | |
fad4d5b1 AM |
168 | |
169 | /* | |
170 | * Only regions addressed with PAGE granularity may be | |
171 | * MMAPed securely. | |
172 | */ | |
173 | if (!(vdev->regions[i].addr & ~PAGE_MASK) && | |
174 | !(vdev->regions[i].size & ~PAGE_MASK)) | |
175 | vdev->regions[i].flags |= | |
176 | VFIO_REGION_INFO_FLAG_MMAP; | |
177 | ||
e8909e67 AM |
178 | break; |
179 | case IORESOURCE_IO: | |
180 | vdev->regions[i].type = VFIO_PLATFORM_REGION_TYPE_PIO; | |
181 | break; | |
182 | default: | |
183 | goto err; | |
184 | } | |
185 | } | |
186 | ||
187 | vdev->num_regions = cnt; | |
188 | ||
189 | return 0; | |
190 | err: | |
191 | kfree(vdev->regions); | |
192 | return -EINVAL; | |
193 | } | |
194 | ||
195 | static void vfio_platform_regions_cleanup(struct vfio_platform_device *vdev) | |
196 | { | |
6e3f2645 AM |
197 | int i; |
198 | ||
199 | for (i = 0; i < vdev->num_regions; i++) | |
200 | iounmap(vdev->regions[i].ioaddr); | |
201 | ||
e8909e67 AM |
202 | vdev->num_regions = 0; |
203 | kfree(vdev->regions); | |
204 | } | |
205 | ||
5afec274 SK |
206 | static int vfio_platform_call_reset(struct vfio_platform_device *vdev, |
207 | const char **extra_dbg) | |
f084aa74 | 208 | { |
d30daa33 SK |
209 | if (VFIO_PLATFORM_IS_ACPI(vdev)) { |
210 | dev_info(vdev->device, "reset\n"); | |
211 | return vfio_platform_acpi_call_reset(vdev, extra_dbg); | |
212 | } else if (vdev->of_reset) { | |
f084aa74 SK |
213 | dev_info(vdev->device, "reset\n"); |
214 | return vdev->of_reset(vdev); | |
215 | } | |
216 | ||
217 | dev_warn(vdev->device, "no reset function found!\n"); | |
218 | return -EINVAL; | |
219 | } | |
220 | ||
5f6c7e08 | 221 | void vfio_platform_close_device(struct vfio_device *core_vdev) |
de49fc0d | 222 | { |
6df62c5b JG |
223 | struct vfio_platform_device *vdev = |
224 | container_of(core_vdev, struct vfio_platform_device, vdev); | |
ab7e5e34 JG |
225 | const char *extra_dbg = NULL; |
226 | int ret; | |
e8909e67 | 227 | |
ab7e5e34 JG |
228 | ret = vfio_platform_call_reset(vdev, &extra_dbg); |
229 | if (WARN_ON(ret && vdev->reset_required)) { | |
230 | dev_warn( | |
231 | vdev->device, | |
232 | "reset driver is required and reset call failed in release (%d) %s\n", | |
233 | ret, extra_dbg ? extra_dbg : ""); | |
e8909e67 | 234 | } |
ab7e5e34 JG |
235 | pm_runtime_put(vdev->device); |
236 | vfio_platform_regions_cleanup(vdev); | |
237 | vfio_platform_irq_cleanup(vdev); | |
de49fc0d | 238 | } |
5f6c7e08 | 239 | EXPORT_SYMBOL_GPL(vfio_platform_close_device); |
de49fc0d | 240 | |
5f6c7e08 | 241 | int vfio_platform_open_device(struct vfio_device *core_vdev) |
de49fc0d | 242 | { |
6df62c5b JG |
243 | struct vfio_platform_device *vdev = |
244 | container_of(core_vdev, struct vfio_platform_device, vdev); | |
ab7e5e34 | 245 | const char *extra_dbg = NULL; |
e8909e67 AM |
246 | int ret; |
247 | ||
ab7e5e34 JG |
248 | ret = vfio_platform_regions_init(vdev); |
249 | if (ret) | |
250 | return ret; | |
415eb9fc | 251 | |
ab7e5e34 JG |
252 | ret = vfio_platform_irq_init(vdev); |
253 | if (ret) | |
254 | goto err_irq; | |
255 | ||
256 | ret = pm_runtime_get_sync(vdev->device); | |
257 | if (ret < 0) | |
258 | goto err_rst; | |
259 | ||
260 | ret = vfio_platform_call_reset(vdev, &extra_dbg); | |
261 | if (ret && vdev->reset_required) { | |
262 | dev_warn( | |
263 | vdev->device, | |
264 | "reset driver is required and reset call failed in open (%d) %s\n", | |
265 | ret, extra_dbg ? extra_dbg : ""); | |
266 | goto err_rst; | |
e8909e67 | 267 | } |
de49fc0d | 268 | return 0; |
e8909e67 | 269 | |
e9944232 | 270 | err_rst: |
415eb9fc | 271 | pm_runtime_put(vdev->device); |
e9944232 | 272 | vfio_platform_irq_cleanup(vdev); |
682704c4 AM |
273 | err_irq: |
274 | vfio_platform_regions_cleanup(vdev); | |
e8909e67 | 275 | return ret; |
de49fc0d | 276 | } |
5f6c7e08 | 277 | EXPORT_SYMBOL_GPL(vfio_platform_open_device); |
de49fc0d | 278 | |
5f6c7e08 KT |
279 | long vfio_platform_ioctl(struct vfio_device *core_vdev, |
280 | unsigned int cmd, unsigned long arg) | |
de49fc0d | 281 | { |
6df62c5b JG |
282 | struct vfio_platform_device *vdev = |
283 | container_of(core_vdev, struct vfio_platform_device, vdev); | |
284 | ||
2e8567bb AM |
285 | unsigned long minsz; |
286 | ||
287 | if (cmd == VFIO_DEVICE_GET_INFO) { | |
288 | struct vfio_device_info info; | |
289 | ||
290 | minsz = offsetofend(struct vfio_device_info, num_irqs); | |
291 | ||
292 | if (copy_from_user(&info, (void __user *)arg, minsz)) | |
293 | return -EFAULT; | |
294 | ||
295 | if (info.argsz < minsz) | |
296 | return -EINVAL; | |
297 | ||
dc5542fb | 298 | if (vfio_platform_has_reset(vdev)) |
813ae660 | 299 | vdev->flags |= VFIO_DEVICE_FLAGS_RESET; |
2e8567bb | 300 | info.flags = vdev->flags; |
e8909e67 | 301 | info.num_regions = vdev->num_regions; |
682704c4 | 302 | info.num_irqs = vdev->num_irqs; |
2e8567bb | 303 | |
8160c4e4 MT |
304 | return copy_to_user((void __user *)arg, &info, minsz) ? |
305 | -EFAULT : 0; | |
de49fc0d | 306 | |
e8909e67 AM |
307 | } else if (cmd == VFIO_DEVICE_GET_REGION_INFO) { |
308 | struct vfio_region_info info; | |
309 | ||
310 | minsz = offsetofend(struct vfio_region_info, offset); | |
311 | ||
312 | if (copy_from_user(&info, (void __user *)arg, minsz)) | |
313 | return -EFAULT; | |
314 | ||
315 | if (info.argsz < minsz) | |
316 | return -EINVAL; | |
317 | ||
318 | if (info.index >= vdev->num_regions) | |
319 | return -EINVAL; | |
320 | ||
321 | /* map offset to the physical address */ | |
322 | info.offset = VFIO_PLATFORM_INDEX_TO_OFFSET(info.index); | |
323 | info.size = vdev->regions[info.index].size; | |
324 | info.flags = vdev->regions[info.index].flags; | |
325 | ||
8160c4e4 MT |
326 | return copy_to_user((void __user *)arg, &info, minsz) ? |
327 | -EFAULT : 0; | |
de49fc0d | 328 | |
682704c4 AM |
329 | } else if (cmd == VFIO_DEVICE_GET_IRQ_INFO) { |
330 | struct vfio_irq_info info; | |
331 | ||
332 | minsz = offsetofend(struct vfio_irq_info, count); | |
333 | ||
334 | if (copy_from_user(&info, (void __user *)arg, minsz)) | |
335 | return -EFAULT; | |
336 | ||
337 | if (info.argsz < minsz) | |
338 | return -EINVAL; | |
339 | ||
340 | if (info.index >= vdev->num_irqs) | |
341 | return -EINVAL; | |
342 | ||
343 | info.flags = vdev->irqs[info.index].flags; | |
344 | info.count = vdev->irqs[info.index].count; | |
345 | ||
8160c4e4 MT |
346 | return copy_to_user((void __user *)arg, &info, minsz) ? |
347 | -EFAULT : 0; | |
de49fc0d | 348 | |
9a36321c AM |
349 | } else if (cmd == VFIO_DEVICE_SET_IRQS) { |
350 | struct vfio_irq_set hdr; | |
351 | u8 *data = NULL; | |
352 | int ret = 0; | |
a1e03e9b | 353 | size_t data_size = 0; |
9a36321c AM |
354 | |
355 | minsz = offsetofend(struct vfio_irq_set, count); | |
356 | ||
357 | if (copy_from_user(&hdr, (void __user *)arg, minsz)) | |
358 | return -EFAULT; | |
359 | ||
a1e03e9b KW |
360 | ret = vfio_set_irqs_validate_and_prepare(&hdr, vdev->num_irqs, |
361 | vdev->num_irqs, &data_size); | |
362 | if (ret) | |
363 | return ret; | |
9a36321c | 364 | |
a1e03e9b KW |
365 | if (data_size) { |
366 | data = memdup_user((void __user *)(arg + minsz), | |
367 | data_size); | |
9a36321c AM |
368 | if (IS_ERR(data)) |
369 | return PTR_ERR(data); | |
370 | } | |
371 | ||
372 | mutex_lock(&vdev->igate); | |
373 | ||
374 | ret = vfio_platform_set_irqs_ioctl(vdev, hdr.flags, hdr.index, | |
375 | hdr.start, hdr.count, data); | |
376 | mutex_unlock(&vdev->igate); | |
377 | kfree(data); | |
378 | ||
379 | return ret; | |
380 | ||
813ae660 | 381 | } else if (cmd == VFIO_DEVICE_RESET) { |
5afec274 | 382 | return vfio_platform_call_reset(vdev, NULL); |
813ae660 | 383 | } |
de49fc0d AM |
384 | |
385 | return -ENOTTY; | |
386 | } | |
5f6c7e08 | 387 | EXPORT_SYMBOL_GPL(vfio_platform_ioctl); |
de49fc0d | 388 | |
1b4bb2ea | 389 | static ssize_t vfio_platform_read_mmio(struct vfio_platform_region *reg, |
6e3f2645 AM |
390 | char __user *buf, size_t count, |
391 | loff_t off) | |
392 | { | |
393 | unsigned int done = 0; | |
394 | ||
1b4bb2ea JM |
395 | if (!reg->ioaddr) { |
396 | reg->ioaddr = | |
4bdc0d67 | 397 | ioremap(reg->addr, reg->size); |
6e3f2645 | 398 | |
1b4bb2ea | 399 | if (!reg->ioaddr) |
6e3f2645 AM |
400 | return -ENOMEM; |
401 | } | |
402 | ||
403 | while (count) { | |
404 | size_t filled; | |
405 | ||
406 | if (count >= 4 && !(off % 4)) { | |
407 | u32 val; | |
408 | ||
1b4bb2ea | 409 | val = ioread32(reg->ioaddr + off); |
6e3f2645 AM |
410 | if (copy_to_user(buf, &val, 4)) |
411 | goto err; | |
412 | ||
413 | filled = 4; | |
414 | } else if (count >= 2 && !(off % 2)) { | |
415 | u16 val; | |
416 | ||
1b4bb2ea | 417 | val = ioread16(reg->ioaddr + off); |
6e3f2645 AM |
418 | if (copy_to_user(buf, &val, 2)) |
419 | goto err; | |
420 | ||
421 | filled = 2; | |
422 | } else { | |
423 | u8 val; | |
424 | ||
1b4bb2ea | 425 | val = ioread8(reg->ioaddr + off); |
6e3f2645 AM |
426 | if (copy_to_user(buf, &val, 1)) |
427 | goto err; | |
428 | ||
429 | filled = 1; | |
430 | } | |
431 | ||
432 | ||
433 | count -= filled; | |
434 | done += filled; | |
435 | off += filled; | |
436 | buf += filled; | |
437 | } | |
438 | ||
439 | return done; | |
440 | err: | |
441 | return -EFAULT; | |
442 | } | |
443 | ||
5f6c7e08 KT |
444 | ssize_t vfio_platform_read(struct vfio_device *core_vdev, |
445 | char __user *buf, size_t count, loff_t *ppos) | |
de49fc0d | 446 | { |
6df62c5b JG |
447 | struct vfio_platform_device *vdev = |
448 | container_of(core_vdev, struct vfio_platform_device, vdev); | |
6e3f2645 AM |
449 | unsigned int index = VFIO_PLATFORM_OFFSET_TO_INDEX(*ppos); |
450 | loff_t off = *ppos & VFIO_PLATFORM_OFFSET_MASK; | |
451 | ||
452 | if (index >= vdev->num_regions) | |
453 | return -EINVAL; | |
454 | ||
455 | if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_READ)) | |
456 | return -EINVAL; | |
457 | ||
458 | if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_MMIO) | |
1b4bb2ea | 459 | return vfio_platform_read_mmio(&vdev->regions[index], |
6e3f2645 AM |
460 | buf, count, off); |
461 | else if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_PIO) | |
462 | return -EINVAL; /* not implemented */ | |
463 | ||
de49fc0d AM |
464 | return -EINVAL; |
465 | } | |
5f6c7e08 | 466 | EXPORT_SYMBOL_GPL(vfio_platform_read); |
de49fc0d | 467 | |
1b4bb2ea | 468 | static ssize_t vfio_platform_write_mmio(struct vfio_platform_region *reg, |
6e3f2645 AM |
469 | const char __user *buf, size_t count, |
470 | loff_t off) | |
471 | { | |
472 | unsigned int done = 0; | |
473 | ||
1b4bb2ea JM |
474 | if (!reg->ioaddr) { |
475 | reg->ioaddr = | |
4bdc0d67 | 476 | ioremap(reg->addr, reg->size); |
6e3f2645 | 477 | |
1b4bb2ea | 478 | if (!reg->ioaddr) |
6e3f2645 AM |
479 | return -ENOMEM; |
480 | } | |
481 | ||
482 | while (count) { | |
483 | size_t filled; | |
484 | ||
485 | if (count >= 4 && !(off % 4)) { | |
486 | u32 val; | |
487 | ||
488 | if (copy_from_user(&val, buf, 4)) | |
489 | goto err; | |
1b4bb2ea | 490 | iowrite32(val, reg->ioaddr + off); |
6e3f2645 AM |
491 | |
492 | filled = 4; | |
493 | } else if (count >= 2 && !(off % 2)) { | |
494 | u16 val; | |
495 | ||
496 | if (copy_from_user(&val, buf, 2)) | |
497 | goto err; | |
1b4bb2ea | 498 | iowrite16(val, reg->ioaddr + off); |
6e3f2645 AM |
499 | |
500 | filled = 2; | |
501 | } else { | |
502 | u8 val; | |
503 | ||
504 | if (copy_from_user(&val, buf, 1)) | |
505 | goto err; | |
1b4bb2ea | 506 | iowrite8(val, reg->ioaddr + off); |
6e3f2645 AM |
507 | |
508 | filled = 1; | |
509 | } | |
510 | ||
511 | count -= filled; | |
512 | done += filled; | |
513 | off += filled; | |
514 | buf += filled; | |
515 | } | |
516 | ||
517 | return done; | |
518 | err: | |
519 | return -EFAULT; | |
520 | } | |
521 | ||
5f6c7e08 KT |
522 | ssize_t vfio_platform_write(struct vfio_device *core_vdev, const char __user *buf, |
523 | size_t count, loff_t *ppos) | |
de49fc0d | 524 | { |
6df62c5b JG |
525 | struct vfio_platform_device *vdev = |
526 | container_of(core_vdev, struct vfio_platform_device, vdev); | |
6e3f2645 AM |
527 | unsigned int index = VFIO_PLATFORM_OFFSET_TO_INDEX(*ppos); |
528 | loff_t off = *ppos & VFIO_PLATFORM_OFFSET_MASK; | |
529 | ||
530 | if (index >= vdev->num_regions) | |
531 | return -EINVAL; | |
532 | ||
533 | if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_WRITE)) | |
534 | return -EINVAL; | |
535 | ||
536 | if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_MMIO) | |
1b4bb2ea | 537 | return vfio_platform_write_mmio(&vdev->regions[index], |
6e3f2645 AM |
538 | buf, count, off); |
539 | else if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_PIO) | |
540 | return -EINVAL; /* not implemented */ | |
541 | ||
de49fc0d AM |
542 | return -EINVAL; |
543 | } | |
5f6c7e08 | 544 | EXPORT_SYMBOL_GPL(vfio_platform_write); |
de49fc0d | 545 | |
fad4d5b1 AM |
546 | static int vfio_platform_mmap_mmio(struct vfio_platform_region region, |
547 | struct vm_area_struct *vma) | |
548 | { | |
549 | u64 req_len, pgoff, req_start; | |
550 | ||
551 | req_len = vma->vm_end - vma->vm_start; | |
552 | pgoff = vma->vm_pgoff & | |
553 | ((1U << (VFIO_PLATFORM_OFFSET_SHIFT - PAGE_SHIFT)) - 1); | |
554 | req_start = pgoff << PAGE_SHIFT; | |
555 | ||
556 | if (region.size < PAGE_SIZE || req_start + req_len > region.size) | |
557 | return -EINVAL; | |
558 | ||
559 | vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); | |
560 | vma->vm_pgoff = (region.addr >> PAGE_SHIFT) + pgoff; | |
561 | ||
562 | return remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, | |
563 | req_len, vma->vm_page_prot); | |
564 | } | |
565 | ||
5f6c7e08 | 566 | int vfio_platform_mmap(struct vfio_device *core_vdev, struct vm_area_struct *vma) |
de49fc0d | 567 | { |
6df62c5b JG |
568 | struct vfio_platform_device *vdev = |
569 | container_of(core_vdev, struct vfio_platform_device, vdev); | |
fad4d5b1 AM |
570 | unsigned int index; |
571 | ||
572 | index = vma->vm_pgoff >> (VFIO_PLATFORM_OFFSET_SHIFT - PAGE_SHIFT); | |
573 | ||
574 | if (vma->vm_end < vma->vm_start) | |
575 | return -EINVAL; | |
576 | if (!(vma->vm_flags & VM_SHARED)) | |
577 | return -EINVAL; | |
578 | if (index >= vdev->num_regions) | |
579 | return -EINVAL; | |
580 | if (vma->vm_start & ~PAGE_MASK) | |
581 | return -EINVAL; | |
582 | if (vma->vm_end & ~PAGE_MASK) | |
583 | return -EINVAL; | |
584 | ||
585 | if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_MMAP)) | |
586 | return -EINVAL; | |
587 | ||
588 | if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_READ) | |
589 | && (vma->vm_flags & VM_READ)) | |
590 | return -EINVAL; | |
591 | ||
592 | if (!(vdev->regions[index].flags & VFIO_REGION_INFO_FLAG_WRITE) | |
593 | && (vma->vm_flags & VM_WRITE)) | |
594 | return -EINVAL; | |
595 | ||
596 | vma->vm_private_data = vdev; | |
597 | ||
598 | if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_MMIO) | |
599 | return vfio_platform_mmap_mmio(vdev->regions[index], vma); | |
600 | ||
601 | else if (vdev->regions[index].type & VFIO_PLATFORM_REGION_TYPE_PIO) | |
602 | return -EINVAL; /* not implemented */ | |
603 | ||
de49fc0d AM |
604 | return -EINVAL; |
605 | } | |
5f6c7e08 | 606 | EXPORT_SYMBOL_GPL(vfio_platform_mmap); |
de49fc0d | 607 | |
2e062856 | 608 | static int vfio_platform_of_probe(struct vfio_platform_device *vdev, |
a12a9368 SK |
609 | struct device *dev) |
610 | { | |
611 | int ret; | |
612 | ||
613 | ret = device_property_read_string(dev, "compatible", | |
614 | &vdev->compat); | |
615 | if (ret) | |
a88a7b3e | 616 | dev_err(dev, "Cannot retrieve compat for %s\n", vdev->name); |
a12a9368 SK |
617 | |
618 | return ret; | |
619 | } | |
620 | ||
621 | /* | |
622 | * There can be two kernel build combinations. One build where | |
623 | * ACPI is not selected in Kconfig and another one with the ACPI Kconfig. | |
624 | * | |
625 | * In the first case, vfio_platform_acpi_probe will return since | |
626 | * acpi_disabled is 1. DT user will not see any kind of messages from | |
627 | * ACPI. | |
628 | * | |
629 | * In the second case, both DT and ACPI is compiled in but the system is | |
630 | * booting with any of these combinations. | |
631 | * | |
632 | * If the firmware is DT type, then acpi_disabled is 1. The ACPI probe routine | |
633 | * terminates immediately without any messages. | |
634 | * | |
635 | * If the firmware is ACPI type, then acpi_disabled is 0. All other checks are | |
636 | * valid checks. We cannot claim that this system is DT. | |
637 | */ | |
5f6c7e08 KT |
638 | int vfio_platform_init_common(struct vfio_platform_device *vdev) |
639 | { | |
640 | int ret; | |
641 | struct device *dev = vdev->vdev.dev; | |
642 | ||
643 | ret = vfio_platform_acpi_probe(vdev, dev); | |
644 | if (ret) | |
645 | ret = vfio_platform_of_probe(vdev, dev); | |
646 | ||
647 | if (ret) | |
648 | return ret; | |
649 | ||
650 | vdev->device = dev; | |
651 | mutex_init(&vdev->igate); | |
652 | ||
653 | ret = vfio_platform_get_reset(vdev); | |
654 | if (ret && vdev->reset_required) | |
655 | dev_err(dev, "No reset function found for device %s\n", | |
656 | vdev->name); | |
657 | return ret; | |
658 | } | |
659 | EXPORT_SYMBOL_GPL(vfio_platform_init_common); | |
660 | ||
661 | void vfio_platform_release_common(struct vfio_platform_device *vdev) | |
662 | { | |
663 | vfio_platform_put_reset(vdev); | |
664 | } | |
665 | EXPORT_SYMBOL_GPL(vfio_platform_release_common); | |
666 | ||
e086497d EA |
667 | void __vfio_platform_register_reset(struct vfio_platform_reset_node *node) |
668 | { | |
669 | mutex_lock(&driver_lock); | |
670 | list_add(&node->link, &reset_list); | |
671 | mutex_unlock(&driver_lock); | |
672 | } | |
673 | EXPORT_SYMBOL_GPL(__vfio_platform_register_reset); | |
674 | ||
675 | void vfio_platform_unregister_reset(const char *compat, | |
676 | vfio_platform_reset_fn_t fn) | |
677 | { | |
678 | struct vfio_platform_reset_node *iter, *temp; | |
679 | ||
680 | mutex_lock(&driver_lock); | |
681 | list_for_each_entry_safe(iter, temp, &reset_list, link) { | |
7aef80cf | 682 | if (!strcmp(iter->compat, compat) && (iter->of_reset == fn)) { |
e086497d EA |
683 | list_del(&iter->link); |
684 | break; | |
685 | } | |
686 | } | |
687 | ||
688 | mutex_unlock(&driver_lock); | |
689 | ||
690 | } | |
691 | EXPORT_SYMBOL_GPL(vfio_platform_unregister_reset); | |
692 | ||
32a2d71c EA |
693 | MODULE_VERSION(DRIVER_VERSION); |
694 | MODULE_LICENSE("GPL v2"); | |
695 | MODULE_AUTHOR(DRIVER_AUTHOR); | |
696 | MODULE_DESCRIPTION(DRIVER_DESC); |