ANDROID: drivers/arm-smmu-v3-kvm: Support IOMMU_DOMAIN_IDENTITY

Since all the hyp bits are there, we can use IOMMU_DOMAIN_IDENTITY
with KVM_IOMMU_IDMAPPED_DOMAIN.

This can be discovered per device from device tree using flag
"iommu-idmapped"

Bug: 277989609
Bug: 278749606
Change-Id: Icf003b4c2789f6025be77db54a670a5d0888e603
Signed-off-by: Mostafa Saleh <smostafa@google.com>
This commit is contained in:
Mostafa Saleh
2024-03-10 10:24:35 +00:00
parent cc7bd0ce70
commit 03ce7b849c
4 changed files with 141 additions and 4 deletions
@@ -34,6 +34,7 @@ struct kvm_arm_smmu_master {
struct device *dev;
struct xarray domains;
u32 ssid_bits;
bool idmapped; /* Stage-2 is transparently identity mapped*/
};
struct kvm_arm_smmu_domain {
@@ -145,6 +146,7 @@ static struct iommu_device *kvm_arm_smmu_probe_device(struct device *dev)
device_property_read_u32(dev, "pasid-num-bits", &master->ssid_bits);
master->ssid_bits = min(smmu->ssid_bits, master->ssid_bits);
xa_init(&master->domains);
master->idmapped = device_property_read_bool(dev, "iommu-idmapped");
dev_iommu_priv_set(dev, master);
if (!device_link_add(dev, smmu->dev,
DL_FLAG_PM_RUNTIME |
@@ -164,10 +166,10 @@ static struct iommu_domain *kvm_arm_smmu_domain_alloc(unsigned type)
* We don't support
* - IOMMU_DOMAIN_DMA_FQ because lazy unmap would clash with memory
* donation to guests.
* - IOMMU_DOMAIN_IDENTITY: Requires a stage-2 only transparent domain.
*/
if (type != IOMMU_DOMAIN_DMA &&
type != IOMMU_DOMAIN_UNMANAGED)
type != IOMMU_DOMAIN_UNMANAGED &&
type != IOMMU_DOMAIN_IDENTITY)
return ERR_PTR(-EOPNOTSUPP);
kvm_smmu_domain = kzalloc(sizeof(*kvm_smmu_domain), GFP_KERNEL);
@@ -194,6 +196,18 @@ static int kvm_arm_smmu_domain_finalize(struct kvm_arm_smmu_domain *kvm_smmu_dom
if (kvm_smmu_domain->smmu)
return 0;
kvm_smmu_domain->smmu = smmu;
if (kvm_smmu_domain->domain.type == IOMMU_DOMAIN_IDENTITY) {
kvm_smmu_domain->id = KVM_IOMMU_DOMAIN_IDMAP_ID;
/*
* Identity domains doesn't use the DMA API, so no need to
* set the domain aperture.
*/
return 0;
}
/* Default to stage-1. */
if (smmu->features & ARM_SMMU_FEAT_TRANS_S1) {
ias = (smmu->features & ARM_SMMU_FEAT_VAX) ? 52 : 48;
@@ -250,7 +264,6 @@ static int kvm_arm_smmu_domain_finalize(struct kvm_arm_smmu_domain *kvm_smmu_dom
return ret;
}
kvm_smmu_domain->smmu = smmu;
return 0;
}
@@ -260,7 +273,7 @@ static void kvm_arm_smmu_domain_free(struct iommu_domain *domain)
struct kvm_arm_smmu_domain *kvm_smmu_domain = to_kvm_smmu_domain(domain);
struct arm_smmu_device *smmu = kvm_smmu_domain->smmu;
if (smmu) {
if (smmu && (kvm_smmu_domain->domain.type != IOMMU_DOMAIN_IDENTITY)) {
ret = kvm_call_hyp_nvhe(__pkvm_host_iommu_free_domain, kvm_smmu_domain->id);
ida_free(&kvm_arm_smmu_domain_ida, kvm_smmu_domain->id);
}
@@ -381,6 +394,15 @@ static int kvm_arm_smmu_attach_dev(struct iommu_domain *domain,
return kvm_arm_smmu_set_dev_pasid(domain, dev, 0);
}
static int kvm_arm_smmu_def_domain_type(struct device *dev)
{
struct kvm_arm_smmu_master *master = dev_iommu_priv_get(dev);
if (master->idmapped && atomic_pages)
return IOMMU_DOMAIN_IDENTITY;
return 0;
}
static bool kvm_arm_smmu_capable(struct device *dev, enum iommu_cap cap)
{
struct kvm_arm_smmu_master *master = dev_iommu_priv_get(dev);
@@ -474,6 +496,7 @@ static struct iommu_ops kvm_arm_smmu_ops = {
.pgsize_bitmap = -1UL,
.remove_dev_pasid = kvm_arm_smmu_remove_dev_pasid,
.owner = THIS_MODULE,
.def_domain_type = kvm_arm_smmu_def_domain_type,
.default_domain_ops = &(const struct iommu_domain_ops) {
.attach_dev = kvm_arm_smmu_attach_dev,
.free = kvm_arm_smmu_domain_free,
@@ -1042,6 +1065,15 @@ static int kvm_arm_smmu_v3_init_drv(void)
ret = kvm_iommu_init_hyp(ksym_ref_addr_nvhe(smmu_ops), &atomic_mc);
if (ret)
return ret;
/* Preemptively allocate the identity domain. */
if (atomic_pages) {
ret = kvm_call_hyp_nvhe_mc(__pkvm_host_iommu_alloc_domain,
KVM_IOMMU_DOMAIN_IDMAP_ID,
KVM_IOMMU_DOMAIN_IDMAP_TYPE);
if (ret)
return ret;
}
return kvm_arm_smmu_v3_post_init();
err_free:
@@ -43,6 +43,7 @@ extern const struct pkvm_module_ops *mod_ops;
#define __pkvm_host_unuse_dma(x, y) CALL_FROM_OPS(pkvm_host_unuse_dma, x, y)
#define kvm_iommu_donate_pages_atomic(x) CALL_FROM_OPS(iommu_donate_pages_atomic, x)
#define kvm_iommu_reclaim_pages_atomic(x, y) CALL_FROM_OPS(iommu_reclaim_pages_atomic, x, y)
#define kvm_iommu_snapshot_host_stage2(x) CALL_FROM_OPS(iommu_snapshot_host_stage2, x)
#endif
#endif /* __ARM_SMMU_V3_MODULE__ */
@@ -1069,6 +1069,7 @@ static int smmu_domain_finalise(struct hyp_arm_smmu_v3_device *smmu,
int ret;
struct io_pgtable_cfg cfg;
struct hyp_arm_smmu_v3_domain *smmu_domain = domain->priv;
struct arm_lpae_io_pgtable *data;
if (smmu_domain->type == KVM_ARM_SMMU_DOMAIN_S1) {
size_t ias = (smmu->features & ARM_SMMU_FEAT_VAX) ? 52 : 48;
@@ -1097,6 +1098,11 @@ static int smmu_domain_finalise(struct hyp_arm_smmu_v3_device *smmu,
hyp_spin_lock(&smmu_domain->pgt_lock);
smmu_domain->pgtable = kvm_arm_io_pgtable_alloc(&cfg, domain, &ret);
hyp_spin_unlock(&smmu_domain->pgt_lock);
if (ret)
return ret;
data = io_pgtable_to_data(smmu_domain->pgtable);
data->idmapped = (domain->domain_id == KVM_IOMMU_DOMAIN_IDMAP_ID);
return ret;
}
@@ -1194,6 +1200,7 @@ static int smmu_attach_dev(struct kvm_hyp_iommu *iommu, struct kvm_hyp_iommu_dom
struct hyp_arm_smmu_v3_device *smmu = to_smmu(iommu);
struct hyp_arm_smmu_v3_domain *smmu_domain = domain->priv;
struct domain_iommu_node *iommu_node = NULL;
bool init_idmap = false;
hyp_write_lock(&smmu_domain->list_lock);
kvm_iommu_lock(iommu);
@@ -1203,6 +1210,21 @@ static int smmu_attach_dev(struct kvm_hyp_iommu *iommu, struct kvm_hyp_iommu_dom
goto out_unlock;
}
/*
* BYPASS domains only supported on stage-2 instances, that is over restrictive
* but for now as stage-1 is limited to VA_BITS to match the kernel, it might
* not cover the ia bits, we don't support it.
*/
if (smmu_domain->type == KVM_ARM_SMMU_DOMAIN_BYPASS) {
if (smmu->features & ARM_SMMU_FEAT_TRANS_S2) {
smmu_domain->type = KVM_ARM_SMMU_DOMAIN_S2;
} else {
ret = -EINVAL;
goto out_unlock;
}
}
if (!smmu_existing_in_domain(smmu, smmu_domain)) {
if (!smmu_domain_compat(smmu, smmu_domain)) {
ret = -EBUSY;
@@ -1223,6 +1245,8 @@ static int smmu_attach_dev(struct kvm_hyp_iommu *iommu, struct kvm_hyp_iommu_dom
ret = smmu_domain_finalise(smmu, domain);
if (ret)
goto out_unlock;
if (domain->domain_id == KVM_IOMMU_DOMAIN_IDMAP_ID)
init_idmap = true;
}
if (smmu_domain->type == KVM_ARM_SMMU_DOMAIN_S2) {
@@ -1266,6 +1290,10 @@ out_unlock:
kvm_iommu_unlock(iommu);
hyp_write_unlock(&smmu_domain->list_lock);
if (init_idmap)
ret = kvm_iommu_snapshot_host_stage2(domain);
return ret;
}
@@ -1510,6 +1538,80 @@ int smmu_resume(struct kvm_hyp_iommu *iommu)
return 0;
}
/*
* Although SMMU can support multiple granules, it must at least support PAGE_SIZE
* as the CPU, and for the IDMAP domains, we only use this granule.
* As we optimize for memory usage and performance, we try to use block mappings
* when possible.
*/
static size_t smmu_pgsize_idmap(size_t size, u64 paddr)
{
size_t pgsizes;
size_t pgsize_bitmask = 0;
if (PAGE_SIZE == SZ_4K) {
pgsize_bitmask = SZ_4K | SZ_2M | SZ_1G;
} else if (PAGE_SIZE == SZ_16K) {
pgsize_bitmask = SZ_16K | SZ_32M;
} else if (PAGE_SIZE == SZ_64K){
pgsize_bitmask = SZ_64K | SZ_512M;
}
/* All page sizes that fit the size */
pgsizes = pgsize_bitmask & GENMASK_ULL(__fls(size), 0);
/* Address must be aligned to page size */
if (likely(paddr))
pgsizes &= GENMASK_ULL(__ffs(paddr), 0);
WARN_ON(!pgsizes);
return BIT(__fls(pgsizes));
}
static void smmu_host_stage2_idmap(struct kvm_hyp_iommu_domain *domain,
phys_addr_t start, phys_addr_t end, int prot)
{
size_t size = end - start;
size_t pgsize, pgcount;
size_t mapped, unmapped;
int ret;
struct hyp_arm_smmu_v3_domain *smmu_domain = domain->priv;
struct io_pgtable *pgtable = smmu_domain->pgtable;
end = min(end, BIT(pgtable->cfg.oas));
if (start >= end)
return;
if (prot) {
if (!(prot & IOMMU_MMIO))
prot |= IOMMU_CACHE;
while (size) {
mapped = 0;
pgsize = smmu_pgsize_idmap(size, start);
pgcount = size / pgsize;
ret = pgtable->ops.map_pages(&pgtable->ops, start, start,
pgsize, pgcount, prot, 0, &mapped);
size -= mapped;
start += mapped;
if (!mapped || ret)
return;
}
} else {
while (size) {
pgsize = smmu_pgsize_idmap(size, start);
pgcount = size / pgsize;
unmapped = pgtable->ops.unmap_pages(&pgtable->ops, start,
pgsize, pgcount, NULL);
size -= unmapped;
start += unmapped;
if (!unmapped)
return;
}
}
}
#ifdef MODULE
int smmu_init_hyp_module(const struct pkvm_module_ops *ops)
{
@@ -1536,4 +1638,5 @@ struct kvm_iommu_ops smmu_ops = {
.dabt_handler = smmu_dabt_handler,
.suspend = smmu_suspend,
.resume = smmu_resume,
.host_stage2_idmap = smmu_host_stage2_idmap,
};
@@ -38,6 +38,7 @@ extern struct hyp_arm_smmu_v3_device *kvm_nvhe_sym(kvm_hyp_arm_smmu_v3_smmus);
#define kvm_hyp_arm_smmu_v3_smmus kvm_nvhe_sym(kvm_hyp_arm_smmu_v3_smmus)
enum kvm_arm_smmu_domain_type {
KVM_ARM_SMMU_DOMAIN_BYPASS = KVM_IOMMU_DOMAIN_IDMAP_TYPE,
KVM_ARM_SMMU_DOMAIN_S1,
KVM_ARM_SMMU_DOMAIN_S2,
KVM_ARM_SMMU_DOMAIN_MAX,