diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h index 4b7d10f95ee4..5bbeef180f97 100644 --- a/arch/arm64/include/asm/kvm_asm.h +++ b/arch/arm64/include/asm/kvm_asm.h @@ -114,6 +114,7 @@ enum __kvm_host_smccc_func { __KVM_HOST_SMCCC_FUNC___pkvm_host_iommu_map_pages, __KVM_HOST_SMCCC_FUNC___pkvm_host_iommu_unmap_pages, __KVM_HOST_SMCCC_FUNC___pkvm_host_iommu_iova_to_phys, + __KVM_HOST_SMCCC_FUNC___pkvm_host_hvc_pd, /* * Start of the dynamically registered hypercalls. Start a bit diff --git a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h index 8a5554615e40..e4a94696b10e 100644 --- a/arch/arm64/kvm/hyp/include/nvhe/pkvm.h +++ b/arch/arm64/kvm/hyp/include/nvhe/pkvm.h @@ -8,6 +8,7 @@ #define __ARM64_KVM_NVHE_PKVM_H__ #include +#include #include #include @@ -146,4 +147,33 @@ void pkvm_poison_pvmfw_pages(void); int pkvm_timer_init(void); void pkvm_udelay(unsigned long usecs); +#define MAX_POWER_DOMAINS 32 + +struct kvm_power_domain_ops { + int (*power_on)(struct kvm_power_domain *pd); + int (*power_off)(struct kvm_power_domain *pd); +}; + +int pkvm_init_hvc_pd(struct kvm_power_domain *pd, + const struct kvm_power_domain_ops *ops); + +int pkvm_host_hvc_pd(u64 device_id, u64 on); + +/* + * Register a power domain. When the hypervisor catches power requests from the + * host for this power domain, it calls the power ops with @pd as argument. + */ +static inline int pkvm_init_power_domain(struct kvm_power_domain *pd, + const struct kvm_power_domain_ops *ops) +{ + switch (pd->type) { + case KVM_POWER_DOMAIN_NONE: + return 0; + case KVM_POWER_DOMAIN_HOST_HVC: + return pkvm_init_hvc_pd(pd, ops); + default: + return -EOPNOTSUPP; + } +} + #endif /* __ARM64_KVM_NVHE_PKVM_H__ */ diff --git a/arch/arm64/kvm/hyp/nvhe/Makefile b/arch/arm64/kvm/hyp/nvhe/Makefile index 9e1b74c661d2..950d34ba6e50 100644 --- a/arch/arm64/kvm/hyp/nvhe/Makefile +++ b/arch/arm64/kvm/hyp/nvhe/Makefile @@ -8,7 +8,7 @@ CFLAGS_switch.nvhe.o += -Wno-override-init hyp-obj-y := timer-sr.o sysreg-sr.o debug-sr.o switch.o tlb.o hyp-init.o host.o \ hyp-main.o hyp-smp.o psci-relay.o alloc.o early_alloc.o page_alloc.o \ cache.o setup.o mm.o mem_protect.o sys_regs.o pkvm.o stacktrace.o ffa.o \ - serial.o alloc_mgt.o iommu/iommu.o + serial.o alloc_mgt.o iommu/iommu.o power/hvc.o hyp-obj-y += ../vgic-v3-sr.o ../aarch32.o ../vgic-v2-cpuif-proxy.o ../entry.o \ ../fpsimd.o ../hyp-entry.o ../exception.o ../pgtable.o hyp-obj-$(CONFIG_LIST_HARDENED) += list_debug.o diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c index 919bd6233baf..7daae75dce8d 100644 --- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c +++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c @@ -1682,6 +1682,14 @@ static void handle___pkvm_host_iommu_iova_to_phys(struct kvm_cpu_context *host_c cpu_reg(host_ctxt, 1) = kvm_iommu_iova_to_phys(domain, iova); } +static void handle___pkvm_host_hvc_pd(struct kvm_cpu_context *host_ctxt) +{ + DECLARE_REG(u64, device_id, host_ctxt, 1); + DECLARE_REG(u64, on, host_ctxt, 2); + + cpu_reg(host_ctxt, 1) = pkvm_host_hvc_pd(device_id, on); +} + typedef void (*hcall_t)(struct kvm_cpu_context *); #define HANDLE_FUNC(x) [__KVM_HOST_SMCCC_FUNC_##x] = (hcall_t)handle_##x @@ -1746,6 +1754,7 @@ static const hcall_t host_hcall[] = { HANDLE_FUNC(__pkvm_host_iommu_map_pages), HANDLE_FUNC(__pkvm_host_iommu_unmap_pages), HANDLE_FUNC(__pkvm_host_iommu_iova_to_phys), + HANDLE_FUNC(__pkvm_host_hvc_pd), }; static void handle_host_hcall(struct kvm_cpu_context *host_ctxt) diff --git a/arch/arm64/kvm/hyp/nvhe/power/hvc.c b/arch/arm64/kvm/hyp/nvhe/power/hvc.c new file mode 100644 index 000000000000..f4d811847e73 --- /dev/null +++ b/arch/arm64/kvm/hyp/nvhe/power/hvc.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2023 Google LLC + * Author: Mostafa Saleh + */ + +#include + +struct hvc_power_domain { + struct kvm_power_domain *pd; + const struct kvm_power_domain_ops *ops; +}; + +struct hvc_power_domain handlers[MAX_POWER_DOMAINS]; + +int pkvm_init_hvc_pd(struct kvm_power_domain *pd, + const struct kvm_power_domain_ops *ops) +{ + if (pd->device_id >= MAX_POWER_DOMAINS) + return -E2BIG; + + handlers[pd->device_id].ops = ops; + handlers[pd->device_id].pd = pd; + + return 0; +} + +int pkvm_host_hvc_pd(u64 device_id, u64 on) +{ + struct hvc_power_domain *pd; + + if (device_id >= MAX_POWER_DOMAINS) + return -E2BIG; + + device_id = array_index_nospec(device_id, MAX_POWER_DOMAINS); + pd = &handlers[device_id]; + + if (!pd->ops) + return -ENOENT; + + if (on) + pd->ops->power_on(pd->pd); + else + pd->ops->power_off(pd->pd); + + return 0; +} diff --git a/include/kvm/power_domain.h b/include/kvm/power_domain.h new file mode 100644 index 000000000000..f6a9c5cdfebb --- /dev/null +++ b/include/kvm/power_domain.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __KVM_POWER_DOMAIN_H +#define __KVM_POWER_DOMAIN_H + +enum kvm_power_domain_type { + KVM_POWER_DOMAIN_NONE, + KVM_POWER_DOMAIN_HOST_HVC, +}; + +struct kvm_power_domain { + enum kvm_power_domain_type type; + union { + u64 device_id; /* HOST_HVC device ID*/ + }; +}; + +#endif /* __KVM_POWER_DOMAIN_H */