/** Per hart specific data to manage state transition **/
struct sbi_hsm_data {
atomic_t state;
+ unsigned long suspend_type;
+ unsigned long saved_mie;
+ unsigned long saved_mip;
};
static inline int __sbi_hsm_hart_get_state(u32 hartid)
int sbi_hsm_hart_interruptible_mask(const struct sbi_domain *dom,
ulong hbase, ulong *out_hmask)
{
+ int hstate;
ulong i, hmask, dmask;
ulong hend = sbi_scratch_last_hartid() + 1;
dmask = sbi_domain_get_assigned_hartmask(dom, hbase);
for (i = hbase; i < hend; i++) {
hmask = 1UL << (i - hbase);
- if ((dmask & hmask) &&
- (__sbi_hsm_hart_get_state(i) == SBI_HSM_STATE_STARTED))
- *out_hmask |= hmask;
+ if (dmask & hmask) {
+ hstate = __sbi_hsm_hart_get_state(i);
+ if (hstate == SBI_HSM_STATE_STARTED ||
+ hstate == SBI_HSM_STATE_SUSPENDED)
+ *out_hmask |= hmask;
+ }
}
return 0;
return 0;
}
+
+static int __sbi_hsm_suspend_ret_default(struct sbi_scratch *scratch)
+{
+ /* Wait for interrupt */
+ wfi();
+
+ return 0;
+}
+
+static void __sbi_hsm_suspend_non_ret_save(struct sbi_scratch *scratch)
+{
+ struct sbi_hsm_data *hdata = sbi_scratch_offset_ptr(scratch,
+ hart_data_offset);
+
+ /*
+ * We will be resuming in warm-boot path so the MIE and MIP CSRs
+ * will be back to initial state. It is possible that HART has
+ * configured timer event before going to suspend state so we
+ * should save MIE and MIP CSRs and restore it after resuming.
+ *
+ * Further, the M-mode bits in MIP CSR are read-only and set by
+ * external devices (such as interrupt controller) whereas all
+ * VS-mode bits in MIP are read-only alias of bits in HVIP CSR.
+ *
+ * This means we should only save/restore S-mode bits of MIP CSR
+ * such as MIP.SSIP and MIP.STIP.
+ */
+
+ hdata->saved_mie = csr_read(CSR_MIE);
+ hdata->saved_mip = csr_read(CSR_MIP) & (MIP_SSIP | MIP_STIP);
+}
+
+static void __sbi_hsm_suspend_non_ret_restore(struct sbi_scratch *scratch)
+{
+ struct sbi_hsm_data *hdata = sbi_scratch_offset_ptr(scratch,
+ hart_data_offset);
+
+ csr_write(CSR_MIE, hdata->saved_mie);
+ csr_write(CSR_MIP, (hdata->saved_mip & (MIP_SSIP | MIP_STIP)));
+}
+
+static int __sbi_hsm_suspend_non_ret_default(struct sbi_scratch *scratch,
+ ulong raddr)
+{
+ void (*jump_warmboot)(void) = (void (*)(void))scratch->warmboot_addr;
+
+ /*
+ * Save some of the M-mode CSRs which should be restored after
+ * resuming from suspend state
+ */
+ __sbi_hsm_suspend_non_ret_save(scratch);
+
+ /* Wait for interrupt */
+ wfi();
+
+ /*
+ * Directly jump to warm reboot to simulate resume from a
+ * non-retentive suspend.
+ */
+ jump_warmboot();
+
+ return 0;
+}
+
+void sbi_hsm_hart_resume_start(struct sbi_scratch *scratch)
+{
+ int oldstate;
+ struct sbi_hsm_data *hdata = sbi_scratch_offset_ptr(scratch,
+ hart_data_offset);
+
+ /* If current HART was SUSPENDED then set RESUME_PENDING state */
+ oldstate = atomic_cmpxchg(&hdata->state, SBI_HSM_STATE_SUSPENDED,
+ SBI_HSM_STATE_RESUME_PENDING);
+ if (oldstate != SBI_HSM_STATE_SUSPENDED) {
+ sbi_printf("%s: ERR: The hart is in invalid state [%u]\n",
+ __func__, oldstate);
+ sbi_hart_hang();
+ }
+}
+
+void sbi_hsm_hart_resume_finish(struct sbi_scratch *scratch)
+{
+ u32 oldstate;
+ struct sbi_hsm_data *hdata = sbi_scratch_offset_ptr(scratch,
+ hart_data_offset);
+
+ /* If current HART was RESUME_PENDING then set STARTED state */
+ oldstate = atomic_cmpxchg(&hdata->state, SBI_HSM_STATE_RESUME_PENDING,
+ SBI_HSM_STATE_STARTED);
+ if (oldstate != SBI_HSM_STATE_RESUME_PENDING) {
+ sbi_printf("%s: ERR: The hart is in invalid state [%u]\n",
+ __func__, oldstate);
+ sbi_hart_hang();
+ }
+
+ /*
+ * Restore some of the M-mode CSRs which we are re-configured by
+ * the warm-boot sequence.
+ */
+ __sbi_hsm_suspend_non_ret_restore(scratch);
+}
+
+int sbi_hsm_hart_suspend(struct sbi_scratch *scratch, u32 suspend_type,
+ ulong raddr, ulong rmode, ulong priv)
+{
+ int oldstate, ret;
+ const struct sbi_domain *dom = sbi_domain_thishart_ptr();
+ const struct sbi_platform *plat = sbi_platform_ptr(scratch);
+ struct sbi_hsm_data *hdata = sbi_scratch_offset_ptr(scratch,
+ hart_data_offset);
+
+ /* For now, we only allow suspend from S-mode or U-mode. */
+
+ /* Sanity check on domain assigned to current HART */
+ if (!dom)
+ return SBI_EINVAL;
+
+ /* Sanity check on suspend type */
+ if (SBI_HSM_SUSPEND_RET_DEFAULT < suspend_type &&
+ suspend_type < SBI_HSM_SUSPEND_RET_PLATFORM)
+ return SBI_EINVAL;
+ if (SBI_HSM_SUSPEND_NON_RET_DEFAULT < suspend_type &&
+ suspend_type < SBI_HSM_SUSPEND_NON_RET_PLATFORM)
+ return SBI_EINVAL;
+
+ /* Additional sanity check for non-retentive suspend */
+ if (suspend_type & SBI_HSM_SUSP_NON_RET_BIT) {
+ if (rmode != PRV_S && rmode != PRV_U)
+ return SBI_EINVAL;
+ if (dom && !sbi_domain_check_addr(dom, raddr, rmode,
+ SBI_DOMAIN_EXECUTE))
+ return SBI_EINVALID_ADDR;
+ }
+
+ /* Save the resume address and resume mode */
+ scratch->next_arg1 = priv;
+ scratch->next_addr = raddr;
+ scratch->next_mode = rmode;
+
+ /* Directly move from STARTED to SUSPENDED state */
+ oldstate = atomic_cmpxchg(&hdata->state, SBI_HSM_STATE_STARTED,
+ SBI_HSM_STATE_SUSPENDED);
+ if (oldstate != SBI_HSM_STATE_STARTED) {
+ sbi_printf("%s: ERR: The hart is in invalid state [%u]\n",
+ __func__, oldstate);
+ ret = SBI_EDENIED;
+ goto fail_restore_state;
+ }
+
+ /* Save the suspend type */
+ hdata->suspend_type = suspend_type;
+
+ /* Try platform specific suspend */
+ ret = sbi_platform_hart_suspend(plat, suspend_type,
+ scratch->warmboot_addr);
+ if (ret == SBI_ENOTSUPP) {
+ /* Try generic implementation of default suspend types */
+ if (suspend_type == SBI_HSM_SUSPEND_RET_DEFAULT) {
+ ret = __sbi_hsm_suspend_ret_default(scratch);
+ } else if (suspend_type == SBI_HSM_SUSPEND_NON_RET_DEFAULT) {
+ ret = __sbi_hsm_suspend_non_ret_default(scratch,
+ scratch->warmboot_addr);
+ }
+ }
+
+fail_restore_state:
+ /*
+ * We might have successfully resumed from retentive suspend
+ * or suspend failed. In both cases, we restore state of hart.
+ */
+ oldstate = atomic_cmpxchg(&hdata->state, SBI_HSM_STATE_SUSPENDED,
+ SBI_HSM_STATE_STARTED);
+ if (oldstate != SBI_HSM_STATE_SUSPENDED) {
+ sbi_printf("%s: ERR: The hart is in invalid state [%u]\n",
+ __func__, oldstate);
+ sbi_hart_hang();
+ }
+
+ return ret;
+}
scratch->next_mode, FALSE);
}
-static void __noreturn init_warmboot(struct sbi_scratch *scratch, u32 hartid)
+static void init_warm_startup(struct sbi_scratch *scratch, u32 hartid)
{
int rc;
unsigned long *init_count;
const struct sbi_platform *plat = sbi_platform_ptr(scratch);
- wait_for_coldboot(scratch, hartid);
-
if (!init_count_offset)
sbi_hart_hang();
(*init_count)++;
sbi_hsm_prepare_next_jump(scratch, hartid);
+}
+
+static void init_warm_resume(struct sbi_scratch *scratch)
+{
+ int rc;
+
+ sbi_hsm_hart_resume_start(scratch);
+
+ rc = sbi_hart_reinit(scratch);
+ if (rc)
+ sbi_hart_hang();
+
+ rc = sbi_hart_pmp_configure(scratch);
+ if (rc)
+ sbi_hart_hang();
+
+ sbi_hsm_hart_resume_finish(scratch);
+}
+
+static void __noreturn init_warmboot(struct sbi_scratch *scratch, u32 hartid)
+{
+ int hstate;
+
+ wait_for_coldboot(scratch, hartid);
+
+ hstate = sbi_hsm_hart_get_state(sbi_domain_thishart_ptr(), hartid);
+ if (hstate < 0)
+ sbi_hart_hang();
+
+ if (hstate == SBI_HSM_STATE_SUSPENDED)
+ init_warm_resume(scratch);
+ else
+ init_warm_startup(scratch, hartid);
+
sbi_hart_switch_mode(hartid, scratch->next_arg1,
scratch->next_addr,
scratch->next_mode, FALSE);