[PATCH 02/10] lib: sbi: domain: adaptation for supporting VIRQ couriering domain context switch

Raymond Mao raymondmaoca at gmail.com
Thu May 14 15:57:48 PDT 2026


From: Raymond Mao <raymond.mao at riscstar.com>

Prerequisite adaptations for introducing VIRQ couriering domain
context switch.

MIDELEG:
- Save/restore MIDELEG in domain contexts and initialize per-domain
  defaults.

SEIP notification:
- Add virq_seip_notify flag, and use it to enable SEIP delegation
  for SEIP-notify domains.
- Add smode_notify_pending and helper function to store the S-mode
  notification status, and use it as a flag to fire the pending
  notification after domain context switch.

Return domain context switch:
- Add sbi_domain_context_exit_to_prev() to return to the previous
  domain without scanning for another candidate.
- Introduce per-hart deferred return flags and APIs to request/consume
  them.
- Perform the actual return-to-prev at the end of sbi_trap_handler()
  to avoid corrupting mepc during ecall handling.

Additionally, fix two return domain-switch protential issues:
- When a domain switch occurs inside sbi_trap_handler(), return the
  switched-to trap context from scratch instead of the original trap
  entry context. This prevents the trap restore path from resuming with
  stale state from the previous domain.
- When returning to an S-mode target domain, copy the restored
  CSR_SSTATUS SIE/SPIE/SPP bits into trap_ctx->regs.mstatus. The final
  trap exit writes CSR_MSTATUS from the trap context, so stale mstatus
  bits can otherwise clear S-mode interrupt enable and leave a pending
  SEIP undelivered.

Signed-off-by: Raymond Mao <raymond.mao at riscstar.com>
---
 include/sbi/sbi_domain.h         |   2 +
 include/sbi/sbi_domain_context.h |  24 +++++
 lib/sbi/sbi_domain_context.c     | 152 ++++++++++++++++++++++++++++++-
 lib/sbi/sbi_trap.c               |  16 ++++
 4 files changed, 192 insertions(+), 2 deletions(-)

diff --git a/include/sbi/sbi_domain.h b/include/sbi/sbi_domain.h
index 16edd4ce..c507023c 100644
--- a/include/sbi/sbi_domain.h
+++ b/include/sbi/sbi_domain.h
@@ -217,6 +217,8 @@ struct sbi_domain {
 	bool system_suspend_allowed;
 	/** Identifies whether to include the firmware region */
 	bool fw_region_inited;
+	/** Whether to notify S-mode for VIRQ couriering */
+	bool virq_seip_notify;
 };
 
 /** The root domain instance */
diff --git a/include/sbi/sbi_domain_context.h b/include/sbi/sbi_domain_context.h
index 31a3a7f8..88450fcb 100644
--- a/include/sbi/sbi_domain_context.h
+++ b/include/sbi/sbi_domain_context.h
@@ -28,6 +28,30 @@ int sbi_domain_context_enter(struct sbi_domain *dom);
  */
 int sbi_domain_context_exit(void);
 
+/**
+ * Exit the current domain context and return to the previous context
+ * if one exists. This will not attempt to start other domains.
+ *
+ * @return 0 on success and negative error code on failure
+ */
+int sbi_domain_context_exit_to_prev(void);
+
+void sbi_domain_context_request_return_to_prev(void);
+bool sbi_domain_context_need_return_to_prev(void);
+void sbi_domain_context_mark_switched(void);
+bool sbi_domain_context_consume_switched(void);
+
+/**
+ * Mark a pending S-mode notification for a target domain context.
+ *
+ * @param dom pointer to domain
+ * @param hartindex hart index
+ *
+ * @return true if notification was already pending, false otherwise
+ */
+bool sbi_domain_context_pending_notify_smode(struct sbi_domain *dom,
+					     u32 hartindex);
+
 /**
  * Initialize domain context support
  *
diff --git a/lib/sbi/sbi_domain_context.c b/lib/sbi/sbi_domain_context.c
index 158f4990..ee84b2f1 100644
--- a/lib/sbi/sbi_domain_context.c
+++ b/lib/sbi/sbi_domain_context.c
@@ -12,6 +12,7 @@
 #include <sbi/sbi_hart.h>
 #include <sbi/sbi_hart_protection.h>
 #include <sbi/sbi_heap.h>
+#include <sbi/sbi_irqchip.h>
 #include <sbi/sbi_scratch.h>
 #include <sbi/sbi_string.h>
 #include <sbi/sbi_domain.h>
@@ -42,6 +43,8 @@ struct hart_context {
 	unsigned long sip;
 	/** Supervisor address translation and protection register */
 	unsigned long satp;
+	/** Machine interrupt delegation register */
+	unsigned long mideleg;
 	/** Counter-enable register */
 	unsigned long scounteren;
 	/** Supervisor environment configuration register */
@@ -55,9 +58,13 @@ struct hart_context {
 	struct hart_context *prev_ctx;
 	/** Is context initialized and runnable */
 	bool initialized;
+	/** Pending S-mode notification to deliver after switch */
+	bool smode_notify_pending;
 };
 
 static struct sbi_domain_data dcpriv;
