From 770ec56a176b80e8f940202168dc7a4cab423c77 Mon Sep 17 00:00:00 2001 From: David Brazdil Date: Mon, 14 Feb 2022 20:58:22 +0000 Subject: [PATCH] ANDROID: KVM: arm64: iommu: Suspend/resume callbacks Add suspend/resume callbacks for IOMMU devices. The EL1 kernel driver is expected to call these when the IOMMU device is powered on but is about to be used or about to stop being used. pkvm_iommu_suspend/resume are exported for use by kernel modules. Bug: 190463801 Change-Id: Ia4ab37fe96879d451ce82f4278b3ff33a0b9685b Signed-off-by: David Brazdil (cherry picked from commit ca47ae70c77f327be95d8fb6930caa143bb200c9) Signed-off-by: Mostafa Saleh --- arch/arm64/include/asm/kvm_asm.h | 1 + arch/arm64/include/asm/kvm_host.h | 7 +++++ arch/arm64/kvm/hyp/include/nvhe/iommu.h | 9 ++++++ arch/arm64/kvm/hyp/nvhe/hyp-main.c | 9 ++++++ arch/arm64/kvm/hyp/nvhe/iommu.c | 37 +++++++++++++++++++++++++ arch/arm64/kvm/iommu.c | 14 ++++++++++ 6 files changed, 77 insertions(+) diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h index 4a525a9961d0..5868c6a64f99 100644 --- a/arch/arm64/include/asm/kvm_asm.h +++ b/arch/arm64/include/asm/kvm_asm.h @@ -83,6 +83,7 @@ enum __kvm_host_smccc_func { __KVM_HOST_SMCCC_FUNC___pkvm_vcpu_sync_state, __KVM_HOST_SMCCC_FUNC___pkvm_iommu_driver_init, __KVM_HOST_SMCCC_FUNC___pkvm_iommu_register, + __KVM_HOST_SMCCC_FUNC___pkvm_iommu_pm_notify, }; #define DECLARE_KVM_VHE_SYM(sym) extern char sym[] diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h index 5effda685562..1e7da0b6a329 100644 --- a/arch/arm64/include/asm/kvm_host.h +++ b/arch/arm64/include/asm/kvm_host.h @@ -397,9 +397,16 @@ enum pkvm_iommu_driver_id { PKVM_IOMMU_NR_DRIVERS, }; +enum pkvm_iommu_pm_event { + PKVM_IOMMU_PM_SUSPEND, + PKVM_IOMMU_PM_RESUME, +}; + int pkvm_iommu_driver_init(enum pkvm_iommu_driver_id drv_id, void *data, size_t size); int pkvm_iommu_register(struct device *dev, enum pkvm_iommu_driver_id drv_id, phys_addr_t pa, size_t size); +int pkvm_iommu_suspend(struct device *dev); +int pkvm_iommu_resume(struct device *dev); struct vcpu_reset_state { unsigned long pc; diff --git a/arch/arm64/kvm/hyp/include/nvhe/iommu.h b/arch/arm64/kvm/hyp/include/nvhe/iommu.h index 51935ab93efa..452d5de0f215 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/iommu.h +++ b/arch/arm64/kvm/hyp/include/nvhe/iommu.h @@ -7,6 +7,8 @@ #include +struct pkvm_iommu; + struct pkvm_iommu_ops { /* * Global driver initialization called before devices are registered. @@ -23,6 +25,10 @@ struct pkvm_iommu_ops { */ int (*validate)(phys_addr_t base, size_t size); + /* Power management callbacks. Called with host lock held. */ + int (*suspend)(struct pkvm_iommu *dev); + int (*resume)(struct pkvm_iommu *dev); + /* Amount of memory allocated per-device for use by the driver. */ size_t data_size; }; @@ -34,6 +40,7 @@ struct pkvm_iommu { phys_addr_t pa; void *va; size_t size; + bool powered; char data[]; }; @@ -42,6 +49,8 @@ int __pkvm_iommu_register(unsigned long dev_id, enum pkvm_iommu_driver_id drv_id, phys_addr_t dev_pa, size_t dev_size, void *kern_mem_va, size_t mem_size); +int __pkvm_iommu_pm_notify(unsigned long dev_id, + enum pkvm_iommu_pm_event event); int pkvm_iommu_host_stage2_adjust_range(phys_addr_t addr, phys_addr_t *start, phys_addr_t *end); diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c index c9acd573b77a..d597ba27ee38 100644 --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c @@ -1136,6 +1136,14 @@ static void handle___pkvm_iommu_register(struct kvm_cpu_context *host_ctxt) dev_size, mem, mem_size); } +static void handle___pkvm_iommu_pm_notify(struct kvm_cpu_context *host_ctxt) +{ + DECLARE_REG(unsigned long, dev_id, host_ctxt, 1); + DECLARE_REG(enum pkvm_iommu_pm_event, event, host_ctxt, 2); + + cpu_reg(host_ctxt, 1) = __pkvm_iommu_pm_notify(dev_id, event); +} + typedef void (*hcall_t)(struct kvm_cpu_context *); #define HANDLE_FUNC(x) [__KVM_HOST_SMCCC_FUNC_##x] = (hcall_t)handle_##x @@ -1172,6 +1180,7 @@ static const hcall_t host_hcall[] = { HANDLE_FUNC(__pkvm_vcpu_sync_state), HANDLE_FUNC(__pkvm_iommu_driver_init), HANDLE_FUNC(__pkvm_iommu_register), + HANDLE_FUNC(__pkvm_iommu_pm_notify), }; static void handle_host_hcall(struct kvm_cpu_context *host_ctxt) diff --git a/arch/arm64/kvm/hyp/nvhe/iommu.c b/arch/arm64/kvm/hyp/nvhe/iommu.c index f2132c381766..3ad158369413 100644 --- a/arch/arm64/kvm/hyp/nvhe/iommu.c +++ b/arch/arm64/kvm/hyp/nvhe/iommu.c @@ -166,6 +166,19 @@ static bool validate_against_existing_iommus(struct pkvm_iommu *dev) return true; } +static struct pkvm_iommu *find_iommu_by_id(unsigned long id) +{ + struct pkvm_iommu *dev; + + assert_host_component_locked(); + + list_for_each_entry(dev, &iommu_list, list) { + if (dev->id == id) + return dev; + } + return NULL; +} + /* * Initialize EL2 IOMMU driver. * @@ -289,6 +302,30 @@ out: return ret; } +int __pkvm_iommu_pm_notify(unsigned long dev_id, enum pkvm_iommu_pm_event event) +{ + struct pkvm_iommu *dev; + int ret; + + host_lock_component(); + dev = find_iommu_by_id(dev_id); + if (dev) { + if (event == PKVM_IOMMU_PM_SUSPEND) { + ret = dev->ops->suspend ? dev->ops->suspend(dev) : 0; + dev->powered = !!ret; + } else if (event == PKVM_IOMMU_PM_RESUME) { + ret = dev->ops->resume ? dev->ops->resume(dev) : 0; + dev->powered = !ret; + } else { + ret = -EINVAL; + } + } else { + ret = -ENODEV; + } + host_unlock_component(); + return ret; +} + /* * Check host memory access against IOMMUs' MMIO regions. * Returns -EPERM if the address is within the bounds of a registered device. diff --git a/arch/arm64/kvm/iommu.c b/arch/arm64/kvm/iommu.c index a845be0c8fa9..e13d36ec57e7 100644 --- a/arch/arm64/kvm/iommu.c +++ b/arch/arm64/kvm/iommu.c @@ -40,3 +40,17 @@ int pkvm_iommu_register(struct device *dev, enum pkvm_iommu_driver_id drv_id, } return ret; } + +int pkvm_iommu_suspend(struct device *dev) +{ + return kvm_call_hyp_nvhe(__pkvm_iommu_pm_notify, dev_to_id(dev), + PKVM_IOMMU_PM_SUSPEND); +} +EXPORT_SYMBOL_GPL(pkvm_iommu_suspend); + +int pkvm_iommu_resume(struct device *dev) +{ + return kvm_call_hyp_nvhe(__pkvm_iommu_pm_notify, dev_to_id(dev), + PKVM_IOMMU_PM_RESUME); +} +EXPORT_SYMBOL_GPL(pkvm_iommu_resume);