162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (c) 2021-2022, NVIDIA CORPORATION & AFFILIATES 462306a36Sopenharmony_ci */ 562306a36Sopenharmony_ci#include <linux/iommu.h> 662306a36Sopenharmony_ci#include <uapi/linux/iommufd.h> 762306a36Sopenharmony_ci 862306a36Sopenharmony_ci#include "iommufd_private.h" 962306a36Sopenharmony_ci 1062306a36Sopenharmony_civoid iommufd_hw_pagetable_destroy(struct iommufd_object *obj) 1162306a36Sopenharmony_ci{ 1262306a36Sopenharmony_ci struct iommufd_hw_pagetable *hwpt = 1362306a36Sopenharmony_ci container_of(obj, struct iommufd_hw_pagetable, obj); 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_ci if (!list_empty(&hwpt->hwpt_item)) { 1662306a36Sopenharmony_ci mutex_lock(&hwpt->ioas->mutex); 1762306a36Sopenharmony_ci list_del(&hwpt->hwpt_item); 1862306a36Sopenharmony_ci mutex_unlock(&hwpt->ioas->mutex); 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci iopt_table_remove_domain(&hwpt->ioas->iopt, hwpt->domain); 2162306a36Sopenharmony_ci } 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ci if (hwpt->domain) 2462306a36Sopenharmony_ci iommu_domain_free(hwpt->domain); 2562306a36Sopenharmony_ci 2662306a36Sopenharmony_ci refcount_dec(&hwpt->ioas->obj.users); 2762306a36Sopenharmony_ci} 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_civoid iommufd_hw_pagetable_abort(struct iommufd_object *obj) 3062306a36Sopenharmony_ci{ 3162306a36Sopenharmony_ci struct iommufd_hw_pagetable *hwpt = 3262306a36Sopenharmony_ci container_of(obj, struct iommufd_hw_pagetable, obj); 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci /* The ioas->mutex must be held until finalize is called. */ 3562306a36Sopenharmony_ci lockdep_assert_held(&hwpt->ioas->mutex); 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci if (!list_empty(&hwpt->hwpt_item)) { 3862306a36Sopenharmony_ci list_del_init(&hwpt->hwpt_item); 3962306a36Sopenharmony_ci iopt_table_remove_domain(&hwpt->ioas->iopt, hwpt->domain); 4062306a36Sopenharmony_ci } 4162306a36Sopenharmony_ci iommufd_hw_pagetable_destroy(obj); 4262306a36Sopenharmony_ci} 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ciint iommufd_hw_pagetable_enforce_cc(struct iommufd_hw_pagetable *hwpt) 4562306a36Sopenharmony_ci{ 4662306a36Sopenharmony_ci if (hwpt->enforce_cache_coherency) 4762306a36Sopenharmony_ci return 0; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci if (hwpt->domain->ops->enforce_cache_coherency) 5062306a36Sopenharmony_ci hwpt->enforce_cache_coherency = 5162306a36Sopenharmony_ci hwpt->domain->ops->enforce_cache_coherency( 5262306a36Sopenharmony_ci hwpt->domain); 5362306a36Sopenharmony_ci if (!hwpt->enforce_cache_coherency) 5462306a36Sopenharmony_ci return -EINVAL; 5562306a36Sopenharmony_ci return 0; 5662306a36Sopenharmony_ci} 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci/** 5962306a36Sopenharmony_ci * iommufd_hw_pagetable_alloc() - Get an iommu_domain for a device 6062306a36Sopenharmony_ci * @ictx: iommufd context 6162306a36Sopenharmony_ci * @ioas: IOAS to associate the domain with 6262306a36Sopenharmony_ci * @idev: Device to get an iommu_domain for 6362306a36Sopenharmony_ci * @immediate_attach: True if idev should be attached to the hwpt 6462306a36Sopenharmony_ci * 6562306a36Sopenharmony_ci * Allocate a new iommu_domain and return it as a hw_pagetable. The HWPT 6662306a36Sopenharmony_ci * will be linked to the given ioas and upon return the underlying iommu_domain 6762306a36Sopenharmony_ci * is fully popoulated. 6862306a36Sopenharmony_ci * 6962306a36Sopenharmony_ci * The caller must hold the ioas->mutex until after 7062306a36Sopenharmony_ci * iommufd_object_abort_and_destroy() or iommufd_object_finalize() is called on 7162306a36Sopenharmony_ci * the returned hwpt. 7262306a36Sopenharmony_ci */ 7362306a36Sopenharmony_cistruct iommufd_hw_pagetable * 7462306a36Sopenharmony_ciiommufd_hw_pagetable_alloc(struct iommufd_ctx *ictx, struct iommufd_ioas *ioas, 7562306a36Sopenharmony_ci struct iommufd_device *idev, bool immediate_attach) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci struct iommufd_hw_pagetable *hwpt; 7862306a36Sopenharmony_ci int rc; 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci lockdep_assert_held(&ioas->mutex); 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_ci hwpt = iommufd_object_alloc(ictx, hwpt, IOMMUFD_OBJ_HW_PAGETABLE); 8362306a36Sopenharmony_ci if (IS_ERR(hwpt)) 8462306a36Sopenharmony_ci return hwpt; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci INIT_LIST_HEAD(&hwpt->hwpt_item); 8762306a36Sopenharmony_ci /* Pairs with iommufd_hw_pagetable_destroy() */ 8862306a36Sopenharmony_ci refcount_inc(&ioas->obj.users); 8962306a36Sopenharmony_ci hwpt->ioas = ioas; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci hwpt->domain = iommu_domain_alloc(idev->dev->bus); 9262306a36Sopenharmony_ci if (!hwpt->domain) { 9362306a36Sopenharmony_ci rc = -ENOMEM; 9462306a36Sopenharmony_ci goto out_abort; 9562306a36Sopenharmony_ci } 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci /* 9862306a36Sopenharmony_ci * Set the coherency mode before we do iopt_table_add_domain() as some 9962306a36Sopenharmony_ci * iommus have a per-PTE bit that controls it and need to decide before 10062306a36Sopenharmony_ci * doing any maps. It is an iommu driver bug to report 10162306a36Sopenharmony_ci * IOMMU_CAP_ENFORCE_CACHE_COHERENCY but fail enforce_cache_coherency on 10262306a36Sopenharmony_ci * a new domain. 10362306a36Sopenharmony_ci */ 10462306a36Sopenharmony_ci if (idev->enforce_cache_coherency) { 10562306a36Sopenharmony_ci rc = iommufd_hw_pagetable_enforce_cc(hwpt); 10662306a36Sopenharmony_ci if (WARN_ON(rc)) 10762306a36Sopenharmony_ci goto out_abort; 10862306a36Sopenharmony_ci } 10962306a36Sopenharmony_ci 11062306a36Sopenharmony_ci /* 11162306a36Sopenharmony_ci * immediate_attach exists only to accommodate iommu drivers that cannot 11262306a36Sopenharmony_ci * directly allocate a domain. These drivers do not finish creating the 11362306a36Sopenharmony_ci * domain until attach is completed. Thus we must have this call 11462306a36Sopenharmony_ci * sequence. Once those drivers are fixed this should be removed. 11562306a36Sopenharmony_ci */ 11662306a36Sopenharmony_ci if (immediate_attach) { 11762306a36Sopenharmony_ci rc = iommufd_hw_pagetable_attach(hwpt, idev); 11862306a36Sopenharmony_ci if (rc) 11962306a36Sopenharmony_ci goto out_abort; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci rc = iopt_table_add_domain(&hwpt->ioas->iopt, hwpt->domain); 12362306a36Sopenharmony_ci if (rc) 12462306a36Sopenharmony_ci goto out_detach; 12562306a36Sopenharmony_ci list_add_tail(&hwpt->hwpt_item, &hwpt->ioas->hwpt_list); 12662306a36Sopenharmony_ci return hwpt; 12762306a36Sopenharmony_ci 12862306a36Sopenharmony_ciout_detach: 12962306a36Sopenharmony_ci if (immediate_attach) 13062306a36Sopenharmony_ci iommufd_hw_pagetable_detach(idev); 13162306a36Sopenharmony_ciout_abort: 13262306a36Sopenharmony_ci iommufd_object_abort_and_destroy(ictx, &hwpt->obj); 13362306a36Sopenharmony_ci return ERR_PTR(rc); 13462306a36Sopenharmony_ci} 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ciint iommufd_hwpt_alloc(struct iommufd_ucmd *ucmd) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci struct iommu_hwpt_alloc *cmd = ucmd->cmd; 13962306a36Sopenharmony_ci struct iommufd_hw_pagetable *hwpt; 14062306a36Sopenharmony_ci struct iommufd_device *idev; 14162306a36Sopenharmony_ci struct iommufd_ioas *ioas; 14262306a36Sopenharmony_ci int rc; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci if (cmd->flags || cmd->__reserved) 14562306a36Sopenharmony_ci return -EOPNOTSUPP; 14662306a36Sopenharmony_ci 14762306a36Sopenharmony_ci idev = iommufd_get_device(ucmd, cmd->dev_id); 14862306a36Sopenharmony_ci if (IS_ERR(idev)) 14962306a36Sopenharmony_ci return PTR_ERR(idev); 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci ioas = iommufd_get_ioas(ucmd->ictx, cmd->pt_id); 15262306a36Sopenharmony_ci if (IS_ERR(ioas)) { 15362306a36Sopenharmony_ci rc = PTR_ERR(ioas); 15462306a36Sopenharmony_ci goto out_put_idev; 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci 15762306a36Sopenharmony_ci mutex_lock(&ioas->mutex); 15862306a36Sopenharmony_ci hwpt = iommufd_hw_pagetable_alloc(ucmd->ictx, ioas, idev, false); 15962306a36Sopenharmony_ci if (IS_ERR(hwpt)) { 16062306a36Sopenharmony_ci rc = PTR_ERR(hwpt); 16162306a36Sopenharmony_ci goto out_unlock; 16262306a36Sopenharmony_ci } 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci cmd->out_hwpt_id = hwpt->obj.id; 16562306a36Sopenharmony_ci rc = iommufd_ucmd_respond(ucmd, sizeof(*cmd)); 16662306a36Sopenharmony_ci if (rc) 16762306a36Sopenharmony_ci goto out_hwpt; 16862306a36Sopenharmony_ci iommufd_object_finalize(ucmd->ictx, &hwpt->obj); 16962306a36Sopenharmony_ci goto out_unlock; 17062306a36Sopenharmony_ci 17162306a36Sopenharmony_ciout_hwpt: 17262306a36Sopenharmony_ci iommufd_object_abort_and_destroy(ucmd->ictx, &hwpt->obj); 17362306a36Sopenharmony_ciout_unlock: 17462306a36Sopenharmony_ci mutex_unlock(&ioas->mutex); 17562306a36Sopenharmony_ci iommufd_put_object(&ioas->obj); 17662306a36Sopenharmony_ciout_put_idev: 17762306a36Sopenharmony_ci iommufd_put_object(&idev->obj); 17862306a36Sopenharmony_ci return rc; 17962306a36Sopenharmony_ci} 180