diff --git a/arch/arm64/kvm/hyp/include/nvhe/iommu.h b/arch/arm64/kvm/hyp/include/nvhe/iommu.h index d6d7447fbac8..17f24a8eb1b9 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/iommu.h +++ b/arch/arm64/kvm/hyp/include/nvhe/iommu.h @@ -40,6 +40,13 @@ struct kvm_iommu_ops { u32 endpoint_id, u32 pasid, u32 pasid_bits); int (*detach_dev)(struct kvm_hyp_iommu *iommu, struct kvm_hyp_iommu_domain *domain, u32 endpoint_id, u32 pasid); + int (*map_pages)(struct kvm_hyp_iommu_domain *domain, unsigned long iova, + phys_addr_t paddr, size_t pgsize, + size_t pgcount, int prot, size_t *total_mapped); + size_t (*unmap_pages)(struct kvm_hyp_iommu_domain *domain, unsigned long iova, + size_t pgsize, size_t pgcount); + phys_addr_t (*iova_to_phys)(struct kvm_hyp_iommu_domain *domain, unsigned long iova); + }; int kvm_iommu_init(void); diff --git a/arch/arm64/kvm/hyp/nvhe/iommu/iommu.c b/arch/arm64/kvm/hyp/nvhe/iommu/iommu.c index df2dbe4c0121..83321cc5f466 100644 --- a/arch/arm64/kvm/hyp/nvhe/iommu/iommu.c +++ b/arch/arm64/kvm/hyp/nvhe/iommu/iommu.c @@ -263,22 +263,96 @@ int kvm_iommu_detach_dev(pkvm_handle_t iommu_id, pkvm_handle_t domain_id, return ret; } +#define IOMMU_PROT_MASK (IOMMU_READ | IOMMU_WRITE | IOMMU_CACHE |\ + IOMMU_NOEXEC | IOMMU_MMIO | IOMMU_PRIV) + size_t kvm_iommu_map_pages(pkvm_handle_t domain_id, unsigned long iova, phys_addr_t paddr, size_t pgsize, size_t pgcount, int prot) { - return 0; + size_t size; + int ret; + size_t total_mapped = 0; + struct kvm_hyp_iommu_domain *domain; + + if (prot & ~IOMMU_PROT_MASK) + return 0; + + if (__builtin_mul_overflow(pgsize, pgcount, &size) || + iova + size < iova || paddr + size < paddr) + return 0; + + domain = handle_to_domain(domain_id); + if (!domain || domain_get(domain)) + return 0; + + ret = __pkvm_host_use_dma(paddr, size); + if (ret) + return 0; + + kvm_iommu_ops->map_pages(domain, iova, paddr, pgsize, pgcount, prot, &total_mapped); + + pgcount -= total_mapped / pgsize; + /* + * unuse the bits that haven't been mapped yet. The host calls back + * either to continue mapping, or to unmap and unuse what's been done + * so far. + */ + if (pgcount) + __pkvm_host_unuse_dma(paddr + total_mapped, pgcount * pgsize); + + domain_put(domain); + return total_mapped; } size_t kvm_iommu_unmap_pages(pkvm_handle_t domain_id, unsigned long iova, size_t pgsize, size_t pgcount) { - return 0; + size_t size; + size_t unmapped; + struct kvm_hyp_iommu_domain *domain; + + if (!pgsize || !pgcount) + return 0; + + if (__builtin_mul_overflow(pgsize, pgcount, &size) || + iova + size < iova) + return 0; + + domain = handle_to_domain(domain_id); + if (!domain || domain_get(domain)) + return 0; + + /* + * Unlike map, the common code doesn't call the __pkvm_host_unuse_dma, + * because this means that we need either walk the table using iova_to_phys + * similar to VFIO then unmap and call this function, or unmap leaf (page or + * block) at a time, where both might be suboptimal. + * For some IOMMU, we can do 2 walks where one only invalidate the pages + * and the other decrement the refcount. + * As, semantics for this might differ between IOMMUs and it's hard to + * standardized, we leave that to the driver. + */ + unmapped = kvm_iommu_ops->unmap_pages(domain, iova, pgsize, + pgcount); + + domain_put(domain); + return unmapped; } phys_addr_t kvm_iommu_iova_to_phys(pkvm_handle_t domain_id, unsigned long iova) { - return 0; + phys_addr_t phys = 0; + struct kvm_hyp_iommu_domain *domain; + + domain = handle_to_domain( domain_id); + + if (!domain || domain_get(domain)) + return 0; + + phys = kvm_iommu_ops->iova_to_phys(domain, iova); + domain_put(domain); + return phys; } /* Must be called from the IOMMU driver per IOMMU */