fix(cpus): avoid SME related loss of context on powerdown

Travis' and Gelas' TRMs tell us to disable SME (set PSTATE.{ZA, SM} to
0) when we're attempting to power down. What they don't tell us is that
if this isn't done, the powerdown request will be rejected. On the
CPU_OFF path that's not a problem - we can force SVCR to 0 and be
certain the core will power off.

On the suspend to powerdown path, however, we cannot do this. The TRM
also tells us that the sequence could also be aborted on eg. GIC
interrupts. If this were to happen when we have overwritten SVCR to 0,
upon a return to the caller they would experience a loss of context. We
know that at least Linux may call into PSCI with SVCR != 0. One option
is to save the entire SME context which would be quite expensive just to
work around. Another option is to downgrade the request to a normal
suspend when SME was left on. This option is better as this is expected
to happen rarely enough to ignore the wasted power and we don't want to
burden the generic (correct) path with needless context management.

Signed-off-by: Boyan Karatotev <boyan.karatotev@arm.com>
Change-Id: I698fa8490ebf51461f6aa8bba84f9827c5c46ad4
diff --git a/lib/psci/psci_main.c b/lib/psci/psci_main.c
index 7ac0e02..45be63a 100644
--- a/lib/psci/psci_main.c
+++ b/lib/psci/psci_main.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013-2022, Arm Limited and Contributors. All rights reserved.
+ * Copyright (c) 2013-2024, Arm Limited and Contributors. All rights reserved.
  *
  * SPDX-License-Identifier: BSD-3-Clause
  */
@@ -8,6 +8,7 @@
 #include <string.h>
 
 #include <arch.h>
+#include <arch_features.h>
 #include <arch_helpers.h>
 #include <common/debug.h>
 #include <lib/pmf/pmf.h>
@@ -64,6 +65,19 @@
 	plat_local_state_t prev[PLAT_MAX_PWR_LVL];
 #endif
 
+#if ERRATA_SME_POWER_DOWN
+	/*
+	 * If SME isn't off, attempting a real power down will only end up being
+	 * rejected. If we got called with SME on, fall back to a normal
+	 * suspend. We can't force SME off as in the event the power down is
+	 * rejected for another reason (eg GIC) we'd lose the SME context.
+	 */
+	if (is_feat_sme_supported() && read_svcr() != 0) {
+		power_state &= ~(PSTATE_TYPE_MASK << PSTATE_TYPE_SHIFT);
+		power_state &= ~(PSTATE_PWR_LVL_MASK << PSTATE_PWR_LVL_SHIFT);
+	}
+#endif /* ERRATA_SME_POWER_DOWN */
+
 	/* Validate the power_state parameter */
 	rc = psci_validate_power_state(power_state, &state_info);
 	if (rc != PSCI_E_SUCCESS) {