blob: 0336a4e659a1961604f3d6bf6be9a0dd313c8452 [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0
/*
* pKVM hyp driver for the Arm SMMUv3
*
* Copyright (C) 2022 Linaro Ltd.
*/
#include <asm/kvm_hyp.h>
#include <nvhe/iommu.h>
#include <nvhe/mem_protect.h>
#include <nvhe/trap_handler.h>
#include "arm_smmu_v3.h"
#include "../../../io-pgtable-arm.h"
#define ARM_SMMU_POLL_TIMEOUT_US 100000 /* 100ms arbitrary timeout */
size_t __ro_after_init kvm_hyp_arm_smmu_v3_count;
struct hyp_arm_smmu_v3_device *kvm_hyp_arm_smmu_v3_smmus;
#define for_each_smmu(smmu) \
for ((smmu) = kvm_hyp_arm_smmu_v3_smmus; \
(smmu) != &kvm_hyp_arm_smmu_v3_smmus[kvm_hyp_arm_smmu_v3_count]; \
(smmu)++)
/*
* Wait until @cond is true.
* Return 0 on success, or -ETIMEDOUT
*/
#define smmu_wait(_cond) \
({ \
int __ret = 0; \
u64 delay = pkvm_time_get() + ARM_SMMU_POLL_TIMEOUT_US; \
\
while (!(_cond)) { \
if (pkvm_time_get() >= delay) { \
__ret = -ETIMEDOUT; \
break; \
} \
} \
__ret; \
})
#define smmu_wait_event(_smmu, _cond) \
({ \
if ((_smmu)->features & ARM_SMMU_FEAT_SEV) { \
while (!(_cond)) \
wfe(); \
} \
smmu_wait(_cond); \
})
static struct io_pgtable *idmap_pgtable;
static int smmu_write_cr0(struct hyp_arm_smmu_v3_device *smmu, u32 val)
{
writel_relaxed(val, smmu->base + ARM_SMMU_CR0);
return smmu_wait(readl_relaxed(smmu->base + ARM_SMMU_CR0ACK) == val);
}
/* Transfer ownership of structures from host to hyp */
static int smmu_take_pages(u64 phys, size_t size)
{
WARN_ON(!PAGE_ALIGNED(phys) || !PAGE_ALIGNED(size));
return __pkvm_host_donate_hyp(phys >> PAGE_SHIFT, size >> PAGE_SHIFT);
}
static void smmu_reclaim_pages(u64 phys, size_t size)
{
WARN_ON(!PAGE_ALIGNED(phys) || !PAGE_ALIGNED(size));
WARN_ON(__pkvm_hyp_donate_host(phys >> PAGE_SHIFT, size >> PAGE_SHIFT));
}
static bool smmu_cmdq_full(struct arm_smmu_queue *cmdq)
{
struct arm_smmu_ll_queue *llq = &cmdq->llq;
WRITE_ONCE(llq->cons, readl_relaxed(cmdq->cons_reg));
return queue_full(llq);
}
static bool smmu_cmdq_empty(struct arm_smmu_queue *cmdq)
{
struct arm_smmu_ll_queue *llq = &cmdq->llq;
WRITE_ONCE(llq->cons, readl_relaxed(cmdq->cons_reg));
return queue_empty(llq);
}
static int smmu_add_cmd(struct hyp_arm_smmu_v3_device *smmu,
struct arm_smmu_cmdq_ent *ent)
{
int ret;
u64 cmd[CMDQ_ENT_DWORDS];
struct arm_smmu_queue *q = &smmu->cmdq;
struct arm_smmu_ll_queue *llq = &q->llq;
ret = smmu_wait_event(smmu, !smmu_cmdq_full(&smmu->cmdq));
if (ret)
return ret;
ret = arm_smmu_cmdq_build_cmd(cmd, ent);
if (ret)
return ret;
queue_write(Q_ENT(q, llq->prod), cmd, CMDQ_ENT_DWORDS);
llq->prod = queue_inc_prod_n(llq, 1);
writel_relaxed(llq->prod, q->prod_reg);
return 0;
}
static int smmu_sync_cmd(struct hyp_arm_smmu_v3_device *smmu)
{
int ret;
struct arm_smmu_cmdq_ent cmd = {
.opcode = CMDQ_OP_CMD_SYNC,
};
ret = smmu_add_cmd(smmu, &cmd);
if (ret)
return ret;
return smmu_wait_event(smmu, smmu_cmdq_empty(&smmu->cmdq));
}
static int smmu_send_cmd(struct hyp_arm_smmu_v3_device *smmu,
struct arm_smmu_cmdq_ent *cmd)
{
int ret;
if (smmu->power_is_off)
return 0;
ret = smmu_add_cmd(smmu, cmd);
if (ret)
return ret;
return smmu_sync_cmd(smmu);
}
static void __smmu_add_cmd(struct hyp_arm_smmu_v3_device *smmu, void *unused,
struct arm_smmu_cmdq_ent *cmd)
{
WARN_ON(smmu_add_cmd(smmu, cmd));
}
static int smmu_tlb_inv_range_smmu(struct hyp_arm_smmu_v3_device *smmu,
struct arm_smmu_cmdq_ent *cmd,
unsigned long iova, size_t size, size_t granule)
{
int ret;
hyp_spin_lock(&smmu->lock);
if (smmu->power_is_off) {
hyp_spin_unlock(&smmu->lock);
return 0;
}
arm_smmu_tlb_inv_build(cmd, iova, size, granule,
idmap_pgtable->cfg.pgsize_bitmap, smmu,
__smmu_add_cmd, NULL);
ret = smmu_sync_cmd(smmu);
hyp_spin_unlock(&smmu->lock);
return ret;
}
static void smmu_tlb_inv_range(unsigned long iova, size_t size, size_t granule,
bool leaf)
{
struct arm_smmu_cmdq_ent cmd = {
.opcode = CMDQ_OP_TLBI_S2_IPA,
.tlbi = {
.leaf = leaf,
.vmid = 0,
},
};
struct hyp_arm_smmu_v3_device *smmu;
for_each_smmu(smmu)
WARN_ON(smmu_tlb_inv_range_smmu(smmu, &cmd, iova, size, granule));
}
static void smmu_tlb_flush_walk(unsigned long iova, size_t size,
size_t granule, void *cookie)
{
smmu_tlb_inv_range(iova, size, granule, false);
}
static void smmu_tlb_add_page(struct iommu_iotlb_gather *gather,
unsigned long iova, size_t granule,
void *cookie)
{
smmu_tlb_inv_range(iova, granule, granule, true);
}
static const struct iommu_flush_ops smmu_tlb_ops = {
.tlb_flush_walk = smmu_tlb_flush_walk,
.tlb_add_page = smmu_tlb_add_page,
};
static int smmu_sync_ste(struct hyp_arm_smmu_v3_device *smmu, u32 sid, unsigned long ste)
{
struct arm_smmu_cmdq_ent cmd = {
.opcode = CMDQ_OP_CFGI_STE,
.cfgi.sid = sid,
.cfgi.leaf = true,
};
/*
* In case of 2 level STEs, L2 is allocated as cacheable, so flush it everytime
* we update the STE.
*/
if (!(smmu->features & ARM_SMMU_FEAT_COHERENCY) &&
(smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB))
kvm_flush_dcache_to_poc(ste, sizeof(struct arm_smmu_ste));
return smmu_send_cmd(smmu, &cmd);
}
static int smmu_alloc_l2_strtab(struct hyp_arm_smmu_v3_device *smmu, u32 sid)
{
struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
struct arm_smmu_strtab_l1 *l1_desc;
struct arm_smmu_strtab_l2 *l2table;
l1_desc = &cfg->l2.l1tab[arm_smmu_strtab_l1_idx(sid)];
if (l1_desc->l2ptr)
return 0;
l2table = kvm_iommu_donate_pages(get_order(sizeof(*l2table)));
if (!l2table)
return -ENOMEM;
arm_smmu_write_strtab_l1_desc(l1_desc, hyp_virt_to_phys(l2table));
if (!(smmu->features & ARM_SMMU_FEAT_COHERENCY))
kvm_flush_dcache_to_poc(l1_desc, sizeof(*l1_desc));
return 0;
}
static struct arm_smmu_ste *
smmu_get_ste_ptr(struct hyp_arm_smmu_v3_device *smmu, u32 sid)
{
struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) {
u32 l1_idx = arm_smmu_strtab_l1_idx(sid);
struct arm_smmu_strtab_l2 *l2ptr;
if (l1_idx >= cfg->l2.num_l1_ents)
return NULL;
l2ptr = hyp_phys_to_virt(cfg->l2.l1tab[l1_idx].l2ptr & STRTAB_L1_DESC_L2PTR_MASK);
/* Two-level walk */
return &l2ptr->stes[arm_smmu_strtab_l2_idx(sid)];
}
if (sid >= cfg->linear.num_ents)
return NULL;
/* Simple linear lookup */
return &cfg->linear.table[sid];
}
__maybe_unused
static struct arm_smmu_ste *
smmu_get_alloc_ste_ptr(struct hyp_arm_smmu_v3_device *smmu, u32 sid)
{
if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) {
int ret = smmu_alloc_l2_strtab(smmu, sid);
if (ret)
return NULL;
}
return smmu_get_ste_ptr(smmu, sid);
}
static int smmu_init_registers(struct hyp_arm_smmu_v3_device *smmu)
{
u64 val, old;
int ret;
if (!(readl_relaxed(smmu->base + ARM_SMMU_GBPA) & GBPA_ABORT))
return -EINVAL;
/* Initialize all RW registers that will be read by the SMMU */
ret = smmu_write_cr0(smmu, 0);
if (ret)
return ret;
val = FIELD_PREP(CR1_TABLE_SH, ARM_SMMU_SH_ISH) |
FIELD_PREP(CR1_TABLE_OC, CR1_CACHE_WB) |
FIELD_PREP(CR1_TABLE_IC, CR1_CACHE_WB) |
FIELD_PREP(CR1_QUEUE_SH, ARM_SMMU_SH_ISH) |
FIELD_PREP(CR1_QUEUE_OC, CR1_CACHE_WB) |
FIELD_PREP(CR1_QUEUE_IC, CR1_CACHE_WB);
writel_relaxed(val, smmu->base + ARM_SMMU_CR1);
writel_relaxed(CR2_PTM, smmu->base + ARM_SMMU_CR2);
val = readl_relaxed(smmu->base + ARM_SMMU_GERROR);
old = readl_relaxed(smmu->base + ARM_SMMU_GERRORN);
/* Service Failure Mode is fatal */
if ((val ^ old) & GERROR_SFM_ERR)
return -EIO;
/* Clear pending errors */
writel_relaxed(val, smmu->base + ARM_SMMU_GERRORN);
return 0;
}
/* Put the device in a state that can be probed by the host driver. */
static void smmu_deinit_device(struct hyp_arm_smmu_v3_device *smmu)
{
int i;
size_t nr_pages = smmu->mmio_size >> PAGE_SHIFT;
for (i = 0 ; i < nr_pages ; ++i) {
u64 pfn = (smmu->mmio_addr >> PAGE_SHIFT) + i;
WARN_ON(__pkvm_hyp_donate_host_mmio(pfn));
}
}
static int smmu_init_cmdq(struct hyp_arm_smmu_v3_device *smmu)
{
size_t cmdq_size;
int ret;
enum kvm_pgtable_prot prot = PAGE_HYP;
cmdq_size = (1 << (smmu->cmdq.llq.max_n_shift)) *
CMDQ_ENT_DWORDS * 8;
if (!(smmu->features & ARM_SMMU_FEAT_COHERENCY))
prot |= KVM_PGTABLE_PROT_NORMAL_NC;
ret = ___pkvm_host_donate_hyp(smmu->cmdq.base_dma >> PAGE_SHIFT,
PAGE_ALIGN(cmdq_size) >> PAGE_SHIFT, prot);
if (ret)
return ret;
smmu->cmdq.base = hyp_phys_to_virt(smmu->cmdq.base_dma);
smmu->cmdq.prod_reg = smmu->base + ARM_SMMU_CMDQ_PROD;
smmu->cmdq.cons_reg = smmu->base + ARM_SMMU_CMDQ_CONS;
memset(smmu->cmdq.base, 0, cmdq_size);
writel_relaxed(0, smmu->cmdq.prod_reg);
writel_relaxed(0, smmu->cmdq.cons_reg);
return 0;
}
static int smmu_init_strtab(struct hyp_arm_smmu_v3_device *smmu)
{
size_t strtab_size;
u64 strtab_base;
struct arm_smmu_strtab_cfg *cfg = &smmu->strtab_cfg;
enum kvm_pgtable_prot prot = PAGE_HYP;
if (!(smmu->features & ARM_SMMU_FEAT_COHERENCY))
prot |= KVM_PGTABLE_PROT_NORMAL_NC;
if (smmu->features & ARM_SMMU_FEAT_2_LVL_STRTAB) {
strtab_size = PAGE_ALIGN(cfg->l2.num_l1_ents * sizeof(struct arm_smmu_strtab_l1));
strtab_base = (u64)cfg->l2.l1_dma;
cfg->linear.table = hyp_phys_to_virt(strtab_base);
} else {
strtab_size = PAGE_ALIGN(cfg->linear.num_ents * sizeof(struct arm_smmu_ste));
strtab_base = (u64)cfg->linear.ste_dma;
cfg->l2.l1tab = hyp_phys_to_virt(strtab_base);
}
return ___pkvm_host_donate_hyp(hyp_phys_to_pfn(strtab_base),
strtab_size >> PAGE_SHIFT, prot);
}
static int smmu_reset_device(struct hyp_arm_smmu_v3_device *smmu)
{
int ret;
struct arm_smmu_cmdq_ent cfgi_cmd = {
.opcode = CMDQ_OP_CFGI_ALL,
};
struct arm_smmu_cmdq_ent tlbi_cmd = {
.opcode = CMDQ_OP_TLBI_NSNH_ALL,
};
/* Invalidate all cached configs and TLBs */
ret = smmu_write_cr0(smmu, CR0_CMDQEN);
if (ret)
return ret;
ret = smmu_add_cmd(smmu, &cfgi_cmd);
if (ret)
goto err_disable_cmdq;
ret = smmu_add_cmd(smmu, &tlbi_cmd);
if (ret)
goto err_disable_cmdq;
ret = smmu_sync_cmd(smmu);
if (ret)
goto err_disable_cmdq;
/* Enable translation */
return smmu_write_cr0(smmu, CR0_SMMUEN | CR0_CMDQEN | CR0_ATSCHK | CR0_EVTQEN);
err_disable_cmdq:
return smmu_write_cr0(smmu, 0);
}
static int smmu_init_evtq(struct hyp_arm_smmu_v3_device *smmu)
{
size_t evtq_size, evtq_nr_pages;
size_t i;
int ret;
evtq_size = (1 << (smmu->evtq.llq.max_n_shift)) *
EVTQ_ENT_DWORDS * 8;
evtq_nr_pages = PAGE_ALIGN(evtq_size) >> PAGE_SHIFT;
for (i = 0 ; i < evtq_nr_pages ; ++i) {
u64 evtq_pfn = hyp_phys_to_pfn(smmu->evtq.base_dma) + i;
/*
* Evtq is not accessed by hyp, but set in shared state
* to prevent donation/sharing it to VMs.
*/
ret = __pkvm_host_share_hyp(evtq_pfn);
if (ret)
return ret;
}
return hyp_pin_shared_mem(hyp_phys_to_virt(smmu->evtq.base_dma),
hyp_phys_to_virt(smmu->evtq.base_dma + evtq_size));
}
static int smmu_power_on(struct kvm_power_domain *pd)
{
/*
* We currently assume that the device retains its architectural state
* across power off, hence no save/restore.
*/
struct hyp_arm_smmu_v3_device *smmu = container_of(pd, struct hyp_arm_smmu_v3_device,
power_domain);
hyp_spin_lock(&smmu->lock);
smmu->power_is_off = false;
hyp_spin_unlock(&smmu->lock);
return 0;
}
static int smmu_power_off(struct kvm_power_domain *pd)
{
struct hyp_arm_smmu_v3_device *smmu = container_of(pd, struct hyp_arm_smmu_v3_device,
power_domain);
hyp_spin_lock(&smmu->lock);
smmu->power_is_off = true;
hyp_spin_unlock(&smmu->lock);
return 0;
}
static const struct kvm_power_domain_ops smmu_power_ops = {
.power_on = smmu_power_on,
.power_off = smmu_power_off,
};
static int smmu_init_device(struct hyp_arm_smmu_v3_device *smmu)
{
int i;
size_t nr_pages;
int ret;
if (!PAGE_ALIGNED(smmu->mmio_addr | smmu->mmio_size))
return -EINVAL;
nr_pages = smmu->mmio_size >> PAGE_SHIFT;
for (i = 0 ; i < nr_pages ; ++i) {
u64 pfn = (smmu->mmio_addr >> PAGE_SHIFT) + i;
/*
* This should never happen, so it's fine to be strict to avoid
* complicated error handling.
*/
WARN_ON(__pkvm_host_donate_hyp_mmio(pfn));
}
smmu->base = hyp_phys_to_virt(smmu->mmio_addr);
hyp_spin_lock_init(&smmu->lock);
BUILD_BUG_ON(sizeof(smmu->lock) != sizeof(hyp_spinlock_t));
ret = smmu_init_registers(smmu);
if (ret)
goto out_err;
ret = smmu_init_cmdq(smmu);
if (ret)
goto out_err;
ret = smmu_init_evtq(smmu);
if (ret)
goto out_err;
ret = smmu_init_strtab(smmu);
if (ret)
goto out_err;
ret = pkvm_init_power_domain(&smmu->power_domain, &smmu_power_ops);
if (ret)
goto out_err;
ret = smmu_reset_device(smmu);
if (ret)
goto out_err;
return ret;
out_err:
smmu_deinit_device(smmu);
return ret;
}
static struct hyp_arm_smmu_v3_device *smmu_id_to_ptr(pkvm_handle_t smmu_id)
{
if (smmu_id >= kvm_hyp_arm_smmu_v3_count)
return NULL;
smmu_id = array_index_nospec(smmu_id, kvm_hyp_arm_smmu_v3_count);
return &kvm_hyp_arm_smmu_v3_smmus[smmu_id];
}
static void smmu_init_s2_ste(struct arm_smmu_ste *ste)
{
struct io_pgtable_cfg *cfg;
u64 ts, sl, ic, oc, sh, tg, ps;
cfg = &idmap_pgtable->cfg;
ps = cfg->arm_lpae_s2_cfg.vtcr.ps;
tg = cfg->arm_lpae_s2_cfg.vtcr.tg;
sh = cfg->arm_lpae_s2_cfg.vtcr.sh;
oc = cfg->arm_lpae_s2_cfg.vtcr.orgn;
ic = cfg->arm_lpae_s2_cfg.vtcr.irgn;
sl = cfg->arm_lpae_s2_cfg.vtcr.sl;
ts = cfg->arm_lpae_s2_cfg.vtcr.tsz;
ste->data[0] = STRTAB_STE_0_V |
FIELD_PREP(STRTAB_STE_0_CFG, STRTAB_STE_0_CFG_S2_TRANS);
ste->data[1] = FIELD_PREP(STRTAB_STE_1_SHCFG, STRTAB_STE_1_SHCFG_INCOMING);
ste->data[2] = FIELD_PREP(STRTAB_STE_2_VTCR,
FIELD_PREP(STRTAB_STE_2_VTCR_S2PS, ps) |
FIELD_PREP(STRTAB_STE_2_VTCR_S2TG, tg) |
FIELD_PREP(STRTAB_STE_2_VTCR_S2SH0, sh) |
FIELD_PREP(STRTAB_STE_2_VTCR_S2OR0, oc) |
FIELD_PREP(STRTAB_STE_2_VTCR_S2IR0, ic) |
FIELD_PREP(STRTAB_STE_2_VTCR_S2SL0, sl) |
FIELD_PREP(STRTAB_STE_2_VTCR_S2T0SZ, ts)) |
FIELD_PREP(STRTAB_STE_2_S2VMID, 0) |
STRTAB_STE_2_S2AA64 | STRTAB_STE_2_S2R;
ste->data[3] = cfg->arm_lpae_s2_cfg.vttbr & STRTAB_STE_3_S2TTB_MASK;
}
static int smmu_enable_dev(pkvm_handle_t iommu, pkvm_handle_t dev)
{
static struct arm_smmu_ste *ste, target;
struct hyp_arm_smmu_v3_device *smmu = smmu_id_to_ptr(iommu);
int ret;
if (!smmu)
return -ENODEV;
hyp_spin_lock(&smmu->lock);
ste = smmu_get_alloc_ste_ptr(smmu, dev);
if (!ste) {
ret = - EINVAL;
goto out_ret;
}
smmu_init_s2_ste(&target);
WRITE_ONCE(ste->data[1], target.data[1]);
WRITE_ONCE(ste->data[2], target.data[2]);
WRITE_ONCE(ste->data[3], target.data[3]);
smmu_sync_ste(smmu, dev, (unsigned long)ste);
WRITE_ONCE(ste->data[0], target.data[0]);
ret = smmu_sync_ste(smmu, dev, (unsigned long)ste);
out_ret:
hyp_spin_unlock(&smmu->lock);
return ret;
}
static int smmu_disable_dev(pkvm_handle_t iommu, pkvm_handle_t dev)
{
static struct arm_smmu_ste *ste;
struct hyp_arm_smmu_v3_device *smmu = smmu_id_to_ptr(iommu);
int ret;
if (!smmu)
return -ENODEV;
hyp_spin_lock(&smmu->lock);
ste = smmu_get_alloc_ste_ptr(smmu, dev);
if (!ste) {
ret = -EINVAL;
goto out_ret;
}
WRITE_ONCE(ste->data[0], 0);
smmu_sync_ste(smmu, dev, (unsigned long)ste);
WRITE_ONCE(ste->data[1], 0);
WRITE_ONCE(ste->data[2], 0);
WRITE_ONCE(ste->data[3], 0);
ret = smmu_sync_ste(smmu, dev, (unsigned long)ste);
out_ret:
hyp_spin_unlock(&smmu->lock);
return ret;
}
static int smmu_init_pgt(void)
{
/* Default values overridden based on SMMUs common features. */
struct io_pgtable_cfg cfg = (struct io_pgtable_cfg) {
.tlb = &smmu_tlb_ops,
.pgsize_bitmap = -1,
.ias = 48,
.oas = 48,
.coherent_walk = true,
};
int ret = 0;
struct hyp_arm_smmu_v3_device *smmu;
for_each_smmu(smmu) {
cfg.ias = min(cfg.ias, smmu->ias);
cfg.oas = min(cfg.oas, smmu->oas);
cfg.pgsize_bitmap &= smmu->pgsize_bitmap;
cfg.coherent_walk &= !!(smmu->features & ARM_SMMU_FEAT_COHERENCY);
}
/* At least PAGE_SIZE must be supported by all SMMUs*/
if ((cfg.pgsize_bitmap & PAGE_SIZE) == 0)
return -EINVAL;
idmap_pgtable = kvm_arm_io_pgtable_alloc(&cfg, NULL, ARM_64_LPAE_S2, &ret);
return ret;
}
static int smmu_init(void)
{
int ret;
struct hyp_arm_smmu_v3_device *smmu;
size_t smmu_arr_size = PAGE_ALIGN(sizeof(*kvm_hyp_arm_smmu_v3_smmus) *
kvm_hyp_arm_smmu_v3_count);
phys_addr_t smmu_arr_phys;
kvm_hyp_arm_smmu_v3_smmus = kern_hyp_va(kvm_hyp_arm_smmu_v3_smmus);
smmu_arr_phys = hyp_virt_to_phys(kvm_hyp_arm_smmu_v3_smmus);
ret = smmu_take_pages(smmu_arr_phys, smmu_arr_size);
if (ret)
return ret;
for_each_smmu(smmu) {
ret = smmu_init_device(smmu);
if (ret)
goto out_reclaim_smmu;
}
return smmu_init_pgt();
out_reclaim_smmu:
while (smmu != kvm_hyp_arm_smmu_v3_smmus)
smmu_deinit_device(--smmu);
smmu_reclaim_pages(smmu_arr_phys, smmu_arr_size);
return ret;
}
static size_t smmu_pgsize_idmap(size_t size, u64 paddr, size_t pgsize_bitmap)
{
size_t pgsizes;
/* Remove page sizes that are larger than the current size */
pgsizes = pgsize_bitmap & GENMASK_ULL(__fls(size), 0);
/* Remove page sizes that the address is not aligned to. */
if (likely(paddr))
pgsizes &= GENMASK_ULL(__ffs(paddr), 0);
WARN_ON(!pgsizes);
/* Return the larget page size that fits. */
return BIT(__fls(pgsizes));
}
static void smmu_host_stage2_idmap(phys_addr_t start, phys_addr_t end, int prot)
{
size_t size = end - start;
size_t pgsize = PAGE_SIZE, pgcount;
size_t mapped, unmapped;
int ret;
struct io_pgtable *pgtable = idmap_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;
/*
* We handle pages size for memory and MMIO differently:
* - memory: Map everything with PAGE_SIZE, that is guaranteed to
* find memory as we allocated enough pages to cover the entire
* memory, we do that as io-pgtable-arm doesn't support
* split_blk_unmap logic any more, so we can't break blocks once
* mapped to tables.
* - MMIO: Unlike memory, pKVM allocate 1G to for all MMIO, while
* the MMIO space can be large, as it is assumed to cover the
* whole IAS that is not memory, we have to use block mappings,
* that is fine for MMIO as it is never donated at the moment,
* so we never need to unmap MMIO at the run time triggereing
* split block logic.
*/
if (prot & IOMMU_MMIO)
pgsize = smmu_pgsize_idmap(size, start, pgtable->cfg.pgsize_bitmap);
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 {
/* Shouldn't happen. */
WARN_ON(prot & IOMMU_MMIO);
while (size) {
pgcount = size / pgsize;
unmapped = pgtable->ops.unmap_pages(&pgtable->ops, start,
pgsize, pgcount, NULL);
size -= unmapped;
start += unmapped;
if (!unmapped)
return;
}
/* Some memory were not unmapped. */
WARN_ON(size);
}
}
static bool smmu_dabt_device(struct hyp_arm_smmu_v3_device *smmu,
struct user_pt_regs *regs,
u64 esr, u32 off)
{
bool is_write = esr & ESR_ELx_WNR;
unsigned int len = BIT((esr & ESR_ELx_SAS) >> ESR_ELx_SAS_SHIFT);
int rd = (esr & ESR_ELx_SRT_MASK) >> ESR_ELx_SRT_SHIFT;
const u32 no_access = 0;
const u32 read_write = (u32)(-1);
const u32 read_only = is_write ? no_access : read_write;
u32 mask = no_access;
/*
* Only handle MMIO access with u32 size and alignment.
* We don't need to change 64-bit registers for now.
*/
if ((len != sizeof(u32)) || (off & (sizeof(u32) - 1)))
return false;
switch (off) {
case ARM_SMMU_EVTQ_PROD + SZ_64K:
mask = read_write;
break;
case ARM_SMMU_EVTQ_CONS + SZ_64K:
mask = read_write;
break;
case ARM_SMMU_GERROR:
mask = read_only;
break;
case ARM_SMMU_GERRORN:
mask = read_write;
break;
};
if (!mask)
return false;
if (is_write)
writel_relaxed(regs->regs[rd] & mask, smmu->base + off);
else
regs->regs[rd] = readl_relaxed(smmu->base + off);
return true;
}
static bool smmu_dabt_handler(struct user_pt_regs *regs, u64 esr, u64 addr)
{
struct hyp_arm_smmu_v3_device *smmu;
for_each_smmu(smmu) {
if (addr < smmu->mmio_addr || addr >= smmu->mmio_addr + smmu->mmio_size)
continue;
return smmu_dabt_device(smmu, regs, esr, addr - smmu->mmio_addr);
}
return false;
}
/* Shared with the kernel driver in EL1 */
struct kvm_iommu_ops smmu_ops = {
.init = smmu_init,
.host_stage2_idmap = smmu_host_stage2_idmap,
.enable_dev = smmu_enable_dev,
.disable_dev = smmu_disable_dev,
.dabt_handler = smmu_dabt_handler,
};