|
4 | 4 | #include <linux/iommufd.h>
|
5 | 5 | #include <linux/slab.h>
|
6 | 6 | #include <linux/iommu.h>
|
| 7 | +#include "../iommu-priv.h" |
7 | 8 |
|
8 | 9 | #include "io_pagetable.h"
|
9 | 10 | #include "iommufd_private.h"
|
@@ -408,6 +409,84 @@ iommufd_device_do_attach(struct iommufd_device *idev,
|
408 | 409 | return NULL;
|
409 | 410 | }
|
410 | 411 |
|
| 412 | +static struct iommufd_hw_pagetable * |
| 413 | +iommufd_device_do_replace(struct iommufd_device *idev, |
| 414 | + struct iommufd_hw_pagetable *hwpt) |
| 415 | +{ |
| 416 | + struct iommufd_group *igroup = idev->igroup; |
| 417 | + struct iommufd_hw_pagetable *old_hwpt; |
| 418 | + unsigned int num_devices = 0; |
| 419 | + struct iommufd_device *cur; |
| 420 | + int rc; |
| 421 | + |
| 422 | + mutex_lock(&idev->igroup->lock); |
| 423 | + |
| 424 | + if (igroup->hwpt == NULL) { |
| 425 | + rc = -EINVAL; |
| 426 | + goto err_unlock; |
| 427 | + } |
| 428 | + |
| 429 | + if (hwpt == igroup->hwpt) { |
| 430 | + mutex_unlock(&idev->igroup->lock); |
| 431 | + return NULL; |
| 432 | + } |
| 433 | + |
| 434 | + /* Try to upgrade the domain we have */ |
| 435 | + list_for_each_entry(cur, &igroup->device_list, group_item) { |
| 436 | + num_devices++; |
| 437 | + if (cur->enforce_cache_coherency) { |
| 438 | + rc = iommufd_hw_pagetable_enforce_cc(hwpt); |
| 439 | + if (rc) |
| 440 | + goto err_unlock; |
| 441 | + } |
| 442 | + } |
| 443 | + |
| 444 | + old_hwpt = igroup->hwpt; |
| 445 | + if (hwpt->ioas != old_hwpt->ioas) { |
| 446 | + list_for_each_entry(cur, &igroup->device_list, group_item) { |
| 447 | + rc = iopt_table_enforce_dev_resv_regions( |
| 448 | + &hwpt->ioas->iopt, cur->dev, NULL); |
| 449 | + if (rc) |
| 450 | + goto err_unresv; |
| 451 | + } |
| 452 | + } |
| 453 | + |
| 454 | + rc = iommufd_group_setup_msi(idev->igroup, hwpt); |
| 455 | + if (rc) |
| 456 | + goto err_unresv; |
| 457 | + |
| 458 | + rc = iommu_group_replace_domain(igroup->group, hwpt->domain); |
| 459 | + if (rc) |
| 460 | + goto err_unresv; |
| 461 | + |
| 462 | + if (hwpt->ioas != old_hwpt->ioas) { |
| 463 | + list_for_each_entry(cur, &igroup->device_list, group_item) |
| 464 | + iopt_remove_reserved_iova(&old_hwpt->ioas->iopt, |
| 465 | + cur->dev); |
| 466 | + } |
| 467 | + |
| 468 | + igroup->hwpt = hwpt; |
| 469 | + |
| 470 | + /* |
| 471 | + * Move the refcounts held by the device_list to the new hwpt. Retain a |
| 472 | + * refcount for this thread as the caller will free it. |
| 473 | + */ |
| 474 | + refcount_add(num_devices, &hwpt->obj.users); |
| 475 | + if (num_devices > 1) |
| 476 | + WARN_ON(refcount_sub_and_test(num_devices - 1, |
| 477 | + &old_hwpt->obj.users)); |
| 478 | + mutex_unlock(&idev->igroup->lock); |
| 479 | + |
| 480 | + /* Caller must destroy old_hwpt */ |
| 481 | + return old_hwpt; |
| 482 | +err_unresv: |
| 483 | + list_for_each_entry(cur, &igroup->device_list, group_item) |
| 484 | + iopt_remove_reserved_iova(&hwpt->ioas->iopt, cur->dev); |
| 485 | +err_unlock: |
| 486 | + mutex_unlock(&idev->igroup->lock); |
| 487 | + return ERR_PTR(rc); |
| 488 | +} |
| 489 | + |
411 | 490 | typedef struct iommufd_hw_pagetable *(*attach_fn)(
|
412 | 491 | struct iommufd_device *idev, struct iommufd_hw_pagetable *hwpt);
|
413 | 492 |
|
@@ -566,6 +645,28 @@ int iommufd_device_attach(struct iommufd_device *idev, u32 *pt_id)
|
566 | 645 | }
|
567 | 646 | EXPORT_SYMBOL_NS_GPL(iommufd_device_attach, IOMMUFD);
|
568 | 647 |
|
| 648 | +/** |
| 649 | + * iommufd_device_replace - Change the device's iommu_domain |
| 650 | + * @idev: device to change |
| 651 | + * @pt_id: Input a IOMMUFD_OBJ_IOAS, or IOMMUFD_OBJ_HW_PAGETABLE |
| 652 | + * Output the IOMMUFD_OBJ_HW_PAGETABLE ID |
| 653 | + * |
| 654 | + * This is the same as:: |
| 655 | + * |
| 656 | + * iommufd_device_detach(); |
| 657 | + * iommufd_device_attach(); |
| 658 | + * |
| 659 | + * If it fails then no change is made to the attachment. The iommu driver may |
| 660 | + * implement this so there is no disruption in translation. This can only be |
| 661 | + * called if iommufd_device_attach() has already succeeded. |
| 662 | + */ |
| 663 | +int iommufd_device_replace(struct iommufd_device *idev, u32 *pt_id) |
| 664 | +{ |
| 665 | + return iommufd_device_change_pt(idev, pt_id, |
| 666 | + &iommufd_device_do_replace); |
| 667 | +} |
| 668 | +EXPORT_SYMBOL_NS_GPL(iommufd_device_replace, IOMMUFD); |
| 669 | + |
569 | 670 | /**
|
570 | 671 | * iommufd_device_detach - Disconnect a device to an iommu_domain
|
571 | 672 | * @idev: device to detach
|
|
0 commit comments