+static unsigned long sbi_domain_defer_return_mask;
+static unsigned long sbi_domain_switched_mask;
 
 static inline struct hart_context *hart_context_get(struct sbi_domain *dom,
 						    u32 hartindex)
@@ -126,16 +133,32 @@ static int switch_to_next_domain_context(struct hart_context *ctx,
 	sbi_hart_protection_unconfigure(scratch);
 	sbi_hart_protection_configure(scratch);
 
-	/* Save current CSR context and restore target domain's CSR context */
+	/*
+	 * Save current CSR context and restore target domain's CSR context.
+	 *
+	 * If the trap came from S-mode (MPP=S), MEPC holds the S-mode return
+	 * point. In that case, save MEPC as the SEPC for the current domain
+	 * so returning resumes correctly after a VIRQ-driven domain switch.
+	 */
 	ctx->sstatus	= csr_swap(CSR_SSTATUS, dom_ctx->sstatus);
 	ctx->sie	= csr_swap(CSR_SIE, dom_ctx->sie);
 	ctx->stvec	= csr_swap(CSR_STVEC, dom_ctx->stvec);
 	ctx->sscratch	= csr_swap(CSR_SSCRATCH, dom_ctx->sscratch);
-	ctx->sepc	= csr_swap(CSR_SEPC, dom_ctx->sepc);
+	{
+		unsigned long cur_sepc = csr_read(CSR_SEPC);
+
+		if (((csr_read(CSR_MSTATUS) & MSTATUS_MPP) >>
+		     MSTATUS_MPP_SHIFT) == PRV_S)
+			cur_sepc = csr_read(CSR_MEPC);
+		ctx->sepc = cur_sepc;
+		csr_write(CSR_SEPC, dom_ctx->sepc);
+	}
 	ctx->scause	= csr_swap(CSR_SCAUSE, dom_ctx->scause);
 	ctx->stval	= csr_swap(CSR_STVAL, dom_ctx->stval);
 	ctx->sip	= csr_swap(CSR_SIP, dom_ctx->sip);
 	ctx->satp	= csr_swap(CSR_SATP, dom_ctx->satp);
+	if (misa_extension('S'))
+		ctx->mideleg = csr_swap(CSR_MIDELEG, dom_ctx->mideleg);
 	if (sbi_hart_priv_version(scratch) >= SBI_HART_PRIV_VER_1_10)
 		ctx->scounteren = csr_swap(CSR_SCOUNTEREN, dom_ctx->scounteren);
 	if (sbi_hart_priv_version(scratch) >= SBI_HART_PRIV_VER_1_12)
@@ -146,7 +169,38 @@ static int switch_to_next_domain_context(struct hart_context *ctx,
 	/* Save current trap state and restore target domain's trap state */
 	trap_ctx = sbi_trap_get_context(scratch);
 	sbi_memcpy(&ctx->trap_ctx, trap_ctx, sizeof(*trap_ctx));
+	if (((csr_read(CSR_MSTATUS) & MSTATUS_MPP) >> MSTATUS_MPP_SHIFT) ==
+	    PRV_S) {
+		/* Preserve S-mode return PC in the saved trap context */
+		ctx->trap_ctx.regs.mepc = ctx->sepc;
+	}
+	/* Ensure M-mode trap context fields are refreshed */
+	ctx->trap_ctx.regs.mepc = csr_read(CSR_MEPC);
+	ctx->trap_ctx.regs.mstatus = csr_read(CSR_MSTATUS);
 	sbi_memcpy(trap_ctx, &dom_ctx->trap_ctx, sizeof(*trap_ctx));
+	if (((csr_read(CSR_MSTATUS) & MSTATUS_MPP) >> MSTATUS_MPP_SHIFT) ==
+	    PRV_S) {
+		/* Ensure target trap context returns to its S-mode PC */
+		trap_ctx->regs.mepc = dom_ctx->sepc;
+	}
+	if (target_dom->next_mode == PRV_S) {
+		trap_ctx->regs.mstatus &= ~MSTATUS_MPP;
+		trap_ctx->regs.mstatus |= (PRV_S << MSTATUS_MPP_SHIFT);
+		trap_ctx->regs.mstatus &= ~(SSTATUS_SIE | SSTATUS_SPIE |
+					    SSTATUS_SPP);
+		trap_ctx->regs.mstatus |= csr_read(CSR_SSTATUS) &
+					  (SSTATUS_SIE | SSTATUS_SPIE |
+					   SSTATUS_SPP);
+	}
+	/* Keep CSR_MEPC aligned with the active trap context */
+	csr_write(CSR_MEPC, trap_ctx->regs.mepc);
+
+	/* Deliver pending S-mode notification after switching context */
+	if (dom_ctx->smode_notify_pending) {
+		if (!sbi_irqchip_notify_smode_get())
+			sbi_irqchip_notify_smode_set();
+		dom_ctx->smode_notify_pending = false;
+	}
 
 	/* Mark current context structure initialized because context saved */
 	ctx->initialized = true;
@@ -163,6 +217,7 @@ static int switch_to_next_domain_context(struct hart_context *ctx,
 		else
 			sbi_hsm_hart_stop(scratch, true);
 	}
+	sbi_domain_context_mark_switched();
 	return 0;
 }
 
@@ -182,6 +237,19 @@ static int hart_context_init(u32 hartindex)
 
 		/* Bind context and domain */
 		ctx->dom = dom;
+		/*
+		 * Default MIDELEG policy: root domain keeps SEI delegated;
+		 * non-root domains keep SEI delegated only when VIRQ uses
+		 * mip.SEIP for notification.
+		 */
+		if (misa_extension('S')) {
+			unsigned long mideleg = csr_read(CSR_MIDELEG);
+
+			if (dom == &root || dom->virq_seip_notify)
+				ctx->mideleg = mideleg | MIP_SEIP;
+			else
+				ctx->mideleg = mideleg & ~MIP_SEIP;
+		}
 		hart_context_set(dom, hartindex, ctx);
 	}
 
