diff --git a/xen/arch/arm/domain.c b/xen/arch/arm/domain.c index 94b9858ad2..581f82bddd 100644 --- a/xen/arch/arm/domain.c +++ b/xen/arch/arm/domain.c @@ -12,6 +12,8 @@ #include #include +#include + #include #include #include @@ -24,10 +26,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include "vpci.h" @@ -770,8 +774,18 @@ int arch_domain_teardown(struct domain *d) return 0; } +static void resume_ctx_reset(struct resume_info *ctx) +{ + if ( ctx->ctxt ) + free_vcpu_guest_context(ctx->ctxt); + + memset(ctx, 0, sizeof(*ctx)); +} + void arch_domain_destroy(struct domain *d) { + resume_ctx_reset(&d->arch.resume_ctx); + tee_free_domain_ctx(d); /* IOMMU page table is shared with P2M, always call * iommu_domain_destroy() before p2m_final_teardown(). @@ -806,6 +820,28 @@ void arch_domain_creation_finished(struct domain *d) p2m_domain_creation_finished(d); } +void arch_domain_resume(struct domain *d) +{ + struct resume_info *ctx = &d->arch.resume_ctx; + + /* + * It is still possible to call domain_shutdown() with a suspend reason + * via some hypercalls, such as SCHEDOP_shutdown or SCHEDOP_remote_shutdown. + * In these cases, the resume context will be empty, so there is no + * suspend-specific state to restore. + */ + if ( !ctx->wake_cpu ) + return; + + ASSERT(d->shutdown_code == SHUTDOWN_suspend); + + domain_lock(d); + arch_vcpu_apply_guest_context(ctx->wake_cpu, ctx->ctxt); + domain_unlock(d); + + resume_ctx_reset(ctx); +} + static int is_guest_pv32_psr(uint32_t psr) { switch (psr & PSR_MODE_MASK) @@ -848,15 +884,32 @@ static int is_guest_pv64_psr(uint64_t psr) } #endif +void arch_vcpu_apply_guest_context(struct vcpu *v, + const struct vcpu_guest_context *ctxt) +{ + vcpu_regs_user_to_hyp(v, &ctxt->user_regs); + + v->arch.sctlr = ctxt->sctlr; + v->arch.ttbr0 = ctxt->ttbr0; + v->arch.ttbr1 = ctxt->ttbr1; + v->arch.ttbcr = ctxt->ttbcr; + + v->is_initialised = 1; + + if ( ctxt->flags & VGCF_online ) + clear_bit(_VPF_down, &v->pause_flags); + else + set_bit(_VPF_down, &v->pause_flags); +} + /* * Initialise vCPU state. The context may be supplied by an external entity, so * we need to validate it. */ -int arch_set_info_guest( - struct vcpu *v, vcpu_guest_context_u c) +int arch_vcpu_validate_guest_context(const struct vcpu *v, + const struct vcpu_guest_context *ctxt) { - struct vcpu_guest_context *ctxt = c.nat; - struct vcpu_guest_core_regs *regs = &c.nat->user_regs; + const struct vcpu_guest_core_regs *regs = &ctxt->user_regs; if ( is_32bit_domain(v->domain) ) { @@ -885,19 +938,24 @@ int arch_set_info_guest( } #endif - vcpu_regs_user_to_hyp(v, regs); + return 0; +} - v->arch.sctlr = ctxt->sctlr; - v->arch.ttbr0 = ctxt->ttbr0; - v->arch.ttbr1 = ctxt->ttbr1; - v->arch.ttbcr = ctxt->ttbcr; +/* + * Initialise vCPU state. The context may be supplied by an external entity, so + * we need to validate it. + */ +int arch_set_info_guest( + struct vcpu *v, vcpu_guest_context_u c) +{ + struct vcpu_guest_context *ctxt = c.nat; + int rc; - v->is_initialised = 1; + rc = arch_vcpu_validate_guest_context(v, ctxt); + if ( rc ) + return rc; - if ( ctxt->flags & VGCF_online ) - clear_bit(_VPF_down, &v->pause_flags); - else - set_bit(_VPF_down, &v->pause_flags); + arch_vcpu_apply_guest_context(v, ctxt); return 0; } diff --git a/xen/arch/arm/include/asm/domain.h b/xen/arch/arm/include/asm/domain.h index 758ad807e4..ffe5d0d9f0 100644 --- a/xen/arch/arm/include/asm/domain.h +++ b/xen/arch/arm/include/asm/domain.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -126,6 +127,7 @@ struct arch_domain void *sci_data; #endif + struct resume_info resume_ctx; } __cacheline_aligned; struct arch_vcpu @@ -290,6 +292,11 @@ static inline register_t vcpuid_to_vaffinity(unsigned int vcpuid) return vaff; } +int arch_vcpu_validate_guest_context(const struct vcpu *v, + const struct vcpu_guest_context *ctxt); +void arch_vcpu_apply_guest_context(struct vcpu *v, + const struct vcpu_guest_context *ctxt); + static inline struct vcpu_guest_context *alloc_vcpu_guest_context(void) { return xmalloc(struct vcpu_guest_context); diff --git a/xen/arch/arm/include/asm/perfc_defn.h b/xen/arch/arm/include/asm/perfc_defn.h index effd25b69e..8dfcac7e3b 100644 --- a/xen/arch/arm/include/asm/perfc_defn.h +++ b/xen/arch/arm/include/asm/perfc_defn.h @@ -33,6 +33,7 @@ PERFCOUNTER(vpsci_system_reset, "vpsci: system_reset") PERFCOUNTER(vpsci_cpu_suspend, "vpsci: cpu_suspend") PERFCOUNTER(vpsci_cpu_affinity_info, "vpsci: cpu_affinity_info") PERFCOUNTER(vpsci_features, "vpsci: features") +PERFCOUNTER(vpsci_system_suspend, "vpsci: system_suspend") PERFCOUNTER(vcpu_kick, "vcpu: notify other vcpu") diff --git a/xen/arch/arm/include/asm/psci.h b/xen/arch/arm/include/asm/psci.h index 4780972621..48a93e6b79 100644 --- a/xen/arch/arm/include/asm/psci.h +++ b/xen/arch/arm/include/asm/psci.h @@ -47,10 +47,12 @@ void call_psci_system_reset(void); #define PSCI_0_2_FN32_SYSTEM_OFF PSCI_0_2_FN32(8) #define PSCI_0_2_FN32_SYSTEM_RESET PSCI_0_2_FN32(9) #define PSCI_1_0_FN32_PSCI_FEATURES PSCI_0_2_FN32(10) +#define PSCI_1_0_FN32_SYSTEM_SUSPEND PSCI_0_2_FN32(14) #define PSCI_0_2_FN64_CPU_SUSPEND PSCI_0_2_FN64(1) #define PSCI_0_2_FN64_CPU_ON PSCI_0_2_FN64(3) #define PSCI_0_2_FN64_AFFINITY_INFO PSCI_0_2_FN64(4) +#define PSCI_1_0_FN64_SYSTEM_SUSPEND PSCI_0_2_FN64(14) /* PSCI v0.2 affinity level state returned by AFFINITY_INFO */ #define PSCI_0_2_AFFINITY_LEVEL_ON 0 diff --git a/xen/arch/arm/include/asm/suspend.h b/xen/arch/arm/include/asm/suspend.h new file mode 100644 index 0000000000..31a98a1f1b --- /dev/null +++ b/xen/arch/arm/include/asm/suspend.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef ARM_SUSPEND_H +#define ARM_SUSPEND_H + +struct domain; +struct vcpu; +struct vcpu_guest_context; + +struct resume_info { + struct vcpu_guest_context *ctxt; + struct vcpu *wake_cpu; +}; + +void arch_domain_resume(struct domain *d); + +#endif /* ARM_SUSPEND_H */ + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */ diff --git a/xen/arch/arm/include/asm/vpsci.h b/xen/arch/arm/include/asm/vpsci.h index 0cca5e6830..69d40f9d7f 100644 --- a/xen/arch/arm/include/asm/vpsci.h +++ b/xen/arch/arm/include/asm/vpsci.h @@ -23,7 +23,7 @@ #include /* Number of function implemented by virtual PSCI (only 0.2 or later) */ -#define VPSCI_NR_FUNCS 12 +#define VPSCI_NR_FUNCS 14 /* Functions handle PSCI calls from the guests */ bool do_vpsci_0_1_call(struct cpu_user_regs *regs, uint32_t fid); diff --git a/xen/arch/arm/vpsci.c b/xen/arch/arm/vpsci.c index 7ba9ccd94b..bd87ec430d 100644 --- a/xen/arch/arm/vpsci.c +++ b/xen/arch/arm/vpsci.c @@ -4,38 +4,24 @@ #include #include +#include #include #include #include #include -static int do_common_cpu_on(register_t target_cpu, register_t entry_point, - register_t context_id) +static int vpsci_build_guest_context(struct vcpu *v, register_t entry_point, + register_t context_id, + struct vcpu_guest_context **out) { - struct vcpu *v; - struct domain *d = current->domain; + bool is_thumb = entry_point & 1; struct vcpu_guest_context *ctxt; int rc; - bool is_thumb = entry_point & 1; - register_t vcpuid; - vcpuid = vaffinity_to_vcpuid(target_cpu); - - if ( (v = domain_vcpu(d, vcpuid)) == NULL ) - return PSCI_INVALID_PARAMETERS; - - /* THUMB set is not allowed with 64-bit domain */ - if ( is_64bit_domain(d) && is_thumb ) - return PSCI_INVALID_ADDRESS; - - if ( !test_bit(_VPF_down, &v->pause_flags) ) - return PSCI_ALREADY_ON; - - if ( (ctxt = alloc_vcpu_guest_context()) == NULL ) - return PSCI_DENIED; - - vgic_clear_pending_irqs(v); + ctxt = alloc_vcpu_guest_context(); + if ( ctxt == NULL ) + return -ENOMEM; memset(ctxt, 0, sizeof(*ctxt)); ctxt->user_regs.pc64 = (u64) entry_point; @@ -48,7 +34,7 @@ static int do_common_cpu_on(register_t target_cpu, register_t entry_point, * x0/r0_usr are always updated because for PSCI 0.1 the general * purpose registers are undefined upon CPU_on. */ - if ( is_32bit_domain(d) ) + if ( is_32bit_domain(v->domain) ) { ctxt->user_regs.cpsr = PSR_GUEST32_INIT; /* Start the VCPU with THUMB set if it's requested by the kernel */ @@ -69,15 +55,51 @@ static int do_common_cpu_on(register_t target_cpu, register_t entry_point, #endif ctxt->flags = VGCF_online; + rc = arch_vcpu_validate_guest_context(v, ctxt); + if ( rc ) + { + free_vcpu_guest_context(ctxt); + return rc; + } + + *out = ctxt; + return 0; +} + +static int do_common_cpu_on(register_t target_cpu, register_t entry_point, + register_t context_id) +{ + struct vcpu *v; + struct domain *d = current->domain; + struct vcpu_guest_context *ctxt = NULL; + int rc; + bool is_thumb = entry_point & 1; + register_t vcpuid; + + vcpuid = vaffinity_to_vcpuid(target_cpu); + + if ( (v = domain_vcpu(d, vcpuid)) == NULL ) + return PSCI_INVALID_PARAMETERS; + + /* THUMB set is not allowed with 64-bit domain */ + if ( is_64bit_domain(d) && is_thumb ) + return PSCI_INVALID_ADDRESS; + + if ( !test_bit(_VPF_down, &v->pause_flags) ) + return PSCI_ALREADY_ON; + + rc = vpsci_build_guest_context(v, entry_point, context_id, &ctxt); + if ( rc < 0 ) + return PSCI_DENIED; + + vgic_clear_pending_irqs(v); + domain_lock(d); - rc = arch_set_info_guest(v, ctxt); + arch_vcpu_apply_guest_context(v, ctxt); domain_unlock(d); free_vcpu_guest_context(ctxt); - if ( rc < 0 ) - return PSCI_DENIED; - vcpu_wake(v); return PSCI_SUCCESS; @@ -197,6 +219,56 @@ static void do_psci_0_2_system_reset(void) domain_shutdown(d,SHUTDOWN_reboot); } +static int32_t do_psci_1_0_system_suspend(register_t epoint, register_t cid) +{ + int32_t rc; + struct vcpu_guest_context *ctxt; + struct vcpu *v; + struct domain *d = current->domain; + bool is_thumb = epoint & 1; + struct resume_info *rctx = &d->arch.resume_ctx; + + /* THUMB set is not allowed with 64-bit domain */ + if ( is_64bit_domain(d) && is_thumb ) + return PSCI_INVALID_ADDRESS; + + /* SYSTEM_SUSPEND is not supported for the hardware domain yet */ + if ( is_hardware_domain(d) ) + return PSCI_NOT_SUPPORTED; + + /* Ensure that all CPUs other than the calling one are offline */ + domain_lock(d); + for_each_vcpu ( d, v ) + { + if ( v != current && is_vcpu_online(v) ) + { + domain_unlock(d); + return PSCI_DENIED; + } + } + domain_unlock(d); + + rc = vpsci_build_guest_context(current, epoint, cid, &ctxt); + if ( rc ) + return PSCI_DENIED; + + rc = domain_shutdown(d, SHUTDOWN_suspend); + if ( rc ) + { + free_vcpu_guest_context(ctxt); + return PSCI_DENIED; + } + + rctx->ctxt = ctxt; + rctx->wake_cpu = current; + + gprintk(XENLOG_DEBUG, + "SYSTEM_SUSPEND requested, epoint=%#"PRIregister", cid=%#"PRIregister"\n", + epoint, cid); + + return rc; +} + static int32_t do_psci_1_0_features(uint32_t psci_func_id) { /* /!\ Ordered by function ID and not name */ @@ -216,6 +288,9 @@ static int32_t do_psci_1_0_features(uint32_t psci_func_id) case PSCI_1_0_FN32_PSCI_FEATURES: case ARM_SMCCC_VERSION_FID: return 0; + case PSCI_1_0_FN32_SYSTEM_SUSPEND: + case PSCI_1_0_FN64_SYSTEM_SUSPEND: + return is_hardware_domain(current->domain) ? PSCI_NOT_SUPPORTED : 0; default: return PSCI_NOT_SUPPORTED; } @@ -344,6 +419,23 @@ bool do_vpsci_0_2_call(struct cpu_user_regs *regs, uint32_t fid) return true; } + case PSCI_1_0_FN32_SYSTEM_SUSPEND: + case PSCI_1_0_FN64_SYSTEM_SUSPEND: + { + register_t epoint = PSCI_ARG(regs, 1); + register_t cid = PSCI_ARG(regs, 2); + + if ( fid == PSCI_1_0_FN32_SYSTEM_SUSPEND ) + { + epoint &= GENMASK(31, 0); + cid &= GENMASK(31, 0); + } + + perfc_incr(vpsci_system_suspend); + PSCI_SET_RESULT(regs, do_psci_1_0_system_suspend(epoint, cid)); + return true; + } + default: return false; } diff --git a/xen/common/domain.c b/xen/common/domain.c index 30cfea3045..bb9e210c28 100644 --- a/xen/common/domain.c +++ b/xen/common/domain.c @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -1435,6 +1436,9 @@ void domain_resume(struct domain *d) spin_lock(&d->shutdown_lock); d->is_shutting_down = d->is_shut_down = 0; + + arch_domain_resume(d); + d->shutdown_code = SHUTDOWN_CODE_INVALID; for_each_vcpu ( d, v ) diff --git a/xen/include/xen/suspend.h b/xen/include/xen/suspend.h new file mode 100644 index 0000000000..6f94fd53b0 --- /dev/null +++ b/xen/include/xen/suspend.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef XEN_SUSPEND_H +#define XEN_SUSPEND_H + +#if __has_include() +#include +#else +static inline void arch_domain_resume(struct domain *d) {} +#endif + +#endif /* XEN_SUSPEND_H */ + +/* + * Local variables: + * mode: C + * c-file-style: "BSD" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: nil + * End: + */