[PATCH v12 13/13] xen/arm: Add host system suspend backend
Mykola Kvach <[email protected]>
| Newsgroups | gmane.comp.emulators.xen.devel |
|---|---|
| Message-ID | <9d3cf11530edf478ae7a597f1c0934f4b7b1a270.1787838455.git.mykola_kvach@epam.com> |
From: Mirela Simonovic <[email protected]> Add the Xen-wide suspend/resume backend used after a control-domain vPSCI SYSTEM_SUSPEND request has been accepted. The vPSCI policy, runtime driver blockers and control-domain sequencing checks are handled by the preceding commit; this change adds the code that actually drives the host suspend attempt. The backend runs from a tasklet scheduled on pCPU0, because non-boot CPUs are disabled during suspend. It freezes domains, disables the scheduler and then disables non-boot CPUs. Host-side suspend participants are handled in phases. IOMMU and console state are suspended first. Local IRQs are then disabled before suspending timer and GIC state. On resume or failure, the completed suspend phases are unwound in reverse: GIC and timer state are restored while IRQs are still disabled, local IRQs are restored, and then console and IOMMU state are restored. On boot, init_ttbr is normally initialized during secondary CPU hotplug. On uniprocessor systems this can leave init_ttbr uninitialized, so set it from the boot CPU before entering suspend. Note: the code is behind CONFIG_HAS_SYSTEM_SUSPEND, which is currently only selected when UNSUPPORTED is set and MPU is not set. Signed-off-by: Mirela Simonovic <[email protected]> Signed-off-by: Saeed Nowshadi <[email protected]> Signed-off-by: Mykyta Poturai <[email protected]> Signed-off-by: Mykola Kvach <[email protected]> --- Changes in V10: - Re-apply boot CPU local errata/workaround handling after SYSTEM_SUSPEND, before resuming the rest of the host suspend path. - Move set_init_ttbr() declaration to asm/mmu/mm.h, since it is MMU-specific. Changes in V9: - Split vPSCI availability policy, runtime host-suspend blockers and the domain-readiness precheck into the preceding commit. - Trigger the host suspend backend from the control-domain SYSTEM_SUSPEND path. - Reorder the host suspend/resume phases so the timer is suspended with local IRQs disabled and local IRQs are restored after the GIC and timer resume paths, before the console and IOMMU resume paths. - Move HAS_HWDOM_SYSTEM_SUSPEND and related logic to policy patch. Changes in V8: - Add a pre-suspend check in system_suspend() after scheduler_disable() to require all domains to be in the shut down state with SHUTDOWN_suspend before proceeding with the global suspend flow. - Drop the common-level depends on !ARM_64 || !SYSTEM_SUSPEND from CONFIG_HAS_HWDOM_SHUTDOWN_ON_SUSPEND and model the ARM64 suspend case with an arch-selected capability instead. - Rename CONFIG_HAS_HWDOM_SHUTDOWN_ON_SUSPEND to CONFIG_HAS_HWDOM_SYSTEM_SUSPEND. - Rename need_hwdom_shutdown() to want_hwdom_shutdown(). Changes in V7: - Control domain is responsible for host suspend. - Add an empty inline host_system_suspend() function when SYSTEM_SUSPEND config is disabled. - Use IS_ENABLED() for config checking instead of #ifdef. - Replace #ifdef checks in domain_shutdown() with IS_ENABLED() to simplify control flow. - Factor hardware domain shutdown condition into a helper (need_hwdom_shutdown()) to avoid preprocessor directives inside the function. - Squash with iommu suspend/resume commit. --- xen/arch/arm/Kconfig | 1 + xen/arch/arm/cpuerrata.c | 7 +- xen/arch/arm/include/asm/cpuerrata.h | 1 + xen/arch/arm/include/asm/mmu/mm.h | 2 + xen/arch/arm/include/asm/suspend.h | 2 + xen/arch/arm/mmu/smpboot.c | 2 +- xen/arch/arm/suspend.c | 156 +++++++++++++++++++++++++++ xen/arch/arm/vpsci.c | 10 +- 8 files changed, 177 insertions(+), 4 deletions(-) diff --git a/xen/arch/arm/Kconfig b/xen/arch/arm/Kconfig index 9027aa17eb..da1585ec50 100644 --- a/xen/arch/arm/Kconfig +++ b/xen/arch/arm/Kconfig @@ -9,6 +9,7 @@ config ARM_64 select 64BIT select HAS_DOMAIN_TYPE select HAS_FAST_MULTIPLY + select HAS_SYSTEM_SUSPEND if !MPU && UNSUPPORTED select HAS_VPCI_GUEST_SUPPORT if PCI_PASSTHROUGH config ARM diff --git a/xen/arch/arm/cpuerrata.c b/xen/arch/arm/cpuerrata.c index 3a32183618..e6499aaab3 100644 --- a/xen/arch/arm/cpuerrata.c +++ b/xen/arch/arm/cpuerrata.c @@ -782,6 +782,11 @@ void check_local_cpu_errata(void) update_cpu_capabilities(arm_errata, "enabled workaround for"); } +int enable_local_cpu_errata_workarounds(void) +{ + return enable_nonboot_cpu_caps(arm_errata); +} + void __init enable_errata_workarounds(void) { enable_cpu_capabilities(arm_errata); @@ -818,7 +823,7 @@ static int cpu_errata_callback(struct notifier_block *nfb, * fixed to expect an error at CPU_STARTING phase. */ ASSERT(system_state != SYS_STATE_boot); - rc = enable_nonboot_cpu_caps(arm_errata); + rc = enable_local_cpu_errata_workarounds(); break; default: break; diff --git a/xen/arch/arm/include/asm/cpuerrata.h b/xen/arch/arm/include/asm/cpuerrata.h index 1799a16d7e..b93521326f 100644 --- a/xen/arch/arm/include/asm/cpuerrata.h +++ b/xen/arch/arm/include/asm/cpuerrata.h @@ -5,6 +5,7 @@ #include <asm/alternative.h> void check_local_cpu_errata(void); +int enable_local_cpu_errata_workarounds(void); void enable_errata_workarounds(void); #define CHECK_WORKAROUND_HELPER(erratum, feature, arch) \ diff --git a/xen/arch/arm/include/asm/mmu/mm.h b/xen/arch/arm/include/asm/mmu/mm.h index 7f4d59137d..ee73a77777 100644 --- a/xen/arch/arm/include/asm/mmu/mm.h +++ b/xen/arch/arm/include/asm/mmu/mm.h @@ -110,6 +110,8 @@ void dump_pt_walk(paddr_t ttbr, paddr_t addr, extern void switch_ttbr(uint64_t ttbr); extern void relocate_and_switch_ttbr(uint64_t ttbr); +void set_init_ttbr(lpae_t *root); + #endif /* __ARM_MMU_MM_H__ */ /* diff --git a/xen/arch/arm/include/asm/suspend.h b/xen/arch/arm/include/asm/suspend.h index 50dc6e9fdf..889a6509d9 100644 --- a/xen/arch/arm/include/asm/suspend.h +++ b/xen/arch/arm/include/asm/suspend.h @@ -41,11 +41,13 @@ int prepare_resume_ctx(void); void hyp_resume(void); bool host_system_suspend_allowed(void); void host_system_suspend_disable(const char *reason); +void host_system_suspend(struct domain *d); #else /* !CONFIG_SYSTEM_SUSPEND */ static inline bool host_system_suspend_allowed(void) { return false; } static inline void host_system_suspend_disable(const char *reason) {} +static inline void host_system_suspend(struct domain *d) {} #endif diff --git a/xen/arch/arm/mmu/smpboot.c b/xen/arch/arm/mmu/smpboot.c index 37e91d72b7..ff508ecf40 100644 --- a/xen/arch/arm/mmu/smpboot.c +++ b/xen/arch/arm/mmu/smpboot.c @@ -72,7 +72,7 @@ static void clear_boot_pagetables(void) clear_table(boot_third); } -static void set_init_ttbr(lpae_t *root) +void set_init_ttbr(lpae_t *root) { /* * init_ttbr is part of the identity mapping which is read-only. So diff --git a/xen/arch/arm/suspend.c b/xen/arch/arm/suspend.c index c7c26bcf03..3fe2ffa4fb 100644 --- a/xen/arch/arm/suspend.c +++ b/xen/arch/arm/suspend.c @@ -1,10 +1,18 @@ /* SPDX-License-Identifier: GPL-2.0-only */ +#include <asm/cpuerrata.h> +#include <asm/cpufeature.h> +#include <asm/gic.h> #include <asm/psci.h> #include <asm/suspend.h> +#include <xen/console.h> +#include <xen/cpu.h> +#include <xen/iommu.h> #include <xen/lib.h> +#include <xen/sched.h> #include <xen/serial.h> +#include <xen/tasklet.h> struct resume_cpu_context resume_cpu_context; @@ -44,6 +52,154 @@ void host_system_suspend_disable(const char *reason) reason ? reason : "unsupported suspend/resume path"); } +/* Xen suspend. data identifies the domain that initiated suspend. */ +static void system_suspend(void *data) +{ + int status; + unsigned long flags; + struct domain *d = (struct domain *)data; + + BUG_ON(system_state != SYS_STATE_active); + + system_state = SYS_STATE_suspend; + + printk("Xen suspending...\n"); + + freeze_domains(); + scheduler_disable(); + + /* + * Non-boot CPUs have to be disabled on suspend and enabled on resume + * (hotplug-based mechanism). Disabling non-boot CPUs will lead to PSCI + * CPU_OFF to be called by each non-boot CPU. Depending on the underlying + * platform capabilities, this may lead to the physical powering down of + * CPUs. + */ + status = disable_nonboot_cpus(); + if ( status ) + { + system_state = SYS_STATE_resume; + goto resume_nonboot_cpus; + } + + console_start_sync(); + status = iommu_suspend(); + if ( status ) + { + system_state = SYS_STATE_resume; + goto resume_end_sync; + } + + status = console_suspend(); + if ( status ) + { + dprintk(XENLOG_ERR, "Failed to suspend the console, err=%d\n", status); + system_state = SYS_STATE_resume; + goto resume_iommu; + } + + local_irq_save(flags); + + time_suspend(); + + status = gic_suspend(); + if ( status ) + { + system_state = SYS_STATE_resume; + goto resume_time; + } + + set_init_ttbr(xen_pgtable); + + /* + * Enable identity mapping before entering suspend to simplify + * the resume path + */ + update_boot_mapping(true); + + if ( prepare_resume_ctx() ) + { + status = call_psci_system_suspend(); + /* + * If suspend is finalized properly by above system suspend PSCI call, + * the code below in this 'if' branch will never execute. Execution + * will continue from hyp_resume which is the hypervisor's resume point. + * In hyp_resume CPU context will be restored and since link-register is + * restored as well, it will appear to return from prepare_resume_ctx. + * The difference in returning from prepare_resume_ctx on system suspend + * versus resume is in function's return value: on suspend, the return + * value is a non-zero value, on resume it is zero. That is why the + * control flow will not re-enter this 'if' branch on resume. + */ + if ( status ) + dprintk(XENLOG_WARNING, "PSCI system suspend failed, err=%d\n", + status); + + system_state = SYS_STATE_resume; + } + else + { + system_state = SYS_STATE_resume; + + /* + * CPU0 resumes directly from hyp_resume(), bypassing the CPU hotplug + * path that re-checks and re-enables errata workarounds for secondary + * CPUs. + */ + check_local_cpu_errata(); + check_local_cpu_features(); + BUG_ON(enable_local_cpu_errata_workarounds()); + } + + update_boot_mapping(false); + + gic_resume(); + + resume_time: + time_resume(); + + local_irq_restore(flags); + + console_resume(); + + resume_iommu: + iommu_resume(); + + resume_end_sync: + console_end_sync(); + + resume_nonboot_cpus: + /* + * The rcu_barrier() has to be added to ensure that the per cpu area is + * freed before a non-boot CPU tries to initialize it (_free_percpu_area() + * has to be called before the init_percpu_area()). This scenario occurs + * when non-boot CPUs are hot-unplugged on suspend and hotplugged on resume. + */ + rcu_barrier(); + enable_nonboot_cpus(); + + scheduler_enable(); + thaw_domains(); + + system_state = SYS_STATE_active; + + printk("Resume (status %d)\n", status); + + domain_resume(d); +} + +static DECLARE_TASKLET(system_suspend_tasklet, system_suspend, NULL); + +void host_system_suspend(struct domain *d) +{ + system_suspend_tasklet.data = (void *)d; + /* + * The suspend procedure has to be finalized by the pCPU#0 (non-boot pCPUs + * will be disabled during the suspend). + */ + tasklet_schedule_on_cpu(&system_suspend_tasklet, 0); +} + /* * Local variables: * mode: C diff --git a/xen/arch/arm/vpsci.c b/xen/arch/arm/vpsci.c index a41355d75d..5134e75c24 100644 --- a/xen/arch/arm/vpsci.c +++ b/xen/arch/arm/vpsci.c @@ -237,7 +237,8 @@ static bool domain_in_suspend_state(struct domain *d) return suspended; } -static int32_t domain_psci_system_suspend_policy(struct domain *d) +static int32_t domain_psci_system_suspend_policy(struct domain *d, + bool *host_suspend) { struct domain *other; bool last_awake_control_domain = true; @@ -300,6 +301,7 @@ static int32_t domain_psci_system_suspend_policy(struct domain *d) if ( !host_system_suspend_allowed() ) return PSCI_DENIED; + *host_suspend = true; return 0; } @@ -310,6 +312,7 @@ static int32_t do_psci_1_0_system_suspend(register_t epoint, register_t cid) struct vcpu *v; struct domain *d = current->domain; bool is_thumb = epoint & 1; + bool host_suspend = false; struct resume_info *rctx = &d->arch.resume_ctx; /* THUMB set is not allowed with 64-bit domain */ @@ -334,7 +337,7 @@ static int32_t do_psci_1_0_system_suspend(register_t epoint, register_t cid) spin_lock(&vpsci_system_suspend_lock); - rc = domain_psci_system_suspend_policy(d); + rc = domain_psci_system_suspend_policy(d, &host_suspend); if ( !rc ) { rc = domain_shutdown(d, SHUTDOWN_suspend); @@ -359,6 +362,9 @@ static int32_t do_psci_1_0_system_suspend(register_t epoint, register_t cid) "SYSTEM_SUSPEND requested, epoint=%#"PRIregister", cid=%#"PRIregister"\n", epoint, cid); + if ( host_suspend ) + host_system_suspend(d); + return rc; } -- 2.43.0