From b414161ca3041c41f52e81da66f2a204d5e118f1 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Brucker Date: Thu, 12 Dec 2024 18:03:50 +0000 Subject: [PATCH] BACKPORT: FROMLIST: KVM: arm64: smmu-v3: Initialize registers Ensure all writable registers are properly initialized. We do not touch registers that will not be read by the SMMU due to disabled features. Link: https://lore.kernel.org/all/20241212180423.1578358-27-smostafa@google.com/ Bug: 357781595 Bug: 384432312 Change-Id: I2f8ed283765c3b9fe577239be96b84164a6a23f1 Signed-off-by: Jean-Philippe Brucker Signed-off-by: Mostafa Saleh --- arch/arm64/kvm/hyp/nvhe/iommu/arm-smmu-v3.c | 130 +++++++++++++++++++- include/kvm/arm_smmu_v3.h | 11 ++ 2 files changed, 140 insertions(+), 1 deletion(-) diff --git a/arch/arm64/kvm/hyp/nvhe/iommu/arm-smmu-v3.c b/arch/arm64/kvm/hyp/nvhe/iommu/arm-smmu-v3.c index d2a570c9f3ec..f7e60c188cb0 100644 --- a/arch/arm64/kvm/hyp/nvhe/iommu/arm-smmu-v3.c +++ b/arch/arm64/kvm/hyp/nvhe/iommu/arm-smmu-v3.c @@ -4,16 +4,144 @@ * * Copyright (C) 2022 Linaro Ltd. */ +#include #include #include #include +#include +#include +#include + +#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 __i = 0; \ + int __ret = 0; \ + \ + while (!(_cond)) { \ + if (++__i > ARM_SMMU_POLL_TIMEOUT_US) { \ + __ret = -ETIMEDOUT; \ + break; \ + } \ + pkvm_udelay(1); \ + } \ + __ret; \ +}) + +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 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); + writel_relaxed(0, smmu->base + ARM_SMMU_IRQ_CTRL); + + 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; +} + +static int smmu_init_device(struct hyp_arm_smmu_v3_device *smmu) +{ + int ret; + + if (!PAGE_ALIGNED(smmu->mmio_addr | smmu->mmio_size)) + return -EINVAL; + + ret = ___pkvm_host_donate_hyp(smmu->mmio_addr >> PAGE_SHIFT, + smmu->mmio_size >> PAGE_SHIFT, + /* accept_mmio */ true); + if (ret) + return ret; + + smmu->base = hyp_phys_to_virt(smmu->mmio_addr); + + ret = smmu_init_registers(smmu); + if (ret) + return ret; + + return kvm_iommu_init_device(&smmu->iommu); +} + static int smmu_init(void) { - return -ENOSYS; + 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 0; +out_reclaim_smmu: + smmu_reclaim_pages(smmu_arr_phys, smmu_arr_size); + return ret; } /* Shared with the kernel driver in EL1 */ diff --git a/include/kvm/arm_smmu_v3.h b/include/kvm/arm_smmu_v3.h index 521028b3ff71..fb24bcef1624 100644 --- a/include/kvm/arm_smmu_v3.h +++ b/include/kvm/arm_smmu_v3.h @@ -5,8 +5,19 @@ #include #include +/* + * Parameters from the trusted host: + * @mmio_addr base address of the SMMU registers + * @mmio_size size of the registers resource + * + * Other members are filled and used at runtime by the SMMU driver. + */ struct hyp_arm_smmu_v3_device { struct kvm_hyp_iommu iommu; + phys_addr_t mmio_addr; + size_t mmio_size; + + void __iomem *base; }; extern size_t kvm_nvhe_sym(kvm_hyp_arm_smmu_v3_count);