BACKPORT: FROMLIST: KVM: arm64: iommu: Add map/unmap() operations

Handle map(), unmap() and iova_to_phys() hypercalls.

In addition to map/unmap, the hypervisor has to ensure that all
mapped pages are tracked, so be before each map()
__pkvm_host_use_dma() would be called to ensure that.

Similarly, on unmap() we need to decrement the refcount using
__pkvm_host_unuse_dma().

However, doing this in standard way as mentioned in the comments is
challenging, so we leave that to the driver.

Also, the hypervisor only guarantees that there are no races
between alloc/free domain operations using the domain refcount to
avoid using extra locks.

Link: https://lore.kernel.org/all/20241212180423.1578358-19-smostafa@google.com/

Bug: 357781595
Bug: 384432312

Change-Id: I69e3eb5d89692c31e7014a08414f80aba686844b
Signed-off-by: Mostafa Saleh <smostafa@google.com>
Signed-off-by: Jean-Philippe Brucker <jean-philippe@linaro.org>
This commit is contained in:
Mostafa Saleh
2024-12-12 18:03:42 +00:00
parent 14364a71d0
commit a88725cf54
2 changed files with 84 additions and 3 deletions
+7
View File
@@ -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);
+77 -3
View File
@@ -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 */