Commit | Line | Data |
---|---|---|
d61fc96f GH |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Mediated virtual PCI display host device driver | |
4 | * | |
5 | * See mdpy-defs.h for device specs | |
6 | * | |
7 | * (c) Gerd Hoffmann <kraxel@redhat.com> | |
8 | * | |
9 | * based on mtty driver which is: | |
10 | * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. | |
11 | * Author: Neo Jia <cjia@nvidia.com> | |
12 | * Kirti Wankhede <kwankhede@nvidia.com> | |
13 | * | |
14 | * This program is free software; you can redistribute it and/or modify | |
15 | * it under the terms of the GNU General Public License version 2 as | |
16 | * published by the Free Software Foundation. | |
17 | */ | |
18 | #include <linux/init.h> | |
19 | #include <linux/module.h> | |
d61fc96f GH |
20 | #include <linux/kernel.h> |
21 | #include <linux/slab.h> | |
22 | #include <linux/vmalloc.h> | |
23 | #include <linux/cdev.h> | |
24 | #include <linux/vfio.h> | |
25 | #include <linux/iommu.h> | |
26 | #include <linux/sysfs.h> | |
27 | #include <linux/mdev.h> | |
28 | #include <linux/pci.h> | |
29 | #include <drm/drm_fourcc.h> | |
30 | #include "mdpy-defs.h" | |
31 | ||
32 | #define MDPY_NAME "mdpy" | |
33 | #define MDPY_CLASS_NAME "mdpy" | |
34 | ||
35 | #define MDPY_CONFIG_SPACE_SIZE 0xff | |
36 | #define MDPY_MEMORY_BAR_OFFSET PAGE_SIZE | |
37 | #define MDPY_DISPLAY_REGION 16 | |
38 | ||
39 | #define STORE_LE16(addr, val) (*(u16 *)addr = val) | |
40 | #define STORE_LE32(addr, val) (*(u32 *)addr = val) | |
41 | ||
42 | ||
43 | MODULE_LICENSE("GPL v2"); | |
44 | ||
d61fc96f GH |
45 | #define MDPY_TYPE_1 "vga" |
46 | #define MDPY_TYPE_2 "xga" | |
47 | #define MDPY_TYPE_3 "hd" | |
48 | ||
da44c340 CH |
49 | static struct mdpy_type { |
50 | struct mdev_type type; | |
d61fc96f GH |
51 | u32 format; |
52 | u32 bytepp; | |
53 | u32 width; | |
54 | u32 height; | |
55 | } mdpy_types[] = { | |
56 | { | |
da44c340 | 57 | .type.sysfs_name = MDPY_TYPE_1, |
0bc79069 | 58 | .type.pretty_name = MDPY_CLASS_NAME "-" MDPY_TYPE_1, |
d61fc96f GH |
59 | .format = DRM_FORMAT_XRGB8888, |
60 | .bytepp = 4, | |
61 | .width = 640, | |
62 | .height = 480, | |
63 | }, { | |
da44c340 | 64 | .type.sysfs_name = MDPY_TYPE_2, |
0bc79069 | 65 | .type.pretty_name = MDPY_CLASS_NAME "-" MDPY_TYPE_2, |
d61fc96f GH |
66 | .format = DRM_FORMAT_XRGB8888, |
67 | .bytepp = 4, | |
68 | .width = 1024, | |
69 | .height = 768, | |
70 | }, { | |
da44c340 | 71 | .type.sysfs_name = MDPY_TYPE_3, |
0bc79069 | 72 | .type.pretty_name = MDPY_CLASS_NAME "-" MDPY_TYPE_3, |
d61fc96f GH |
73 | .format = DRM_FORMAT_XRGB8888, |
74 | .bytepp = 4, | |
75 | .width = 1920, | |
76 | .height = 1080, | |
77 | }, | |
78 | }; | |
79 | ||
da44c340 CH |
80 | static struct mdev_type *mdpy_mdev_types[] = { |
81 | &mdpy_types[0].type, | |
82 | &mdpy_types[1].type, | |
83 | &mdpy_types[2].type, | |
84 | }; | |
85 | ||
d61fc96f | 86 | static dev_t mdpy_devt; |
1f92d6a7 RM |
87 | static const struct class mdpy_class = { |
88 | .name = MDPY_CLASS_NAME, | |
89 | }; | |
d61fc96f GH |
90 | static struct cdev mdpy_cdev; |
91 | static struct device mdpy_dev; | |
89345d51 | 92 | static struct mdev_parent mdpy_parent; |
437e4136 | 93 | static const struct vfio_device_ops mdpy_dev_ops; |
d61fc96f GH |
94 | |
95 | /* State of each mdev device */ | |
96 | struct mdev_state { | |
437e4136 | 97 | struct vfio_device vdev; |
d61fc96f GH |
98 | u8 *vconfig; |
99 | u32 bar_mask; | |
100 | struct mutex ops_lock; | |
101 | struct mdev_device *mdev; | |
102 | struct vfio_device_info dev_info; | |
103 | ||
104 | const struct mdpy_type *type; | |
105 | u32 memsize; | |
106 | void *memblk; | |
107 | }; | |
108 | ||
d61fc96f GH |
109 | static void mdpy_create_config_space(struct mdev_state *mdev_state) |
110 | { | |
111 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_VENDOR_ID], | |
112 | MDPY_PCI_VENDOR_ID); | |
113 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_DEVICE_ID], | |
114 | MDPY_PCI_DEVICE_ID); | |
115 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_SUBSYSTEM_VENDOR_ID], | |
116 | MDPY_PCI_SUBVENDOR_ID); | |
117 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_SUBSYSTEM_ID], | |
118 | MDPY_PCI_SUBDEVICE_ID); | |
119 | ||
120 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_COMMAND], | |
121 | PCI_COMMAND_IO | PCI_COMMAND_MEMORY); | |
122 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_STATUS], | |
123 | PCI_STATUS_CAP_LIST); | |
124 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_CLASS_DEVICE], | |
125 | PCI_CLASS_DISPLAY_OTHER); | |
126 | mdev_state->vconfig[PCI_CLASS_REVISION] = 0x01; | |
127 | ||
128 | STORE_LE32((u32 *) &mdev_state->vconfig[PCI_BASE_ADDRESS_0], | |
129 | PCI_BASE_ADDRESS_SPACE_MEMORY | | |
130 | PCI_BASE_ADDRESS_MEM_TYPE_32 | | |
131 | PCI_BASE_ADDRESS_MEM_PREFETCH); | |
132 | mdev_state->bar_mask = ~(mdev_state->memsize) + 1; | |
133 | ||
134 | /* vendor specific capability for the config registers */ | |
135 | mdev_state->vconfig[PCI_CAPABILITY_LIST] = MDPY_VENDORCAP_OFFSET; | |
136 | mdev_state->vconfig[MDPY_VENDORCAP_OFFSET + 0] = 0x09; /* vendor cap */ | |
137 | mdev_state->vconfig[MDPY_VENDORCAP_OFFSET + 1] = 0x00; /* next ptr */ | |
138 | mdev_state->vconfig[MDPY_VENDORCAP_OFFSET + 2] = MDPY_VENDORCAP_SIZE; | |
139 | STORE_LE32((u32 *) &mdev_state->vconfig[MDPY_FORMAT_OFFSET], | |
140 | mdev_state->type->format); | |
141 | STORE_LE32((u32 *) &mdev_state->vconfig[MDPY_WIDTH_OFFSET], | |
142 | mdev_state->type->width); | |
143 | STORE_LE32((u32 *) &mdev_state->vconfig[MDPY_HEIGHT_OFFSET], | |
144 | mdev_state->type->height); | |
145 | } | |
146 | ||
147 | static void handle_pci_cfg_write(struct mdev_state *mdev_state, u16 offset, | |
148 | char *buf, u32 count) | |
149 | { | |
150 | struct device *dev = mdev_dev(mdev_state->mdev); | |
151 | u32 cfg_addr; | |
152 | ||
153 | switch (offset) { | |
154 | case PCI_BASE_ADDRESS_0: | |
155 | cfg_addr = *(u32 *)buf; | |
156 | ||
157 | if (cfg_addr == 0xffffffff) { | |
158 | cfg_addr = (cfg_addr & mdev_state->bar_mask); | |
159 | } else { | |
160 | cfg_addr &= PCI_BASE_ADDRESS_MEM_MASK; | |
161 | if (cfg_addr) | |
162 | dev_info(dev, "BAR0 @ 0x%x\n", cfg_addr); | |
163 | } | |
164 | ||
165 | cfg_addr |= (mdev_state->vconfig[offset] & | |
166 | ~PCI_BASE_ADDRESS_MEM_MASK); | |
167 | STORE_LE32(&mdev_state->vconfig[offset], cfg_addr); | |
168 | break; | |
169 | } | |
170 | } | |
171 | ||
437e4136 JG |
172 | static ssize_t mdev_access(struct mdev_state *mdev_state, char *buf, |
173 | size_t count, loff_t pos, bool is_write) | |
d61fc96f | 174 | { |
d61fc96f GH |
175 | int ret = 0; |
176 | ||
177 | mutex_lock(&mdev_state->ops_lock); | |
178 | ||
179 | if (pos < MDPY_CONFIG_SPACE_SIZE) { | |
180 | if (is_write) | |
181 | handle_pci_cfg_write(mdev_state, pos, buf, count); | |
182 | else | |
183 | memcpy(buf, (mdev_state->vconfig + pos), count); | |
184 | ||
185 | } else if ((pos >= MDPY_MEMORY_BAR_OFFSET) && | |
186 | (pos + count <= | |
187 | MDPY_MEMORY_BAR_OFFSET + mdev_state->memsize)) { | |
188 | pos -= MDPY_MEMORY_BAR_OFFSET; | |
189 | if (is_write) | |
190 | memcpy(mdev_state->memblk, buf, count); | |
191 | else | |
192 | memcpy(buf, mdev_state->memblk, count); | |
193 | ||
194 | } else { | |
437e4136 JG |
195 | dev_info(mdev_state->vdev.dev, |
196 | "%s: %s @0x%llx (unhandled)\n", __func__, | |
197 | is_write ? "WR" : "RD", pos); | |
d61fc96f GH |
198 | ret = -1; |
199 | goto accessfailed; | |
200 | } | |
201 | ||
202 | ret = count; | |
203 | ||
204 | ||
205 | accessfailed: | |
206 | mutex_unlock(&mdev_state->ops_lock); | |
207 | ||
208 | return ret; | |
209 | } | |
210 | ||
437e4136 | 211 | static int mdpy_reset(struct mdev_state *mdev_state) |
d61fc96f | 212 | { |
d61fc96f GH |
213 | u32 stride, i; |
214 | ||
215 | /* initialize with gray gradient */ | |
216 | stride = mdev_state->type->width * mdev_state->type->bytepp; | |
217 | for (i = 0; i < mdev_state->type->height; i++) | |
218 | memset(mdev_state->memblk + i * stride, | |
219 | i * 255 / mdev_state->type->height, | |
220 | stride); | |
221 | return 0; | |
222 | } | |
223 | ||
603c09f2 | 224 | static int mdpy_init_dev(struct vfio_device *vdev) |
d61fc96f | 225 | { |
603c09f2 YL |
226 | struct mdev_state *mdev_state = |
227 | container_of(vdev, struct mdev_state, vdev); | |
228 | struct mdev_device *mdev = to_mdev_device(vdev->dev); | |
adc9d1f6 | 229 | const struct mdpy_type *type = |
da44c340 | 230 | container_of(mdev->type, struct mdpy_type, type); |
d61fc96f | 231 | u32 fbsize; |
603c09f2 | 232 | int ret = -ENOMEM; |
d61fc96f | 233 | |
d61fc96f | 234 | mdev_state->vconfig = kzalloc(MDPY_CONFIG_SPACE_SIZE, GFP_KERNEL); |
603c09f2 YL |
235 | if (!mdev_state->vconfig) |
236 | return ret; | |
d61fc96f | 237 | |
d61fc96f GH |
238 | fbsize = roundup_pow_of_two(type->width * type->height * type->bytepp); |
239 | ||
240 | mdev_state->memblk = vmalloc_user(fbsize); | |
603c09f2 YL |
241 | if (!mdev_state->memblk) |
242 | goto out_vconfig; | |
d61fc96f GH |
243 | |
244 | mutex_init(&mdev_state->ops_lock); | |
245 | mdev_state->mdev = mdev; | |
603c09f2 | 246 | mdev_state->type = type; |
d61fc96f GH |
247 | mdev_state->memsize = fbsize; |
248 | mdpy_create_config_space(mdev_state); | |
437e4136 | 249 | mdpy_reset(mdev_state); |
d61fc96f | 250 | |
0bc79069 CH |
251 | dev_info(vdev->dev, "%s: %s (%dx%d)\n", __func__, type->type.pretty_name, |
252 | type->width, type->height); | |
603c09f2 YL |
253 | return 0; |
254 | ||
255 | out_vconfig: | |
256 | kfree(mdev_state->vconfig); | |
257 | return ret; | |
258 | } | |
259 | ||
260 | static int mdpy_probe(struct mdev_device *mdev) | |
261 | { | |
262 | struct mdev_state *mdev_state; | |
263 | int ret; | |
264 | ||
265 | mdev_state = vfio_alloc_device(mdev_state, vdev, &mdev->dev, | |
266 | &mdpy_dev_ops); | |
267 | if (IS_ERR(mdev_state)) | |
268 | return PTR_ERR(mdev_state); | |
437e4136 | 269 | |
c68ea0d0 | 270 | ret = vfio_register_emulated_iommu_dev(&mdev_state->vdev); |
ae03c377 | 271 | if (ret) |
603c09f2 | 272 | goto err_put_vdev; |
437e4136 | 273 | dev_set_drvdata(&mdev->dev, mdev_state); |
d61fc96f | 274 | return 0; |
603c09f2 YL |
275 | |
276 | err_put_vdev: | |
277 | vfio_put_device(&mdev_state->vdev); | |
278 | return ret; | |
279 | } | |
280 | ||
281 | static void mdpy_release_dev(struct vfio_device *vdev) | |
282 | { | |
283 | struct mdev_state *mdev_state = | |
284 | container_of(vdev, struct mdev_state, vdev); | |
285 | ||
ae03c377 | 286 | vfree(mdev_state->memblk); |
ae03c377 | 287 | kfree(mdev_state->vconfig); |
d61fc96f GH |
288 | } |
289 | ||
437e4136 | 290 | static void mdpy_remove(struct mdev_device *mdev) |
d61fc96f | 291 | { |
437e4136 | 292 | struct mdev_state *mdev_state = dev_get_drvdata(&mdev->dev); |
d61fc96f | 293 | |
437e4136 | 294 | dev_info(&mdev->dev, "%s\n", __func__); |
d61fc96f | 295 | |
437e4136 | 296 | vfio_unregister_group_dev(&mdev_state->vdev); |
603c09f2 | 297 | vfio_put_device(&mdev_state->vdev); |
d61fc96f GH |
298 | } |
299 | ||
437e4136 | 300 | static ssize_t mdpy_read(struct vfio_device *vdev, char __user *buf, |
d61fc96f GH |
301 | size_t count, loff_t *ppos) |
302 | { | |
437e4136 JG |
303 | struct mdev_state *mdev_state = |
304 | container_of(vdev, struct mdev_state, vdev); | |
d61fc96f GH |
305 | unsigned int done = 0; |
306 | int ret; | |
307 | ||
308 | while (count) { | |
309 | size_t filled; | |
310 | ||
311 | if (count >= 4 && !(*ppos % 4)) { | |
312 | u32 val; | |
313 | ||
437e4136 JG |
314 | ret = mdev_access(mdev_state, (char *)&val, sizeof(val), |
315 | *ppos, false); | |
d61fc96f GH |
316 | if (ret <= 0) |
317 | goto read_err; | |
318 | ||
319 | if (copy_to_user(buf, &val, sizeof(val))) | |
320 | goto read_err; | |
321 | ||
322 | filled = 4; | |
323 | } else if (count >= 2 && !(*ppos % 2)) { | |
324 | u16 val; | |
325 | ||
437e4136 | 326 | ret = mdev_access(mdev_state, (char *)&val, sizeof(val), |
d61fc96f GH |
327 | *ppos, false); |
328 | if (ret <= 0) | |
329 | goto read_err; | |
330 | ||
331 | if (copy_to_user(buf, &val, sizeof(val))) | |
332 | goto read_err; | |
333 | ||
334 | filled = 2; | |
335 | } else { | |
336 | u8 val; | |
337 | ||
437e4136 | 338 | ret = mdev_access(mdev_state, (char *)&val, sizeof(val), |
d61fc96f GH |
339 | *ppos, false); |
340 | if (ret <= 0) | |
341 | goto read_err; | |
342 | ||
343 | if (copy_to_user(buf, &val, sizeof(val))) | |
344 | goto read_err; | |
345 | ||
346 | filled = 1; | |
347 | } | |
348 | ||
349 | count -= filled; | |
350 | done += filled; | |
351 | *ppos += filled; | |
352 | buf += filled; | |
353 | } | |
354 | ||
355 | return done; | |
356 | ||
357 | read_err: | |
358 | return -EFAULT; | |
359 | } | |
360 | ||
437e4136 | 361 | static ssize_t mdpy_write(struct vfio_device *vdev, const char __user *buf, |
d61fc96f GH |
362 | size_t count, loff_t *ppos) |
363 | { | |
437e4136 JG |
364 | struct mdev_state *mdev_state = |
365 | container_of(vdev, struct mdev_state, vdev); | |
d61fc96f GH |
366 | unsigned int done = 0; |
367 | int ret; | |
368 | ||
369 | while (count) { | |
370 | size_t filled; | |
371 | ||
372 | if (count >= 4 && !(*ppos % 4)) { | |
373 | u32 val; | |
374 | ||
375 | if (copy_from_user(&val, buf, sizeof(val))) | |
376 | goto write_err; | |
377 | ||
437e4136 | 378 | ret = mdev_access(mdev_state, (char *)&val, sizeof(val), |
d61fc96f GH |
379 | *ppos, true); |
380 | if (ret <= 0) | |
381 | goto write_err; | |
382 | ||
383 | filled = 4; | |
384 | } else if (count >= 2 && !(*ppos % 2)) { | |
385 | u16 val; | |
386 | ||
387 | if (copy_from_user(&val, buf, sizeof(val))) | |
388 | goto write_err; | |
389 | ||
437e4136 | 390 | ret = mdev_access(mdev_state, (char *)&val, sizeof(val), |
d61fc96f GH |
391 | *ppos, true); |
392 | if (ret <= 0) | |
393 | goto write_err; | |
394 | ||
395 | filled = 2; | |
396 | } else { | |
397 | u8 val; | |
398 | ||
399 | if (copy_from_user(&val, buf, sizeof(val))) | |
400 | goto write_err; | |
401 | ||
437e4136 | 402 | ret = mdev_access(mdev_state, (char *)&val, sizeof(val), |
d61fc96f GH |
403 | *ppos, true); |
404 | if (ret <= 0) | |
405 | goto write_err; | |
406 | ||
407 | filled = 1; | |
408 | } | |
409 | count -= filled; | |
410 | done += filled; | |
411 | *ppos += filled; | |
412 | buf += filled; | |
413 | } | |
414 | ||
415 | return done; | |
416 | write_err: | |
417 | return -EFAULT; | |
418 | } | |
419 | ||
437e4136 | 420 | static int mdpy_mmap(struct vfio_device *vdev, struct vm_area_struct *vma) |
d61fc96f | 421 | { |
437e4136 JG |
422 | struct mdev_state *mdev_state = |
423 | container_of(vdev, struct mdev_state, vdev); | |
d61fc96f GH |
424 | |
425 | if (vma->vm_pgoff != MDPY_MEMORY_BAR_OFFSET >> PAGE_SHIFT) | |
426 | return -EINVAL; | |
427 | if (vma->vm_end < vma->vm_start) | |
428 | return -EINVAL; | |
429 | if (vma->vm_end - vma->vm_start > mdev_state->memsize) | |
430 | return -EINVAL; | |
431 | if ((vma->vm_flags & VM_SHARED) == 0) | |
432 | return -EINVAL; | |
433 | ||
8c2acfe8 | 434 | return remap_vmalloc_range(vma, mdev_state->memblk, 0); |
d61fc96f GH |
435 | } |
436 | ||
437e4136 | 437 | static int mdpy_get_region_info(struct mdev_state *mdev_state, |
d61fc96f GH |
438 | struct vfio_region_info *region_info, |
439 | u16 *cap_type_id, void **cap_type) | |
440 | { | |
d61fc96f GH |
441 | if (region_info->index >= VFIO_PCI_NUM_REGIONS && |
442 | region_info->index != MDPY_DISPLAY_REGION) | |
443 | return -EINVAL; | |
444 | ||
445 | switch (region_info->index) { | |
446 | case VFIO_PCI_CONFIG_REGION_INDEX: | |
447 | region_info->offset = 0; | |
448 | region_info->size = MDPY_CONFIG_SPACE_SIZE; | |
449 | region_info->flags = (VFIO_REGION_INFO_FLAG_READ | | |
450 | VFIO_REGION_INFO_FLAG_WRITE); | |
451 | break; | |
452 | case VFIO_PCI_BAR0_REGION_INDEX: | |
453 | case MDPY_DISPLAY_REGION: | |
454 | region_info->offset = MDPY_MEMORY_BAR_OFFSET; | |
455 | region_info->size = mdev_state->memsize; | |
456 | region_info->flags = (VFIO_REGION_INFO_FLAG_READ | | |
457 | VFIO_REGION_INFO_FLAG_WRITE | | |
458 | VFIO_REGION_INFO_FLAG_MMAP); | |
459 | break; | |
460 | default: | |
461 | region_info->size = 0; | |
462 | region_info->offset = 0; | |
463 | region_info->flags = 0; | |
464 | } | |
465 | ||
466 | return 0; | |
467 | } | |
468 | ||
437e4136 | 469 | static int mdpy_get_irq_info(struct vfio_irq_info *irq_info) |
d61fc96f GH |
470 | { |
471 | irq_info->count = 0; | |
472 | return 0; | |
473 | } | |
474 | ||
437e4136 | 475 | static int mdpy_get_device_info(struct vfio_device_info *dev_info) |
d61fc96f GH |
476 | { |
477 | dev_info->flags = VFIO_DEVICE_FLAGS_PCI; | |
478 | dev_info->num_regions = VFIO_PCI_NUM_REGIONS; | |
479 | dev_info->num_irqs = VFIO_PCI_NUM_IRQS; | |
480 | return 0; | |
481 | } | |
482 | ||
437e4136 | 483 | static int mdpy_query_gfx_plane(struct mdev_state *mdev_state, |
d61fc96f GH |
484 | struct vfio_device_gfx_plane_info *plane) |
485 | { | |
d61fc96f GH |
486 | if (plane->flags & VFIO_GFX_PLANE_TYPE_PROBE) { |
487 | if (plane->flags == (VFIO_GFX_PLANE_TYPE_PROBE | | |
488 | VFIO_GFX_PLANE_TYPE_REGION)) | |
489 | return 0; | |
490 | return -EINVAL; | |
491 | } | |
492 | ||
493 | if (plane->flags != VFIO_GFX_PLANE_TYPE_REGION) | |
494 | return -EINVAL; | |
495 | ||
496 | plane->drm_format = mdev_state->type->format; | |
497 | plane->width = mdev_state->type->width; | |
498 | plane->height = mdev_state->type->height; | |
499 | plane->stride = (mdev_state->type->width * | |
500 | mdev_state->type->bytepp); | |
501 | plane->size = mdev_state->memsize; | |
502 | plane->region_index = MDPY_DISPLAY_REGION; | |
503 | ||
504 | /* unused */ | |
505 | plane->drm_format_mod = 0; | |
506 | plane->x_pos = 0; | |
507 | plane->y_pos = 0; | |
508 | plane->x_hot = 0; | |
509 | plane->y_hot = 0; | |
510 | ||
511 | return 0; | |
512 | } | |
513 | ||
437e4136 | 514 | static long mdpy_ioctl(struct vfio_device *vdev, unsigned int cmd, |
d61fc96f GH |
515 | unsigned long arg) |
516 | { | |
517 | int ret = 0; | |
518 | unsigned long minsz; | |
437e4136 JG |
519 | struct mdev_state *mdev_state = |
520 | container_of(vdev, struct mdev_state, vdev); | |
d61fc96f GH |
521 | |
522 | switch (cmd) { | |
523 | case VFIO_DEVICE_GET_INFO: | |
524 | { | |
525 | struct vfio_device_info info; | |
526 | ||
527 | minsz = offsetofend(struct vfio_device_info, num_irqs); | |
528 | ||
529 | if (copy_from_user(&info, (void __user *)arg, minsz)) | |
530 | return -EFAULT; | |
531 | ||
532 | if (info.argsz < minsz) | |
533 | return -EINVAL; | |
534 | ||
437e4136 | 535 | ret = mdpy_get_device_info(&info); |
d61fc96f GH |
536 | if (ret) |
537 | return ret; | |
538 | ||
539 | memcpy(&mdev_state->dev_info, &info, sizeof(info)); | |
540 | ||
541 | if (copy_to_user((void __user *)arg, &info, minsz)) | |
542 | return -EFAULT; | |
543 | ||
544 | return 0; | |
545 | } | |
546 | case VFIO_DEVICE_GET_REGION_INFO: | |
547 | { | |
548 | struct vfio_region_info info; | |
549 | u16 cap_type_id = 0; | |
550 | void *cap_type = NULL; | |
551 | ||
552 | minsz = offsetofend(struct vfio_region_info, offset); | |
553 | ||
554 | if (copy_from_user(&info, (void __user *)arg, minsz)) | |
555 | return -EFAULT; | |
556 | ||
557 | if (info.argsz < minsz) | |
558 | return -EINVAL; | |
559 | ||
437e4136 | 560 | ret = mdpy_get_region_info(mdev_state, &info, &cap_type_id, |
d61fc96f GH |
561 | &cap_type); |
562 | if (ret) | |
563 | return ret; | |
564 | ||
565 | if (copy_to_user((void __user *)arg, &info, minsz)) | |
566 | return -EFAULT; | |
567 | ||
568 | return 0; | |
569 | } | |
570 | ||
571 | case VFIO_DEVICE_GET_IRQ_INFO: | |
572 | { | |
573 | struct vfio_irq_info info; | |
574 | ||
575 | minsz = offsetofend(struct vfio_irq_info, count); | |
576 | ||
577 | if (copy_from_user(&info, (void __user *)arg, minsz)) | |
578 | return -EFAULT; | |
579 | ||
580 | if ((info.argsz < minsz) || | |
581 | (info.index >= mdev_state->dev_info.num_irqs)) | |
582 | return -EINVAL; | |
583 | ||
437e4136 | 584 | ret = mdpy_get_irq_info(&info); |
d61fc96f GH |
585 | if (ret) |
586 | return ret; | |
587 | ||
588 | if (copy_to_user((void __user *)arg, &info, minsz)) | |
589 | return -EFAULT; | |
590 | ||
591 | return 0; | |
592 | } | |
593 | ||
594 | case VFIO_DEVICE_QUERY_GFX_PLANE: | |
595 | { | |
a7bea9f4 | 596 | struct vfio_device_gfx_plane_info plane = {}; |
d61fc96f GH |
597 | |
598 | minsz = offsetofend(struct vfio_device_gfx_plane_info, | |
599 | region_index); | |
600 | ||
601 | if (copy_from_user(&plane, (void __user *)arg, minsz)) | |
602 | return -EFAULT; | |
603 | ||
604 | if (plane.argsz < minsz) | |
605 | return -EINVAL; | |
606 | ||
437e4136 | 607 | ret = mdpy_query_gfx_plane(mdev_state, &plane); |
d61fc96f GH |
608 | if (ret) |
609 | return ret; | |
610 | ||
611 | if (copy_to_user((void __user *)arg, &plane, minsz)) | |
612 | return -EFAULT; | |
613 | ||
614 | return 0; | |
615 | } | |
616 | ||
617 | case VFIO_DEVICE_SET_IRQS: | |
618 | return -EINVAL; | |
619 | ||
620 | case VFIO_DEVICE_RESET: | |
437e4136 | 621 | return mdpy_reset(mdev_state); |
d61fc96f GH |
622 | } |
623 | return -ENOTTY; | |
624 | } | |
625 | ||
d61fc96f GH |
626 | static ssize_t |
627 | resolution_show(struct device *dev, struct device_attribute *attr, | |
628 | char *buf) | |
629 | { | |
437e4136 | 630 | struct mdev_state *mdev_state = dev_get_drvdata(dev); |
d61fc96f GH |
631 | |
632 | return sprintf(buf, "%dx%d\n", | |
633 | mdev_state->type->width, | |
634 | mdev_state->type->height); | |
635 | } | |
636 | static DEVICE_ATTR_RO(resolution); | |
637 | ||
638 | static struct attribute *mdev_dev_attrs[] = { | |
639 | &dev_attr_resolution.attr, | |
640 | NULL, | |
641 | }; | |
642 | ||
643 | static const struct attribute_group mdev_dev_group = { | |
644 | .name = "vendor", | |
645 | .attrs = mdev_dev_attrs, | |
646 | }; | |
647 | ||
6cbf507f | 648 | static const struct attribute_group *mdev_dev_groups[] = { |
d61fc96f GH |
649 | &mdev_dev_group, |
650 | NULL, | |
651 | }; | |
652 | ||
685a1537 | 653 | static ssize_t mdpy_show_description(struct mdev_type *mtype, char *buf) |
d61fc96f | 654 | { |
da44c340 | 655 | struct mdpy_type *type = container_of(mtype, struct mdpy_type, type); |
d61fc96f GH |
656 | |
657 | return sprintf(buf, "virtual display, %dx%d framebuffer\n", | |
698f99ed | 658 | type->width, type->height); |
d61fc96f | 659 | } |
d61fc96f | 660 | |
437e4136 | 661 | static const struct vfio_device_ops mdpy_dev_ops = { |
603c09f2 YL |
662 | .init = mdpy_init_dev, |
663 | .release = mdpy_release_dev, | |
437e4136 JG |
664 | .read = mdpy_read, |
665 | .write = mdpy_write, | |
666 | .ioctl = mdpy_ioctl, | |
667 | .mmap = mdpy_mmap, | |
0a782d15 YL |
668 | .bind_iommufd = vfio_iommufd_emulated_bind, |
669 | .unbind_iommufd = vfio_iommufd_emulated_unbind, | |
670 | .attach_ioas = vfio_iommufd_emulated_attach_ioas, | |
8cfa7186 | 671 | .detach_ioas = vfio_iommufd_emulated_detach_ioas, |
437e4136 JG |
672 | }; |
673 | ||
674 | static struct mdev_driver mdpy_driver = { | |
290aac5d | 675 | .device_api = VFIO_DEVICE_API_PCI_STRING, |
9c799c22 | 676 | .max_instances = 4, |
437e4136 JG |
677 | .driver = { |
678 | .name = "mdpy", | |
679 | .owner = THIS_MODULE, | |
680 | .mod_name = KBUILD_MODNAME, | |
681 | .dev_groups = mdev_dev_groups, | |
682 | }, | |
683 | .probe = mdpy_probe, | |
684 | .remove = mdpy_remove, | |
685a1537 | 685 | .show_description = mdpy_show_description, |
d61fc96f GH |
686 | }; |
687 | ||
688 | static const struct file_operations vd_fops = { | |
689 | .owner = THIS_MODULE, | |
690 | }; | |
691 | ||
692 | static void mdpy_device_release(struct device *dev) | |
693 | { | |
694 | /* nothing */ | |
695 | } | |
696 | ||
697 | static int __init mdpy_dev_init(void) | |
698 | { | |
699 | int ret = 0; | |
700 | ||
18bc04bc | 701 | ret = alloc_chrdev_region(&mdpy_devt, 0, MINORMASK + 1, MDPY_NAME); |
d61fc96f GH |
702 | if (ret < 0) { |
703 | pr_err("Error: failed to register mdpy_dev, err: %d\n", ret); | |
704 | return ret; | |
705 | } | |
706 | cdev_init(&mdpy_cdev, &vd_fops); | |
18bc04bc | 707 | cdev_add(&mdpy_cdev, mdpy_devt, MINORMASK + 1); |
d61fc96f GH |
708 | pr_info("%s: major %d\n", __func__, MAJOR(mdpy_devt)); |
709 | ||
437e4136 JG |
710 | ret = mdev_register_driver(&mdpy_driver); |
711 | if (ret) | |
712 | goto err_cdev; | |
713 | ||
1f92d6a7 RM |
714 | ret = class_register(&mdpy_class); |
715 | if (ret) | |
437e4136 | 716 | goto err_driver; |
1f92d6a7 | 717 | mdpy_dev.class = &mdpy_class; |
d61fc96f GH |
718 | mdpy_dev.release = mdpy_device_release; |
719 | dev_set_name(&mdpy_dev, "%s", MDPY_NAME); | |
720 | ||
721 | ret = device_register(&mdpy_dev); | |
722 | if (ret) | |
ce389573 | 723 | goto err_put; |
d61fc96f | 724 | |
da44c340 CH |
725 | ret = mdev_register_parent(&mdpy_parent, &mdpy_dev, &mdpy_driver, |
726 | mdpy_mdev_types, | |
727 | ARRAY_SIZE(mdpy_mdev_types)); | |
d61fc96f | 728 | if (ret) |
437e4136 | 729 | goto err_device; |
d61fc96f GH |
730 | |
731 | return 0; | |
732 | ||
437e4136 | 733 | err_device: |
ce389573 AW |
734 | device_del(&mdpy_dev); |
735 | err_put: | |
736 | put_device(&mdpy_dev); | |
1f92d6a7 | 737 | class_unregister(&mdpy_class); |
437e4136 JG |
738 | err_driver: |
739 | mdev_unregister_driver(&mdpy_driver); | |
740 | err_cdev: | |
d61fc96f | 741 | cdev_del(&mdpy_cdev); |
18bc04bc | 742 | unregister_chrdev_region(mdpy_devt, MINORMASK + 1); |
d61fc96f GH |
743 | return ret; |
744 | } | |
745 | ||
746 | static void __exit mdpy_dev_exit(void) | |
747 | { | |
748 | mdpy_dev.bus = NULL; | |
89345d51 | 749 | mdev_unregister_parent(&mdpy_parent); |
d61fc96f GH |
750 | |
751 | device_unregister(&mdpy_dev); | |
437e4136 | 752 | mdev_unregister_driver(&mdpy_driver); |
d61fc96f | 753 | cdev_del(&mdpy_cdev); |
18bc04bc | 754 | unregister_chrdev_region(mdpy_devt, MINORMASK + 1); |
1f92d6a7 | 755 | class_unregister(&mdpy_class); |
d61fc96f GH |
756 | } |
757 | ||
9c799c22 JG |
758 | module_param_named(count, mdpy_driver.max_instances, int, 0444); |
759 | MODULE_PARM_DESC(count, "number of " MDPY_NAME " devices"); | |
760 | ||
d61fc96f GH |
761 | module_init(mdpy_dev_init) |
762 | module_exit(mdpy_dev_exit) |