diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2018-06-12 16:11:26 -0400 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2018-06-12 16:11:26 -0400 |
| commit | 467590e055f5c714fb457803250415879d0da9e5 (patch) | |
| tree | 7533a26dcd19023d5eeac54f62432097c85f4ef3 | |
| parent | 763f96944c954ce0e00a10a7bdfe29adbe4f92eb (diff) | |
| parent | c1abca96b252a9627f99f39215b84e5de92bf1e3 (diff) | |
Merge tag 'vfio-v4.18-rc1' of git://github.com/awilliam/linux-vfio
Pull VFIO updates from Alex Williamson:
- Bind type1 task tracking to group_leader to facilitate vCPU hotplug
in QEMU (Alex Williamson)
- Sample mdev display drivers, including region-based host and guest
Linux drivers and bochs compatible dmabuf device
(Gerd Hoffmann)
- Fix vfio-platform reset module leak (Geert Uytterhoeven)
- vfio-platform error message consistency (Geert Uytterhoeven)
- Global checking for mdev uuid collisions rather than per parent
device (Alex Williamson)
- Use match_string() helper (Yisheng Xie)
- vfio-platform PM domain fixes (Geert Uytterhoeven)
- Fix sample mbochs driver build dependency (Arnd Bergmann)
* tag 'vfio-v4.18-rc1' of git://github.com/awilliam/linux-vfio:
samples: mbochs: add DMA_SHARED_BUFFER dependency
vfio: platform: Fix using devices in PM Domains
vfio: use match_string() helper
vfio/mdev: Re-order sysfs attribute creation
vfio/mdev: Check globally for duplicate devices
vfio: platform: Make printed error messages more consistent
vfio: platform: Fix reset module leak in error path
sample: vfio bochs vbe display (host device for bochs-drm)
sample: vfio mdev display - guest driver
sample: vfio mdev display - host device
vfio/type1: Fix task tracking for QEMU vCPU hotplug
| -rw-r--r-- | Documentation/vfio-mediated-device.txt | 5 | ||||
| -rw-r--r-- | drivers/vfio/mdev/mdev_core.c | 102 | ||||
| -rw-r--r-- | drivers/vfio/mdev/mdev_private.h | 2 | ||||
| -rw-r--r-- | drivers/vfio/mdev/mdev_sysfs.c | 14 | ||||
| -rw-r--r-- | drivers/vfio/platform/vfio_platform_common.c | 30 | ||||
| -rw-r--r-- | drivers/vfio/vfio.c | 11 | ||||
| -rw-r--r-- | drivers/vfio/vfio_iommu_type1.c | 73 | ||||
| -rw-r--r-- | samples/Kconfig | 31 | ||||
| -rw-r--r-- | samples/vfio-mdev/Makefile | 3 | ||||
| -rw-r--r-- | samples/vfio-mdev/mbochs.c | 1406 | ||||
| -rw-r--r-- | samples/vfio-mdev/mdpy-defs.h | 22 | ||||
| -rw-r--r-- | samples/vfio-mdev/mdpy-fb.c | 232 | ||||
| -rw-r--r-- | samples/vfio-mdev/mdpy.c | 807 |
13 files changed, 2622 insertions, 116 deletions
diff --git a/Documentation/vfio-mediated-device.txt b/Documentation/vfio-mediated-device.txt index 1b3950346532..c3f69bcaf96e 100644 --- a/Documentation/vfio-mediated-device.txt +++ b/Documentation/vfio-mediated-device.txt | |||
| @@ -145,6 +145,11 @@ The functions in the mdev_parent_ops structure are as follows: | |||
| 145 | * create: allocate basic resources in a driver for a mediated device | 145 | * create: allocate basic resources in a driver for a mediated device |
| 146 | * remove: free resources in a driver when a mediated device is destroyed | 146 | * remove: free resources in a driver when a mediated device is destroyed |
| 147 | 147 | ||
| 148 | (Note that mdev-core provides no implicit serialization of create/remove | ||
| 149 | callbacks per mdev parent device, per mdev type, or any other categorization. | ||
| 150 | Vendor drivers are expected to be fully asynchronous in this respect or | ||
| 151 | provide their own internal resource protection.) | ||
| 152 | |||
| 148 | The callbacks in the mdev_parent_ops structure are as follows: | 153 | The callbacks in the mdev_parent_ops structure are as follows: |
| 149 | 154 | ||
| 150 | * open: open callback of mediated device | 155 | * open: open callback of mediated device |
diff --git a/drivers/vfio/mdev/mdev_core.c b/drivers/vfio/mdev/mdev_core.c index 126991046eb7..0212f0ee8aea 100644 --- a/drivers/vfio/mdev/mdev_core.c +++ b/drivers/vfio/mdev/mdev_core.c | |||
| @@ -66,34 +66,6 @@ uuid_le mdev_uuid(struct mdev_device *mdev) | |||
| 66 | } | 66 | } |
| 67 | EXPORT_SYMBOL(mdev_uuid); | 67 | EXPORT_SYMBOL(mdev_uuid); |
| 68 | 68 | ||
| 69 | static int _find_mdev_device(struct device *dev, void *data) | ||
| 70 | { | ||
| 71 | struct mdev_device *mdev; | ||
| 72 | |||
| 73 | if (!dev_is_mdev(dev)) | ||
| 74 | return 0; | ||
| 75 | |||
| 76 | mdev = to_mdev_device(dev); | ||
| 77 | |||
| 78 | if (uuid_le_cmp(mdev->uuid, *(uuid_le *)data) == 0) | ||
| 79 | return 1; | ||
| 80 | |||
| 81 | return 0; | ||
| 82 | } | ||
| 83 | |||
| 84 | static bool mdev_device_exist(struct mdev_parent *parent, uuid_le uuid) | ||
| 85 | { | ||
| 86 | struct device *dev; | ||
| 87 | |||
| 88 | dev = device_find_child(parent->dev, &uuid, _find_mdev_device); | ||
| 89 | if (dev) { | ||
| 90 | put_device(dev); | ||
| 91 | return true; | ||
| 92 | } | ||
| 93 | |||
| 94 | return false; | ||
| 95 | } | ||
| 96 | |||
| 97 | /* Should be called holding parent_list_lock */ | 69 | /* Should be called holding parent_list_lock */ |
| 98 | static struct mdev_parent *__find_parent_device(struct device *dev) | 70 | static struct mdev_parent *__find_parent_device(struct device *dev) |
| 99 | { | 71 | { |
| @@ -221,7 +193,6 @@ int mdev_register_device(struct device *dev, const struct mdev_parent_ops *ops) | |||
| 221 | } | 193 | } |
| 222 | 194 | ||
| 223 | kref_init(&parent->ref); | 195 | kref_init(&parent->ref); |
| 224 | mutex_init(&parent->lock); | ||
| 225 | 196 | ||
| 226 | parent->dev = dev; | 197 | parent->dev = dev; |
| 227 | parent->ops = ops; | 198 | parent->ops = ops; |
| @@ -297,6 +268,10 @@ static void mdev_device_release(struct device *dev) | |||
| 297 | { | 268 | { |
| 298 | struct mdev_device *mdev = to_mdev_device(dev); | 269 | struct mdev_device *mdev = to_mdev_device(dev); |
| 299 | 270 | ||
| 271 | mutex_lock(&mdev_list_lock); | ||
| 272 | list_del(&mdev->next); | ||
| 273 | mutex_unlock(&mdev_list_lock); | ||
| 274 | |||
| 300 | dev_dbg(&mdev->dev, "MDEV: destroying\n"); | 275 | dev_dbg(&mdev->dev, "MDEV: destroying\n"); |
| 301 | kfree(mdev); | 276 | kfree(mdev); |
| 302 | } | 277 | } |
| @@ -304,7 +279,7 @@ static void mdev_device_release(struct device *dev) | |||
| 304 | int mdev_device_create(struct kobject *kobj, struct device *dev, uuid_le uuid) | 279 | int mdev_device_create(struct kobject *kobj, struct device *dev, uuid_le uuid) |
| 305 | { | 280 | { |
| 306 | int ret; | 281 | int ret; |
| 307 | struct mdev_device *mdev; | 282 | struct mdev_device *mdev, *tmp; |
| 308 | struct mdev_parent *parent; | 283 | struct mdev_parent *parent; |
| 309 | struct mdev_type *type = to_mdev_type(kobj); | 284 | struct mdev_type *type = to_mdev_type(kobj); |
| 310 | 285 | ||
| @@ -312,21 +287,28 @@ int mdev_device_create(struct kobject *kobj, struct device *dev, uuid_le uuid) | |||
| 312 | if (!parent) | 287 | if (!parent) |
| 313 | return -EINVAL; | 288 | return -EINVAL; |
| 314 | 289 | ||
| 315 | mutex_lock(&parent->lock); | 290 | mutex_lock(&mdev_list_lock); |
| 316 | 291 | ||
| 317 | /* Check for duplicate */ | 292 | /* Check for duplicate */ |
| 318 | if (mdev_device_exist(parent, uuid)) { | 293 | list_for_each_entry(tmp, &mdev_list, next) { |
| 319 | ret = -EEXIST; | 294 | if (!uuid_le_cmp(tmp->uuid, uuid)) { |
| 320 | goto create_err; | 295 | mutex_unlock(&mdev_list_lock); |
| 296 | ret = -EEXIST; | ||
| 297 | goto mdev_fail; | ||
| 298 | } | ||
| 321 | } | 299 | } |
| 322 | 300 | ||
| 323 | mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); | 301 | mdev = kzalloc(sizeof(*mdev), GFP_KERNEL); |
| 324 | if (!mdev) { | 302 | if (!mdev) { |
| 303 | mutex_unlock(&mdev_list_lock); | ||
| 325 | ret = -ENOMEM; | 304 | ret = -ENOMEM; |
| 326 | goto create_err; | 305 | goto mdev_fail; |
| 327 | } | 306 | } |
| 328 | 307 | ||
| 329 | memcpy(&mdev->uuid, &uuid, sizeof(uuid_le)); | 308 | memcpy(&mdev->uuid, &uuid, sizeof(uuid_le)); |
| 309 | list_add(&mdev->next, &mdev_list); | ||
| 310 | mutex_unlock(&mdev_list_lock); | ||
| 311 | |||
| 330 | mdev->parent = parent; | 312 | mdev->parent = parent; |
| 331 | kref_init(&mdev->ref); | 313 | kref_init(&mdev->ref); |
| 332 | 314 | ||
| @@ -338,35 +320,28 @@ int mdev_device_create(struct kobject *kobj, struct device *dev, uuid_le uuid) | |||
| 338 | ret = device_register(&mdev->dev); | 320 | ret = device_register(&mdev->dev); |
| 339 | if (ret) { | 321 | if (ret) { |
| 340 | put_device(&mdev->dev); | 322 | put_device(&mdev->dev); |
| 341 | goto create_err; | 323 | goto mdev_fail; |
| 342 | } | 324 | } |
| 343 | 325 | ||
| 344 | ret = mdev_device_create_ops(kobj, mdev); | 326 | ret = mdev_device_create_ops(kobj, mdev); |
| 345 | if (ret) | 327 | if (ret) |
| 346 | goto create_failed; | 328 | goto create_fail; |
| 347 | 329 | ||
| 348 | ret = mdev_create_sysfs_files(&mdev->dev, type); | 330 | ret = mdev_create_sysfs_files(&mdev->dev, type); |
| 349 | if (ret) { | 331 | if (ret) { |
| 350 | mdev_device_remove_ops(mdev, true); | 332 | mdev_device_remove_ops(mdev, true); |
| 351 | goto create_failed; | 333 | goto create_fail; |
| 352 | } | 334 | } |
| 353 | 335 | ||
| 354 | mdev->type_kobj = kobj; | 336 | mdev->type_kobj = kobj; |
| 337 | mdev->active = true; | ||
| 355 | dev_dbg(&mdev->dev, "MDEV: created\n"); | 338 | dev_dbg(&mdev->dev, "MDEV: created\n"); |
| 356 | 339 | ||
| 357 | mutex_unlock(&parent->lock); | 340 | return 0; |
| 358 | |||
| 359 | mutex_lock(&mdev_list_lock); | ||
| 360 | list_add(&mdev->next, &mdev_list); | ||
| 361 | mutex_unlock(&mdev_list_lock); | ||
| 362 | |||
| 363 | return ret; | ||
| 364 | 341 | ||
| 365 | create_failed: | 342 | create_fail: |
| 366 | device_unregister(&mdev->dev); | 343 | device_unregister(&mdev->dev); |
| 367 | 344 | mdev_fail: | |
| 368 | create_err: | ||
| 369 | mutex_unlock(&parent->lock); | ||
| 370 | mdev_put_parent(parent); | 345 | mdev_put_parent(parent); |
| 371 | return ret; | 346 | return ret; |
| 372 | } | 347 | } |
| @@ -377,44 +352,39 @@ int mdev_device_remove(struct device *dev, bool force_remove) | |||
| 377 | struct mdev_parent *parent; | 352 | struct mdev_parent *parent; |
| 378 | struct mdev_type *type; | 353 | struct mdev_type *type; |
| 379 | int ret; | 354 | int ret; |
| 380 | bool found = false; | ||
| 381 | 355 | ||
| 382 | mdev = to_mdev_device(dev); | 356 | mdev = to_mdev_device(dev); |
| 383 | 357 | ||
| 384 | mutex_lock(&mdev_list_lock); | 358 | mutex_lock(&mdev_list_lock); |
| 385 | list_for_each_entry(tmp, &mdev_list, next) { | 359 | list_for_each_entry(tmp, &mdev_list, next) { |
| 386 | if (tmp == mdev) { | 360 | if (tmp == mdev) |
| 387 | found = true; | ||
| 388 | break; | 361 | break; |
| 389 | } | ||
| 390 | } | 362 | } |
| 391 | 363 | ||
| 392 | if (found) | 364 | if (tmp != mdev) { |
| 393 | list_del(&mdev->next); | 365 | mutex_unlock(&mdev_list_lock); |
| 366 | return -ENODEV; | ||
| 367 | } | ||
| 394 | 368 | ||
| 395 | mutex_unlock(&mdev_list_lock); | 369 | if (!mdev->active) { |
| 370 | mutex_unlock(&mdev_list_lock); | ||
| 371 | return -EAGAIN; | ||
| 372 | } | ||
| 396 | 373 | ||
| 397 | if (!found) | 374 | mdev->active = false; |
| 398 | return -ENODEV; | 375 | mutex_unlock(&mdev_list_lock); |
| 399 | 376 | ||
| 400 | type = to_mdev_type(mdev->type_kobj); | 377 | type = to_mdev_type(mdev->type_kobj); |
| 401 | parent = mdev->parent; | 378 | parent = mdev->parent; |
| 402 | mutex_lock(&parent->lock); | ||
| 403 | 379 | ||
| 404 | ret = mdev_device_remove_ops(mdev, force_remove); | 380 | ret = mdev_device_remove_ops(mdev, force_remove); |
| 405 | if (ret) { | 381 | if (ret) { |
| 406 | mutex_unlock(&parent->lock); | 382 | mdev->active = true; |
| 407 | |||
| 408 | mutex_lock(&mdev_list_lock); | ||
| 409 | list_add(&mdev->next, &mdev_list); | ||
| 410 | mutex_unlock(&mdev_list_lock); | ||
| 411 | |||
| 412 | return ret; | 383 | return ret; |
| 413 | } | 384 | } |
| 414 | 385 | ||
| 415 | mdev_remove_sysfs_files(dev, type); | 386 | mdev_remove_sysfs_files(dev, type); |
| 416 | device_unregister(dev); | 387 | device_unregister(dev); |
| 417 | mutex_unlock(&parent->lock); | ||
| 418 | mdev_put_parent(parent); | 388 | mdev_put_parent(parent); |
| 419 | 389 | ||
| 420 | return 0; | 390 | return 0; |
diff --git a/drivers/vfio/mdev/mdev_private.h b/drivers/vfio/mdev/mdev_private.h index a9cefd70a705..b5819b7d7ef7 100644 --- a/drivers/vfio/mdev/mdev_private.h +++ b/drivers/vfio/mdev/mdev_private.h | |||
| @@ -20,7 +20,6 @@ struct mdev_parent { | |||
| 20 | struct device *dev; | 20 | struct device *dev; |
| 21 | const struct mdev_parent_ops *ops; | 21 | const struct mdev_parent_ops *ops; |
| 22 | struct kref ref; | 22 | struct kref ref; |
| 23 | struct mutex lock; | ||
| 24 | struct list_head next; | 23 | struct list_head next; |
| 25 | struct kset *mdev_types_kset; | 24 | struct kset *mdev_types_kset; |
| 26 | struct list_head type_list; | 25 | struct list_head type_list; |
| @@ -34,6 +33,7 @@ struct mdev_device { | |||
| 34 | struct kref ref; | 33 | struct kref ref; |
| 35 | struct list_head next; | 34 | struct list_head next; |
| 36 | struct kobject *type_kobj; | 35 | struct kobject *type_kobj; |
| 36 | bool active; | ||
| 37 | }; | 37 | }; |
| 38 | 38 | ||
| 39 | #define to_mdev_device(dev) container_of(dev, struct mdev_device, dev) | 39 | #define to_mdev_device(dev) container_of(dev, struct mdev_device, dev) |
diff --git a/drivers/vfio/mdev/mdev_sysfs.c b/drivers/vfio/mdev/mdev_sysfs.c index 802df210929b..249472f05509 100644 --- a/drivers/vfio/mdev/mdev_sysfs.c +++ b/drivers/vfio/mdev/mdev_sysfs.c | |||
| @@ -257,24 +257,24 @@ int mdev_create_sysfs_files(struct device *dev, struct mdev_type *type) | |||
| 257 | { | 257 | { |
| 258 | int ret; | 258 | int ret; |
| 259 | 259 | ||
| 260 | ret = sysfs_create_files(&dev->kobj, mdev_device_attrs); | ||
| 261 | if (ret) | ||
| 262 | return ret; | ||
| 263 | |||
| 264 | ret = sysfs_create_link(type->devices_kobj, &dev->kobj, dev_name(dev)); | 260 | ret = sysfs_create_link(type->devices_kobj, &dev->kobj, dev_name(dev)); |
| 265 | if (ret) | 261 | if (ret) |
| 266 | goto device_link_failed; | 262 | return ret; |
| 267 | 263 | ||
| 268 | ret = sysfs_create_link(&dev->kobj, &type->kobj, "mdev_type"); | 264 | ret = sysfs_create_link(&dev->kobj, &type->kobj, "mdev_type"); |
| 269 | if (ret) | 265 | if (ret) |
| 270 | goto type_link_failed; | 266 | goto type_link_failed; |
| 271 | 267 | ||
| 268 | ret = sysfs_create_files(&dev->kobj, mdev_device_attrs); | ||
| 269 | if (ret) | ||
| 270 | goto create_files_failed; | ||
| 271 | |||
| 272 | return ret; | 272 | return ret; |
| 273 | 273 | ||
| 274 | create_files_failed: | ||
| 275 | sysfs_remove_link(&dev->kobj, "mdev_type"); | ||
| 274 | type_link_failed: | 276 | type_link_failed: |
| 275 | sysfs_remove_link(type->devices_kobj, dev_name(dev)); | 277 | sysfs_remove_link(type->devices_kobj, dev_name(dev)); |
| 276 | device_link_failed: | ||
| 277 | sysfs_remove_files(&dev->kobj, mdev_device_attrs); | ||
| 278 | return ret; | 278 | return ret; |
| 279 | } | 279 | } |
| 280 | 280 | ||
diff --git a/drivers/vfio/platform/vfio_platform_common.c b/drivers/vfio/platform/vfio_platform_common.c index 4c27f4be3c3d..c0cd824be2b7 100644 --- a/drivers/vfio/platform/vfio_platform_common.c +++ b/drivers/vfio/platform/vfio_platform_common.c | |||
| @@ -17,6 +17,7 @@ | |||
| 17 | #include <linux/iommu.h> | 17 | #include <linux/iommu.h> |
| 18 | #include <linux/module.h> | 18 | #include <linux/module.h> |
| 19 | #include <linux/mutex.h> | 19 | #include <linux/mutex.h> |
| 20 | #include <linux/pm_runtime.h> | ||
| 20 | #include <linux/slab.h> | 21 | #include <linux/slab.h> |
| 21 | #include <linux/types.h> | 22 | #include <linux/types.h> |
| 22 | #include <linux/uaccess.h> | 23 | #include <linux/uaccess.h> |
| @@ -239,6 +240,7 @@ static void vfio_platform_release(void *device_data) | |||
| 239 | ret, extra_dbg ? extra_dbg : ""); | 240 | ret, extra_dbg ? extra_dbg : ""); |
| 240 | WARN_ON(1); | 241 | WARN_ON(1); |
| 241 | } | 242 | } |
| 243 | pm_runtime_put(vdev->device); | ||
| 242 | vfio_platform_regions_cleanup(vdev); | 244 | vfio_platform_regions_cleanup(vdev); |
| 243 | vfio_platform_irq_cleanup(vdev); | 245 | vfio_platform_irq_cleanup(vdev); |
| 244 | } | 246 | } |
| @@ -269,6 +271,10 @@ static int vfio_platform_open(void *device_data) | |||
| 269 | if (ret) | 271 | if (ret) |
| 270 | goto err_irq; | 272 | goto err_irq; |
| 271 | 273 | ||
| 274 | ret = pm_runtime_get_sync(vdev->device); | ||
| 275 | if (ret < 0) | ||
| 276 | goto err_pm; | ||
| 277 | |||
| 272 | ret = vfio_platform_call_reset(vdev, &extra_dbg); | 278 | ret = vfio_platform_call_reset(vdev, &extra_dbg); |
| 273 | if (ret && vdev->reset_required) { | 279 | if (ret && vdev->reset_required) { |
| 274 | dev_warn(vdev->device, "reset driver is required and reset call failed in open (%d) %s\n", | 280 | dev_warn(vdev->device, "reset driver is required and reset call failed in open (%d) %s\n", |
| @@ -283,6 +289,8 @@ static int vfio_platform_open(void *device_data) | |||
| 283 | return 0; | 289 | return 0; |
| 284 | 290 | ||
| 285 | err_rst: | 291 | err_rst: |
| 292 | pm_runtime_put(vdev->device); | ||
| 293 | err_pm: | ||
| 286 | vfio_platform_irq_cleanup(vdev); | 294 | vfio_platform_irq_cleanup(vdev); |
| 287 | err_irq: | 295 | err_irq: |
| 288 | vfio_platform_regions_cleanup(vdev); | 296 | vfio_platform_regions_cleanup(vdev); |
| @@ -630,8 +638,7 @@ static int vfio_platform_of_probe(struct vfio_platform_device *vdev, | |||
| 630 | ret = device_property_read_string(dev, "compatible", | 638 | ret = device_property_read_string(dev, "compatible", |
| 631 | &vdev->compat); | 639 | &vdev->compat); |
| 632 | if (ret) | 640 | if (ret) |
| 633 | pr_err("VFIO: cannot retrieve compat for %s\n", | 641 | pr_err("VFIO: Cannot retrieve compat for %s\n", vdev->name); |
| 634 | vdev->name); | ||
| 635 | 642 | ||
| 636 | return ret; | 643 | return ret; |
| 637 | } | 644 | } |
| @@ -673,7 +680,7 @@ int vfio_platform_probe_common(struct vfio_platform_device *vdev, | |||
| 673 | 680 | ||
| 674 | ret = vfio_platform_get_reset(vdev); | 681 | ret = vfio_platform_get_reset(vdev); |
| 675 | if (ret && vdev->reset_required) { | 682 | if (ret && vdev->reset_required) { |
| 676 | pr_err("vfio: no reset function found for device %s\n", | 683 | pr_err("VFIO: No reset function found for device %s\n", |
| 677 | vdev->name); | 684 | vdev->name); |
| 678 | return ret; | 685 | return ret; |
| 679 | } | 686 | } |
| @@ -681,18 +688,24 @@ int vfio_platform_probe_common(struct vfio_platform_device *vdev, | |||
| 681 | group = vfio_iommu_group_get(dev); | 688 | group = vfio_iommu_group_get(dev); |
| 682 | if (!group) { | 689 | if (!group) { |
| 683 | pr_err("VFIO: No IOMMU group for device %s\n", vdev->name); | 690 | pr_err("VFIO: No IOMMU group for device %s\n", vdev->name); |
| 684 | return -EINVAL; | 691 | ret = -EINVAL; |
| 692 | goto put_reset; | ||
| 685 | } | 693 | } |
| 686 | 694 | ||
| 687 | ret = vfio_add_group_dev(dev, &vfio_platform_ops, vdev); | 695 | ret = vfio_add_group_dev(dev, &vfio_platform_ops, vdev); |
| 688 | if (ret) { | 696 | if (ret) |
| 689 | vfio_iommu_group_put(group, dev); | 697 | goto put_iommu; |
| 690 | return ret; | ||
| 691 | } | ||
| 692 | 698 | ||
| 693 | mutex_init(&vdev->igate); | 699 | mutex_init(&vdev->igate); |
| 694 | 700 | ||
| 701 | pm_runtime_enable(vdev->device); | ||
| 695 | return 0; | 702 | return 0; |
| 703 | |||
| 704 | put_iommu: | ||
| 705 | vfio_iommu_group_put(group, dev); | ||
| 706 | put_reset: | ||
| 707 | vfio_platform_put_reset(vdev); | ||
| 708 | return ret; | ||
| 696 | } | 709 | } |
| 697 | EXPORT_SYMBOL_GPL(vfio_platform_probe_common); | 710 | EXPORT_SYMBOL_GPL(vfio_platform_probe_common); |
| 698 | 711 | ||
| @@ -703,6 +716,7 @@ struct vfio_platform_device *vfio_platform_remove_common(struct device *dev) | |||
| 703 | vdev = vfio_del_group_dev(dev); | 716 | vdev = vfio_del_group_dev(dev); |
| 704 | 717 | ||
| 705 | if (vdev) { | 718 | if (vdev) { |
| 719 | pm_runtime_disable(vdev->device); | ||
| 706 | vfio_platform_put_reset(vdev); | 720 | vfio_platform_put_reset(vdev); |
| 707 | vfio_iommu_group_put(dev->iommu_group, dev); | 721 | vfio_iommu_group_put(dev->iommu_group, dev); |
| 708 | } | 722 | } |
diff --git a/drivers/vfio/vfio.c b/drivers/vfio/vfio.c index 721f97f8dac1..64833879f75d 100644 --- a/drivers/vfio/vfio.c +++ b/drivers/vfio/vfio.c | |||
| @@ -630,8 +630,6 @@ static const char * const vfio_driver_whitelist[] = { "pci-stub" }; | |||
| 630 | 630 | ||
| 631 | static bool vfio_dev_whitelisted(struct device *dev, struct device_driver *drv) | 631 | static bool vfio_dev_whitelisted(struct device *dev, struct device_driver *drv) |
| 632 | { | 632 | { |
| 633 | int i; | ||
| 634 | |||
| 635 | if (dev_is_pci(dev)) { | 633 | if (dev_is_pci(dev)) { |
| 636 | struct pci_dev *pdev = to_pci_dev(dev); | 634 | struct pci_dev *pdev = to_pci_dev(dev); |
| 637 | 635 | ||
| @@ -639,12 +637,9 @@ static bool vfio_dev_whitelisted(struct device *dev, struct device_driver *drv) | |||
| 639 | return true; | 637 | return true; |
| 640 | } | 638 | } |
| 641 | 639 | ||
| 642 | for (i = 0; i < ARRAY_SIZE(vfio_driver_whitelist); i++) { | 640 | return match_string(vfio_driver_whitelist, |
| 643 | if (!strcmp(drv->name, vfio_driver_whitelist[i])) | 641 | ARRAY_SIZE(vfio_driver_whitelist), |
| 644 | return true; | 642 | drv->name) >= 0; |
| 645 | } | ||
| 646 | |||
| 647 | return false; | ||
| 648 | } | 643 | } |
| 649 | 644 | ||
| 650 | /* | 645 | /* |
diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c index 3c082451ab1a..2c75b33db4ac 100644 --- a/drivers/vfio/vfio_iommu_type1.c +++ b/drivers/vfio/vfio_iommu_type1.c | |||
| @@ -83,6 +83,7 @@ struct vfio_dma { | |||
| 83 | size_t size; /* Map size (bytes) */ | 83 | size_t size; /* Map size (bytes) */ |
| 84 | int prot; /* IOMMU_READ/WRITE */ | 84 | int prot; /* IOMMU_READ/WRITE */ |
| 85 | bool iommu_mapped; | 85 | bool iommu_mapped; |
| 86 | bool lock_cap; /* capable(CAP_IPC_LOCK) */ | ||
| 86 | struct task_struct *task; | 87 | struct task_struct *task; |
| 87 | struct rb_root pfn_list; /* Ex-user pinned pfn list */ | 88 | struct rb_root pfn_list; /* Ex-user pinned pfn list */ |
| 88 | }; | 89 | }; |
| @@ -253,29 +254,25 @@ static int vfio_iova_put_vfio_pfn(struct vfio_dma *dma, struct vfio_pfn *vpfn) | |||
| 253 | return ret; | 254 | return ret; |
| 254 | } | 255 | } |
| 255 | 256 | ||
| 256 | static int vfio_lock_acct(struct task_struct *task, long npage, bool *lock_cap) | 257 | static int vfio_lock_acct(struct vfio_dma *dma, long npage, bool async) |
| 257 | { | 258 | { |
| 258 | struct mm_struct *mm; | 259 | struct mm_struct *mm; |
| 259 | bool is_current; | ||
| 260 | int ret; | 260 | int ret; |
| 261 | 261 | ||
| 262 | if (!npage) | 262 | if (!npage) |
| 263 | return 0; | 263 | return 0; |
| 264 | 264 | ||
| 265 | is_current = (task->mm == current->mm); | 265 | mm = async ? get_task_mm(dma->task) : dma->task->mm; |
| 266 | |||
| 267 | mm = is_current ? task->mm : get_task_mm(task); | ||
| 268 | if (!mm) | 266 | if (!mm) |
| 269 | return -ESRCH; /* process exited */ | 267 | return -ESRCH; /* process exited */ |
| 270 | 268 | ||
| 271 | ret = down_write_killable(&mm->mmap_sem); | 269 | ret = down_write_killable(&mm->mmap_sem); |
| 272 | if (!ret) { | 270 | if (!ret) { |
| 273 | if (npage > 0) { | 271 | if (npage > 0) { |
| 274 | if (lock_cap ? !*lock_cap : | 272 | if (!dma->lock_cap) { |
| 275 | !has_capability(task, CAP_IPC_LOCK)) { | ||
| 276 | unsigned long limit; | 273 | unsigned long limit; |
| 277 | 274 | ||
| 278 | limit = task_rlimit(task, | 275 | limit = task_rlimit(dma->task, |
| 279 | RLIMIT_MEMLOCK) >> PAGE_SHIFT; | 276 | RLIMIT_MEMLOCK) >> PAGE_SHIFT; |
| 280 | 277 | ||
| 281 | if (mm->locked_vm + npage > limit) | 278 | if (mm->locked_vm + npage > limit) |
| @@ -289,7 +286,7 @@ static int vfio_lock_acct(struct task_struct *task, long npage, bool *lock_cap) | |||
| 289 | up_write(&mm->mmap_sem); | 286 | up_write(&mm->mmap_sem); |
| 290 | } | 287 | } |
| 291 | 288 | ||
| 292 | if (!is_current) | 289 | if (async) |
| 293 | mmput(mm); | 290 | mmput(mm); |
| 294 | 291 | ||
| 295 | return ret; | 292 | return ret; |
| @@ -400,7 +397,7 @@ static int vaddr_get_pfn(struct mm_struct *mm, unsigned long vaddr, | |||
| 400 | */ | 397 | */ |
| 401 | static long vfio_pin_pages_remote(struct vfio_dma *dma, unsigned long vaddr, | 398 | static long vfio_pin_pages_remote(struct vfio_dma *dma, unsigned long vaddr, |
| 402 | long npage, unsigned long *pfn_base, | 399 | long npage, unsigned long *pfn_base, |
| 403 | bool lock_cap, unsigned long limit) | 400 | unsigned long limit) |
| 404 | { | 401 | { |
| 405 | unsigned long pfn = 0; | 402 | unsigned long pfn = 0; |
| 406 | long ret, pinned = 0, lock_acct = 0; | 403 | long ret, pinned = 0, lock_acct = 0; |
| @@ -423,7 +420,7 @@ static long vfio_pin_pages_remote(struct vfio_dma *dma, unsigned long vaddr, | |||
| 423 | * pages are already counted against the user. | 420 | * pages are already counted against the user. |
| 424 | */ | 421 | */ |
| 425 | if (!rsvd && !vfio_find_vpfn(dma, iova)) { | 422 | if (!rsvd && !vfio_find_vpfn(dma, iova)) { |
| 426 | if (!lock_cap && current->mm->locked_vm + 1 > limit) { | 423 | if (!dma->lock_cap && current->mm->locked_vm + 1 > limit) { |
| 427 | put_pfn(*pfn_base, dma->prot); | 424 | put_pfn(*pfn_base, dma->prot); |
| 428 | pr_warn("%s: RLIMIT_MEMLOCK (%ld) exceeded\n", __func__, | 425 | pr_warn("%s: RLIMIT_MEMLOCK (%ld) exceeded\n", __func__, |
| 429 | limit << PAGE_SHIFT); | 426 | limit << PAGE_SHIFT); |
| @@ -449,7 +446,7 @@ static long vfio_pin_pages_remote(struct vfio_dma *dma, unsigned long vaddr, | |||
| 449 | } | 446 | } |
| 450 | 447 | ||
| 451 | if (!rsvd && !vfio_find_vpfn(dma, iova)) { | 448 | if (!rsvd && !vfio_find_vpfn(dma, iova)) { |
| 452 | if (!lock_cap && | 449 | if (!dma->lock_cap && |
| 453 | current->mm->locked_vm + lock_acct + 1 > limit) { | 450 | current->mm->locked_vm + lock_acct + 1 > limit) { |
| 454 | put_pfn(pfn, dma->prot); | 451 | put_pfn(pfn, dma->prot); |
| 455 | pr_warn("%s: RLIMIT_MEMLOCK (%ld) exceeded\n", | 452 | pr_warn("%s: RLIMIT_MEMLOCK (%ld) exceeded\n", |
| @@ -462,7 +459,7 @@ static long vfio_pin_pages_remote(struct vfio_dma *dma, unsigned long vaddr, | |||
| 462 | } | 459 | } |
| 463 | 460 | ||
| 464 | out: | 461 | out: |
| 465 | ret = vfio_lock_acct(current, lock_acct, &lock_cap); | 462 | ret = vfio_lock_acct(dma, lock_acct, false); |
| 466 | 463 | ||
| 467 | unpin_out: | 464 | unpin_out: |
| 468 | if (ret) { | 465 | if (ret) { |
| @@ -493,7 +490,7 @@ static long vfio_unpin_pages_remote(struct vfio_dma *dma, dma_addr_t iova, | |||
| 493 | } | 490 | } |
| 494 | 491 | ||
| 495 | if (do_accounting) | 492 | if (do_accounting) |
| 496 | vfio_lock_acct(dma->task, locked - unlocked, NULL); | 493 | vfio_lock_acct(dma, locked - unlocked, true); |
| 497 | 494 | ||
| 498 | return unlocked; | 495 | return unlocked; |
| 499 | } | 496 | } |
| @@ -510,7 +507,7 @@ static int vfio_pin_page_external(struct vfio_dma *dma, unsigned long vaddr, | |||
| 510 | 507 | ||
| 511 | ret = vaddr_get_pfn(mm, vaddr, dma->prot, pfn_base); | 508 | ret = vaddr_get_pfn(mm, vaddr, dma->prot, pfn_base); |
| 512 | if (!ret && do_accounting && !is_invalid_reserved_pfn(*pfn_base)) { | 509 | if (!ret && do_accounting && !is_invalid_reserved_pfn(*pfn_base)) { |
| 513 | ret = vfio_lock_acct(dma->task, 1, NULL); | 510 | ret = vfio_lock_acct(dma, 1, true); |
| 514 | if (ret) { | 511 | if (ret) { |
| 515 | put_pfn(*pfn_base, dma->prot); | 512 | put_pfn(*pfn_base, dma->prot); |
| 516 | if (ret == -ENOMEM) | 513 | if (ret == -ENOMEM) |
| @@ -537,7 +534,7 @@ static int vfio_unpin_page_external(struct vfio_dma *dma, dma_addr_t iova, | |||
| 537 | unlocked = vfio_iova_put_vfio_pfn(dma, vpfn); | 534 | unlocked = vfio_iova_put_vfio_pfn(dma, vpfn); |
| 538 | 535 | ||
| 539 | if (do_accounting) | 536 | if (do_accounting) |
| 540 | vfio_lock_acct(dma->task, -unlocked, NULL); | 537 | vfio_lock_acct(dma, -unlocked, true); |
| 541 | 538 | ||
| 542 | return unlocked; | 539 | return unlocked; |
| 543 | } | 540 | } |
| @@ -829,7 +826,7 @@ static long vfio_unmap_unpin(struct vfio_iommu *iommu, struct vfio_dma *dma, | |||
| 829 | unlocked += vfio_sync_unpin(dma, domain, &unmapped_region_list); | 826 | unlocked += vfio_sync_unpin(dma, domain, &unmapped_region_list); |
| 830 | 827 | ||
| 831 | if (do_accounting) { | 828 | if (do_accounting) { |
| 832 | vfio_lock_acct(dma->task, -unlocked, NULL); | 829 | vfio_lock_acct(dma, -unlocked, true); |
| 833 | return 0; | 830 | return 0; |
| 834 | } | 831 | } |
| 835 | return unlocked; | 832 | return unlocked; |
| @@ -1044,14 +1041,12 @@ static int vfio_pin_map_dma(struct vfio_iommu *iommu, struct vfio_dma *dma, | |||
| 1044 | size_t size = map_size; | 1041 | size_t size = map_size; |
| 1045 | long npage; | 1042 | long npage; |
| 1046 | unsigned long pfn, limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; | 1043 | unsigned long pfn, limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; |
| 1047 | bool lock_cap = capable(CAP_IPC_LOCK); | ||
| 1048 | int ret = 0; | 1044 | int ret = 0; |
| 1049 | 1045 | ||
| 1050 | while (size) { | 1046 | while (size) { |
| 1051 | /* Pin a contiguous chunk of memory */ | 1047 | /* Pin a contiguous chunk of memory */ |
| 1052 | npage = vfio_pin_pages_remote(dma, vaddr + dma->size, | 1048 | npage = vfio_pin_pages_remote(dma, vaddr + dma->size, |
| 1053 | size >> PAGE_SHIFT, &pfn, | 1049 | size >> PAGE_SHIFT, &pfn, limit); |
| 1054 | lock_cap, limit); | ||
| 1055 | if (npage <= 0) { | 1050 | if (npage <= 0) { |
| 1056 | WARN_ON(!npage); | 1051 | WARN_ON(!npage); |
| 1057 | ret = (int)npage; | 1052 | ret = (int)npage; |
| @@ -1126,8 +1121,36 @@ static int vfio_dma_do_map(struct vfio_iommu *iommu, | |||
| 1126 | dma->iova = iova; | 1121 | dma->iova = iova; |
| 1127 | dma->vaddr = vaddr; | 1122 | dma->vaddr = vaddr; |
| 1128 | dma->prot = prot; | 1123 | dma->prot = prot; |
| 1129 | get_task_struct(current); | 1124 | |
| 1130 | dma->task = current; | 1125 | /* |
| 1126 | * We need to be able to both add to a task's locked memory and test | ||
| 1127 | * against the locked memory limit and we need to be able to do both | ||
| 1128 | * outside of this call path as pinning can be asynchronous via the | ||
| 1129 | * external interfaces for mdev devices. RLIMIT_MEMLOCK requires a | ||
| 1130 | * task_struct and VM locked pages requires an mm_struct, however | ||
| 1131 | * holding an indefinite mm reference is not recommended, therefore we | ||
| 1132 | * only hold a reference to a task. We could hold a reference to | ||
| 1133 | * current, however QEMU uses this call path through vCPU threads, | ||
| 1134 | * which can be killed resulting in a NULL mm and failure in the unmap | ||
| 1135 | * path when called via a different thread. Avoid this problem by | ||
| 1136 | * using the group_leader as threads within the same group require | ||
| 1137 | * both CLONE_THREAD and CLONE_VM and will therefore use the same | ||
| 1138 | * mm_struct. | ||
| 1139 | * | ||
| 1140 | * Previously we also used the task for testing CAP_IPC_LOCK at the | ||
| 1141 | * time of pinning and accounting, however has_capability() makes use | ||
| 1142 | * of real_cred, a copy-on-write field, so we can't guarantee that it | ||
| 1143 | * matches group_leader, or in fact that it might not change by the | ||
| 1144 | * time it's evaluated. If a process were to call MAP_DMA with | ||
| 1145 | * CAP_IPC_LOCK but later drop it, it doesn't make sense that they | ||
| 1146 | * possibly see different results for an iommu_mapped vfio_dma vs | ||
| 1147 | * externally mapped. Therefore track CAP_IPC_LOCK in vfio_dma at the | ||
| 1148 | * time of calling MAP_DMA. | ||
| 1149 | */ | ||
| 1150 | get_task_struct(current->group_leader); | ||
| 1151 | dma->task = current->group_leader; | ||
| 1152 | dma->lock_cap = capable(CAP_IPC_LOCK); | ||
| 1153 | |||
| 1131 | dma->pfn_list = RB_ROOT; | 1154 | dma->pfn_list = RB_ROOT; |
| 1132 | 1155 | ||
| 1133 | /* Insert zero-sized and grow as we map chunks of it */ | 1156 | /* Insert zero-sized and grow as we map chunks of it */ |
| @@ -1162,7 +1185,6 @@ static int vfio_iommu_replay(struct vfio_iommu *iommu, | |||
| 1162 | struct vfio_domain *d; | 1185 | struct vfio_domain *d; |
| 1163 | struct rb_node *n; | 1186 | struct rb_node *n; |
| 1164 | unsigned long limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; | 1187 | unsigned long limit = rlimit(RLIMIT_MEMLOCK) >> PAGE_SHIFT; |
| 1165 | bool lock_cap = capable(CAP_IPC_LOCK); | ||
| 1166 | int ret; | 1188 | int ret; |
| 1167 | 1189 | ||
| 1168 | /* Arbitrarily pick the first domain in the list for lookups */ | 1190 | /* Arbitrarily pick the first domain in the list for lookups */ |
| @@ -1209,8 +1231,7 @@ static int vfio_iommu_replay(struct vfio_iommu *iommu, | |||
| 1209 | 1231 | ||
| 1210 | npage = vfio_pin_pages_remote(dma, vaddr, | 1232 | npage = vfio_pin_pages_remote(dma, vaddr, |
| 1211 | n >> PAGE_SHIFT, | 1233 | n >> PAGE_SHIFT, |
| 1212 | &pfn, lock_cap, | 1234 | &pfn, limit); |
| 1213 | limit); | ||
| 1214 | if (npage <= 0) { | 1235 | if (npage <= 0) { |
| 1215 | WARN_ON(!npage); | 1236 | WARN_ON(!npage); |
| 1216 | ret = (int)npage; | 1237 | ret = (int)npage; |
| @@ -1487,7 +1508,7 @@ static void vfio_iommu_unmap_unpin_reaccount(struct vfio_iommu *iommu) | |||
| 1487 | if (!is_invalid_reserved_pfn(vpfn->pfn)) | 1508 | if (!is_invalid_reserved_pfn(vpfn->pfn)) |
| 1488 | locked++; | 1509 | locked++; |
| 1489 | } | 1510 | } |
| 1490 | vfio_lock_acct(dma->task, locked - unlocked, NULL); | 1511 | vfio_lock_acct(dma, locked - unlocked, true); |
| 1491 | } | 1512 | } |
| 1492 | } | 1513 | } |
| 1493 | 1514 | ||
diff --git a/samples/Kconfig b/samples/Kconfig index 3db002b9e1d3..bd133efc1a56 100644 --- a/samples/Kconfig +++ b/samples/Kconfig | |||
| @@ -115,6 +115,37 @@ config SAMPLE_VFIO_MDEV_MTTY | |||
| 115 | Build a virtual tty sample driver for use as a VFIO | 115 | Build a virtual tty sample driver for use as a VFIO |
| 116 | mediated device | 116 | mediated device |
| 117 | 117 | ||
| 118 | config SAMPLE_VFIO_MDEV_MDPY | ||
| 119 | tristate "Build VFIO mdpy example mediated device sample code -- loadable modules only" | ||
| 120 | depends on VFIO_MDEV_DEVICE && m | ||
| 121 | help | ||
| 122 | Build a virtual display sample driver for use as a VFIO | ||
| 123 | mediated device. It is a simple framebuffer and supports | ||
| 124 | the region display interface (VFIO_GFX_PLANE_TYPE_REGION). | ||
| 125 | |||
| 126 | config SAMPLE_VFIO_MDEV_MDPY_FB | ||
| 127 | tristate "Build VFIO mdpy example guest fbdev driver -- loadable module only" | ||
| 128 | depends on FB && m | ||
| 129 | select FB_CFB_FILLRECT | ||
| 130 | select FB_CFB_COPYAREA | ||
| 131 | select FB_CFB_IMAGEBLIT | ||
| 132 | help | ||
| 133 | Guest fbdev driver for the virtual display sample driver. | ||
| 134 | |||
| 135 | config SAMPLE_VFIO_MDEV_MBOCHS | ||
| 136 | tristate "Build VFIO mdpy example mediated device sample code -- loadable modules only" | ||
| 137 | depends on VFIO_MDEV_DEVICE && m | ||
| 138 | select DMA_SHARED_BUFFER | ||
| 139 | help | ||
| 140 | Build a virtual display sample driver for use as a VFIO | ||
| 141 | mediated device. It supports the region display interface | ||
| 142 | (VFIO_GFX_PLANE_TYPE_DMABUF). | ||
| 143 | Emulate enough of qemu stdvga to make bochs-drm.ko happy. | ||
| 144 | That is basically the vram memory bar and the bochs dispi | ||
| 145 | interface vbe registers in the mmio register bar. | ||
| 146 | Specifically it does *not* include any legacy vga stuff. | ||
| 147 | Device looks a lot like "qemu -device secondary-vga". | ||
| 148 | |||
| 118 | config SAMPLE_STATX | 149 | config SAMPLE_STATX |
| 119 | bool "Build example extended-stat using code" | 150 | bool "Build example extended-stat using code" |
| 120 | depends on BROKEN | 151 | depends on BROKEN |
diff --git a/samples/vfio-mdev/Makefile b/samples/vfio-mdev/Makefile index cbbd868a50a8..7db889ca135c 100644 --- a/samples/vfio-mdev/Makefile +++ b/samples/vfio-mdev/Makefile | |||
| @@ -1 +1,4 @@ | |||
| 1 | obj-$(CONFIG_SAMPLE_VFIO_MDEV_MTTY) += mtty.o | 1 | obj-$(CONFIG_SAMPLE_VFIO_MDEV_MTTY) += mtty.o |
| 2 | obj-$(CONFIG_SAMPLE_VFIO_MDEV_MDPY) += mdpy.o | ||
| 3 | obj-$(CONFIG_SAMPLE_VFIO_MDEV_MDPY_FB) += mdpy-fb.o | ||
| 4 | obj-$(CONFIG_SAMPLE_VFIO_MDEV_MBOCHS) += mbochs.o | ||
diff --git a/samples/vfio-mdev/mbochs.c b/samples/vfio-mdev/mbochs.c new file mode 100644 index 000000000000..2960e26c6ea4 --- /dev/null +++ b/samples/vfio-mdev/mbochs.c | |||
| @@ -0,0 +1,1406 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | /* | ||
| 3 | * Mediated virtual PCI display host device driver | ||
| 4 | * | ||
| 5 | * Emulate enough of qemu stdvga to make bochs-drm.ko happy. That is | ||
| 6 | * basically the vram memory bar and the bochs dispi interface vbe | ||
| 7 | * registers in the mmio register bar. Specifically it does *not* | ||
| 8 | * include any legacy vga stuff. Device looks a lot like "qemu -device | ||
| 9 | * secondary-vga". | ||
| 10 | * | ||
| 11 | * (c) Gerd Hoffmann <kraxel@redhat.com> | ||
| 12 | * | ||
| 13 | * based on mtty driver which is: | ||
| 14 | * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved. | ||
| 15 | * Author: Neo Jia <cjia@nvidia.com> | ||
| 16 | * Kirti Wankhede <kwankhede@nvidia.com> | ||
| 17 | * | ||
| 18 | * This program is free software; you can redistribute it and/or modify | ||
| 19 | * it under the terms of the GNU General Public License version 2 as | ||
| 20 | * published by the Free Software Foundation. | ||
| 21 | */ | ||
| 22 | #include <linux/init.h> | ||
| 23 | #include <linux/module.h> | ||
| 24 | #include <linux/device.h> | ||
| 25 | #include <linux/kernel.h> | ||
| 26 | #include <linux/slab.h> | ||
| 27 | #include <linux/vmalloc.h> | ||
| 28 | #include <linux/cdev.h> | ||
| 29 | #include <linux/vfio.h> | ||
| 30 | #include <linux/iommu.h> | ||
| 31 | #include <linux/sysfs.h> | ||
| 32 | #include <linux/mdev.h> | ||
| 33 | #include <linux/pci.h> | ||
| 34 | #include <linux/dma-buf.h> | ||
| 35 | #include <linux/highmem.h> | ||
| 36 | #include <drm/drm_fourcc.h> | ||
| 37 | #include <drm/drm_rect.h> | ||
| 38 | #include <drm/drm_modeset_lock.h> | ||
| 39 | #include <drm/drm_property.h> | ||
| 40 | #include <drm/drm_plane.h> | ||
| 41 | |||
| 42 | |||
| 43 | #define VBE_DISPI_INDEX_ID 0x0 | ||
| 44 | #define VBE_DISPI_INDEX_XRES 0x1 | ||
| 45 | #define VBE_DISPI_INDEX_YRES 0x2 | ||
| 46 | #define VBE_DISPI_INDEX_BPP 0x3 | ||
| 47 | #define VBE_DISPI_INDEX_ENABLE 0x4 | ||
| 48 | #define VBE_DISPI_INDEX_BANK 0x5 | ||
| 49 | #define VBE_DISPI_INDEX_VIRT_WIDTH 0x6 | ||
| 50 | #define VBE_DISPI_INDEX_VIRT_HEIGHT 0x7 | ||
| 51 | #define VBE_DISPI_INDEX_X_OFFSET 0x8 | ||
| 52 | #define VBE_DISPI_INDEX_Y_OFFSET 0x9 | ||
| 53 | #define VBE_DISPI_INDEX_VIDEO_MEMORY_64K 0xa | ||
| 54 | #define VBE_DISPI_INDEX_COUNT 0xb | ||
| 55 | |||
| 56 | #define VBE_DISPI_ID0 0xB0C0 | ||
| 57 | #define VBE_DISPI_ID1 0xB0C1 | ||
| 58 | #define VBE_DISPI_ID2 0xB0C2 | ||
| 59 | #define VBE_DISPI_ID3 0xB0C3 | ||
| 60 | #define VBE_DISPI_ID4 0xB0C4 | ||
| 61 | #define VBE_DISPI_ID5 0xB0C5 | ||
| 62 | |||
| 63 | #define VBE_DISPI_DISABLED 0x00 | ||
| 64 | #define VBE_DISPI_ENABLED 0x01 | ||
| 65 | #define VBE_DISPI_GETCAPS 0x02 | ||
| 66 | #define VBE_DISPI_8BIT_DAC 0x20 | ||
| 67 | #define VBE_DISPI_LFB_ENABLED 0x40 | ||
| 68 | #define VBE_DISPI_NOCLEARMEM 0x80 | ||
| 69 | |||
| 70 | |||
| 71 | #define MBOCHS_NAME "mbochs" | ||
| 72 | #define MBOCHS_CLASS_NAME "mbochs" | ||
| 73 | |||
| 74 | #define MBOCHS_CONFIG_SPACE_SIZE 0xff | ||
| 75 | #define MBOCHS_MMIO_BAR_OFFSET PAGE_SIZE | ||
| 76 | #define MBOCHS_MMIO_BAR_SIZE PAGE_SIZE | ||
| 77 | #define MBOCHS_MEMORY_BAR_OFFSET (MBOCHS_MMIO_BAR_OFFSET + \ | ||
| 78 | MBOCHS_MMIO_BAR_SIZE) | ||
| 79 | |||
| 80 | #define STORE_LE16(addr, val) (*(u16 *)addr = val) | ||
| 81 | #define STORE_LE32(addr, val) (*(u32 *)addr = val) | ||
| 82 | |||
| 83 | |||
| 84 | MODULE_LICENSE("GPL v2"); | ||
| 85 | |||
| 86 | static int max_mbytes = 256; | ||
| 87 | module_param_named(count, max_mbytes, int, 0444); | ||
| 88 | MODULE_PARM_DESC(mem, "megabytes available to " MBOCHS_NAME " devices"); | ||
| 89 | |||
| 90 | |||
| 91 | #define MBOCHS_TYPE_1 "small" | ||
| 92 | #define MBOCHS_TYPE_2 "medium" | ||
| 93 | #define MBOCHS_TYPE_3 "large" | ||
| 94 | |||
| 95 | static const struct mbochs_type { | ||
| 96 | const char *name; | ||
| 97 | u32 mbytes; | ||
| 98 | } mbochs_types[] = { | ||
| 99 | { | ||
| 100 | .name = MBOCHS_CLASS_NAME "-" MBOCHS_TYPE_1, | ||
| 101 | .mbytes = 4, | ||
| 102 | }, { | ||
| 103 | .name = MBOCHS_CLASS_NAME "-" MBOCHS_TYPE_2, | ||
| 104 | .mbytes = 16, | ||
| 105 | }, { | ||
| 106 | .name = MBOCHS_CLASS_NAME "-" MBOCHS_TYPE_3, | ||
| 107 | .mbytes = 64, | ||
| 108 | }, | ||
| 109 | }; | ||
| 110 | |||
| 111 | |||
| 112 | static dev_t mbochs_devt; | ||
| 113 | static struct class *mbochs_class; | ||
| 114 | static struct cdev mbochs_cdev; | ||
| 115 | static struct device mbochs_dev; | ||
| 116 | static int mbochs_used_mbytes; | ||
| 117 | |||
| 118 | struct mbochs_mode { | ||
| 119 | u32 drm_format; | ||
| 120 | u32 bytepp; | ||
| 121 | u32 width; | ||
| 122 | u32 height; | ||
| 123 | u32 stride; | ||
| 124 | u32 __pad; | ||
| 125 | u64 offset; | ||
| 126 | u64 size; | ||
| 127 | }; | ||
| 128 | |||
| 129 | struct mbochs_dmabuf { | ||
| 130 | struct mbochs_mode mode; | ||
| 131 | u32 id; | ||
| 132 | struct page **pages; | ||
| 133 | pgoff_t pagecount; | ||
| 134 | struct dma_buf *buf; | ||
| 135 | struct mdev_state *mdev_state; | ||
| 136 | struct list_head next; | ||
| 137 | bool unlinked; | ||
| 138 | }; | ||
| 139 | |||
| 140 | /* State of each mdev device */ | ||
| 141 | struct mdev_state { | ||
| 142 | u8 *vconfig; | ||
| 143 | u64 bar_mask[3]; | ||
| 144 | u32 memory_bar_mask; | ||
| 145 | struct mutex ops_lock; | ||
| 146 | struct mdev_device *mdev; | ||
| 147 | struct vfio_device_info dev_info; | ||
| 148 | |||
| 149 | const struct mbochs_type *type; | ||
| 150 | u16 vbe[VBE_DISPI_INDEX_COUNT]; | ||
| 151 | u64 memsize; | ||
| 152 | struct page **pages; | ||
| 153 | pgoff_t pagecount; | ||
| 154 | |||
| 155 | struct list_head dmabufs; | ||
| 156 | u32 active_id; | ||
| 157 | u32 next_id; | ||
| 158 | }; | ||
| 159 | |||
| 160 | static const char *vbe_name_list[VBE_DISPI_INDEX_COUNT] = { | ||
| 161 | [VBE_DISPI_INDEX_ID] = "id", | ||
| 162 | [VBE_DISPI_INDEX_XRES] = "xres", | ||
| 163 | [VBE_DISPI_INDEX_YRES] = "yres", | ||
| 164 | [VBE_DISPI_INDEX_BPP] = "bpp", | ||
| 165 | [VBE_DISPI_INDEX_ENABLE] = "enable", | ||
| 166 | [VBE_DISPI_INDEX_BANK] = "bank", | ||
| 167 | [VBE_DISPI_INDEX_VIRT_WIDTH] = "virt-width", | ||
| 168 | [VBE_DISPI_INDEX_VIRT_HEIGHT] = "virt-height", | ||
| 169 | [VBE_DISPI_INDEX_X_OFFSET] = "x-offset", | ||
| 170 | [VBE_DISPI_INDEX_Y_OFFSET] = "y-offset", | ||
| 171 | [VBE_DISPI_INDEX_VIDEO_MEMORY_64K] = "video-mem", | ||
| 172 | }; | ||
| 173 | |||
| 174 | static const char *vbe_name(u32 index) | ||
| 175 | { | ||
| 176 | if (index < ARRAY_SIZE(vbe_name_list)) | ||
| 177 | return vbe_name_list[index]; | ||
| 178 | return "(invalid)"; | ||
| 179 | } | ||
| 180 | |||
| 181 | static struct page *mbochs_get_page(struct mdev_state *mdev_state, | ||
| 182 | pgoff_t pgoff); | ||
| 183 | |||
| 184 | static const struct mbochs_type *mbochs_find_type(struct kobject *kobj) | ||
| 185 | { | ||
| 186 | int i; | ||
| 187 | |||
| 188 | for (i = 0; i < ARRAY_SIZE(mbochs_types); i++) | ||
| 189 | if (strcmp(mbochs_types[i].name, kobj->name) == 0) | ||
| 190 | return mbochs_types + i; | ||
| 191 | return NULL; | ||
| 192 | } | ||
| 193 | |||
| 194 | static void mbochs_create_config_space(struct mdev_state *mdev_state) | ||
| 195 | { | ||
| 196 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_VENDOR_ID], | ||
| 197 | 0x1234); | ||
| 198 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_DEVICE_ID], | ||
| 199 | 0x1111); | ||
| 200 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_SUBSYSTEM_VENDOR_ID], | ||
| 201 | PCI_SUBVENDOR_ID_REDHAT_QUMRANET); | ||
| 202 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_SUBSYSTEM_ID], | ||
| 203 | PCI_SUBDEVICE_ID_QEMU); | ||
| 204 | |||
| 205 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_COMMAND], | ||
| 206 | PCI_COMMAND_IO | PCI_COMMAND_MEMORY); | ||
| 207 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_CLASS_DEVICE], | ||
| 208 | PCI_CLASS_DISPLAY_OTHER); | ||
| 209 | mdev_state->vconfig[PCI_CLASS_REVISION] = 0x01; | ||
| 210 | |||
| 211 | STORE_LE32((u32 *) &mdev_state->vconfig[PCI_BASE_ADDRESS_0], | ||
| 212 | PCI_BASE_ADDRESS_SPACE_MEMORY | | ||
| 213 | PCI_BASE_ADDRESS_MEM_TYPE_32 | | ||
| 214 | PCI_BASE_ADDRESS_MEM_PREFETCH); | ||
| 215 | mdev_state->bar_mask[0] = ~(mdev_state->memsize) + 1; | ||
| 216 | |||
| 217 | STORE_LE32((u32 *) &mdev_state->vconfig[PCI_BASE_ADDRESS_2], | ||
| 218 | PCI_BASE_ADDRESS_SPACE_MEMORY | | ||
| 219 | PCI_BASE_ADDRESS_MEM_TYPE_32); | ||
| 220 | mdev_state->bar_mask[2] = ~(MBOCHS_MMIO_BAR_SIZE) + 1; | ||
| 221 | } | ||
| 222 | |||
| 223 | static int mbochs_check_framebuffer(struct mdev_state *mdev_state, | ||
| 224 | struct mbochs_mode *mode) | ||
| 225 | { | ||
| 226 | struct device *dev = mdev_dev(mdev_state->mdev); | ||
| 227 | u16 *vbe = mdev_state->vbe; | ||
| 228 | u32 virt_width; | ||
| 229 | |||
| 230 | WARN_ON(!mutex_is_locked(&mdev_state->ops_lock)); | ||
| 231 | |||
| 232 | if (!(vbe[VBE_DISPI_INDEX_ENABLE] & VBE_DISPI_ENABLED)) | ||
| 233 | goto nofb; | ||
| 234 | |||
| 235 | memset(mode, 0, sizeof(*mode)); | ||
| 236 | switch (vbe[VBE_DISPI_INDEX_BPP]) { | ||
| 237 | case 32: | ||
| 238 | mode->drm_format = DRM_FORMAT_XRGB8888; | ||
| 239 | mode->bytepp = 4; | ||
| 240 | break; | ||
| 241 | default: | ||
| 242 | dev_info_ratelimited(dev, "%s: bpp %d not supported\n", | ||
| 243 | __func__, vbe[VBE_DISPI_INDEX_BPP]); | ||
| 244 | goto nofb; | ||
| 245 | } | ||
| 246 | |||
| 247 | mode->width = vbe[VBE_DISPI_INDEX_XRES]; | ||
| 248 | mode->height = vbe[VBE_DISPI_INDEX_YRES]; | ||
| 249 | virt_width = vbe[VBE_DISPI_INDEX_VIRT_WIDTH]; | ||
| 250 | if (virt_width < mode->width) | ||
| 251 | virt_width = mode->width; | ||
| 252 | mode->stride = virt_width * mode->bytepp; | ||
| 253 | mode->size = (u64)mode->stride * mode->height; | ||
| 254 | mode->offset = ((u64)vbe[VBE_DISPI_INDEX_X_OFFSET] * mode->bytepp + | ||
| 255 | (u64)vbe[VBE_DISPI_INDEX_Y_OFFSET] * mode->stride); | ||
| 256 | |||
| 257 | if (mode->width < 64 || mode->height < 64) { | ||
| 258 | dev_info_ratelimited(dev, "%s: invalid resolution %dx%d\n", | ||
| 259 | __func__, mode->width, mode->height); | ||
| 260 | goto nofb; | ||
| 261 | } | ||
| 262 | if (mode->offset + mode->size > mdev_state->memsize) { | ||
| 263 | dev_info_ratelimited(dev, "%s: framebuffer memory overflow\n", | ||
| 264 | __func__); | ||
| 265 | goto nofb; | ||
| 266 | } | ||
| 267 | |||
| 268 | return 0; | ||
| 269 | |||
| 270 | nofb: | ||
| 271 | memset(mode, 0, sizeof(*mode)); | ||
| 272 | return -EINVAL; | ||
| 273 | } | ||
| 274 | |||
| 275 | static bool mbochs_modes_equal(struct mbochs_mode *mode1, | ||
| 276 | struct mbochs_mode *mode2) | ||
| 277 | { | ||
| 278 | return memcmp(mode1, mode2, sizeof(struct mbochs_mode)) == 0; | ||
| 279 | } | ||
| 280 | |||
| 281 | static void handle_pci_cfg_write(struct mdev_state *mdev_state, u16 offset, | ||
| 282 | char *buf, u32 count) | ||
| 283 | { | ||
| 284 | struct device *dev = mdev_dev(mdev_state->mdev); | ||
| 285 | int index = (offset - PCI_BASE_ADDRESS_0) / 0x04; | ||
| 286 | u32 cfg_addr; | ||
| 287 | |||
| 288 | switch (offset) { | ||
| 289 | case PCI_BASE_ADDRESS_0: | ||
| 290 | case PCI_BASE_ADDRESS_2: | ||
| 291 | cfg_addr = *(u32 *)buf; | ||
| 292 | |||
| 293 | if (cfg_addr == 0xffffffff) { | ||
| 294 | cfg_addr = (cfg_addr & mdev_state->bar_mask[index]); | ||
| 295 | } else { | ||
| 296 | cfg_addr &= PCI_BASE_ADDRESS_MEM_MASK; | ||
| 297 | if (cfg_addr) | ||
| 298 | dev_info(dev, "BAR #%d @ 0x%x\n", | ||
| 299 | index, cfg_addr); | ||
| 300 | } | ||
| 301 | |||
| 302 | cfg_addr |= (mdev_state->vconfig[offset] & | ||
| 303 | ~PCI_BASE_ADDRESS_MEM_MASK); | ||
| 304 | STORE_LE32(&mdev_state->vconfig[offset], cfg_addr); | ||
| 305 | break; | ||
| 306 | } | ||
| 307 | } | ||
| 308 | |||
| 309 | static void handle_mmio_write(struct mdev_state *mdev_state, u16 offset, | ||
| 310 | char *buf, u32 count) | ||
| 311 | { | ||
| 312 | struct device *dev = mdev_dev(mdev_state->mdev); | ||
| 313 | int index; | ||
| 314 | u16 reg16; | ||
| 315 | |||
| 316 | switch (offset) { | ||
| 317 | case 0x400 ... 0x41f: /* vga ioports remapped */ | ||
| 318 | goto unhandled; | ||
| 319 | case 0x500 ... 0x515: /* bochs dispi interface */ | ||
| 320 | if (count != 2) | ||
| 321 | goto unhandled; | ||
| 322 | index = (offset - 0x500) / 2; | ||
| 323 | reg16 = *(u16 *)buf; | ||
| 324 | if (index < ARRAY_SIZE(mdev_state->vbe)) | ||
| 325 | mdev_state->vbe[index] = reg16; | ||
| 326 | dev_dbg(dev, "%s: vbe write %d = %d (%s)\n", | ||
| 327 | __func__, index, reg16, vbe_name(index)); | ||
| 328 | break; | ||
| 329 | case 0x600 ... 0x607: /* qemu extended regs */ | ||
| 330 | goto unhandled; | ||
| 331 | default: | ||
| 332 | unhandled: | ||
| 333 | dev_dbg(dev, "%s: @0x%03x, count %d (unhandled)\n", | ||
| 334 | __func__, offset, count); | ||
| 335 | break; | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | static void handle_mmio_read(struct mdev_state *mdev_state, u16 offset, | ||
| 340 | char *buf, u32 count) | ||
| 341 | { | ||
| 342 | struct device *dev = mdev_dev(mdev_state->mdev); | ||
| 343 | u16 reg16 = 0; | ||
| 344 | int index; | ||
| 345 | |||
| 346 | switch (offset) { | ||
| 347 | case 0x500 ... 0x515: /* bochs dispi interface */ | ||
| 348 | if (count != 2) | ||
| 349 | goto unhandled; | ||
| 350 | index = (offset - 0x500) / 2; | ||
| 351 | if (index < ARRAY_SIZE(mdev_state->vbe)) | ||
| 352 | reg16 = mdev_state->vbe[index]; | ||
| 353 | dev_dbg(dev, "%s: vbe read %d = %d (%s)\n", | ||
| 354 | __func__, index, reg16, vbe_name(index)); | ||
| 355 | *(u16 *)buf = reg16; | ||
| 356 | break; | ||
| 357 | default: | ||
| 358 | unhandled: | ||
| 359 | dev_dbg(dev, "%s: @0x%03x, count %d (unhandled)\n", | ||
| 360 | __func__, offset, count); | ||
| 361 | memset(buf, 0, count); | ||
| 362 | break; | ||
| 363 | } | ||
| 364 | } | ||
| 365 | |||
| 366 | static ssize_t mdev_access(struct mdev_device *mdev, char *buf, size_t count, | ||
| 367 | loff_t pos, bool is_write) | ||
| 368 | { | ||
| 369 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 370 | struct device *dev = mdev_dev(mdev); | ||
| 371 | struct page *pg; | ||
| 372 | loff_t poff; | ||
| 373 | char *map; | ||
| 374 | int ret = 0; | ||
| 375 | |||
| 376 | mutex_lock(&mdev_state->ops_lock); | ||
| 377 | |||
| 378 | if (pos < MBOCHS_CONFIG_SPACE_SIZE) { | ||
| 379 | if (is_write) | ||
| 380 | handle_pci_cfg_write(mdev_state, pos, buf, count); | ||
| 381 | else | ||
| 382 | memcpy(buf, (mdev_state->vconfig + pos), count); | ||
| 383 | |||
| 384 | } else if (pos >= MBOCHS_MMIO_BAR_OFFSET && | ||
| 385 | pos + count <= MBOCHS_MEMORY_BAR_OFFSET) { | ||
| 386 | pos -= MBOCHS_MMIO_BAR_OFFSET; | ||
| 387 | if (is_write) | ||
| 388 | handle_mmio_write(mdev_state, pos, buf, count); | ||
| 389 | else | ||
| 390 | handle_mmio_read(mdev_state, pos, buf, count); | ||
| 391 | |||
| 392 | } else if (pos >= MBOCHS_MEMORY_BAR_OFFSET && | ||
| 393 | pos + count <= | ||
| 394 | MBOCHS_MEMORY_BAR_OFFSET + mdev_state->memsize) { | ||
| 395 | pos -= MBOCHS_MMIO_BAR_OFFSET; | ||
| 396 | poff = pos & ~PAGE_MASK; | ||
| 397 | pg = mbochs_get_page(mdev_state, pos >> PAGE_SHIFT); | ||
| 398 | map = kmap(pg); | ||
| 399 | if (is_write) | ||
| 400 | memcpy(map + poff, buf, count); | ||
| 401 | else | ||
| 402 | memcpy(buf, map + poff, count); | ||
| 403 | kunmap(pg); | ||
| 404 | put_page(pg); | ||
| 405 | |||
| 406 | } else { | ||
| 407 | dev_dbg(dev, "%s: %s @0x%llx (unhandled)\n", | ||
| 408 | __func__, is_write ? "WR" : "RD", pos); | ||
| 409 | ret = -1; | ||
| 410 | goto accessfailed; | ||
| 411 | } | ||
| 412 | |||
| 413 | ret = count; | ||
| 414 | |||
| 415 | |||
| 416 | accessfailed: | ||
| 417 | mutex_unlock(&mdev_state->ops_lock); | ||
| 418 | |||
| 419 | return ret; | ||
| 420 | } | ||
| 421 | |||
| 422 | static int mbochs_reset(struct mdev_device *mdev) | ||
| 423 | { | ||
| 424 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 425 | u32 size64k = mdev_state->memsize / (64 * 1024); | ||
| 426 | int i; | ||
| 427 | |||
| 428 | for (i = 0; i < ARRAY_SIZE(mdev_state->vbe); i++) | ||
| 429 | mdev_state->vbe[i] = 0; | ||
| 430 | mdev_state->vbe[VBE_DISPI_INDEX_ID] = VBE_DISPI_ID5; | ||
| 431 | mdev_state->vbe[VBE_DISPI_INDEX_VIDEO_MEMORY_64K] = size64k; | ||
| 432 | return 0; | ||
| 433 | } | ||
| 434 | |||
| 435 | static int mbochs_create(struct kobject *kobj, struct mdev_device *mdev) | ||
| 436 | { | ||
| 437 | const struct mbochs_type *type = mbochs_find_type(kobj); | ||
| 438 | struct device *dev = mdev_dev(mdev); | ||
| 439 | struct mdev_state *mdev_state; | ||
| 440 | |||
| 441 | if (!type) | ||
| 442 | type = &mbochs_types[0]; | ||
| 443 | if (type->mbytes + mbochs_used_mbytes > max_mbytes) | ||
| 444 | return -ENOMEM; | ||
| 445 | |||
| 446 | mdev_state = kzalloc(sizeof(struct mdev_state), GFP_KERNEL); | ||
| 447 | if (mdev_state == NULL) | ||
| 448 | return -ENOMEM; | ||
| 449 | |||
| 450 | mdev_state->vconfig = kzalloc(MBOCHS_CONFIG_SPACE_SIZE, GFP_KERNEL); | ||
| 451 | if (mdev_state->vconfig == NULL) | ||
| 452 | goto err_mem; | ||
| 453 | |||
| 454 | mdev_state->memsize = type->mbytes * 1024 * 1024; | ||
| 455 | mdev_state->pagecount = mdev_state->memsize >> PAGE_SHIFT; | ||
| 456 | mdev_state->pages = kcalloc(mdev_state->pagecount, | ||
| 457 | sizeof(struct page *), | ||
| 458 | GFP_KERNEL); | ||
| 459 | if (!mdev_state->pages) | ||
| 460 | goto err_mem; | ||
| 461 | |||
| 462 | dev_info(dev, "%s: %s, %d MB, %ld pages\n", __func__, | ||
| 463 | kobj->name, type->mbytes, mdev_state->pagecount); | ||
| 464 | |||
| 465 | mutex_init(&mdev_state->ops_lock); | ||
| 466 | mdev_state->mdev = mdev; | ||
| 467 | mdev_set_drvdata(mdev, mdev_state); | ||
| 468 | INIT_LIST_HEAD(&mdev_state->dmabufs); | ||
| 469 | mdev_state->next_id = 1; | ||
| 470 | |||
| 471 | mdev_state->type = type; | ||
| 472 | mbochs_create_config_space(mdev_state); | ||
| 473 | mbochs_reset(mdev); | ||
| 474 | |||
| 475 | mbochs_used_mbytes += type->mbytes; | ||
| 476 | return 0; | ||
| 477 | |||
| 478 | err_mem: | ||
| 479 | kfree(mdev_state->vconfig); | ||
| 480 | kfree(mdev_state); | ||
| 481 | return -ENOMEM; | ||
| 482 | } | ||
| 483 | |||
| 484 | static int mbochs_remove(struct mdev_device *mdev) | ||
| 485 | { | ||
| 486 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 487 | |||
| 488 | mbochs_used_mbytes -= mdev_state->type->mbytes; | ||
| 489 | mdev_set_drvdata(mdev, NULL); | ||
| 490 | kfree(mdev_state->pages); | ||
| 491 | kfree(mdev_state->vconfig); | ||
| 492 | kfree(mdev_state); | ||
| 493 | return 0; | ||
| 494 | } | ||
| 495 | |||
| 496 | static ssize_t mbochs_read(struct mdev_device *mdev, char __user *buf, | ||
| 497 | size_t count, loff_t *ppos) | ||
| 498 | { | ||
| 499 | unsigned int done = 0; | ||
| 500 | int ret; | ||
| 501 | |||
| 502 | while (count) { | ||
| 503 | size_t filled; | ||
| 504 | |||
| 505 | if (count >= 4 && !(*ppos % 4)) { | ||
| 506 | u32 val; | ||
| 507 | |||
| 508 | ret = mdev_access(mdev, (char *)&val, sizeof(val), | ||
| 509 | *ppos, false); | ||
| 510 | if (ret <= 0) | ||
| 511 | goto read_err; | ||
| 512 | |||
| 513 | if (copy_to_user(buf, &val, sizeof(val))) | ||
| 514 | goto read_err; | ||
| 515 | |||
| 516 | filled = 4; | ||
| 517 | } else if (count >= 2 && !(*ppos % 2)) { | ||
| 518 | u16 val; | ||
| 519 | |||
| 520 | ret = mdev_access(mdev, (char *)&val, sizeof(val), | ||
| 521 | *ppos, false); | ||
| 522 | if (ret <= 0) | ||
| 523 | goto read_err; | ||
| 524 | |||
| 525 | if (copy_to_user(buf, &val, sizeof(val))) | ||
| 526 | goto read_err; | ||
| 527 | |||
| 528 | filled = 2; | ||
| 529 | } else { | ||
| 530 | u8 val; | ||
| 531 | |||
| 532 | ret = mdev_access(mdev, (char *)&val, sizeof(val), | ||
| 533 | *ppos, false); | ||
| 534 | if (ret <= 0) | ||
| 535 | goto read_err; | ||
| 536 | |||
| 537 | if (copy_to_user(buf, &val, sizeof(val))) | ||
| 538 | goto read_err; | ||
| 539 | |||
| 540 | filled = 1; | ||
| 541 | } | ||
| 542 | |||
| 543 | count -= filled; | ||
| 544 | done += filled; | ||
| 545 | *ppos += filled; | ||
| 546 | buf += filled; | ||
| 547 | } | ||
| 548 | |||
| 549 | return done; | ||
| 550 | |||
| 551 | read_err: | ||
| 552 | return -EFAULT; | ||
| 553 | } | ||
| 554 | |||
| 555 | static ssize_t mbochs_write(struct mdev_device *mdev, const char __user *buf, | ||
| 556 | size_t count, loff_t *ppos) | ||
| 557 | { | ||
| 558 | unsigned int done = 0; | ||
| 559 | int ret; | ||
| 560 | |||
| 561 | while (count) { | ||
| 562 | size_t filled; | ||
| 563 | |||
| 564 | if (count >= 4 && !(*ppos % 4)) { | ||
| 565 | u32 val; | ||
| 566 | |||
| 567 | if (copy_from_user(&val, buf, sizeof(val))) | ||
| 568 | goto write_err; | ||
| 569 | |||
| 570 | ret = mdev_access(mdev, (char *)&val, sizeof(val), | ||
| 571 | *ppos, true); | ||
| 572 | if (ret <= 0) | ||
| 573 | goto write_err; | ||
| 574 | |||
| 575 | filled = 4; | ||
| 576 | } else if (count >= 2 && !(*ppos % 2)) { | ||
| 577 | u16 val; | ||
| 578 | |||
| 579 | if (copy_from_user(&val, buf, sizeof(val))) | ||
| 580 | goto write_err; | ||
| 581 | |||
| 582 | ret = mdev_access(mdev, (char *)&val, sizeof(val), | ||
| 583 | *ppos, true); | ||
| 584 | if (ret <= 0) | ||
| 585 | goto write_err; | ||
| 586 | |||
| 587 | filled = 2; | ||
| 588 | } else { | ||
| 589 | u8 val; | ||
| 590 | |||
| 591 | if (copy_from_user(&val, buf, sizeof(val))) | ||
| 592 | goto write_err; | ||
| 593 | |||
| 594 | ret = mdev_access(mdev, (char *)&val, sizeof(val), | ||
| 595 | *ppos, true); | ||
| 596 | if (ret <= 0) | ||
| 597 | goto write_err; | ||
| 598 | |||
| 599 | filled = 1; | ||
| 600 | } | ||
| 601 | count -= filled; | ||
| 602 | done += filled; | ||
| 603 | *ppos += filled; | ||
| 604 | buf += filled; | ||
| 605 | } | ||
| 606 | |||
| 607 | return done; | ||
| 608 | write_err: | ||
| 609 | return -EFAULT; | ||
| 610 | } | ||
| 611 | |||
| 612 | static struct page *__mbochs_get_page(struct mdev_state *mdev_state, | ||
| 613 | pgoff_t pgoff) | ||
| 614 | { | ||
| 615 | WARN_ON(!mutex_is_locked(&mdev_state->ops_lock)); | ||
| 616 | |||
| 617 | if (!mdev_state->pages[pgoff]) { | ||
| 618 | mdev_state->pages[pgoff] = | ||
| 619 | alloc_pages(GFP_HIGHUSER | __GFP_ZERO, 0); | ||
| 620 | if (!mdev_state->pages[pgoff]) | ||
| 621 | return NULL; | ||
| 622 | } | ||
| 623 | |||
| 624 | get_page(mdev_state->pages[pgoff]); | ||
| 625 | return mdev_state->pages[pgoff]; | ||
| 626 | } | ||
| 627 | |||
| 628 | static struct page *mbochs_get_page(struct mdev_state *mdev_state, | ||
| 629 | pgoff_t pgoff) | ||
| 630 | { | ||
| 631 | struct page *page; | ||
| 632 | |||
| 633 | if (WARN_ON(pgoff >= mdev_state->pagecount)) | ||
| 634 | return NULL; | ||
| 635 | |||
| 636 | mutex_lock(&mdev_state->ops_lock); | ||
| 637 | page = __mbochs_get_page(mdev_state, pgoff); | ||
| 638 | mutex_unlock(&mdev_state->ops_lock); | ||
| 639 | |||
| 640 | return page; | ||
| 641 | } | ||
| 642 | |||
| 643 | static void mbochs_put_pages(struct mdev_state *mdev_state) | ||
| 644 | { | ||
| 645 | struct device *dev = mdev_dev(mdev_state->mdev); | ||
| 646 | int i, count = 0; | ||
| 647 | |||
| 648 | WARN_ON(!mutex_is_locked(&mdev_state->ops_lock)); | ||
| 649 | |||
| 650 | for (i = 0; i < mdev_state->pagecount; i++) { | ||
| 651 | if (!mdev_state->pages[i]) | ||
| 652 | continue; | ||
| 653 | put_page(mdev_state->pages[i]); | ||
| 654 | mdev_state->pages[i] = NULL; | ||
| 655 | count++; | ||
| 656 | } | ||
| 657 | dev_dbg(dev, "%s: %d pages released\n", __func__, count); | ||
| 658 | } | ||
| 659 | |||
| 660 | static int mbochs_region_vm_fault(struct vm_fault *vmf) | ||
| 661 | { | ||
| 662 | struct vm_area_struct *vma = vmf->vma; | ||
| 663 | struct mdev_state *mdev_state = vma->vm_private_data; | ||
| 664 | pgoff_t page_offset = (vmf->address - vma->vm_start) >> PAGE_SHIFT; | ||
| 665 | |||
| 666 | if (page_offset >= mdev_state->pagecount) | ||
| 667 | return VM_FAULT_SIGBUS; | ||
| 668 | |||
| 669 | vmf->page = mbochs_get_page(mdev_state, page_offset); | ||
| 670 | if (!vmf->page) | ||
| 671 | return VM_FAULT_SIGBUS; | ||
| 672 | |||
| 673 | return 0; | ||
| 674 | } | ||
| 675 | |||
| 676 | static const struct vm_operations_struct mbochs_region_vm_ops = { | ||
| 677 | .fault = mbochs_region_vm_fault, | ||
| 678 | }; | ||
| 679 | |||
| 680 | static int mbochs_mmap(struct mdev_device *mdev, struct vm_area_struct *vma) | ||
| 681 | { | ||
| 682 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 683 | |||
| 684 | if (vma->vm_pgoff != MBOCHS_MEMORY_BAR_OFFSET >> PAGE_SHIFT) | ||
| 685 | return -EINVAL; | ||
| 686 | if (vma->vm_end < vma->vm_start) | ||
| 687 | return -EINVAL; | ||
| 688 | if (vma->vm_end - vma->vm_start > mdev_state->memsize) | ||
| 689 | return -EINVAL; | ||
| 690 | if ((vma->vm_flags & VM_SHARED) == 0) | ||
| 691 | return -EINVAL; | ||
| 692 | |||
| 693 | vma->vm_ops = &mbochs_region_vm_ops; | ||
| 694 | vma->vm_private_data = mdev_state; | ||
| 695 | return 0; | ||
| 696 | } | ||
| 697 | |||
| 698 | static int mbochs_dmabuf_vm_fault(struct vm_fault *vmf) | ||
| 699 | { | ||
| 700 | struct vm_area_struct *vma = vmf->vma; | ||
| 701 | struct mbochs_dmabuf *dmabuf = vma->vm_private_data; | ||
| 702 | |||
| 703 | if (WARN_ON(vmf->pgoff >= dmabuf->pagecount)) | ||
| 704 | return VM_FAULT_SIGBUS; | ||
| 705 | |||
| 706 | vmf->page = dmabuf->pages[vmf->pgoff]; | ||
| 707 | get_page(vmf->page); | ||
| 708 | return 0; | ||
| 709 | } | ||
| 710 | |||
| 711 | static const struct vm_operations_struct mbochs_dmabuf_vm_ops = { | ||
| 712 | .fault = mbochs_dmabuf_vm_fault, | ||
| 713 | }; | ||
| 714 | |||
| 715 | static int mbochs_mmap_dmabuf(struct dma_buf *buf, struct vm_area_struct *vma) | ||
| 716 | { | ||
| 717 | struct mbochs_dmabuf *dmabuf = buf->priv; | ||
| 718 | struct device *dev = mdev_dev(dmabuf->mdev_state->mdev); | ||
| 719 | |||
| 720 | dev_dbg(dev, "%s: %d\n", __func__, dmabuf->id); | ||
| 721 | |||
| 722 | if ((vma->vm_flags & VM_SHARED) == 0) | ||
| 723 | return -EINVAL; | ||
| 724 | |||
| 725 | vma->vm_ops = &mbochs_dmabuf_vm_ops; | ||
| 726 | vma->vm_private_data = dmabuf; | ||
| 727 | return 0; | ||
| 728 | } | ||
| 729 | |||
| 730 | static void mbochs_print_dmabuf(struct mbochs_dmabuf *dmabuf, | ||
| 731 | const char *prefix) | ||
| 732 | { | ||
| 733 | struct device *dev = mdev_dev(dmabuf->mdev_state->mdev); | ||
| 734 | u32 fourcc = dmabuf->mode.drm_format; | ||
| 735 | |||
| 736 | dev_dbg(dev, "%s/%d: %c%c%c%c, %dx%d, stride %d, off 0x%llx, size 0x%llx, pages %ld\n", | ||
| 737 | prefix, dmabuf->id, | ||
| 738 | fourcc ? ((fourcc >> 0) & 0xff) : '-', | ||
| 739 | fourcc ? ((fourcc >> 8) & 0xff) : '-', | ||
| 740 | fourcc ? ((fourcc >> 16) & 0xff) : '-', | ||
| 741 | fourcc ? ((fourcc >> 24) & 0xff) : '-', | ||
| 742 | dmabuf->mode.width, dmabuf->mode.height, dmabuf->mode.stride, | ||
| 743 | dmabuf->mode.offset, dmabuf->mode.size, dmabuf->pagecount); | ||
| 744 | } | ||
| 745 | |||
| 746 | static struct sg_table *mbochs_map_dmabuf(struct dma_buf_attachment *at, | ||
| 747 | enum dma_data_direction direction) | ||
| 748 | { | ||
| 749 | struct mbochs_dmabuf *dmabuf = at->dmabuf->priv; | ||
| 750 | struct device *dev = mdev_dev(dmabuf->mdev_state->mdev); | ||
| 751 | struct sg_table *sg; | ||
| 752 | |||
| 753 | dev_dbg(dev, "%s: %d\n", __func__, dmabuf->id); | ||
| 754 | |||
| 755 | sg = kzalloc(sizeof(*sg), GFP_KERNEL); | ||
| 756 | if (!sg) | ||
| 757 | goto err1; | ||
| 758 | if (sg_alloc_table_from_pages(sg, dmabuf->pages, dmabuf->pagecount, | ||
| 759 | 0, dmabuf->mode.size, GFP_KERNEL) < 0) | ||
| 760 | goto err2; | ||
| 761 | if (!dma_map_sg(at->dev, sg->sgl, sg->nents, direction)) | ||
| 762 | goto err3; | ||
| 763 | |||
| 764 | return sg; | ||
| 765 | |||
| 766 | err3: | ||
| 767 | sg_free_table(sg); | ||
| 768 | err2: | ||
| 769 | kfree(sg); | ||
| 770 | err1: | ||
| 771 | return ERR_PTR(-ENOMEM); | ||
| 772 | } | ||
| 773 | |||
| 774 | static void mbochs_unmap_dmabuf(struct dma_buf_attachment *at, | ||
| 775 | struct sg_table *sg, | ||
| 776 | enum dma_data_direction direction) | ||
| 777 | { | ||
| 778 | struct mbochs_dmabuf *dmabuf = at->dmabuf->priv; | ||
| 779 | struct device *dev = mdev_dev(dmabuf->mdev_state->mdev); | ||
| 780 | |||
| 781 | dev_dbg(dev, "%s: %d\n", __func__, dmabuf->id); | ||
| 782 | |||
| 783 | sg_free_table(sg); | ||
| 784 | kfree(sg); | ||
| 785 | } | ||
| 786 | |||
| 787 | static void mbochs_release_dmabuf(struct dma_buf *buf) | ||
| 788 | { | ||
| 789 | struct mbochs_dmabuf *dmabuf = buf->priv; | ||
| 790 | struct mdev_state *mdev_state = dmabuf->mdev_state; | ||
| 791 | struct device *dev = mdev_dev(mdev_state->mdev); | ||
| 792 | pgoff_t pg; | ||
| 793 | |||
| 794 | dev_dbg(dev, "%s: %d\n", __func__, dmabuf->id); | ||
| 795 | |||
| 796 | for (pg = 0; pg < dmabuf->pagecount; pg++) | ||
| 797 | put_page(dmabuf->pages[pg]); | ||
| 798 | |||
| 799 | mutex_lock(&mdev_state->ops_lock); | ||
| 800 | dmabuf->buf = NULL; | ||
| 801 | if (dmabuf->unlinked) | ||
| 802 | kfree(dmabuf); | ||
| 803 | mutex_unlock(&mdev_state->ops_lock); | ||
| 804 | } | ||
| 805 | |||
| 806 | static void *mbochs_kmap_atomic_dmabuf(struct dma_buf *buf, | ||
| 807 | unsigned long page_num) | ||
| 808 | { | ||
| 809 | struct mbochs_dmabuf *dmabuf = buf->priv; | ||
| 810 | struct page *page = dmabuf->pages[page_num]; | ||
| 811 | |||
| 812 | return kmap_atomic(page); | ||
| 813 | } | ||
| 814 | |||
| 815 | static void *mbochs_kmap_dmabuf(struct dma_buf *buf, unsigned long page_num) | ||
| 816 | { | ||
| 817 | struct mbochs_dmabuf *dmabuf = buf->priv; | ||
| 818 | struct page *page = dmabuf->pages[page_num]; | ||
| 819 | |||
| 820 | return kmap(page); | ||
| 821 | } | ||
| 822 | |||
| 823 | static struct dma_buf_ops mbochs_dmabuf_ops = { | ||
| 824 | .map_dma_buf = mbochs_map_dmabuf, | ||
| 825 | .unmap_dma_buf = mbochs_unmap_dmabuf, | ||
| 826 | .release = mbochs_release_dmabuf, | ||
| 827 | .map_atomic = mbochs_kmap_atomic_dmabuf, | ||
| 828 | .map = mbochs_kmap_dmabuf, | ||
| 829 | .mmap = mbochs_mmap_dmabuf, | ||
| 830 | }; | ||
| 831 | |||
| 832 | static struct mbochs_dmabuf *mbochs_dmabuf_alloc(struct mdev_state *mdev_state, | ||
| 833 | struct mbochs_mode *mode) | ||
| 834 | { | ||
| 835 | struct mbochs_dmabuf *dmabuf; | ||
| 836 | pgoff_t page_offset, pg; | ||
| 837 | |||
| 838 | WARN_ON(!mutex_is_locked(&mdev_state->ops_lock)); | ||
| 839 | |||
| 840 | dmabuf = kzalloc(sizeof(struct mbochs_dmabuf), GFP_KERNEL); | ||
| 841 | if (!dmabuf) | ||
| 842 | return NULL; | ||
| 843 | |||
| 844 | dmabuf->mode = *mode; | ||
| 845 | dmabuf->id = mdev_state->next_id++; | ||
| 846 | dmabuf->pagecount = DIV_ROUND_UP(mode->size, PAGE_SIZE); | ||
| 847 | dmabuf->pages = kcalloc(dmabuf->pagecount, sizeof(struct page *), | ||
| 848 | GFP_KERNEL); | ||
| 849 | if (!dmabuf->pages) | ||
| 850 | goto err_free_dmabuf; | ||
| 851 | |||
| 852 | page_offset = dmabuf->mode.offset >> PAGE_SHIFT; | ||
| 853 | for (pg = 0; pg < dmabuf->pagecount; pg++) { | ||
| 854 | dmabuf->pages[pg] = __mbochs_get_page(mdev_state, | ||
| 855 | page_offset + pg); | ||
| 856 | if (!dmabuf->pages[pg]) | ||
| 857 | goto err_free_pages; | ||
| 858 | } | ||
| 859 | |||
| 860 | dmabuf->mdev_state = mdev_state; | ||
| 861 | list_add(&dmabuf->next, &mdev_state->dmabufs); | ||
| 862 | |||
| 863 | mbochs_print_dmabuf(dmabuf, __func__); | ||
| 864 | return dmabuf; | ||
| 865 | |||
| 866 | err_free_pages: | ||
| 867 | while (pg > 0) | ||
| 868 | put_page(dmabuf->pages[--pg]); | ||
| 869 | kfree(dmabuf->pages); | ||
| 870 | err_free_dmabuf: | ||
| 871 | kfree(dmabuf); | ||
| 872 | return NULL; | ||
| 873 | } | ||
| 874 | |||
| 875 | static struct mbochs_dmabuf * | ||
| 876 | mbochs_dmabuf_find_by_mode(struct mdev_state *mdev_state, | ||
| 877 | struct mbochs_mode *mode) | ||
| 878 | { | ||
| 879 | struct mbochs_dmabuf *dmabuf; | ||
| 880 | |||
| 881 | WARN_ON(!mutex_is_locked(&mdev_state->ops_lock)); | ||
| 882 | |||
| 883 | list_for_each_entry(dmabuf, &mdev_state->dmabufs, next) | ||
| 884 | if (mbochs_modes_equal(&dmabuf->mode, mode)) | ||
| 885 | return dmabuf; | ||
| 886 | |||
| 887 | return NULL; | ||
| 888 | } | ||
| 889 | |||
| 890 | static struct mbochs_dmabuf * | ||
| 891 | mbochs_dmabuf_find_by_id(struct mdev_state *mdev_state, u32 id) | ||
| 892 | { | ||
| 893 | struct mbochs_dmabuf *dmabuf; | ||
| 894 | |||
| 895 | WARN_ON(!mutex_is_locked(&mdev_state->ops_lock)); | ||
| 896 | |||
| 897 | list_for_each_entry(dmabuf, &mdev_state->dmabufs, next) | ||
| 898 | if (dmabuf->id == id) | ||
| 899 | return dmabuf; | ||
| 900 | |||
| 901 | return NULL; | ||
| 902 | } | ||
| 903 | |||
| 904 | static int mbochs_dmabuf_export(struct mbochs_dmabuf *dmabuf) | ||
| 905 | { | ||
| 906 | struct mdev_state *mdev_state = dmabuf->mdev_state; | ||
| 907 | struct device *dev = mdev_dev(mdev_state->mdev); | ||
| 908 | DEFINE_DMA_BUF_EXPORT_INFO(exp_info); | ||
| 909 | struct dma_buf *buf; | ||
| 910 | |||
| 911 | WARN_ON(!mutex_is_locked(&mdev_state->ops_lock)); | ||
| 912 | |||
| 913 | if (!IS_ALIGNED(dmabuf->mode.offset, PAGE_SIZE)) { | ||
| 914 | dev_info_ratelimited(dev, "%s: framebuffer not page-aligned\n", | ||
| 915 | __func__); | ||
| 916 | return -EINVAL; | ||
| 917 | } | ||
| 918 | |||
| 919 | exp_info.ops = &mbochs_dmabuf_ops; | ||
| 920 | exp_info.size = dmabuf->mode.size; | ||
| 921 | exp_info.priv = dmabuf; | ||
| 922 | |||
| 923 | buf = dma_buf_export(&exp_info); | ||
| 924 | if (IS_ERR(buf)) { | ||
| 925 | dev_info_ratelimited(dev, "%s: dma_buf_export failed: %ld\n", | ||
| 926 | __func__, PTR_ERR(buf)); | ||
| 927 | return PTR_ERR(buf); | ||
| 928 | } | ||
| 929 | |||
| 930 | dmabuf->buf = buf; | ||
| 931 | dev_dbg(dev, "%s: %d\n", __func__, dmabuf->id); | ||
| 932 | return 0; | ||
| 933 | } | ||
| 934 | |||
| 935 | static int mbochs_get_region_info(struct mdev_device *mdev, | ||
| 936 | struct vfio_region_info *region_info, | ||
| 937 | u16 *cap_type_id, void **cap_type) | ||
| 938 | { | ||
| 939 | struct mdev_state *mdev_state; | ||
| 940 | |||
| 941 | mdev_state = mdev_get_drvdata(mdev); | ||
| 942 | if (!mdev_state) | ||
| 943 | return -EINVAL; | ||
| 944 | |||
| 945 | if (region_info->index >= VFIO_PCI_NUM_REGIONS) | ||
| 946 | return -EINVAL; | ||
| 947 | |||
| 948 | switch (region_info->index) { | ||
| 949 | case VFIO_PCI_CONFIG_REGION_INDEX: | ||
| 950 | region_info->offset = 0; | ||
| 951 | region_info->size = MBOCHS_CONFIG_SPACE_SIZE; | ||
| 952 | region_info->flags = (VFIO_REGION_INFO_FLAG_READ | | ||
| 953 | VFIO_REGION_INFO_FLAG_WRITE); | ||
| 954 | break; | ||
| 955 | case VFIO_PCI_BAR0_REGION_INDEX: | ||
| 956 | region_info->offset = MBOCHS_MEMORY_BAR_OFFSET; | ||
| 957 | region_info->size = mdev_state->memsize; | ||
| 958 | region_info->flags = (VFIO_REGION_INFO_FLAG_READ | | ||
| 959 | VFIO_REGION_INFO_FLAG_WRITE | | ||
| 960 | VFIO_REGION_INFO_FLAG_MMAP); | ||
| 961 | break; | ||
| 962 | case VFIO_PCI_BAR2_REGION_INDEX: | ||
| 963 | region_info->offset = MBOCHS_MMIO_BAR_OFFSET; | ||
| 964 | region_info->size = MBOCHS_MMIO_BAR_SIZE; | ||
| 965 | region_info->flags = (VFIO_REGION_INFO_FLAG_READ | | ||
| 966 | VFIO_REGION_INFO_FLAG_WRITE); | ||
| 967 | break; | ||
| 968 | default: | ||
| 969 | region_info->size = 0; | ||
| 970 | region_info->offset = 0; | ||
| 971 | region_info->flags = 0; | ||
| 972 | } | ||
| 973 | |||
| 974 | return 0; | ||
| 975 | } | ||
| 976 | |||
| 977 | static int mbochs_get_irq_info(struct mdev_device *mdev, | ||
| 978 | struct vfio_irq_info *irq_info) | ||
| 979 | { | ||
| 980 | irq_info->count = 0; | ||
| 981 | return 0; | ||
| 982 | } | ||
| 983 | |||
| 984 | static int mbochs_get_device_info(struct mdev_device *mdev, | ||
| 985 | struct vfio_device_info *dev_info) | ||
| 986 | { | ||
| 987 | dev_info->flags = VFIO_DEVICE_FLAGS_PCI; | ||
| 988 | dev_info->num_regions = VFIO_PCI_NUM_REGIONS; | ||
| 989 | dev_info->num_irqs = VFIO_PCI_NUM_IRQS; | ||
| 990 | return 0; | ||
| 991 | } | ||
| 992 | |||
| 993 | static int mbochs_query_gfx_plane(struct mdev_device *mdev, | ||
| 994 | struct vfio_device_gfx_plane_info *plane) | ||
| 995 | { | ||
| 996 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 997 | struct device *dev = mdev_dev(mdev); | ||
| 998 | struct mbochs_dmabuf *dmabuf; | ||
| 999 | struct mbochs_mode mode; | ||
| 1000 | int ret; | ||
| 1001 | |||
| 1002 | if (plane->flags & VFIO_GFX_PLANE_TYPE_PROBE) { | ||
| 1003 | if (plane->flags == (VFIO_GFX_PLANE_TYPE_PROBE | | ||
| 1004 | VFIO_GFX_PLANE_TYPE_DMABUF)) | ||
| 1005 | return 0; | ||
| 1006 | return -EINVAL; | ||
| 1007 | } | ||
| 1008 | |||
| 1009 | if (plane->flags != VFIO_GFX_PLANE_TYPE_DMABUF) | ||
| 1010 | return -EINVAL; | ||
| 1011 | |||
| 1012 | plane->drm_format_mod = 0; | ||
| 1013 | plane->x_pos = 0; | ||
| 1014 | plane->y_pos = 0; | ||
| 1015 | plane->x_hot = 0; | ||
| 1016 | plane->y_hot = 0; | ||
| 1017 | |||
| 1018 | mutex_lock(&mdev_state->ops_lock); | ||
| 1019 | |||
| 1020 | ret = -EINVAL; | ||
| 1021 | if (plane->drm_plane_type == DRM_PLANE_TYPE_PRIMARY) | ||
| 1022 | ret = mbochs_check_framebuffer(mdev_state, &mode); | ||
| 1023 | if (ret < 0) { | ||
| 1024 | plane->drm_format = 0; | ||
| 1025 | plane->width = 0; | ||
| 1026 | plane->height = 0; | ||
| 1027 | plane->stride = 0; | ||
| 1028 | plane->size = 0; | ||
| 1029 | plane->dmabuf_id = 0; | ||
| 1030 | goto done; | ||
| 1031 | } | ||
| 1032 | |||
| 1033 | dmabuf = mbochs_dmabuf_find_by_mode(mdev_state, &mode); | ||
| 1034 | if (!dmabuf) | ||
| 1035 | mbochs_dmabuf_alloc(mdev_state, &mode); | ||
| 1036 | if (!dmabuf) { | ||
| 1037 | mutex_unlock(&mdev_state->ops_lock); | ||
| 1038 | return -ENOMEM; | ||
| 1039 | } | ||
| 1040 | |||
| 1041 | plane->drm_format = dmabuf->mode.drm_format; | ||
| 1042 | plane->width = dmabuf->mode.width; | ||
| 1043 | plane->height = dmabuf->mode.height; | ||
| 1044 | plane->stride = dmabuf->mode.stride; | ||
| 1045 | plane->size = dmabuf->mode.size; | ||
| 1046 | plane->dmabuf_id = dmabuf->id; | ||
| 1047 | |||
| 1048 | done: | ||
| 1049 | if (plane->drm_plane_type == DRM_PLANE_TYPE_PRIMARY && | ||
| 1050 | mdev_state->active_id != plane->dmabuf_id) { | ||
| 1051 | dev_dbg(dev, "%s: primary: %d => %d\n", __func__, | ||
| 1052 | mdev_state->active_id, plane->dmabuf_id); | ||
| 1053 | mdev_state->active_id = plane->dmabuf_id; | ||
| 1054 | } | ||
| 1055 | mutex_unlock(&mdev_state->ops_lock); | ||
| 1056 | return 0; | ||
| 1057 | } | ||
| 1058 | |||
| 1059 | static int mbochs_get_gfx_dmabuf(struct mdev_device *mdev, | ||
| 1060 | u32 id) | ||
| 1061 | { | ||
| 1062 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 1063 | struct mbochs_dmabuf *dmabuf; | ||
| 1064 | |||
| 1065 | mutex_lock(&mdev_state->ops_lock); | ||
| 1066 | |||
| 1067 | dmabuf = mbochs_dmabuf_find_by_id(mdev_state, id); | ||
| 1068 | if (!dmabuf) { | ||
| 1069 | mutex_unlock(&mdev_state->ops_lock); | ||
| 1070 | return -ENOENT; | ||
| 1071 | } | ||
| 1072 | |||
| 1073 | if (!dmabuf->buf) | ||
| 1074 | mbochs_dmabuf_export(dmabuf); | ||
| 1075 | |||
| 1076 | mutex_unlock(&mdev_state->ops_lock); | ||
| 1077 | |||
| 1078 | if (!dmabuf->buf) | ||
| 1079 | return -EINVAL; | ||
| 1080 | |||
| 1081 | return dma_buf_fd(dmabuf->buf, 0); | ||
| 1082 | } | ||
| 1083 | |||
| 1084 | static long mbochs_ioctl(struct mdev_device *mdev, unsigned int cmd, | ||
| 1085 | unsigned long arg) | ||
| 1086 | { | ||
| 1087 | int ret = 0; | ||
| 1088 | unsigned long minsz; | ||
| 1089 | struct mdev_state *mdev_state; | ||
| 1090 | |||
| 1091 | mdev_state = mdev_get_drvdata(mdev); | ||
| 1092 | |||
| 1093 | switch (cmd) { | ||
| 1094 | case VFIO_DEVICE_GET_INFO: | ||
| 1095 | { | ||
| 1096 | struct vfio_device_info info; | ||
| 1097 | |||
| 1098 | minsz = offsetofend(struct vfio_device_info, num_irqs); | ||
| 1099 | |||
| 1100 | if (copy_from_user(&info, (void __user *)arg, minsz)) | ||
| 1101 | return -EFAULT; | ||
| 1102 | |||
| 1103 | if (info.argsz < minsz) | ||
| 1104 | return -EINVAL; | ||
| 1105 | |||
| 1106 | ret = mbochs_get_device_info(mdev, &info); | ||
| 1107 | if (ret) | ||
| 1108 | return ret; | ||
| 1109 | |||
| 1110 | memcpy(&mdev_state->dev_info, &info, sizeof(info)); | ||
| 1111 | |||
| 1112 | if (copy_to_user((void __user *)arg, &info, minsz)) | ||
| 1113 | return -EFAULT; | ||
| 1114 | |||
| 1115 | return 0; | ||
| 1116 | } | ||
| 1117 | case VFIO_DEVICE_GET_REGION_INFO: | ||
| 1118 | { | ||
| 1119 | struct vfio_region_info info; | ||
| 1120 | u16 cap_type_id = 0; | ||
| 1121 | void *cap_type = NULL; | ||
| 1122 | |||
| 1123 | minsz = offsetofend(struct vfio_region_info, offset); | ||
| 1124 | |||
| 1125 | if (copy_from_user(&info, (void __user *)arg, minsz)) | ||
| 1126 | return -EFAULT; | ||
| 1127 | |||
| 1128 | if (info.argsz < minsz) | ||
| 1129 | return -EINVAL; | ||
| 1130 | |||
| 1131 | ret = mbochs_get_region_info(mdev, &info, &cap_type_id, | ||
| 1132 | &cap_type); | ||
| 1133 | if (ret) | ||
| 1134 | return ret; | ||
| 1135 | |||
| 1136 | if (copy_to_user((void __user *)arg, &info, minsz)) | ||
| 1137 | return -EFAULT; | ||
| 1138 | |||
| 1139 | return 0; | ||
| 1140 | } | ||
| 1141 | |||
| 1142 | case VFIO_DEVICE_GET_IRQ_INFO: | ||
| 1143 | { | ||
| 1144 | struct vfio_irq_info info; | ||
| 1145 | |||
| 1146 | minsz = offsetofend(struct vfio_irq_info, count); | ||
| 1147 | |||
| 1148 | if (copy_from_user(&info, (void __user *)arg, minsz)) | ||
| 1149 | return -EFAULT; | ||
| 1150 | |||
| 1151 | if ((info.argsz < minsz) || | ||
| 1152 | (info.index >= mdev_state->dev_info.num_irqs)) | ||
| 1153 | return -EINVAL; | ||
| 1154 | |||
| 1155 | ret = mbochs_get_irq_info(mdev, &info); | ||
| 1156 | if (ret) | ||
| 1157 | return ret; | ||
| 1158 | |||
| 1159 | if (copy_to_user((void __user *)arg, &info, minsz)) | ||
| 1160 | return -EFAULT; | ||
| 1161 | |||
| 1162 | return 0; | ||
| 1163 | } | ||
| 1164 | |||
| 1165 | case VFIO_DEVICE_QUERY_GFX_PLANE: | ||
| 1166 | { | ||
| 1167 | struct vfio_device_gfx_plane_info plane; | ||
| 1168 | |||
| 1169 | minsz = offsetofend(struct vfio_device_gfx_plane_info, | ||
| 1170 | region_index); | ||
| 1171 | |||
| 1172 | if (copy_from_user(&plane, (void __user *)arg, minsz)) | ||
| 1173 | return -EFAULT; | ||
| 1174 | |||
| 1175 | if (plane.argsz < minsz) | ||
| 1176 | return -EINVAL; | ||
| 1177 | |||
| 1178 | ret = mbochs_query_gfx_plane(mdev, &plane); | ||
| 1179 | if (ret) | ||
| 1180 | return ret; | ||
| 1181 | |||
| 1182 | if (copy_to_user((void __user *)arg, &plane, minsz)) | ||
| 1183 | return -EFAULT; | ||
| 1184 | |||
| 1185 | return 0; | ||
| 1186 | } | ||
| 1187 | |||
| 1188 | case VFIO_DEVICE_GET_GFX_DMABUF: | ||
| 1189 | { | ||
| 1190 | u32 dmabuf_id; | ||
| 1191 | |||
| 1192 | if (get_user(dmabuf_id, (__u32 __user *)arg)) | ||
| 1193 | return -EFAULT; | ||
| 1194 | |||
| 1195 | return mbochs_get_gfx_dmabuf(mdev, dmabuf_id); | ||
| 1196 | } | ||
| 1197 | |||
| 1198 | case VFIO_DEVICE_SET_IRQS: | ||
| 1199 | return -EINVAL; | ||
| 1200 | |||
| 1201 | case VFIO_DEVICE_RESET: | ||
| 1202 | return mbochs_reset(mdev); | ||
| 1203 | } | ||
| 1204 | return -ENOTTY; | ||
| 1205 | } | ||
| 1206 | |||
| 1207 | static int mbochs_open(struct mdev_device *mdev) | ||
| 1208 | { | ||
| 1209 | if (!try_module_get(THIS_MODULE)) | ||
| 1210 | return -ENODEV; | ||
| 1211 | |||
| 1212 | return 0; | ||
| 1213 | } | ||
| 1214 | |||
| 1215 | static void mbochs_close(struct mdev_device *mdev) | ||
| 1216 | { | ||
| 1217 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 1218 | struct mbochs_dmabuf *dmabuf, *tmp; | ||
| 1219 | |||
| 1220 | mutex_lock(&mdev_state->ops_lock); | ||
| 1221 | |||
| 1222 | list_for_each_entry_safe(dmabuf, tmp, &mdev_state->dmabufs, next) { | ||
| 1223 | list_del(&dmabuf->next); | ||
| 1224 | if (dmabuf->buf) { | ||
| 1225 | /* free in mbochs_release_dmabuf() */ | ||
| 1226 | dmabuf->unlinked = true; | ||
| 1227 | } else { | ||
| 1228 | kfree(dmabuf); | ||
| 1229 | } | ||
| 1230 | } | ||
| 1231 | mbochs_put_pages(mdev_state); | ||
| 1232 | |||
| 1233 | mutex_unlock(&mdev_state->ops_lock); | ||
| 1234 | module_put(THIS_MODULE); | ||
| 1235 | } | ||
| 1236 | |||
| 1237 | static ssize_t | ||
| 1238 | memory_show(struct device *dev, struct device_attribute *attr, | ||
| 1239 | char *buf) | ||
| 1240 | { | ||
| 1241 | struct mdev_device *mdev = mdev_from_dev(dev); | ||
| 1242 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 1243 | |||
| 1244 | return sprintf(buf, "%d MB\n", mdev_state->type->mbytes); | ||
| 1245 | } | ||
| 1246 | static DEVICE_ATTR_RO(memory); | ||
| 1247 | |||
| 1248 | static struct attribute *mdev_dev_attrs[] = { | ||
| 1249 | &dev_attr_memory.attr, | ||
| 1250 | NULL, | ||
| 1251 | }; | ||
| 1252 | |||
| 1253 | static const struct attribute_group mdev_dev_group = { | ||
| 1254 | .name = "vendor", | ||
| 1255 | .attrs = mdev_dev_attrs, | ||
| 1256 | }; | ||
| 1257 | |||
| 1258 | const struct attribute_group *mdev_dev_groups[] = { | ||
| 1259 | &mdev_dev_group, | ||
| 1260 | NULL, | ||
| 1261 | }; | ||
| 1262 | |||
| 1263 | static ssize_t | ||
| 1264 | name_show(struct kobject *kobj, struct device *dev, char *buf) | ||
| 1265 | { | ||
| 1266 | return sprintf(buf, "%s\n", kobj->name); | ||
| 1267 | } | ||
| 1268 | MDEV_TYPE_ATTR_RO(name); | ||
| 1269 | |||
| 1270 | static ssize_t | ||
| 1271 | description_show(struct kobject *kobj, struct device *dev, char *buf) | ||
| 1272 | { | ||
| 1273 | const struct mbochs_type *type = mbochs_find_type(kobj); | ||
| 1274 | |||
| 1275 | return sprintf(buf, "virtual display, %d MB video memory\n", | ||
| 1276 | type ? type->mbytes : 0); | ||
| 1277 | } | ||
| 1278 | MDEV_TYPE_ATTR_RO(description); | ||
| 1279 | |||
| 1280 | static ssize_t | ||
| 1281 | available_instances_show(struct kobject *kobj, struct device *dev, char *buf) | ||
| 1282 | { | ||
| 1283 | const struct mbochs_type *type = mbochs_find_type(kobj); | ||
| 1284 | int count = (max_mbytes - mbochs_used_mbytes) / type->mbytes; | ||
| 1285 | |||
| 1286 | return sprintf(buf, "%d\n", count); | ||
| 1287 | } | ||
| 1288 | MDEV_TYPE_ATTR_RO(available_instances); | ||
| 1289 | |||
| 1290 | static ssize_t device_api_show(struct kobject *kobj, struct device *dev, | ||
| 1291 | char *buf) | ||
| 1292 | { | ||
| 1293 | return sprintf(buf, "%s\n", VFIO_DEVICE_API_PCI_STRING); | ||
| 1294 | } | ||
| 1295 | MDEV_TYPE_ATTR_RO(device_api); | ||
| 1296 | |||
| 1297 | static struct attribute *mdev_types_attrs[] = { | ||
| 1298 | &mdev_type_attr_name.attr, | ||
| 1299 | &mdev_type_attr_description.attr, | ||
| 1300 | &mdev_type_attr_device_api.attr, | ||
| 1301 | &mdev_type_attr_available_instances.attr, | ||
| 1302 | NULL, | ||
| 1303 | }; | ||
| 1304 | |||
| 1305 | static struct attribute_group mdev_type_group1 = { | ||
| 1306 | .name = MBOCHS_TYPE_1, | ||
| 1307 | .attrs = mdev_types_attrs, | ||
| 1308 | }; | ||
| 1309 | |||
| 1310 | static struct attribute_group mdev_type_group2 = { | ||
| 1311 | .name = MBOCHS_TYPE_2, | ||
| 1312 | .attrs = mdev_types_attrs, | ||
| 1313 | }; | ||
| 1314 | |||
| 1315 | static struct attribute_group mdev_type_group3 = { | ||
| 1316 | .name = MBOCHS_TYPE_3, | ||
| 1317 | .attrs = mdev_types_attrs, | ||
| 1318 | }; | ||
| 1319 | |||
| 1320 | static struct attribute_group *mdev_type_groups[] = { | ||
| 1321 | &mdev_type_group1, | ||
| 1322 | &mdev_type_group2, | ||
| 1323 | &mdev_type_group3, | ||
| 1324 | NULL, | ||
| 1325 | }; | ||
| 1326 | |||
| 1327 | static const struct mdev_parent_ops mdev_fops = { | ||
| 1328 | .owner = THIS_MODULE, | ||
| 1329 | .mdev_attr_groups = mdev_dev_groups, | ||
| 1330 | .supported_type_groups = mdev_type_groups, | ||
| 1331 | .create = mbochs_create, | ||
| 1332 | .remove = mbochs_remove, | ||
| 1333 | .open = mbochs_open, | ||
| 1334 | .release = mbochs_close, | ||
| 1335 | .read = mbochs_read, | ||
| 1336 | .write = mbochs_write, | ||
| 1337 | .ioctl = mbochs_ioctl, | ||
| 1338 | .mmap = mbochs_mmap, | ||
| 1339 | }; | ||
| 1340 | |||
| 1341 | static const struct file_operations vd_fops = { | ||
| 1342 | .owner = THIS_MODULE, | ||
| 1343 | }; | ||
| 1344 | |||
| 1345 | static void mbochs_device_release(struct device *dev) | ||
| 1346 | { | ||
| 1347 | /* nothing */ | ||
| 1348 | } | ||
| 1349 | |||
| 1350 | static int __init mbochs_dev_init(void) | ||
| 1351 | { | ||
| 1352 | int ret = 0; | ||
| 1353 | |||
| 1354 | ret = alloc_chrdev_region(&mbochs_devt, 0, MINORMASK, MBOCHS_NAME); | ||
| 1355 | if (ret < 0) { | ||
| 1356 | pr_err("Error: failed to register mbochs_dev, err: %d\n", ret); | ||
| 1357 | return ret; | ||
| 1358 | } | ||
| 1359 | cdev_init(&mbochs_cdev, &vd_fops); | ||
| 1360 | cdev_add(&mbochs_cdev, mbochs_devt, MINORMASK); | ||
| 1361 | pr_info("%s: major %d\n", __func__, MAJOR(mbochs_devt)); | ||
| 1362 | |||
| 1363 | mbochs_class = class_create(THIS_MODULE, MBOCHS_CLASS_NAME); | ||
| 1364 | if (IS_ERR(mbochs_class)) { | ||
| 1365 | pr_err("Error: failed to register mbochs_dev class\n"); | ||
| 1366 | ret = PTR_ERR(mbochs_class); | ||
| 1367 | goto failed1; | ||
| 1368 | } | ||
| 1369 | mbochs_dev.class = mbochs_class; | ||
| 1370 | mbochs_dev.release = mbochs_device_release; | ||
| 1371 | dev_set_name(&mbochs_dev, "%s", MBOCHS_NAME); | ||
| 1372 | |||
| 1373 | ret = device_register(&mbochs_dev); | ||
| 1374 | if (ret) | ||
| 1375 | goto failed2; | ||
| 1376 | |||
| 1377 | ret = mdev_register_device(&mbochs_dev, &mdev_fops); | ||
| 1378 | if (ret) | ||
| 1379 | goto failed3; | ||
| 1380 | |||
| 1381 | return 0; | ||
| 1382 | |||
| 1383 | failed3: | ||
| 1384 | device_unregister(&mbochs_dev); | ||
| 1385 | failed2: | ||
| 1386 | class_destroy(mbochs_class); | ||
| 1387 | failed1: | ||
| 1388 | cdev_del(&mbochs_cdev); | ||
| 1389 | unregister_chrdev_region(mbochs_devt, MINORMASK); | ||
| 1390 | return ret; | ||
| 1391 | } | ||
| 1392 | |||
| 1393 | static void __exit mbochs_dev_exit(void) | ||
| 1394 | { | ||
| 1395 | mbochs_dev.bus = NULL; | ||
| 1396 | mdev_unregister_device(&mbochs_dev); | ||
| 1397 | |||
| 1398 | device_unregister(&mbochs_dev); | ||
| 1399 | cdev_del(&mbochs_cdev); | ||
| 1400 | unregister_chrdev_region(mbochs_devt, MINORMASK); | ||
| 1401 | class_destroy(mbochs_class); | ||
| 1402 | mbochs_class = NULL; | ||
| 1403 | } | ||
| 1404 | |||
| 1405 | module_init(mbochs_dev_init) | ||
| 1406 | module_exit(mbochs_dev_exit) | ||
diff --git a/samples/vfio-mdev/mdpy-defs.h b/samples/vfio-mdev/mdpy-defs.h new file mode 100644 index 000000000000..96b3b1b49d34 --- /dev/null +++ b/samples/vfio-mdev/mdpy-defs.h | |||
| @@ -0,0 +1,22 @@ | |||
| 1 | /* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ | ||
| 2 | /* | ||
| 3 | * Simple pci display device. | ||
| 4 | * | ||
| 5 | * Framebuffer memory is pci bar 0. | ||
| 6 | * Configuration (read-only) is in pci config space. | ||
| 7 | * Format field uses drm fourcc codes. | ||
| 8 | * ATM only DRM_FORMAT_XRGB8888 is supported. | ||
| 9 | */ | ||
| 10 | |||
| 11 | /* pci ids */ | ||
| 12 | #define MDPY_PCI_VENDOR_ID 0x1b36 /* redhat */ | ||
| 13 | #define MDPY_PCI_DEVICE_ID 0x000f | ||
| 14 | #define MDPY_PCI_SUBVENDOR_ID PCI_SUBVENDOR_ID_REDHAT_QUMRANET | ||
| 15 | #define MDPY_PCI_SUBDEVICE_ID PCI_SUBDEVICE_ID_QEMU | ||
| 16 | |||
| 17 | /* pci cfg space offsets for fb config (dword) */ | ||
| 18 | #define MDPY_VENDORCAP_OFFSET 0x40 | ||
| 19 | #define MDPY_VENDORCAP_SIZE 0x10 | ||
| 20 | #define MDPY_FORMAT_OFFSET (MDPY_VENDORCAP_OFFSET + 0x04) | ||
| 21 | #define MDPY_WIDTH_OFFSET (MDPY_VENDORCAP_OFFSET + 0x08) | ||
| 22 | #define MDPY_HEIGHT_OFFSET (MDPY_VENDORCAP_OFFSET + 0x0c) | ||
diff --git a/samples/vfio-mdev/mdpy-fb.c b/samples/vfio-mdev/mdpy-fb.c new file mode 100644 index 000000000000..2719bb259653 --- /dev/null +++ b/samples/vfio-mdev/mdpy-fb.c | |||
| @@ -0,0 +1,232 @@ | |||
| 1 | // SPDX-License-Identifier: GPL-2.0 | ||
| 2 | /* | ||
| 3 | * Framebuffer driver for mdpy (mediated virtual pci display device). | ||
| 4 | * | ||
| 5 | * See mdpy-defs.h for device specs | ||
| 6 | * | ||
| 7 | * (c) Gerd Hoffmann <kraxel@redhat.com> | ||
| 8 | * | ||
| 9 | * Using some code snippets from simplefb and cirrusfb. | ||
| 10 | * | ||
| 11 | * This program is free software; you can redistribute it and/or modify it | ||
| 12 | * under the terms and conditions of the GNU General Public License, | ||
| 13 | * version 2, as published by the Free Software Foundation. | ||
| 14 | * | ||
| 15 | * This program is distributed in the hope it will be useful, but WITHOUT | ||
| 16 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| 17 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| 18 | * more details. | ||
| 19 | */ | ||
| 20 | #include <linux/errno.h> | ||
| 21 | #include <linux/fb.h> | ||
| 22 | #include <linux/io.h> | ||
| 23 | #include <linux/pci.h> | ||
| 24 | #include <linux/module.h> | ||
| 25 | #include <drm/drm_fourcc.h> | ||
| 26 | #include "mdpy-defs.h" | ||
| 27 | |||
| 28 | static const struct fb_fix_screeninfo mdpy_fb_fix = { | ||
| 29 | .id = "mdpy-fb", | ||
| 30 | .type = FB_TYPE_PACKED_PIXELS, | ||
| 31 | .visual = FB_VISUAL_TRUECOLOR, | ||
| 32 | .accel = FB_ACCEL_NONE, | ||
| 33 | }; | ||
| 34 | |||
| 35 | static const struct fb_var_screeninfo mdpy_fb_var = { | ||
| 36 | .height = -1, | ||
| 37 | .width = -1, | ||
| 38 | .activate = FB_ACTIVATE_NOW, | ||
| 39 | .vmode = FB_VMODE_NONINTERLACED, | ||
| 40 | |||
| 41 | .bits_per_pixel = 32, | ||
| 42 | .transp.offset = 24, | ||
| 43 | .red.offset = 16, | ||
| 44 | .green.offset = 8, | ||
| 45 | .blue.offset = 0, | ||
| 46 | .transp.length = 8, | ||
| 47 | .red.length = 8, | ||
| 48 | .green.length = 8, | ||
| 49 | .blue.length = 8, | ||
| 50 | }; | ||
| 51 | |||
| 52 | #define PSEUDO_PALETTE_SIZE 16 | ||
| 53 | |||
| 54 | struct mdpy_fb_par { | ||
| 55 | u32 palette[PSEUDO_PALETTE_SIZE]; | ||
| 56 | }; | ||
| 57 | |||
| 58 | static int mdpy_fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, | ||
| 59 | u_int transp, struct fb_info *info) | ||
| 60 | { | ||
| 61 | u32 *pal = info->pseudo_palette; | ||
| 62 | u32 cr = red >> (16 - info->var.red.length); | ||
| 63 | u32 cg = green >> (16 - info->var.green.length); | ||
| 64 | u32 cb = blue >> (16 - info->var.blue.length); | ||
| 65 | u32 value, mask; | ||
| 66 | |||
| 67 | if (regno >= PSEUDO_PALETTE_SIZE) | ||
| 68 | return -EINVAL; | ||
| 69 | |||
| 70 | value = (cr << info->var.red.offset) | | ||
| 71 | (cg << info->var.green.offset) | | ||
| 72 | (cb << info->var.blue.offset); | ||
| 73 | if (info->var.transp.length > 0) { | ||
| 74 | mask = (1 << info->var.transp.length) - 1; | ||
| 75 | mask <<= info->var.transp.offset; | ||
| 76 | value |= mask; | ||
| 77 | } | ||
| 78 | pal[regno] = value; | ||
| 79 | |||
| 80 | return 0; | ||
| 81 | } | ||
| 82 | |||
| 83 | static void mdpy_fb_destroy(struct fb_info *info) | ||
| 84 | { | ||
| 85 | if (info->screen_base) | ||
| 86 | iounmap(info->screen_base); | ||
| 87 | } | ||
| 88 | |||
| 89 | static struct fb_ops mdpy_fb_ops = { | ||
| 90 | .owner = THIS_MODULE, | ||
| 91 | .fb_destroy = mdpy_fb_destroy, | ||
| 92 | .fb_setcolreg = mdpy_fb_setcolreg, | ||
| 93 | .fb_fillrect = cfb_fillrect, | ||
| 94 | .fb_copyarea = cfb_copyarea, | ||
| 95 | .fb_imageblit = cfb_imageblit, | ||
| 96 | }; | ||
| 97 | |||
| 98 | static int mdpy_fb_probe(struct pci_dev *pdev, | ||
| 99 | const struct pci_device_id *ent) | ||
| 100 | { | ||
| 101 | struct fb_info *info; | ||
| 102 | struct mdpy_fb_par *par; | ||
| 103 | u32 format, width, height; | ||
| 104 | int ret; | ||
| 105 | |||
| 106 | ret = pci_enable_device(pdev); | ||
| 107 | if (ret < 0) | ||
| 108 | return ret; | ||
| 109 | |||
| 110 | ret = pci_request_regions(pdev, "mdpy-fb"); | ||
| 111 | if (ret < 0) | ||
| 112 | return ret; | ||
| 113 | |||
| 114 | pci_read_config_dword(pdev, MDPY_FORMAT_OFFSET, &format); | ||
| 115 | pci_read_config_dword(pdev, MDPY_WIDTH_OFFSET, &width); | ||
| 116 | pci_read_config_dword(pdev, MDPY_HEIGHT_OFFSET, &height); | ||
| 117 | if (format != DRM_FORMAT_XRGB8888) { | ||
| 118 | pci_err(pdev, "format mismatch (0x%x != 0x%x)\n", | ||
| 119 | format, DRM_FORMAT_XRGB8888); | ||
| 120 | return -EINVAL; | ||
| 121 | } | ||
| 122 | if (width < 100 || width > 10000) { | ||
| 123 | pci_err(pdev, "width (%d) out of range\n", width); | ||
| 124 | return -EINVAL; | ||
| 125 | } | ||
| 126 | if (height < 100 || height > 10000) { | ||
| 127 | pci_err(pdev, "height (%d) out of range\n", height); | ||
| 128 | return -EINVAL; | ||
| 129 | } | ||
| 130 | pci_info(pdev, "mdpy found: %dx%d framebuffer\n", | ||
| 131 | width, height); | ||
| 132 | |||
| 133 | info = framebuffer_alloc(sizeof(struct mdpy_fb_par), &pdev->dev); | ||
| 134 | if (!info) | ||
| 135 | goto err_release_regions; | ||
| 136 | pci_set_drvdata(pdev, info); | ||
| 137 | par = info->par; | ||
| 138 | |||
| 139 | info->fix = mdpy_fb_fix; | ||
| 140 | info->fix.smem_start = pci_resource_start(pdev, 0); | ||
| 141 | info->fix.smem_len = pci_resource_len(pdev, 0); | ||
| 142 | info->fix.line_length = width * 4; | ||
| 143 | |||
| 144 | info->var = mdpy_fb_var; | ||
| 145 | info->var.xres = width; | ||
| 146 | info->var.yres = height; | ||
| 147 | info->var.xres_virtual = width; | ||
| 148 | info->var.yres_virtual = height; | ||
| 149 | |||
| 150 | info->screen_size = info->fix.smem_len; | ||
| 151 | info->screen_base = ioremap(info->fix.smem_start, | ||
| 152 | info->screen_size); | ||
| 153 | if (!info->screen_base) { | ||
| 154 | pci_err(pdev, "ioremap(pcibar) failed\n"); | ||
| 155 | ret = -EIO; | ||
| 156 | goto err_release_fb; | ||
| 157 | } | ||
| 158 | |||
| 159 | info->apertures = alloc_apertures(1); | ||
| 160 | if (!info->apertures) { | ||
| 161 | ret = -ENOMEM; | ||
| 162 | goto err_unmap; | ||
| 163 | } | ||
| 164 | info->apertures->ranges[0].base = info->fix.smem_start; | ||
| 165 | info->apertures->ranges[0].size = info->fix.smem_len; | ||
| 166 | |||
| 167 | info->fbops = &mdpy_fb_ops; | ||
| 168 | info->flags = FBINFO_DEFAULT; | ||
| 169 | info->pseudo_palette = par->palette; | ||
| 170 | |||
| 171 | ret = register_framebuffer(info); | ||
| 172 | if (ret < 0) { | ||
| 173 | pci_err(pdev, "mdpy-fb device register failed: %d\n", ret); | ||
| 174 | goto err_unmap; | ||
| 175 | } | ||
| 176 | |||
| 177 | pci_info(pdev, "fb%d registered\n", info->node); | ||
| 178 | return 0; | ||
| 179 | |||
| 180 | err_unmap: | ||
| 181 | iounmap(info->screen_base); | ||
| 182 | |||
| 183 | err_release_fb: | ||
| 184 | framebuffer_release(info); | ||
| 185 | |||
| 186 | err_release_regions: | ||
| 187 | pci_release_regions(pdev); | ||
| 188 | |||
| 189 | return ret; | ||
| 190 | } | ||
| 191 | |||
| 192 | static void mdpy_fb_remove(struct pci_dev *pdev) | ||
| 193 | { | ||
| 194 | struct fb_info *info = pci_get_drvdata(pdev); | ||
| 195 | |||
| 196 | unregister_framebuffer(info); | ||
| 197 | framebuffer_release(info); | ||
| 198 | } | ||
| 199 | |||
| 200 | static struct pci_device_id mdpy_fb_pci_table[] = { | ||
| 201 | { | ||
| 202 | .vendor = MDPY_PCI_VENDOR_ID, | ||
| 203 | .device = MDPY_PCI_DEVICE_ID, | ||
| 204 | .subvendor = MDPY_PCI_SUBVENDOR_ID, | ||
| 205 | .subdevice = MDPY_PCI_SUBDEVICE_ID, | ||
| 206 | }, { | ||
| 207 | /* end of list */ | ||
| 208 | } | ||
| 209 | }; | ||
| 210 | |||
| 211 | static struct pci_driver mdpy_fb_pci_driver = { | ||
| 212 | .name = "mdpy-fb", | ||
| 213 | .id_table = mdpy_fb_pci_table, | ||
| 214 | .probe = mdpy_fb_probe, | ||
| 215 | .remove = mdpy_fb_remove, | ||
| 216 | }; | ||
| 217 | |||
| 218 | static int __init mdpy_fb_init(void) | ||
| 219 | { | ||
| 220 | int ret; | ||
| 221 | |||
| 222 | ret = pci_register_driver(&mdpy_fb_pci_driver); | ||
| 223 | if (ret) | ||
| 224 | return ret; | ||
| 225 | |||
| 226 | return 0; | ||
| 227 | } | ||
| 228 | |||
| 229 | module_init(mdpy_fb_init); | ||
| 230 | |||
| 231 | MODULE_DEVICE_TABLE(pci, mdpy_fb_pci_table); | ||
| 232 | MODULE_LICENSE("GPL v2"); | ||
diff --git a/samples/vfio-mdev/mdpy.c b/samples/vfio-mdev/mdpy.c new file mode 100644 index 000000000000..96e7969c473a --- /dev/null +++ b/samples/vfio-mdev/mdpy.c | |||
| @@ -0,0 +1,807 @@ | |||
| 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> | ||
| 20 | #include <linux/device.h> | ||
| 21 | #include <linux/kernel.h> | ||
| 22 | #include <linux/slab.h> | ||
| 23 | #include <linux/vmalloc.h> | ||
| 24 | #include <linux/cdev.h> | ||
| 25 | #include <linux/vfio.h> | ||
| 26 | #include <linux/iommu.h> | ||
| 27 | #include <linux/sysfs.h> | ||
| 28 | #include <linux/mdev.h> | ||
| 29 | #include <linux/pci.h> | ||
| 30 | #include <drm/drm_fourcc.h> | ||
| 31 | #include "mdpy-defs.h" | ||
| 32 | |||
| 33 | #define MDPY_NAME "mdpy" | ||
| 34 | #define MDPY_CLASS_NAME "mdpy" | ||
| 35 | |||
| 36 | #define MDPY_CONFIG_SPACE_SIZE 0xff | ||
| 37 | #define MDPY_MEMORY_BAR_OFFSET PAGE_SIZE | ||
| 38 | #define MDPY_DISPLAY_REGION 16 | ||
| 39 | |||
| 40 | #define STORE_LE16(addr, val) (*(u16 *)addr = val) | ||
| 41 | #define STORE_LE32(addr, val) (*(u32 *)addr = val) | ||
| 42 | |||
| 43 | |||
| 44 | MODULE_LICENSE("GPL v2"); | ||
| 45 | |||
| 46 | static int max_devices = 4; | ||
| 47 | module_param_named(count, max_devices, int, 0444); | ||
| 48 | MODULE_PARM_DESC(count, "number of " MDPY_NAME " devices"); | ||
| 49 | |||
| 50 | |||
| 51 | #define MDPY_TYPE_1 "vga" | ||
| 52 | #define MDPY_TYPE_2 "xga" | ||
| 53 | #define MDPY_TYPE_3 "hd" | ||
| 54 | |||
| 55 | static const struct mdpy_type { | ||
| 56 | const char *name; | ||
| 57 | u32 format; | ||
| 58 | u32 bytepp; | ||
| 59 | u32 width; | ||
| 60 | u32 height; | ||
| 61 | } mdpy_types[] = { | ||
| 62 | { | ||
| 63 | .name = MDPY_CLASS_NAME "-" MDPY_TYPE_1, | ||
| 64 | .format = DRM_FORMAT_XRGB8888, | ||
| 65 | .bytepp = 4, | ||
| 66 | .width = 640, | ||
| 67 | .height = 480, | ||
| 68 | }, { | ||
| 69 | .name = MDPY_CLASS_NAME "-" MDPY_TYPE_2, | ||
| 70 | .format = DRM_FORMAT_XRGB8888, | ||
| 71 | .bytepp = 4, | ||
| 72 | .width = 1024, | ||
| 73 | .height = 768, | ||
| 74 | }, { | ||
| 75 | .name = MDPY_CLASS_NAME "-" MDPY_TYPE_3, | ||
| 76 | .format = DRM_FORMAT_XRGB8888, | ||
| 77 | .bytepp = 4, | ||
| 78 | .width = 1920, | ||
| 79 | .height = 1080, | ||
| 80 | }, | ||
| 81 | }; | ||
| 82 | |||
| 83 | static dev_t mdpy_devt; | ||
| 84 | static struct class *mdpy_class; | ||
| 85 | static struct cdev mdpy_cdev; | ||
| 86 | static struct device mdpy_dev; | ||
| 87 | static u32 mdpy_count; | ||
| 88 | |||
| 89 | /* State of each mdev device */ | ||
| 90 | struct mdev_state { | ||
| 91 | u8 *vconfig; | ||
| 92 | u32 bar_mask; | ||
| 93 | struct mutex ops_lock; | ||
| 94 | struct mdev_device *mdev; | ||
| 95 | struct vfio_device_info dev_info; | ||
| 96 | |||
| 97 | const struct mdpy_type *type; | ||
| 98 | u32 memsize; | ||
| 99 | void *memblk; | ||
| 100 | }; | ||
| 101 | |||
| 102 | static const struct mdpy_type *mdpy_find_type(struct kobject *kobj) | ||
| 103 | { | ||
| 104 | int i; | ||
| 105 | |||
| 106 | for (i = 0; i < ARRAY_SIZE(mdpy_types); i++) | ||
| 107 | if (strcmp(mdpy_types[i].name, kobj->name) == 0) | ||
| 108 | return mdpy_types + i; | ||
| 109 | return NULL; | ||
| 110 | } | ||
| 111 | |||
| 112 | static void mdpy_create_config_space(struct mdev_state *mdev_state) | ||
| 113 | { | ||
| 114 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_VENDOR_ID], | ||
| 115 | MDPY_PCI_VENDOR_ID); | ||
| 116 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_DEVICE_ID], | ||
| 117 | MDPY_PCI_DEVICE_ID); | ||
| 118 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_SUBSYSTEM_VENDOR_ID], | ||
| 119 | MDPY_PCI_SUBVENDOR_ID); | ||
| 120 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_SUBSYSTEM_ID], | ||
| 121 | MDPY_PCI_SUBDEVICE_ID); | ||
| 122 | |||
| 123 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_COMMAND], | ||
| 124 | PCI_COMMAND_IO | PCI_COMMAND_MEMORY); | ||
| 125 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_STATUS], | ||
| 126 | PCI_STATUS_CAP_LIST); | ||
| 127 | STORE_LE16((u16 *) &mdev_state->vconfig[PCI_CLASS_DEVICE], | ||
| 128 | PCI_CLASS_DISPLAY_OTHER); | ||
| 129 | mdev_state->vconfig[PCI_CLASS_REVISION] = 0x01; | ||
| 130 | |||
| 131 | STORE_LE32((u32 *) &mdev_state->vconfig[PCI_BASE_ADDRESS_0], | ||
| 132 | PCI_BASE_ADDRESS_SPACE_MEMORY | | ||
| 133 | PCI_BASE_ADDRESS_MEM_TYPE_32 | | ||
| 134 | PCI_BASE_ADDRESS_MEM_PREFETCH); | ||
| 135 | mdev_state->bar_mask = ~(mdev_state->memsize) + 1; | ||
| 136 | |||
| 137 | /* vendor specific capability for the config registers */ | ||
| 138 | mdev_state->vconfig[PCI_CAPABILITY_LIST] = MDPY_VENDORCAP_OFFSET; | ||
| 139 | mdev_state->vconfig[MDPY_VENDORCAP_OFFSET + 0] = 0x09; /* vendor cap */ | ||
| 140 | mdev_state->vconfig[MDPY_VENDORCAP_OFFSET + 1] = 0x00; /* next ptr */ | ||
| 141 | mdev_state->vconfig[MDPY_VENDORCAP_OFFSET + 2] = MDPY_VENDORCAP_SIZE; | ||
| 142 | STORE_LE32((u32 *) &mdev_state->vconfig[MDPY_FORMAT_OFFSET], | ||
| 143 | mdev_state->type->format); | ||
| 144 | STORE_LE32((u32 *) &mdev_state->vconfig[MDPY_WIDTH_OFFSET], | ||
| 145 | mdev_state->type->width); | ||
| 146 | STORE_LE32((u32 *) &mdev_state->vconfig[MDPY_HEIGHT_OFFSET], | ||
| 147 | mdev_state->type->height); | ||
| 148 | } | ||
| 149 | |||
| 150 | static void handle_pci_cfg_write(struct mdev_state *mdev_state, u16 offset, | ||
| 151 | char *buf, u32 count) | ||
| 152 | { | ||
| 153 | struct device *dev = mdev_dev(mdev_state->mdev); | ||
| 154 | u32 cfg_addr; | ||
| 155 | |||
| 156 | switch (offset) { | ||
| 157 | case PCI_BASE_ADDRESS_0: | ||
| 158 | cfg_addr = *(u32 *)buf; | ||
| 159 | |||
| 160 | if (cfg_addr == 0xffffffff) { | ||
| 161 | cfg_addr = (cfg_addr & mdev_state->bar_mask); | ||
| 162 | } else { | ||
| 163 | cfg_addr &= PCI_BASE_ADDRESS_MEM_MASK; | ||
| 164 | if (cfg_addr) | ||
| 165 | dev_info(dev, "BAR0 @ 0x%x\n", cfg_addr); | ||
| 166 | } | ||
| 167 | |||
| 168 | cfg_addr |= (mdev_state->vconfig[offset] & | ||
| 169 | ~PCI_BASE_ADDRESS_MEM_MASK); | ||
| 170 | STORE_LE32(&mdev_state->vconfig[offset], cfg_addr); | ||
| 171 | break; | ||
| 172 | } | ||
| 173 | } | ||
| 174 | |||
| 175 | static ssize_t mdev_access(struct mdev_device *mdev, char *buf, size_t count, | ||
| 176 | loff_t pos, bool is_write) | ||
| 177 | { | ||
| 178 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 179 | struct device *dev = mdev_dev(mdev); | ||
| 180 | int ret = 0; | ||
| 181 | |||
| 182 | mutex_lock(&mdev_state->ops_lock); | ||
| 183 | |||
| 184 | if (pos < MDPY_CONFIG_SPACE_SIZE) { | ||
| 185 | if (is_write) | ||
| 186 | handle_pci_cfg_write(mdev_state, pos, buf, count); | ||
| 187 | else | ||
| 188 | memcpy(buf, (mdev_state->vconfig + pos), count); | ||
| 189 | |||
| 190 | } else if ((pos >= MDPY_MEMORY_BAR_OFFSET) && | ||
| 191 | (pos + count <= | ||
| 192 | MDPY_MEMORY_BAR_OFFSET + mdev_state->memsize)) { | ||
| 193 | pos -= MDPY_MEMORY_BAR_OFFSET; | ||
| 194 | if (is_write) | ||
| 195 | memcpy(mdev_state->memblk, buf, count); | ||
| 196 | else | ||
| 197 | memcpy(buf, mdev_state->memblk, count); | ||
| 198 | |||
| 199 | } else { | ||
| 200 | dev_info(dev, "%s: %s @0x%llx (unhandled)\n", | ||
| 201 | __func__, is_write ? "WR" : "RD", pos); | ||
| 202 | ret = -1; | ||
| 203 | goto accessfailed; | ||
| 204 | } | ||
| 205 | |||
| 206 | ret = count; | ||
| 207 | |||
| 208 | |||
| 209 | accessfailed: | ||
| 210 | mutex_unlock(&mdev_state->ops_lock); | ||
| 211 | |||
| 212 | return ret; | ||
| 213 | } | ||
| 214 | |||
| 215 | static int mdpy_reset(struct mdev_device *mdev) | ||
| 216 | { | ||
| 217 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 218 | u32 stride, i; | ||
| 219 | |||
| 220 | /* initialize with gray gradient */ | ||
| 221 | stride = mdev_state->type->width * mdev_state->type->bytepp; | ||
| 222 | for (i = 0; i < mdev_state->type->height; i++) | ||
| 223 | memset(mdev_state->memblk + i * stride, | ||
| 224 | i * 255 / mdev_state->type->height, | ||
| 225 | stride); | ||
| 226 | return 0; | ||
| 227 | } | ||
| 228 | |||
| 229 | static int mdpy_create(struct kobject *kobj, struct mdev_device *mdev) | ||
| 230 | { | ||
| 231 | const struct mdpy_type *type = mdpy_find_type(kobj); | ||
| 232 | struct device *dev = mdev_dev(mdev); | ||
| 233 | struct mdev_state *mdev_state; | ||
| 234 | u32 fbsize; | ||
| 235 | |||
| 236 | if (mdpy_count >= max_devices) | ||
| 237 | return -ENOMEM; | ||
| 238 | |||
| 239 | mdev_state = kzalloc(sizeof(struct mdev_state), GFP_KERNEL); | ||
| 240 | if (mdev_state == NULL) | ||
| 241 | return -ENOMEM; | ||
| 242 | |||
| 243 | mdev_state->vconfig = kzalloc(MDPY_CONFIG_SPACE_SIZE, GFP_KERNEL); | ||
| 244 | if (mdev_state->vconfig == NULL) { | ||
| 245 | kfree(mdev_state); | ||
| 246 | return -ENOMEM; | ||
| 247 | } | ||
| 248 | |||
| 249 | if (!type) | ||
| 250 | type = &mdpy_types[0]; | ||
| 251 | fbsize = roundup_pow_of_two(type->width * type->height * type->bytepp); | ||
| 252 | |||
| 253 | mdev_state->memblk = vmalloc_user(fbsize); | ||
| 254 | if (!mdev_state->memblk) { | ||
| 255 | kfree(mdev_state->vconfig); | ||
| 256 | kfree(mdev_state); | ||
| 257 | return -ENOMEM; | ||
| 258 | } | ||
| 259 | dev_info(dev, "%s: %s (%dx%d)\n", | ||
| 260 | __func__, kobj->name, type->width, type->height); | ||
| 261 | |||
| 262 | mutex_init(&mdev_state->ops_lock); | ||
| 263 | mdev_state->mdev = mdev; | ||
| 264 | mdev_set_drvdata(mdev, mdev_state); | ||
| 265 | |||
| 266 | mdev_state->type = type; | ||
| 267 | mdev_state->memsize = fbsize; | ||
| 268 | mdpy_create_config_space(mdev_state); | ||
| 269 | mdpy_reset(mdev); | ||
| 270 | |||
| 271 | mdpy_count++; | ||
| 272 | return 0; | ||
| 273 | } | ||
| 274 | |||
| 275 | static int mdpy_remove(struct mdev_device *mdev) | ||
| 276 | { | ||
| 277 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 278 | struct device *dev = mdev_dev(mdev); | ||
| 279 | |||
| 280 | dev_info(dev, "%s\n", __func__); | ||
| 281 | |||
| 282 | mdev_set_drvdata(mdev, NULL); | ||
| 283 | vfree(mdev_state->memblk); | ||
| 284 | kfree(mdev_state->vconfig); | ||
| 285 | kfree(mdev_state); | ||
| 286 | |||
| 287 | mdpy_count--; | ||
| 288 | return 0; | ||
| 289 | } | ||
| 290 | |||
| 291 | static ssize_t mdpy_read(struct mdev_device *mdev, char __user *buf, | ||
| 292 | size_t count, loff_t *ppos) | ||
| 293 | { | ||
| 294 | unsigned int done = 0; | ||
| 295 | int ret; | ||
| 296 | |||
| 297 | while (count) { | ||
| 298 | size_t filled; | ||
| 299 | |||
| 300 | if (count >= 4 && !(*ppos % 4)) { | ||
| 301 | u32 val; | ||
| 302 | |||
| 303 | ret = mdev_access(mdev, (char *)&val, sizeof(val), | ||
| 304 | *ppos, false); | ||
| 305 | if (ret <= 0) | ||
| 306 | goto read_err; | ||
| 307 | |||
| 308 | if (copy_to_user(buf, &val, sizeof(val))) | ||
| 309 | goto read_err; | ||
| 310 | |||
| 311 | filled = 4; | ||
| 312 | } else if (count >= 2 && !(*ppos % 2)) { | ||
| 313 | u16 val; | ||
| 314 | |||
| 315 | ret = mdev_access(mdev, (char *)&val, sizeof(val), | ||
| 316 | *ppos, false); | ||
| 317 | if (ret <= 0) | ||
| 318 | goto read_err; | ||
| 319 | |||
| 320 | if (copy_to_user(buf, &val, sizeof(val))) | ||
| 321 | goto read_err; | ||
| 322 | |||
| 323 | filled = 2; | ||
| 324 | } else { | ||
| 325 | u8 val; | ||
| 326 | |||
| 327 | ret = mdev_access(mdev, (char *)&val, sizeof(val), | ||
| 328 | *ppos, false); | ||
| 329 | if (ret <= 0) | ||
| 330 | goto read_err; | ||
| 331 | |||
| 332 | if (copy_to_user(buf, &val, sizeof(val))) | ||
| 333 | goto read_err; | ||
| 334 | |||
| 335 | filled = 1; | ||
| 336 | } | ||
| 337 | |||
| 338 | count -= filled; | ||
| 339 | done += filled; | ||
| 340 | *ppos += filled; | ||
| 341 | buf += filled; | ||
| 342 | } | ||
| 343 | |||
| 344 | return done; | ||
| 345 | |||
| 346 | read_err: | ||
| 347 | return -EFAULT; | ||
| 348 | } | ||
| 349 | |||
| 350 | static ssize_t mdpy_write(struct mdev_device *mdev, const char __user *buf, | ||
| 351 | size_t count, loff_t *ppos) | ||
| 352 | { | ||
| 353 | unsigned int done = 0; | ||
| 354 | int ret; | ||
| 355 | |||
| 356 | while (count) { | ||
| 357 | size_t filled; | ||
| 358 | |||
| 359 | if (count >= 4 && !(*ppos % 4)) { | ||
| 360 | u32 val; | ||
| 361 | |||
| 362 | if (copy_from_user(&val, buf, sizeof(val))) | ||
| 363 | goto write_err; | ||
| 364 | |||
| 365 | ret = mdev_access(mdev, (char *)&val, sizeof(val), | ||
| 366 | *ppos, true); | ||
| 367 | if (ret <= 0) | ||
| 368 | goto write_err; | ||
| 369 | |||
| 370 | filled = 4; | ||
| 371 | } else if (count >= 2 && !(*ppos % 2)) { | ||
| 372 | u16 val; | ||
| 373 | |||
| 374 | if (copy_from_user(&val, buf, sizeof(val))) | ||
| 375 | goto write_err; | ||
| 376 | |||
| 377 | ret = mdev_access(mdev, (char *)&val, sizeof(val), | ||
| 378 | *ppos, true); | ||
| 379 | if (ret <= 0) | ||
| 380 | goto write_err; | ||
| 381 | |||
| 382 | filled = 2; | ||
| 383 | } else { | ||
| 384 | u8 val; | ||
| 385 | |||
| 386 | if (copy_from_user(&val, buf, sizeof(val))) | ||
| 387 | goto write_err; | ||
| 388 | |||
| 389 | ret = mdev_access(mdev, (char *)&val, sizeof(val), | ||
| 390 | *ppos, true); | ||
| 391 | if (ret <= 0) | ||
| 392 | goto write_err; | ||
| 393 | |||
| 394 | filled = 1; | ||
| 395 | } | ||
| 396 | count -= filled; | ||
| 397 | done += filled; | ||
| 398 | *ppos += filled; | ||
| 399 | buf += filled; | ||
| 400 | } | ||
| 401 | |||
| 402 | return done; | ||
| 403 | write_err: | ||
| 404 | return -EFAULT; | ||
| 405 | } | ||
| 406 | |||
| 407 | static int mdpy_mmap(struct mdev_device *mdev, struct vm_area_struct *vma) | ||
| 408 | { | ||
| 409 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 410 | |||
| 411 | if (vma->vm_pgoff != MDPY_MEMORY_BAR_OFFSET >> PAGE_SHIFT) | ||
| 412 | return -EINVAL; | ||
| 413 | if (vma->vm_end < vma->vm_start) | ||
| 414 | return -EINVAL; | ||
| 415 | if (vma->vm_end - vma->vm_start > mdev_state->memsize) | ||
| 416 | return -EINVAL; | ||
| 417 | if ((vma->vm_flags & VM_SHARED) == 0) | ||
| 418 | return -EINVAL; | ||
| 419 | |||
| 420 | return remap_vmalloc_range_partial(vma, vma->vm_start, | ||
| 421 | mdev_state->memblk, | ||
| 422 | vma->vm_end - vma->vm_start); | ||
| 423 | } | ||
| 424 | |||
| 425 | static int mdpy_get_region_info(struct mdev_device *mdev, | ||
| 426 | struct vfio_region_info *region_info, | ||
| 427 | u16 *cap_type_id, void **cap_type) | ||
| 428 | { | ||
| 429 | struct mdev_state *mdev_state; | ||
| 430 | |||
| 431 | mdev_state = mdev_get_drvdata(mdev); | ||
| 432 | if (!mdev_state) | ||
| 433 | return -EINVAL; | ||
| 434 | |||
| 435 | if (region_info->index >= VFIO_PCI_NUM_REGIONS && | ||
| 436 | region_info->index != MDPY_DISPLAY_REGION) | ||
| 437 | return -EINVAL; | ||
| 438 | |||
| 439 | switch (region_info->index) { | ||
| 440 | case VFIO_PCI_CONFIG_REGION_INDEX: | ||
| 441 | region_info->offset = 0; | ||
| 442 | region_info->size = MDPY_CONFIG_SPACE_SIZE; | ||
| 443 | region_info->flags = (VFIO_REGION_INFO_FLAG_READ | | ||
| 444 | VFIO_REGION_INFO_FLAG_WRITE); | ||
| 445 | break; | ||
| 446 | case VFIO_PCI_BAR0_REGION_INDEX: | ||
| 447 | case MDPY_DISPLAY_REGION: | ||
| 448 | region_info->offset = MDPY_MEMORY_BAR_OFFSET; | ||
| 449 | region_info->size = mdev_state->memsize; | ||
| 450 | region_info->flags = (VFIO_REGION_INFO_FLAG_READ | | ||
| 451 | VFIO_REGION_INFO_FLAG_WRITE | | ||
| 452 | VFIO_REGION_INFO_FLAG_MMAP); | ||
| 453 | break; | ||
| 454 | default: | ||
| 455 | region_info->size = 0; | ||
| 456 | region_info->offset = 0; | ||
| 457 | region_info->flags = 0; | ||
| 458 | } | ||
| 459 | |||
| 460 | return 0; | ||
| 461 | } | ||
| 462 | |||
| 463 | static int mdpy_get_irq_info(struct mdev_device *mdev, | ||
| 464 | struct vfio_irq_info *irq_info) | ||
| 465 | { | ||
| 466 | irq_info->count = 0; | ||
| 467 | return 0; | ||
| 468 | } | ||
| 469 | |||
| 470 | static int mdpy_get_device_info(struct mdev_device *mdev, | ||
| 471 | struct vfio_device_info *dev_info) | ||
| 472 | { | ||
| 473 | dev_info->flags = VFIO_DEVICE_FLAGS_PCI; | ||
| 474 | dev_info->num_regions = VFIO_PCI_NUM_REGIONS; | ||
| 475 | dev_info->num_irqs = VFIO_PCI_NUM_IRQS; | ||
| 476 | return 0; | ||
| 477 | } | ||
| 478 | |||
| 479 | static int mdpy_query_gfx_plane(struct mdev_device *mdev, | ||
| 480 | struct vfio_device_gfx_plane_info *plane) | ||
| 481 | { | ||
| 482 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 483 | |||
| 484 | if (plane->flags & VFIO_GFX_PLANE_TYPE_PROBE) { | ||
| 485 | if (plane->flags == (VFIO_GFX_PLANE_TYPE_PROBE | | ||
| 486 | VFIO_GFX_PLANE_TYPE_REGION)) | ||
| 487 | return 0; | ||
| 488 | return -EINVAL; | ||
| 489 | } | ||
| 490 | |||
| 491 | if (plane->flags != VFIO_GFX_PLANE_TYPE_REGION) | ||
| 492 | return -EINVAL; | ||
| 493 | |||
| 494 | plane->drm_format = mdev_state->type->format; | ||
| 495 | plane->width = mdev_state->type->width; | ||
| 496 | plane->height = mdev_state->type->height; | ||
| 497 | plane->stride = (mdev_state->type->width * | ||
| 498 | mdev_state->type->bytepp); | ||
| 499 | plane->size = mdev_state->memsize; | ||
| 500 | plane->region_index = MDPY_DISPLAY_REGION; | ||
| 501 | |||
| 502 | /* unused */ | ||
| 503 | plane->drm_format_mod = 0; | ||
| 504 | plane->x_pos = 0; | ||
| 505 | plane->y_pos = 0; | ||
| 506 | plane->x_hot = 0; | ||
| 507 | plane->y_hot = 0; | ||
| 508 | |||
| 509 | return 0; | ||
| 510 | } | ||
| 511 | |||
| 512 | static long mdpy_ioctl(struct mdev_device *mdev, unsigned int cmd, | ||
| 513 | unsigned long arg) | ||
| 514 | { | ||
| 515 | int ret = 0; | ||
| 516 | unsigned long minsz; | ||
| 517 | struct mdev_state *mdev_state; | ||
| 518 | |||
| 519 | mdev_state = mdev_get_drvdata(mdev); | ||
| 520 | |||
| 521 | switch (cmd) { | ||
| 522 | case VFIO_DEVICE_GET_INFO: | ||
| 523 | { | ||
| 524 | struct vfio_device_info info; | ||
| 525 | |||
| 526 | minsz = offsetofend(struct vfio_device_info, num_irqs); | ||
| 527 | |||
| 528 | if (copy_from_user(&info, (void __user *)arg, minsz)) | ||
| 529 | return -EFAULT; | ||
| 530 | |||
| 531 | if (info.argsz < minsz) | ||
| 532 | return -EINVAL; | ||
| 533 | |||
| 534 | ret = mdpy_get_device_info(mdev, &info); | ||
| 535 | if (ret) | ||
| 536 | return ret; | ||
| 537 | |||
| 538 | memcpy(&mdev_state->dev_info, &info, sizeof(info)); | ||
| 539 | |||
| 540 | if (copy_to_user((void __user *)arg, &info, minsz)) | ||
| 541 | return -EFAULT; | ||
| 542 | |||
| 543 | return 0; | ||
| 544 | } | ||
| 545 | case VFIO_DEVICE_GET_REGION_INFO: | ||
| 546 | { | ||
| 547 | struct vfio_region_info info; | ||
| 548 | u16 cap_type_id = 0; | ||
| 549 | void *cap_type = NULL; | ||
| 550 | |||
| 551 | minsz = offsetofend(struct vfio_region_info, offset); | ||
| 552 | |||
| 553 | if (copy_from_user(&info, (void __user *)arg, minsz)) | ||
| 554 | return -EFAULT; | ||
| 555 | |||
| 556 | if (info.argsz < minsz) | ||
| 557 | return -EINVAL; | ||
| 558 | |||
| 559 | ret = mdpy_get_region_info(mdev, &info, &cap_type_id, | ||
| 560 | &cap_type); | ||
| 561 | if (ret) | ||
| 562 | return ret; | ||
| 563 | |||
| 564 | if (copy_to_user((void __user *)arg, &info, minsz)) | ||
| 565 | return -EFAULT; | ||
| 566 | |||
| 567 | return 0; | ||
| 568 | } | ||
| 569 | |||
| 570 | case VFIO_DEVICE_GET_IRQ_INFO: | ||
| 571 | { | ||
| 572 | struct vfio_irq_info info; | ||
| 573 | |||
| 574 | minsz = offsetofend(struct vfio_irq_info, count); | ||
| 575 | |||
| 576 | if (copy_from_user(&info, (void __user *)arg, minsz)) | ||
| 577 | return -EFAULT; | ||
| 578 | |||
| 579 | if ((info.argsz < minsz) || | ||
| 580 | (info.index >= mdev_state->dev_info.num_irqs)) | ||
| 581 | return -EINVAL; | ||
| 582 | |||
| 583 | ret = mdpy_get_irq_info(mdev, &info); | ||
| 584 | if (ret) | ||
| 585 | return ret; | ||
| 586 | |||
| 587 | if (copy_to_user((void __user *)arg, &info, minsz)) | ||
| 588 | return -EFAULT; | ||
| 589 | |||
| 590 | return 0; | ||
| 591 | } | ||
| 592 | |||
| 593 | case VFIO_DEVICE_QUERY_GFX_PLANE: | ||
| 594 | { | ||
| 595 | struct vfio_device_gfx_plane_info plane; | ||
| 596 | |||
| 597 | minsz = offsetofend(struct vfio_device_gfx_plane_info, | ||
| 598 | region_index); | ||
| 599 | |||
| 600 | if (copy_from_user(&plane, (void __user *)arg, minsz)) | ||
| 601 | return -EFAULT; | ||
| 602 | |||
| 603 | if (plane.argsz < minsz) | ||
| 604 | return -EINVAL; | ||
| 605 | |||
| 606 | ret = mdpy_query_gfx_plane(mdev, &plane); | ||
| 607 | if (ret) | ||
| 608 | return ret; | ||
| 609 | |||
| 610 | if (copy_to_user((void __user *)arg, &plane, minsz)) | ||
| 611 | return -EFAULT; | ||
| 612 | |||
| 613 | return 0; | ||
| 614 | } | ||
| 615 | |||
| 616 | case VFIO_DEVICE_SET_IRQS: | ||
| 617 | return -EINVAL; | ||
| 618 | |||
| 619 | case VFIO_DEVICE_RESET: | ||
| 620 | return mdpy_reset(mdev); | ||
| 621 | } | ||
| 622 | return -ENOTTY; | ||
| 623 | } | ||
| 624 | |||
| 625 | static int mdpy_open(struct mdev_device *mdev) | ||
| 626 | { | ||
| 627 | if (!try_module_get(THIS_MODULE)) | ||
| 628 | return -ENODEV; | ||
| 629 | |||
| 630 | return 0; | ||
| 631 | } | ||
| 632 | |||
| 633 | static void mdpy_close(struct mdev_device *mdev) | ||
| 634 | { | ||
| 635 | module_put(THIS_MODULE); | ||
| 636 | } | ||
| 637 | |||
| 638 | static ssize_t | ||
| 639 | resolution_show(struct device *dev, struct device_attribute *attr, | ||
| 640 | char *buf) | ||
| 641 | { | ||
| 642 | struct mdev_device *mdev = mdev_from_dev(dev); | ||
| 643 | struct mdev_state *mdev_state = mdev_get_drvdata(mdev); | ||
| 644 | |||
| 645 | return sprintf(buf, "%dx%d\n", | ||
| 646 | mdev_state->type->width, | ||
| 647 | mdev_state->type->height); | ||
| 648 | } | ||
| 649 | static DEVICE_ATTR_RO(resolution); | ||
| 650 | |||
| 651 | static struct attribute *mdev_dev_attrs[] = { | ||
| 652 | &dev_attr_resolution.attr, | ||
| 653 | NULL, | ||
| 654 | }; | ||
| 655 | |||
| 656 | static const struct attribute_group mdev_dev_group = { | ||
| 657 | .name = "vendor", | ||
| 658 | .attrs = mdev_dev_attrs, | ||
| 659 | }; | ||
| 660 | |||
| 661 | const struct attribute_group *mdev_dev_groups[] = { | ||
| 662 | &mdev_dev_group, | ||
| 663 | NULL, | ||
| 664 | }; | ||
| 665 | |||
| 666 | static ssize_t | ||
| 667 | name_show(struct kobject *kobj, struct device *dev, char *buf) | ||
| 668 | { | ||
| 669 | return sprintf(buf, "%s\n", kobj->name); | ||
| 670 | } | ||
| 671 | MDEV_TYPE_ATTR_RO(name); | ||
| 672 | |||
| 673 | static ssize_t | ||
| 674 | description_show(struct kobject *kobj, struct device *dev, char *buf) | ||
| 675 | { | ||
| 676 | const struct mdpy_type *type = mdpy_find_type(kobj); | ||
| 677 | |||
| 678 | return sprintf(buf, "virtual display, %dx%d framebuffer\n", | ||
| 679 | type ? type->width : 0, | ||
| 680 | type ? type->height : 0); | ||
| 681 | } | ||
| 682 | MDEV_TYPE_ATTR_RO(description); | ||
| 683 | |||
| 684 | static ssize_t | ||
| 685 | available_instances_show(struct kobject *kobj, struct device *dev, char *buf) | ||
| 686 | { | ||
| 687 | return sprintf(buf, "%d\n", max_devices - mdpy_count); | ||
| 688 | } | ||
| 689 | MDEV_TYPE_ATTR_RO(available_instances); | ||
| 690 | |||
| 691 | static ssize_t device_api_show(struct kobject *kobj, struct device *dev, | ||
| 692 | char *buf) | ||
| 693 | { | ||
| 694 | return sprintf(buf, "%s\n", VFIO_DEVICE_API_PCI_STRING); | ||
| 695 | } | ||
| 696 | MDEV_TYPE_ATTR_RO(device_api); | ||
| 697 | |||
| 698 | static struct attribute *mdev_types_attrs[] = { | ||
| 699 | &mdev_type_attr_name.attr, | ||
| 700 | &mdev_type_attr_description.attr, | ||
| 701 | &mdev_type_attr_device_api.attr, | ||
| 702 | &mdev_type_attr_available_instances.attr, | ||
| 703 | NULL, | ||
| 704 | }; | ||
| 705 | |||
| 706 | static struct attribute_group mdev_type_group1 = { | ||
| 707 | .name = MDPY_TYPE_1, | ||
| 708 | .attrs = mdev_types_attrs, | ||
| 709 | }; | ||
| 710 | |||
| 711 | static struct attribute_group mdev_type_group2 = { | ||
| 712 | .name = MDPY_TYPE_2, | ||
| 713 | .attrs = mdev_types_attrs, | ||
| 714 | }; | ||
| 715 | |||
| 716 | static struct attribute_group mdev_type_group3 = { | ||
| 717 | .name = MDPY_TYPE_3, | ||
| 718 | .attrs = mdev_types_attrs, | ||
| 719 | }; | ||
| 720 | |||
| 721 | static struct attribute_group *mdev_type_groups[] = { | ||
| 722 | &mdev_type_group1, | ||
| 723 | &mdev_type_group2, | ||
| 724 | &mdev_type_group3, | ||
| 725 | NULL, | ||
| 726 | }; | ||
| 727 | |||
| 728 | static const struct mdev_parent_ops mdev_fops = { | ||
| 729 | .owner = THIS_MODULE, | ||
| 730 | .mdev_attr_groups = mdev_dev_groups, | ||
| 731 | .supported_type_groups = mdev_type_groups, | ||
| 732 | .create = mdpy_create, | ||
| 733 | .remove = mdpy_remove, | ||
| 734 | .open = mdpy_open, | ||
| 735 | .release = mdpy_close, | ||
| 736 | .read = mdpy_read, | ||
| 737 | .write = mdpy_write, | ||
| 738 | .ioctl = mdpy_ioctl, | ||
| 739 | .mmap = mdpy_mmap, | ||
| 740 | }; | ||
| 741 | |||
| 742 | static const struct file_operations vd_fops = { | ||
| 743 | .owner = THIS_MODULE, | ||
| 744 | }; | ||
| 745 | |||
| 746 | static void mdpy_device_release(struct device *dev) | ||
| 747 | { | ||
| 748 | /* nothing */ | ||
| 749 | } | ||
| 750 | |||
| 751 | static int __init mdpy_dev_init(void) | ||
| 752 | { | ||
| 753 | int ret = 0; | ||
| 754 | |||
| 755 | ret = alloc_chrdev_region(&mdpy_devt, 0, MINORMASK, MDPY_NAME); | ||
| 756 | if (ret < 0) { | ||
| 757 | pr_err("Error: failed to register mdpy_dev, err: %d\n", ret); | ||
| 758 | return ret; | ||
| 759 | } | ||
| 760 | cdev_init(&mdpy_cdev, &vd_fops); | ||
| 761 | cdev_add(&mdpy_cdev, mdpy_devt, MINORMASK); | ||
| 762 | pr_info("%s: major %d\n", __func__, MAJOR(mdpy_devt)); | ||
| 763 | |||
| 764 | mdpy_class = class_create(THIS_MODULE, MDPY_CLASS_NAME); | ||
| 765 | if (IS_ERR(mdpy_class)) { | ||
| 766 | pr_err("Error: failed to register mdpy_dev class\n"); | ||
| 767 | ret = PTR_ERR(mdpy_class); | ||
| 768 | goto failed1; | ||
| 769 | } | ||
| 770 | mdpy_dev.class = mdpy_class; | ||
| 771 | mdpy_dev.release = mdpy_device_release; | ||
| 772 | dev_set_name(&mdpy_dev, "%s", MDPY_NAME); | ||
| 773 | |||
| 774 | ret = device_register(&mdpy_dev); | ||
| 775 | if (ret) | ||
| 776 | goto failed2; | ||
| 777 | |||
| 778 | ret = mdev_register_device(&mdpy_dev, &mdev_fops); | ||
| 779 | if (ret) | ||
| 780 | goto failed3; | ||
| 781 | |||
| 782 | return 0; | ||
| 783 | |||
| 784 | failed3: | ||
| 785 | device_unregister(&mdpy_dev); | ||
| 786 | failed2: | ||
| 787 | class_destroy(mdpy_class); | ||
| 788 | failed1: | ||
| 789 | cdev_del(&mdpy_cdev); | ||
| 790 | unregister_chrdev_region(mdpy_devt, MINORMASK); | ||
| 791 | return ret; | ||
| 792 | } | ||
| 793 | |||
| 794 | static void __exit mdpy_dev_exit(void) | ||
| 795 | { | ||
| 796 | mdpy_dev.bus = NULL; | ||
| 797 | mdev_unregister_device(&mdpy_dev); | ||
| 798 | |||
| 799 | device_unregister(&mdpy_dev); | ||
| 800 | cdev_del(&mdpy_cdev); | ||
| 801 | unregister_chrdev_region(mdpy_devt, MINORMASK); | ||
| 802 | class_destroy(mdpy_class); | ||
| 803 | mdpy_class = NULL; | ||
| 804 | } | ||
| 805 | |||
| 806 | module_init(mdpy_dev_init) | ||
| 807 | module_exit(mdpy_dev_exit) | ||