@@ -271,6 +339,86 @@ int sbi_domain_context_exit(void)
 	return switch_to_next_domain_context(ctx, dom_ctx);
 }
 
+int sbi_domain_context_exit_to_prev(void)
+{
+	struct hart_context *ctx = hart_context_thishart_get();
+	struct hart_context *dom_ctx;
+
+	if (!ctx)
+		return SBI_EINVAL;
+
+	dom_ctx = ctx->prev_ctx;
+	if (!dom_ctx)
+		return SBI_ENOENT;
+
+	/*
+	 * Returning to a previous domain implies it has already executed,
+	 * so its context is runnable even if not marked initialized.
+	 */
+	dom_ctx->initialized = true;
+
+	/* Clear prev context to avoid unintended re-entry */
+	ctx->prev_ctx = NULL;
+
+	return switch_to_next_domain_context(ctx, dom_ctx);
+}
+
+void sbi_domain_context_request_return_to_prev(void)
+{
+	sbi_domain_defer_return_mask |= (1UL << current_hartindex());
+}
+
+bool sbi_domain_context_need_return_to_prev(void)
+{
+	u32 hartindex = current_hartindex();
+	bool need = !!(sbi_domain_defer_return_mask & (1UL << hartindex));
+
+	if (need)
+		sbi_domain_defer_return_mask &= ~(1UL << hartindex);
+
+	return need;
+}
+
+void sbi_domain_context_mark_switched(void)
+{
+	sbi_domain_switched_mask |= (1UL << current_hartindex());
+}
+
+bool sbi_domain_context_consume_switched(void)
+{
+	u32 hartindex = current_hartindex();
+	bool switched = !!(sbi_domain_switched_mask & (1UL << hartindex));
+
+	if (switched)
+		sbi_domain_switched_mask &= ~(1UL << hartindex);
+
+	return switched;
+}
+
+bool sbi_domain_context_pending_notify_smode(struct sbi_domain *dom,
+					      u32 hartindex)
+{
+	struct hart_context *ctx;
+	bool already;
+
+	if (!dom)
+		return false;
+
+	ctx = hart_context_get(dom, hartindex);
+	if (!ctx) {
+		if (hart_context_init(hartindex))
+			return false;
+		ctx = hart_context_get(dom, hartindex);
+		if (!ctx)
+			return false;
+	}
+
+	already = ctx->smode_notify_pending;
+	ctx->smode_notify_pending = true;
+
+	return already;
+}
+
 int sbi_domain_context_init(void)
 {
 	/**
diff --git a/lib/sbi/sbi_trap.c b/lib/sbi/sbi_trap.c
index f41db4d1..79d9ca5a 100644
--- a/lib/sbi/sbi_trap.c
+++ b/lib/sbi/sbi_trap.c
@@ -24,6 +24,7 @@
 #include <sbi/sbi_sse.h>
 #include <sbi/sbi_timer.h>
 #include <sbi/sbi_trap.h>
+#include <sbi/sbi_domain_context.h>
 
 static void sbi_trap_error_one(const struct sbi_trap_context *tcntx,
 			       const char *prefix, u32 hartid, u32 depth)
@@ -372,6 +373,21 @@ trap_done:
 	if (sbi_mstatus_prev_mode(regs->mstatus) != PRV_M)
 		sbi_sse_process_pending_events(regs);
 
+	if (sbi_domain_context_need_return_to_prev()) {
+		int rc = sbi_domain_context_exit_to_prev();
+
+		if (rc && rc != SBI_ENOENT)
+			sbi_printf("return_to_prev failed, rc=%d\n",
+				   rc);
+	}
+
+	if (sbi_domain_context_consume_switched()) {
+		struct sbi_trap_context *newctx = sbi_trap_get_context(scratch);
+
+		sbi_trap_set_context(scratch, newctx->prev_context);
+		return newctx;
+	}
+
 	sbi_trap_set_context(scratch, tcntx->prev_context);
 	return tcntx;
 }
-- 
2.25.1




More information about the opensbi mailing list