OpenSBI Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework
@ 2026-10-29 15:59 Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 01/12] lib: utils: add RPMI SYSTEM_IRQ service support Raymond Mao
                   ` (11 more replies)
  0 siblings, 12 replies; 13+ messages in thread
From: Raymond Mao @ 2026-10-29 15:59 UTC (permalink / raw)
  To: opensbi
  Cc: anup, anup.patel, scott, raymond.mao, robin.randhawa,
	samuel.holland, peter.lin

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

This series introduces a Virtual IRQ (VIRQ) framework for OpenSBI to
support domain-aware wired interrupt couriering.

The main use case is to let OpenSBI receive a host HWIRQ in M-mode,
map it into a per-channel VIRQ number space, route it to a target
domain, notify the target S-mode payload, and let that payload
claim the pending VIRQs through RPMI SYSTEM_IRQ over SBI MPXY.

The implementation is organized as follows:

  - add RPMI SYSTEM_IRQ service support, including SYSIRQ_CLAIM
  - add an optional experimental Yield ecall for explicit completion
  - add irqchip helpers for S-mode pending notification
  - extend domain context switching to support VIRQ-driven
    cross-domain couriering and return-to-previous-domain flow
  - add the core VIRQ mapping, routing, and per-(domain,hart)
    pending queue framework
  - parse sysirq routing rules from DT under /chosen/opensbi-domains
  - derive APLIC target hart routing from sysirq nodes
  - update the irqchip/APLIC path to support deferred completion and
    per-HWIRQ APLIC targets
  - select domain boot harts before activating physical sources
  - document the DT binding and routing rules

The DT routing model is based on "opensbi,mpxy-sysirq" nodes.
Each interrupts-extended entry contributes one routed physical source,
its entry index becomes the VIRQ number within the selected
opensbi,mpxy-channel-id, and opensbi,domain selects the destination
OpenSBI domain.

Claimed sources remain masked while the payload handles their device
conditions. The next valid CLAIM completes the previous batch, or the
payload can complete it explicitly through Yield. Return to a previously
interrupted domain is deferred until pending and claimed work is drained.
The Yield EID is experimental and is not an official SBI assignment.

This version reuses the existing irqchip infrastructure.

Patch 12 contains sample DT overlays for reference only and is marked
[NOT-FOR-UPSTREAM].

The series are also placed at:
https://gitlab.com/riseproject/riscv-optee/opensbi/-/tree/rp022-m1

Test instructions are available at:
https://riscv-optee-docs-c9fea0.gitlab.io/rp022/milestone1/

Changes in v2:

  - Rebase on master and adapt to its irqchip interfaces, dropping the
    old scratch-local provider lookup workaround.
  - Replace the private VIRQ POP/COMPLETE ecall extension with RPMI
    SYSTEM_IRQ 2.0 services over SBI MPXY, including batched SYSIRQ_CLAIM.
  - Add an optional experimental Yield ecall for explicit completion
    after device handling; retain implicit completion through the next
    valid CLAIM.
  - Track outstanding claims per channel and target, preserve failed
    completions for retry, and block return switches until all pending
    and claimed work is drained.
  - Keep SEPC separate from the M-mode trap frame's MEPC and preserve
    the advanced ecall return state across domain switches.
  - Key routes by (chip_uid, hwirq), allowing identical HWIRQ numbers
    on different physical controllers without routing collisions.
  - Select boot harts during domain finalization and activate couriers
    only after boot-hart selection and all mapping/channel setup.
  - Add irqchip-independent courier registration with DT IRQ flags,
    target affinity and rollback; use APLIC IDC indices for affinity.
  - Reject malformed DT argument lists, extra IRQ specifier cells and
    invalid scalar properties; update the binding documentation.

Raymond Mao (12):
  lib: utils: add RPMI SYSTEM_IRQ service support
  lib: sbi: add experimental Yield ECALL extension
  lib: irqchip: add S-mode notification helpers
  lib: sbi: domain: support VIRQ couriering domain context switch
  lib: sbi: domain: ensure boot_hartid is assigned
  lib: sbi: Add Virtual IRQ (VIRQ) subsystem
  lib: sbi: domain: add domain lookup by name
  lib: utils: irqchip: derive APLIC targets from sysirq nodes
  lib: utils: irqchip: Add helper to register VIRQ courier handler
  lib: utils: fdt: parse sysirq routing from DT
  docs: domain: document sysirq VIRQ mapping and routing rules
  [NOT-FOR-UPSTREAM] platform: generic: Add sample dts overlays for
    testing hwirq/domain route rules

 docs/domain_support.md                        |   55 +
 include/sbi/sbi_domain.h                      |    7 +
 include/sbi/sbi_domain_context.h              |   24 +
 include/sbi/sbi_ecall_interface.h             |    6 +
 include/sbi/sbi_irqchip.h                     |    9 +
 include/sbi/sbi_virq.h                        |  513 +++++++
 include/sbi_utils/fdt/fdt_helper.h            |    8 +
 include/sbi_utils/irqchip/aplic.h             |    4 +
 include/sbi_utils/mailbox/rpmi_msgprot.h      |   25 +
 include/sbi_utils/sysirq/rpmi_sysirq.h        |   30 +
 lib/sbi/Kconfig                               |    9 +
 lib/sbi/objects.mk                            |    4 +
 lib/sbi/sbi_domain.c                          |   61 +
 lib/sbi/sbi_domain_context.c                  |  130 +-
 lib/sbi/sbi_ecall_yield.c                     |   42 +
 lib/sbi/sbi_irqchip.c                         |   21 +-
 lib/sbi/sbi_trap.c                            |   11 +
 lib/sbi/sbi_virq.c                            | 1235 +++++++++++++++++
 lib/utils/fdt/fdt_domain.c                    |  116 +-
 lib/utils/fdt/fdt_helper.c                    |   57 +-
 lib/utils/irqchip/aplic.c                     |   57 +-
 lib/utils/irqchip/fdt_irqchip_aplic.c         |  103 ++
 lib/utils/mpxy/objects.mk                     |    2 +
 lib/utils/sysirq/rpmi_sysirq.c                |  266 ++++
 platform/generic/virt/hwirq_bind_domain.dts   |  107 ++
 .../virt/hwirq_bind_domain_linux_bmapp.dts    |  125 ++
 26 files changed, 3017 insertions(+), 10 deletions(-)
 create mode 100644 include/sbi/sbi_virq.h
 create mode 100644 include/sbi_utils/sysirq/rpmi_sysirq.h
 create mode 100644 lib/sbi/sbi_ecall_yield.c
 create mode 100644 lib/sbi/sbi_virq.c
 create mode 100644 lib/utils/sysirq/rpmi_sysirq.c
 create mode 100644 platform/generic/virt/hwirq_bind_domain.dts
 create mode 100644 platform/generic/virt/hwirq_bind_domain_linux_bmapp.dts

-- 
2.25.1


-- 
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 01/12] lib: utils: add RPMI SYSTEM_IRQ service support
  2026-10-29 15:59 [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework Raymond Mao
@ 2026-10-29 15:59 ` Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 02/12] lib: sbi: add experimental Yield ECALL extension Raymond Mao
                   ` (10 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Raymond Mao @ 2026-10-29 15:59 UTC (permalink / raw)
  To: opensbi
  Cc: anup, anup.patel, scott, raymond.mao, robin.randhawa,
	samuel.holland, peter.lin

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

Provide an SBI MPXY implementation of the RPMI SYSTEM_IRQ service
group, version 2.0, with GET_ATTRIBUTES, GET_TARGET_ATTRIBUTES,
GET_IRQ_ATTRIBUTES and SYSIRQ_CLAIM. Other services return NOTSUPP.

Expose backend callbacks for claiming and completing IRQs, allowing
delivery implementations to register domain-owned channels. Target
indices enumerate possible harts and CLAIM is restricted to the
calling domain and hart.

Keep claimed sources outstanding until the next valid CLAIM, after the
payload has handled the previous batch. Preserve failed completion
records for retry. This completion convention introduces no new RPMI
service and leaves backend device handling outside the protocol layer.

Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
 include/sbi_utils/mailbox/rpmi_msgprot.h |  25 +++
 include/sbi_utils/sysirq/rpmi_sysirq.h   |  30 +++
 lib/utils/mpxy/objects.mk                |   2 +
 lib/utils/sysirq/rpmi_sysirq.c           | 266 +++++++++++++++++++++++
 4 files changed, 323 insertions(+)
 create mode 100644 include/sbi_utils/sysirq/rpmi_sysirq.h
 create mode 100644 lib/utils/sysirq/rpmi_sysirq.c

diff --git a/include/sbi_utils/mailbox/rpmi_msgprot.h b/include/sbi_utils/mailbox/rpmi_msgprot.h
index 7ae4abe0..f9d1d177 100644
--- a/include/sbi_utils/mailbox/rpmi_msgprot.h
+++ b/include/sbi_utils/mailbox/rpmi_msgprot.h
@@ -226,6 +226,7 @@ enum rpmi_servicegroup_id {
 	RPMI_SRVGRP_PERFORMANCE = 0x0000A,
 	RPMI_SRVGRP_MANAGEMENT_MODE = 0x000B,
 	RPMI_SRVGRP_LOGGING = 0x000E,
+	RPMI_SRVGRP_SYSTEM_IRQ = 0x000F,
 	RPMI_SRVGRP_ID_MAX_COUNT,
 
 	/* Reserved range for service groups */
@@ -290,6 +291,30 @@ struct rpmi_base_get_platform_info_resp {
 	char plat_info[];
 };
 
+/** RPMI System IRQ ServiceGroup Service IDs */
+enum rpmi_sysirq_service_id {
+	RPMI_SYSIRQ_SRV_ENABLE_NOTIFICATION = 0x01,
+	RPMI_SYSIRQ_SRV_GET_ATTRIBUTES = 0x02,
+	RPMI_SYSIRQ_SRV_GET_TARGET_ATTRIBUTES = 0x03,
+	RPMI_SYSIRQ_SRV_GET_IRQ_ATTRIBUTES = 0x04,
+	RPMI_SYSIRQ_SRV_SET_IRQ_STATE = 0x05,
+	RPMI_SYSIRQ_SRV_GET_IRQ_STATE = 0x06,
+	RPMI_SYSIRQ_SRV_SET_IRQ_TARGET = 0x07,
+	RPMI_SYSIRQ_SRV_GET_IRQ_TARGET = 0x08,
+	RPMI_SYSIRQ_SRV_CLAIM = 0x09,
+	RPMI_SYSIRQ_SRV_ID_MAX_COUNT,
+};
+
+struct rpmi_sysirq_claim_req {
+	u32 sys_target_index;
+};
+
+struct rpmi_sysirq_claim_resp {
+	s32 status;
+	u32 returned;
+	u32 sys_irq_index[];
+};
+
 /** RPMI System MSI ServiceGroup Service IDs */
 enum rpmi_sysmsi_service_id {
 	RPMI_SYSMSI_SRV_ENABLE_NOTIFICATION = 0x01,
diff --git a/include/sbi_utils/sysirq/rpmi_sysirq.h b/include/sbi_utils/sysirq/rpmi_sysirq.h
new file mode 100644
index 00000000..0af2f33b
--- /dev/null
+++ b/include/sbi_utils/sysirq/rpmi_sysirq.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Copyright (c) 2026 RISCstar Solutions Corporation.
+ *
+ * Author: Raymond Mao <raymond.mao@riscstar.com>
+ */
+
+#ifndef __RPMI_SYSIRQ_H__
+#define __RPMI_SYSIRQ_H__
+
+#include <sbi/sbi_types.h>
+
+struct sbi_domain;
+
+/* Callbacks operate on the calling domain and hart; ops must remain valid. */
+struct rpmi_sysirq_ops {
+	int (*claim)(u32 channel_id, u32 *irqs, u32 max_count, u32 *count);
+	int (*complete)(u32 channel_id, u32 irq);
+	void (*drained)(void);
+};
+
+int rpmi_sysirq_register_channel(struct sbi_domain *dom, u32 channel_id,
+				u32 num_irqs, const struct rpmi_sysirq_ops *ops);
+
+bool rpmi_sysirq_available(void);
+
+/* Complete this domain/hart's batches, then ask backends to consider return. */
+int rpmi_sysirq_complete_thishart(u32 *completed);
+
+#endif
diff --git a/lib/utils/mpxy/objects.mk b/lib/utils/mpxy/objects.mk
index 1f1bf0a0..a59a7175 100644
--- a/lib/utils/mpxy/objects.mk
+++ b/lib/utils/mpxy/objects.mk
@@ -32,3 +32,5 @@ libsbiutils-objs-$(CONFIG_FDT_MPXY_RPMI_MM) += mpxy/fdt_mpxy_rpmi_mm.o
 
 carray-fdt_mpxy_drivers-$(CONFIG_FDT_MPXY_RPMI_LOGGING) += fdt_mpxy_rpmi_logging
 libsbiutils-objs-$(CONFIG_FDT_MPXY_RPMI_LOGGING) += mpxy/fdt_mpxy_rpmi_logging.o
+
+libsbiutils-objs-y += sysirq/rpmi_sysirq.o
diff --git a/lib/utils/sysirq/rpmi_sysirq.c b/lib/utils/sysirq/rpmi_sysirq.c
new file mode 100644
index 00000000..8fa37887
--- /dev/null
+++ b/lib/utils/sysirq/rpmi_sysirq.c
@@ -0,0 +1,266 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/*
+ * Copyright (c) 2026 RISCstar Solutions Corporation.
+ *
+ * Author: Raymond Mao <raymond.mao@riscstar.com>
+ */
+
+#include <sbi/sbi_byteorder.h>
+#include <sbi/sbi_console.h>
+#include <sbi/sbi_domain.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_heap.h>
+#include <sbi/sbi_mpxy.h>
+#include <sbi/sbi_scratch.h>
+#include <sbi/sbi_string.h>
+#include <sbi_utils/mailbox/rpmi_msgprot.h>
+#include <sbi_utils/sysirq/rpmi_sysirq.h>
+
+#define SYSIRQ_BATCH_MAX 63U
+#define SYSIRQ_MSG_MAX_LEN ((SYSIRQ_BATCH_MAX + 2) * sizeof(u32))
+#define SYSIRQ_ATTR_RESPONSE_LEN (8 * sizeof(u32))
+
+struct rpmi_sysirq_target {
+	u32 count;
+	u32 claimed[SYSIRQ_BATCH_MAX];
+};
+
+struct rpmi_sysirq_channel {
+	struct sbi_dlist node;
+	struct sbi_mpxy_channel channel;
+	const struct rpmi_sysirq_ops *ops;
+	u32 num_irqs;
+	struct rpmi_sysirq_target targets[];
+};
+
+static SBI_LIST_HEAD(sysirq_channels);
+
+static int sysirq_complete_batch(struct rpmi_sysirq_channel *sc,
+				 struct rpmi_sysirq_target *target,
+				 u32 *completed)
+{
+	u32 irq;
+	int rc;
+
+	while (target->count) {
+		irq = target->claimed[target->count - 1];
+		rc = sc->ops->complete(sc->channel.channel_id, irq);
+		if (rc)
+			return rc;
+		target->count--;
+		(*completed)++;
+	}
+	return SBI_OK;
+}
+
+bool rpmi_sysirq_available(void)
+{
+	return !sbi_list_empty(&sysirq_channels);
+}
+
+int rpmi_sysirq_complete_thishart(u32 *completed)
+{
+	struct sbi_domain *dom = sbi_domain_thishart_ptr();
+	struct rpmi_sysirq_channel *sc;
+	u32 index = 0, hidx;
+	int rc;
+
+	if (!dom || !completed)
+		return SBI_EINVAL;
+	*completed = 0;
+	sbi_hartmask_for_each_hartindex(hidx, dom->possible_harts) {
+		if (hidx == current_hartindex())
+			break;
+		index++;
+	}
+	if (index == sbi_hartmask_weight(dom->possible_harts))
+		return SBI_EINVAL;
+
+	sbi_list_for_each_entry(sc, &sysirq_channels, node) {
+		if (sc->channel.owner_domain != dom)
+			continue;
+		rc = sysirq_complete_batch(sc, &sc->targets[index], completed);
+		if (rc)
+			return rc;
+	}
+
+	/* Only consider return once every owned channel completed successfully. */
+	sbi_list_for_each_entry(sc, &sysirq_channels, node) {
+		if (sc->channel.owner_domain == dom && sc->ops->drained)
+			sc->ops->drained();
+	}
+	return SBI_OK;
+}
+
+static int sysirq_read_attributes(struct sbi_mpxy_channel *channel,
+				  u32 *out, u32 base, u32 count)
+{
+	u32 attrs[] = { RPMI_SRVGRP_SYSTEM_IRQ, RPMI_VERSION(2, 0), 0, 0 };
+	u32 index, i;
+
+	if (base < SBI_MPXY_ATTR_MSGPROTO_ATTR_START)
+		return SBI_EBAD_RANGE;
+	index = base - SBI_MPXY_ATTR_MSGPROTO_ATTR_START;
+	if (index >= array_size(attrs) || count > array_size(attrs) - index)
+		return SBI_EBAD_RANGE;
+
+	for (i = 0; i < count; i++)
+		out[i] = cpu_to_le32(attrs[index + i]);
+
+	return SBI_OK;
+}
+
+static u32 sysirq_target_hartindex(struct sbi_domain *dom, u32 target)
+{
+	u32 index = 0, hidx;
+
+	sbi_hartmask_for_each_hartindex(hidx, dom->possible_harts) {
+		if (index++ == target)
+			return hidx;
+	}
+
+	return -1U;
+}
+
+static int sysirq_send_message(struct sbi_mpxy_channel *channel,
+			       u32 msg_id, void *msg, u32 msg_len,
+			       void *response, u32 response_max_len,
+			       unsigned long *response_len)
+{
+	struct rpmi_sysirq_channel *sc =
+		container_of(channel, struct rpmi_sysirq_channel, channel);
+	struct sbi_domain *dom = channel->owner_domain;
+	struct rpmi_sysirq_target *target;
+	u32 *out = response;
+	u32 index = 0, hartindex, count, max_count, i, completed = 0;
+	s32 status = RPMI_SUCCESS;
+	int rc;
+
+	if (!response || !response_len || response_max_len < sizeof(u32))
+		return SBI_EINVAL;
+	*response_len = sizeof(u32);
+	if (sbi_domain_thishart_ptr() != dom) {
+		status = RPMI_ERR_DENIED;
+		goto done;
+	}
+
+	switch (msg_id) {
+	case RPMI_SYSIRQ_SRV_GET_ATTRIBUTES:
+		if (msg_len) {
+			status = RPMI_ERR_INVALID_PARAM;
+			break;
+		}
+		if (response_max_len < SYSIRQ_ATTR_RESPONSE_LEN)
+			return SBI_EINVAL;
+		sbi_memset(out, 0, SYSIRQ_ATTR_RESPONSE_LEN);
+		out[1] = cpu_to_le32(sbi_hartmask_weight(dom->possible_harts));
+		out[2] = cpu_to_le32(sc->num_irqs);
+		*response_len = SYSIRQ_ATTR_RESPONSE_LEN;
+		break;
+	case RPMI_SYSIRQ_SRV_GET_TARGET_ATTRIBUTES:
+	case RPMI_SYSIRQ_SRV_GET_IRQ_ATTRIBUTES:
+	case RPMI_SYSIRQ_SRV_CLAIM:
+		if (!msg || msg_len != sizeof(u32)) {
+			status = RPMI_ERR_INVALID_PARAM;
+			break;
+		}
+		/* MPXY uses the same shared buffer for request and response. */
+		index = le32_to_cpu(*(u32 *)msg);
+		if (msg_id == RPMI_SYSIRQ_SRV_GET_IRQ_ATTRIBUTES) {
+			if (index >= sc->num_irqs) {
+				status = RPMI_ERR_INVALID_PARAM;
+				break;
+			}
+			if (response_max_len < SYSIRQ_ATTR_RESPONSE_LEN)
+				return SBI_EINVAL;
+			sbi_memset(out, 0, SYSIRQ_ATTR_RESPONSE_LEN);
+			sbi_snprintf((char *)&out[3], 20, "virq%u", index);
+			*response_len = SYSIRQ_ATTR_RESPONSE_LEN;
+			break;
+		}
+		hartindex = sysirq_target_hartindex(dom, index);
+		if (!sbi_hartindex_valid(hartindex)) {
+			status = RPMI_ERR_INVALID_PARAM;
+			break;
+		}
+		if (msg_id == RPMI_SYSIRQ_SRV_GET_TARGET_ATTRIBUTES) {
+			if (response_max_len < SYSIRQ_ATTR_RESPONSE_LEN)
+				return SBI_EINVAL;
+			sbi_memset(out, 0, SYSIRQ_ATTR_RESPONSE_LEN);
+			out[2] = cpu_to_le32(sbi_hartindex_to_hartid(hartindex));
+			sbi_snprintf((char *)&out[3], 20, "hart%u",
+				     sbi_hartindex_to_hartid(hartindex));
+			*response_len = SYSIRQ_ATTR_RESPONSE_LEN;
+			break;
+		}
+		if (hartindex != current_hartindex()) {
+			status = RPMI_ERR_DENIED;
+			break;
+		}
+		if (response_max_len > channel->attrs.msg_data_maxlen)
+			response_max_len = channel->attrs.msg_data_maxlen;
+		if (response_max_len < 3 * sizeof(u32))
+			return SBI_EINVAL;
+		max_count = response_max_len / sizeof(u32) - 2;
+		target = &sc->targets[index];
+		if (max_count > array_size(target->claimed))
+			max_count = array_size(target->claimed);
+		/* A new CLAIM follows device handling of the previous batch. */
+		rc = sysirq_complete_batch(sc, target, &completed);
+		if (rc) {
+			status = RPMI_ERR_FAILED;
+			goto done;
+		}
+		rc = sc->ops->claim(channel->channel_id, target->claimed,
+				    max_count, &count);
+		if (rc) {
+			status = RPMI_ERR_FAILED;
+			break;
+		}
+		target->count = count;
+		out[1] = cpu_to_le32(count);
+		for (i = 0; i < count; i++)
+			out[i + 2] = cpu_to_le32(target->claimed[i]);
+		*response_len = (count + 2) * sizeof(u32);
+		break;
+	default:
+		status = RPMI_ERR_NOTSUPP;
+		break;
+	}
+
+done:
+	out[0] = cpu_to_le32((u32)status);
+	return SBI_OK;
+}
+
+int rpmi_sysirq_register_channel(struct sbi_domain *dom, u32 channel_id,
+				u32 num_irqs, const struct rpmi_sysirq_ops *ops)
+{
+	struct rpmi_sysirq_channel *sc;
+	int rc;
+
+	if (!dom || !dom->possible_harts || !num_irqs || !ops ||
+	    !ops->claim || !ops->complete)
+		return SBI_EINVAL;
+
+	sc = sbi_zalloc(sizeof(*sc) +
+			sbi_hartmask_weight(dom->possible_harts) * sizeof(sc->targets[0]));
+	if (!sc)
+		return SBI_ENOMEM;
+	sc->ops = ops;
+	sc->num_irqs = num_irqs;
+	sc->channel.channel_id = channel_id;
+	sc->channel.owner_domain = dom;
+	sc->channel.attrs.msg_proto_id = SBI_MPXY_MSGPROTO_RPMI_ID;
+	sc->channel.attrs.msg_proto_version = RPMI_VERSION(2, 0);
+	sc->channel.attrs.msg_data_maxlen = SYSIRQ_MSG_MAX_LEN;
+	sc->channel.read_attributes = sysirq_read_attributes;
+	sc->channel.send_message_with_response = sysirq_send_message;
+	rc = sbi_mpxy_register_channel(&sc->channel);
+	if (rc)
+		sbi_free(sc);
+	else
+		sbi_list_add_tail(&sc->node, &sysirq_channels);
+
+	return rc;
+}
-- 
2.25.1


-- 
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 02/12] lib: sbi: add experimental Yield ECALL extension
  2026-10-29 15:59 [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 01/12] lib: utils: add RPMI SYSTEM_IRQ service support Raymond Mao
@ 2026-10-29 15:59 ` Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 03/12] lib: irqchip: add S-mode notification helpers Raymond Mao
                   ` (9 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Raymond Mao @ 2026-10-29 15:59 UTC (permalink / raw)
  To: opensbi
  Cc: anup, anup.patel, scott, raymond.mao, robin.randhawa,
	samuel.holland, peter.lin

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

Add a Yield extension with EID 0x0800594c and FID 0.
The ID is experimental and is not an official SBI assignment.

Complete the calling domain and hart's outstanding SYSIRQ batches
using the same completion mechanism as the next CLAIM. Return the number
completed, including partial progress on error, without claiming new IRQs.
Ask the backend to consider a drained return only after completion
succeeds.

Enable the extension with CONFIG_SBI_ECALL_YIELD and register it only
when SYSIRQ channels exist.

Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
 include/sbi/sbi_ecall_interface.h |  6 +++++
 lib/sbi/Kconfig                   |  9 +++++++
 lib/sbi/objects.mk                |  3 +++
 lib/sbi/sbi_ecall_yield.c         | 42 +++++++++++++++++++++++++++++++
 4 files changed, 60 insertions(+)
 create mode 100644 lib/sbi/sbi_ecall_yield.c

diff --git a/include/sbi/sbi_ecall_interface.h b/include/sbi/sbi_ecall_interface.h
index fd4e77ca..287d3151 100644
--- a/include/sbi/sbi_ecall_interface.h
+++ b/include/sbi/sbi_ecall_interface.h
@@ -39,6 +39,9 @@
 #define SBI_EXT_FWFT				0x46574654
 #define SBI_EXT_MPXY				0x4D505859
 
+/* SBI extension ID for Yield. */
+#define SBI_EXT_YIELD				0x0800594c
+
 /* SBI function IDs for BASE extension*/
 #define SBI_EXT_BASE_GET_SPEC_VERSION		0x0
 #define SBI_EXT_BASE_GET_IMP_ID			0x1
@@ -122,6 +125,9 @@
 #define SBI_EXT_DBTR_TRIGGER_ENABLE	0x6
 #define SBI_EXT_DBTR_TRIGGER_DISABLE	0x7
 
+/* SBI function IDs for the Yield extension. */
+#define SBI_EXT_YIELD_YIELD	0x0
+
 /* SBI function IDs for FW feature extension */
 #define SBI_EXT_FWFT_SET		0x0
 #define SBI_EXT_FWFT_GET		0x1
diff --git a/lib/sbi/Kconfig b/lib/sbi/Kconfig
index 24132eaa..894ce22f 100644
--- a/lib/sbi/Kconfig
+++ b/lib/sbi/Kconfig
@@ -76,6 +76,15 @@ config SBI_ECALL_LEGACY
 	bool "SBI v0.1 legacy extensions"
 	default y
 
+config SBI_ECALL_YIELD
+	bool "Yield extension"
+	default y
+	help
+	  Enable the Yield ECALL extension.
+	  Yield completes the calling domain and hart's claimed SYSIRQs.
+	  A delivery backend may request a context return once work is drained.
+	  Interrupt claiming still uses RPMI SYSTEM_IRQ through SBI MPXY.
+
 config SBI_ECALL_VENDOR
 	bool "Platform-defined vendor extensions"
 	default y
diff --git a/lib/sbi/objects.mk b/lib/sbi/objects.mk
index ae27d0c5..16c141fe 100644
--- a/lib/sbi/objects.mk
+++ b/lib/sbi/objects.mk
@@ -64,6 +64,9 @@ libsbi-objs-$(CONFIG_SBI_ECALL_SSE) += sbi_ecall_sse.o
 carray-sbi_ecall_exts-$(CONFIG_SBI_ECALL_MPXY) += ecall_mpxy
 libsbi-objs-$(CONFIG_SBI_ECALL_MPXY) += sbi_ecall_mpxy.o
 
+carray-sbi_ecall_exts-$(CONFIG_SBI_ECALL_YIELD) += ecall_yield
+libsbi-objs-$(CONFIG_SBI_ECALL_YIELD) += sbi_ecall_yield.o
+
 libsbi-objs-y += sbi_bitmap.o
 libsbi-objs-y += sbi_bitops.o
 libsbi-objs-y += sbi_console.o
diff --git a/lib/sbi/sbi_ecall_yield.c b/lib/sbi/sbi_ecall_yield.c
new file mode 100644
index 00000000..fddc2d6c
--- /dev/null
+++ b/lib/sbi/sbi_ecall_yield.c
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/*
+ * Copyright (c) 2026 RISCstar Solutions Corporation.
+ *
+ * Author: Raymond Mao <raymond.mao@riscstar.com>
+ */
+
+#include <sbi/sbi_ecall.h>
+#include <sbi/sbi_ecall_interface.h>
+#include <sbi/sbi_error.h>
+#include <sbi_utils/sysirq/rpmi_sysirq.h>
+
+static int sbi_ecall_yield_handler(unsigned long extid, unsigned long funcid,
+				   struct sbi_trap_regs *regs,
+				   struct sbi_ecall_return *out)
+{
+	u32 completed = 0;
+	int rc;
+
+	if (funcid != SBI_EXT_YIELD_YIELD)
+		return SBI_ENOTSUPP;
+	rc = rpmi_sysirq_complete_thishart(&completed);
+	out->value = completed;
+	return rc;
+}
+
+struct sbi_ecall_extension ecall_yield;
+
+static int sbi_ecall_yield_register_extensions(void)
+{
+	if (!rpmi_sysirq_available())
+		return SBI_OK;
+	return sbi_ecall_register_extension(&ecall_yield);
+}
+
+struct sbi_ecall_extension ecall_yield = {
+	.name = "yield",
+	.extid_start = SBI_EXT_YIELD,
+	.extid_end = SBI_EXT_YIELD,
+	.register_extensions = sbi_ecall_yield_register_extensions,
+	.handle = sbi_ecall_yield_handler,
+};
-- 
2.25.1


-- 
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 03/12] lib: irqchip: add S-mode notification helpers
  2026-10-29 15:59 [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 01/12] lib: utils: add RPMI SYSTEM_IRQ service support Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 02/12] lib: sbi: add experimental Yield ECALL extension Raymond Mao
@ 2026-10-29 15:59 ` Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 04/12] lib: sbi: domain: support VIRQ couriering domain context switch Raymond Mao
                   ` (8 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Raymond Mao @ 2026-10-29 15:59 UTC (permalink / raw)
  To: opensbi
  Cc: anup, anup.patel, scott, raymond.mao, robin.randhawa,
	samuel.holland, peter.lin

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

Add irqchip helpers to set/clear S-mode notification (SEIP-based).
In addition to set/clear, expose a get API to read the current
notification state so upper layers can do edge-triggered notification.

Allow raw handlers to return SBI_EALREADY when physical completion is
deferred to the recipient, instead of issuing an immediate EOI. Preserve
ordinary completion behavior for other handler results.

Release handler storage when unregistering an interrupt handler so failed
courier setup can roll back without leaking memory.

Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
 include/sbi/sbi_irqchip.h |  9 +++++++++
 lib/sbi/sbi_irqchip.c     | 21 +++++++++++++++++++--
 2 files changed, 28 insertions(+), 2 deletions(-)

diff --git a/include/sbi/sbi_irqchip.h b/include/sbi/sbi_irqchip.h
index f5dbf1c7..174fa930 100644
--- a/include/sbi/sbi_irqchip.h
+++ b/include/sbi/sbi_irqchip.h
@@ -172,4 +172,13 @@ void sbi_irqchip_exit(struct sbi_scratch *scratch);
 bool sbi_irqchip_is_hwirq_enabled(struct sbi_irqchip_device *chip,
 				   u32 hwirq);
 
+/** Notify S-mode for a pending virtual interrupt on this hart. */
+int sbi_irqchip_notify_smode_set(void);
+
+/** Clear S-mode notification for virtual interrupts on this hart. */
+void sbi_irqchip_notify_smode_clear(void);
+
+/** Read S-mode notification state for virtual interrupts on this hart. */
+bool sbi_irqchip_notify_smode_get(void);
+
 #endif
diff --git a/lib/sbi/sbi_irqchip.c b/lib/sbi/sbi_irqchip.c
index 1664bd2d..f9afadc0 100644
--- a/lib/sbi/sbi_irqchip.c
+++ b/lib/sbi/sbi_irqchip.c
@@ -14,6 +14,7 @@
 #include <sbi/sbi_list.h>
 #include <sbi/sbi_platform.h>
 #include <sbi/sbi_scratch.h>
+#include <sbi/riscv_asm.h>
 
 /** Internal irqchip hardware interrupt data */
 struct sbi_irqchip_hwirq_data {
@@ -191,9 +192,8 @@ int sbi_irqchip_raw_handler_default(struct sbi_irqchip_device *chip, u32 hwirq)
 	if (h->callback)
 		rc = h->callback(hwirq, h->priv);
 
-	if (chip->hwirq_eoi)
+	if (chip->hwirq_eoi && rc != SBI_EALREADY)
 		chip->hwirq_eoi(chip, hwirq);
-
 	return rc;
 }
 
@@ -471,6 +471,7 @@ int sbi_irqchip_unregister_handler(struct sbi_irqchip_device *chip,
 	}
 
 	sbi_list_del(&fh->node);
+	sbi_free(fh);
 	return 0;
 }
 
@@ -618,3 +619,19 @@ skip_migrate:
 	if (hd && hd->chip && hd->chip->process_hwirqs)
 		csr_clear(CSR_MIE, MIP_MEIP);
 }
+
+int sbi_irqchip_notify_smode_set(void)
+{
+	csr_set(CSR_MIP, MIP_SEIP);
+	return 0;
+}
+
+void sbi_irqchip_notify_smode_clear(void)
+{
+	csr_clear(CSR_MIP, MIP_SEIP);
+}
+
+bool sbi_irqchip_notify_smode_get(void)
+{
+	return !!(csr_read(CSR_MIP) & MIP_SEIP);
+}
-- 
2.25.1


-- 
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 04/12] lib: sbi: domain: support VIRQ couriering domain context switch
  2026-10-29 15:59 [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework Raymond Mao
                   ` (2 preceding siblings ...)
  2026-10-29 15:59 ` [PATCH v2 03/12] lib: irqchip: add S-mode notification helpers Raymond Mao
@ 2026-10-29 15:59 ` Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 05/12] lib: sbi: domain: ensure boot_hartid is assigned Raymond Mao
                   ` (7 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Raymond Mao @ 2026-10-29 15:59 UTC (permalink / raw)
  To: opensbi
  Cc: anup, anup.patel, scott, raymond.mao, robin.randhawa,
	samuel.holland, peter.lin

From: Raymond Mao <raymond.mao@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 return domain-switch 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.
- Keep SEPC independent of the M-mode trap frame's MEPC and preserve
  the updated trap frame instead of replacing its return state with
  live CSRs. This prevents a resumed domain from repeating an ECALL
  whose result and advanced return PC have already been recorded.

Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
 include/sbi/sbi_domain.h         |   2 +
 include/sbi/sbi_domain_context.h |  24 ++++++
 lib/sbi/sbi_domain_context.c     | 130 ++++++++++++++++++++++++++++++-
 lib/sbi/sbi_trap.c               |  11 +++
 4 files changed, 166 insertions(+), 1 deletion(-)

diff --git a/include/sbi/sbi_domain.h b/include/sbi/sbi_domain.h
index 1f773e41..31cbe01e 100644
--- a/include/sbi/sbi_domain.h
+++ b/include/sbi/sbi_domain.h
@@ -219,6 +219,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 987ac7fa..fe7c3ede 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>
@@ -44,6 +45,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 */
@@ -62,9 +65,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_state dcstate;
+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)
@@ -120,6 +127,7 @@ static int switch_to_next_domain_context(struct hart_context *ctx,
 
 	current_dom = ctx->dom;
 	target_dom = dom_ctx->dom;
+
 	/* Assign current hart to target domain */
 	spin_lock(&current_dom->assigned_harts_lock);
 	sbi_hartmask_clear_hartindex(hartindex, &current_dom->assigned_harts);
@@ -131,7 +139,12 @@ static int switch_to_next_domain_context(struct hart_context *ctx,
 	sbi_hartmask_set_hartindex(hartindex, &target_dom->assigned_harts);
 	spin_unlock(&target_dom->assigned_harts_lock);
 
-	/* Save current CSR context and restore target domain's CSR context */
+	/*
+	 * Save current CSR context and restore target domain's CSR context.
+	 *
+	 * SEPC is the S-mode trap return PC, independent of the M-mode trap
+	 * frame's MEPC. Preserve it even when switching from an S-mode handler.
+	 */
 	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);
@@ -141,6 +154,8 @@ static int switch_to_next_domain_context(struct hart_context *ctx,
 	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)
@@ -163,8 +178,27 @@ 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);
+	/* ECALL/SSE handlers may have updated the frame beyond the live CSRs. */
 	sbi_memcpy(&ctx->trap_ctx, trap_ctx, sizeof(*trap_ctx));
 	sbi_memcpy(trap_ctx, &dom_ctx->trap_ctx, sizeof(*trap_ctx));
+	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;
+	}
 
 	/*
 	 * Re-configure PMP settings for the new domain
@@ -190,6 +224,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;
 }
 
@@ -222,6 +257,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);
 	}
 
@@ -320,6 +368,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 16774fd2..00905497 100644
--- a/lib/sbi/sbi_trap.c
+++ b/lib/sbi/sbi_trap.c
@@ -25,6 +25,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)
@@ -373,6 +374,16 @@ 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())
+		sbi_domain_context_exit_to_prev();
+
+	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


-- 
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 05/12] lib: sbi: domain: ensure boot_hartid is assigned
  2026-10-29 15:59 [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework Raymond Mao
                   ` (3 preceding siblings ...)
  2026-10-29 15:59 ` [PATCH v2 04/12] lib: sbi: domain: support VIRQ couriering domain context switch Raymond Mao
@ 2026-10-29 15:59 ` Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 06/12] lib: sbi: Add Virtual IRQ (VIRQ) subsystem Raymond Mao
                   ` (6 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Raymond Mao @ 2026-10-29 15:59 UTC (permalink / raw)
  To: opensbi
  Cc: anup, anup.patel, scott, raymond.mao, robin.randhawa,
	samuel.holland, peter.lin

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

When boot_hartid points to a hart that is not in the domain's
assigned hartmask (e.g. due to cold boot hart differences),
the domain startup can skip starting the intended boot hart,
leading to intermittent Linux boot failures.
Scan the assigned hartmask and pick the first available hartid
to provide a valid boot target when assigned HARTs are available.

Select boot HARTs during domain finalization, after all domains have
been registered, rather than changing them during startup. This keeps
the selected target stable for physical interrupt affinity setup.
Preserve the configured boot HART when no HART is initially assigned.

Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
 lib/sbi/sbi_domain.c | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/lib/sbi/sbi_domain.c b/lib/sbi/sbi_domain.c
index b3f5892d..604c5b7a 100644
--- a/lib/sbi/sbi_domain.c
+++ b/lib/sbi/sbi_domain.c
@@ -799,6 +799,38 @@ int sbi_domain_root_add_memrange(unsigned long addr, unsigned long size,
 	return 0;
 }
 
+static void domain_select_boot_harts(void)
+{
+	u32 dhart;
+	struct sbi_domain *dom;
+
+	sbi_domain_for_each(dom) {
+		u32 boot_hartindex = sbi_hartid_to_hartindex(dom->boot_hartid);
+		bool boot_assigned = false;
+
+		if (sbi_hartindex_valid(boot_hartindex)) {
+			spin_lock(&dom->assigned_harts_lock);
+			boot_assigned = sbi_hartmask_test_hartindex(
+				boot_hartindex, &dom->assigned_harts);
+			spin_unlock(&dom->assigned_harts_lock);
+		}
+
+		if (!boot_assigned) {
+			u32 new_hartid = -1U;
+
+			spin_lock(&dom->assigned_harts_lock);
+			sbi_hartmask_for_each_hartindex(dhart, &dom->assigned_harts) {
+				new_hartid = sbi_hartindex_to_hartid(dhart);
+				break;
+			}
+			spin_unlock(&dom->assigned_harts_lock);
+
+			if (new_hartid != -1U)
+				dom->boot_hartid = new_hartid;
+		}
+	}
+}
+
 int sbi_domain_startup(struct sbi_scratch *scratch, u32 cold_hartid)
 {
 	int rc;
@@ -873,6 +905,9 @@ int sbi_domain_finalize(struct sbi_scratch *scratch)
 		return rc;
 	}
 
+	/* Select stable boot HARTs before programming physical IRQ targets. */
+	domain_select_boot_harts();
+
 	/*
 	 * Set the finalized flag so that the root domain
 	 * regions can't be changed.
-- 
2.25.1


-- 
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 06/12] lib: sbi: Add Virtual IRQ (VIRQ) subsystem
  2026-10-29 15:59 [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework Raymond Mao
                   ` (4 preceding siblings ...)
  2026-10-29 15:59 ` [PATCH v2 05/12] lib: sbi: domain: ensure boot_hartid is assigned Raymond Mao
@ 2026-10-29 15:59 ` Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 07/12] lib: sbi: domain: add domain lookup by name Raymond Mao
                   ` (5 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Raymond Mao @ 2026-10-29 15:59 UTC (permalink / raw)
  To: opensbi
  Cc: anup, anup.patel, scott, raymond.mao, robin.randhawa,
	samuel.holland, peter.lin

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

VIRQ is an abstraction framework providing per-MPXY-channel
HWIRQ<->VIRQ mapping, per-(domain,hart) VIRQ routing and couriering.
It notifies S-mode payload via irqchip SEIP helpers when a VIRQ is
mapped/routed/enqueued and provides backend callbacks for claiming
and completing pending VIRQs through RPMI SYSTEM_IRQ channels.

VIRQ layer is composed of three major parts:
1. VIRQ mapping and allocation
   - Provides a stable per-MPXY-channel mapping between a host
     physical interrupt endpoint (chip_uid, hwirq) and a VIRQ
     number.
   - VIRQ number allocation uses a scalable bitmap.
2. Physical IRQ->(Domain,hart) routing rules
   - Supports routing rules derived from sysirq nodes by the
     interrupts-extended property, for example:
       interrupts-extended =
         <&aplic HWIRQx IRQ_TYPE>, // virq 0
         <&aplic HWIRQy IRQ_TYPE>, // virq 1
         ...;
   - VIRQ numbers are allocated from zero, implicit from the order
     of the entries in the interrupts-extended property.
   - Cache routes by (chip_uid, hwirq), rejecting duplicate physical
     endpoints while allowing the same HWIRQ on different controllers.
   - Default behavior: if a physical endpoint does not match any
     routing rule, it will be routed to the root domain
     (channel 0) as a fallback.
3. Per-(domain,hart) pending queue couriering
   - Each domain maintains a per-hart ring buffer queue of pending
     VIRQs. A courier handler masks the physical source before
     enqueueing a VIRQ on HWIRQ assertion.
   - The couriering is domain-aware. It switches to the target domain
     when it is not the same as the current one, and requests a return
     to the previous domain after pending and claimed work is drained.
   - S-mode notification is edge-triggered based on the irqchip notify
     state, and is cleared only when the queue becomes empty.

Bind VIRQ queues to the RPMI SYSTEM_IRQ backend. SYSIRQ_CLAIM removes
only the selected channel's entries while preserving other entries in
the shared domain queue. Index zero is a valid VIRQ. Keep claimed sources
masked until device handling is followed by another CLAIM or Yield.
Track outstanding claims and rearm each source through EOI/unmask before
allowing the deferred return domain switch.

Provide an irqchip-independent helper for DT-driven courier registration.
Configure the physical interrupt with the supplied IRQ flags and target
hart, and roll back registration if target setup or interrupt enabling
fails. Store the physical IRQ flags with each route and provide a setup
entry point to activate couriers after domain boot HART selection.

Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
 include/sbi/sbi_domain.h |    2 +
 include/sbi/sbi_virq.h   |  513 ++++++++++++++++
 lib/sbi/objects.mk       |    1 +
 lib/sbi/sbi_domain.c     |    8 +
 lib/sbi/sbi_virq.c       | 1235 ++++++++++++++++++++++++++++++++++++++
 5 files changed, 1759 insertions(+)
 create mode 100644 include/sbi/sbi_virq.h
 create mode 100644 lib/sbi/sbi_virq.c

diff --git a/include/sbi/sbi_domain.h b/include/sbi/sbi_domain.h
index 31cbe01e..3e357e67 100644
--- a/include/sbi/sbi_domain.h
+++ b/include/sbi/sbi_domain.h
@@ -221,6 +221,8 @@ struct sbi_domain {
 	bool fw_region_inited;
 	/** Whether to notify S-mode for VIRQ couriering */
 	bool virq_seip_notify;
+	/** per-domain wired-IRQ courier state */
+	void *virq_priv;
 };
 
 /** The root domain instance */
diff --git a/include/sbi/sbi_virq.h b/include/sbi/sbi_virq.h
new file mode 100644
index 00000000..98f16255
--- /dev/null
+++ b/include/sbi/sbi_virq.h
@@ -0,0 +1,513 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Virtual IRQ (VIRQ) courier/routing layer for OpenSBI.
+ *
+ * This header defines:
+ *   1) VIRQ number allocation and (chip_uid,hwirq) <-> VIRQ mapping
+ *   2) HWIRQ -> Domain routing rules (from DeviceTree "opensbi,mpxy-sysirq")
+ *   3) Per-(domain,hart) pending queue (push in M-mode, MPXY CLAIM in S-mode)
+ *   4) DT-driven courier registration and physical IRQ target configuration
+ *
+ * High-level design intent:
+ *   - All physical host IRQs are handled in M-mode by host irqchip drivers.
+ *   - For each incoming HWIRQ, OpenSBI determines the destination domain using
+ *     DT-defined routing rules and enqueues a VIRQ into the per-(domain,hart)
+ *     pending queue.
+ *   - S-mode payload claims pending VIRQs via RPMI SYSIRQ_CLAIM over SBI MPXY.
+ *     Claimed sources remain masked until a subsequent CLAIM or Yield.
+ *   - After handling each returned batch, S-mode claims again. An empty claim
+ *     requests return to the previous domain when the entire queue is drained.
+ *   - M-mode notifies S-mode via the irqchip notification mechanism.
+ *
+ * Notes:
+ *   - "opensbi,mpxy-sysirq" routing is derived from the sysirq node's
+ *     "interrupts-extended" entries. It does not encode privilege level
+ *     delivery. Hardware delivery (MEI vs SEI) is determined by platform IRQ
+ *     topology and interrupt-parent.
+ *
+ * Copyright (c) 2026 RISCstar Solutions Corporation.
+ *
+ * Author: Raymond Mao <raymond.mao@riscstar.com>
+ */
+
+#ifndef __SBI_VIRQ_H__
+#define __SBI_VIRQ_H__
+
+#include <sbi/sbi_domain.h>
+#include <sbi/sbi_irqchip.h>
+#include <sbi/riscv_locks.h>
+#include <sbi/sbi_types.h>
+
+/*
+ * Current implementation behavior when queue overflows:
+ *   - Drop the incoming VIRQ
+ *   - Return SBI_ENOMEM
+ */
+#define VIRQ_QSIZE  64
+
+/*
+ * Reverse mapping table is chunked to avoid a single large static array.
+ * VIRQ is used as an index into a chunk; chunks are allocated on demand.
+ */
+#define VIRQ_CHUNK_SHIFT   6U
+#define VIRQ_CHUNK_SIZE    (1U << VIRQ_CHUNK_SHIFT)
+#define VIRQ_CHUNK_MASK    (VIRQ_CHUNK_SIZE - 1U)
+
+/* Minimum growth step for forward mapping vector and related metadata. */
+#define VEC_GROW_MIN       16U
+
+/* Sentinel for an invalid or unmapped VIRQ; VIRQ zero is valid. */
+#define VIRQ_INVALID       0xffffffffU
+
+/*
+ * VIRQ allocator and (chip_uid,hwirq) <-> VIRQ mapping
+ */
+
+/*
+ * VIRQ mapping model:
+ *   - Forward mapping:  (chip_uid,hwirq) -> VIRQ
+ *       Implementation: dynamic vector of entries (linear search).
+ *
+ *   - Reverse mapping:  VIRQ -> (chip_uid,hwirq)
+ *       Implementation: chunked table allocated on demand, O(1) lookup.
+ *
+ *   - VIRQ number allocation:
+ *       Implementation: growable bitmap; capacity expands as needed.
+ *
+ * Memory usage scales with the number of installed mappings.
+ */
+
+/* Entry of reverse mapping table: represents (chip_uid,hwirq) endpoint */
+struct virq_entry {
+	u32 chip_uid;
+	u32 hwirq;
+};
+
+/* Chunked reverse mapping table: VIRQ -> (chip_uid,hwirq) */
+struct virq_chunk {
+	struct virq_entry e[VIRQ_CHUNK_SIZE];
+};
+
+/*
+ * (chip_uid, HWIRQ) -> Domain routing rules
+ */
+
+/*
+ * A routing rule maps a single (chip_uid, hwirq) endpoint to a domain.
+ *
+ * Rules are populated once during cold boot while parsing the DT
+ * opensbi-domains configuration (sysirq node "opensbi,mpxy-sysirq").
+ *
+ * DT encodes mapping via "interrupts-extended"; the index within this array
+ * becomes the VIRQ number for the given MPXY channel.
+ *
+ * Policy notes:
+ *   - Duplicate physical endpoints are rejected and return SBI_EALREADY.
+ *   - If no rule matches, routing falls back to the root domain (&root).
+ */
+struct sbi_virq_route_rule {
+	u32 chip_uid;
+	u32 hwirq;
+	u32 hwirq_flags;
+	struct sbi_domain *dom;   /* owner domain */
+	u32 channel_id;           /* VIRQ space/channel */
+};
+
+/*
+ * Courier context passed as 'opaque' to sbi_virq_courier_handler(), created
+ * per host irqchip.
+ *
+ * The courier handler needs to:
+ *   - map (chip_uid,hwirq) -> VIRQ
+ *   - mask/unmask HWIRQ using the correct irqchip device
+ * Therefore the irqchip device pointer is carried here.
+ */
+struct sbi_virq_courier_ctx {
+	struct sbi_irqchip_device *chip;
+};
+
+/*
+ * Per-(domain,hart) pending VIRQ state and queue management
+ */
+
+/*
+ * Per-(domain,hart) VIRQ state.
+ *
+ * Locking:
+ *   - lock protects head/tail, q[], and claimed_count.
+ *
+ * Queue semantics:
+ *   - q[] stores VIRQs pending handling for this (domain,hart).
+ *   - enqueue is performed by M-mode (courier handler) according to route rule
+ *     populated from DT.
+ *   - SYSIRQ_CLAIM consumes pending entries for one MPXY channel.
+ *   - chip and hwirq identify the physical source for EOI and unmask.
+ */
+struct sbi_domain_virq_state {
+	spinlock_t lock;
+	u32 head;
+	u32 tail;
+
+	/* Pending VIRQ ring buffer. */
+	struct {
+		u32 virq;
+		u32 channel_id;
+		u32 hwirq;
+		struct sbi_irqchip_device *chip;
+	} q[VIRQ_QSIZE];
+
+	/* Claimed sources awaiting rearm after S-mode device handling. */
+	u32 claimed_count;
+
+	/* Return only after pending and claimed interrupts are drained. */
+	bool return_to_prev;
+};
+
+/*
+ * Per-domain private VIRQ context.
+ *
+ * Attached to struct sbi_domain and contains per-hart states.
+ */
+struct sbi_domain_virq_priv {
+	/* number of platform harts */
+	u32 nharts;
+
+	/* number of allocated per-hart states */
+	u32 st_count;
+
+	/* per-hart VIRQ state pointer array (indexed by hart index) */
+	struct sbi_domain_virq_state *st_by_hart[];
+};
+
+/* Courier binding used when enqueuing a VIRQ. */
+struct sbi_virq_courier_binding {
+	/* destination domain */
+	struct sbi_domain *dom;
+
+	/* irqchip device that asserted the HWIRQ */
+	struct sbi_irqchip_device *chip;
+
+	/* VIRQ space/channel ID */
+	u32 channel_id;
+
+	/* VIRQ number to enqueue */
+	u32 virq;
+
+	/* Physical source held masked until SYSIRQ_CLAIM. */
+	u32 hwirq;
+};
+
+/*
+ * Public APIs
+ */
+
+/*
+ * Initialize a per-channel VIRQ map.
+ *
+ * @channel_id:
+ *   VIRQ space/channel ID (0 is the default channel).
+ *
+ * @init_virq_cap:
+ *   Initial capacity in VIRQ bits (e.g., 256). Implementation may grow beyond.
+ *
+ * Return:
+ *   SBI_OK on success
+ *   SBI_ENOMEM on allocation failure
+ */
+int sbi_virq_map_init(u32 channel_id, u32 init_virq_cap);
+
+/*
+ * Create or get a stable mapping for (channel_id, chip_uid, hwirq) -> VIRQ.
+ *
+ * @channel_id:
+ *   Paravirt channel ID; VIRQ numbering is local to each channel.
+ *
+ * @chip_uid:
+ *   Unique 32-bit ID of the host irqchip device.
+ *
+ * @hwirq:
+ *   Host HWIRQ number as produced by the irqchip driver (e.g. APLIC claim ID).
+ *
+ * @allow_identity:
+ *   If true, allocator may attempt VIRQ == hwirq for small ranges.
+ *
+ * @identity_limit:
+ *   Upper bound (exclusive) for identity mapping trial: hwirq < identity_limit.
+ *
+ * @out_virq:
+ *   Output pointer receiving the mapped/allocated VIRQ (0 is valid).
+ *
+ * Return:
+ *   SBI_OK on success
+ *   SBI_ENOMEM on allocation failure
+ *   SBI_ENOSPC if allocator cannot allocate
+ *   SBI_EINVAL on invalid parameters
+ */
+int sbi_virq_map_one(u32 channel_id, u32 chip_uid, u32 hwirq,
+		     bool allow_identity, u32 identity_limit, u32 *out_virq);
+
+/*
+ * Force a mapping for (channel_id, chip_uid, hwirq) -> VIRQ.
+ *
+ * @channel_id:
+ *   Paravirt channel ID; VIRQ numbering is local to each channel.
+ *
+ * @chip_uid:
+ *   Unique 32-bit ID of the host irqchip device.
+ *
+ * @hwirq:
+ *   Host HWIRQ number as produced by the irqchip driver.
+ *
+ * @virq:
+ *   VIRQ number to assign (0 is valid).
+ *
+ * Return:
+ *   SBI_OK on success
+ *   SBI_ENOMEM on allocation failure
+ *   SBI_EINVAL on invalid parameters
+ *   SBI_EALREADY if a different mapping already exists
+ */
+int sbi_virq_map_set(u32 channel_id, u32 chip_uid, u32 hwirq, u32 virq);
+
+/*
+ * Ensure VIRQ map capacity for a given channel.
+ *
+ * @channel_id:
+ *   Paravirt channel ID.
+ *
+ * @min_virq_cap:
+ *   Minimum VIRQ bitmap capacity in bits (will be rounded up).
+ *
+ * Return:
+ *   SBI_OK on success
+ *   SBI_EINVAL if the map is not initialized (channel 0)
+ *   SBI_ENOMEM on allocation failure
+ */
+int sbi_virq_map_ensure_cap(u32 channel_id, u32 min_virq_cap);
+
+/*
+ * Lookup existing mapping: (channel_id, chip_uid, hwirq) -> VIRQ.
+ *
+ * @channel_id:
+ *   Paravirt channel ID; VIRQ numbering is local to each channel.
+ *
+ * @chip_uid:
+ *   Irqchip unique id.
+ *
+ * @hwirq:
+ *   Host hwirq number.
+ *
+ * @out_virq:
+ *   Output VIRQ (0 is valid).
+ *
+ * Return:
+ *   SBI_OK if found
+ *   SBI_ENOENT if not mapped
+ *   SBI_EINVAL on invalid input
+ */
+int sbi_virq_hwirq2virq(u32 channel_id, u32 chip_uid, u32 hwirq,
+			u32 *out_virq);
+
+/*
+ * Reverse lookup: (channel_id, VIRQ) -> (chip_uid, hwirq).
+ *
+ * @channel_id:
+ *   Paravirt channel ID; VIRQ numbering is local to each channel.
+ *
+ * @virq:
+ *   VIRQ number to look up.
+ *
+ * @out_chip_uid:
+ *   Output pointer receiving irqchip unique id.
+ *
+ * @out_hwirq:
+ *   Output pointer receiving host hwirq number.
+ *
+ * Return:
+ *   SBI_OK on success
+ *   SBI_EINVAL if virq is VIRQ_INVALID, out of range, not allocated, or
+ *     reverse entry missing
+ */
+int sbi_virq_virq2hwirq(u32 channel_id, u32 virq,
+			u32 *out_chip_uid, u32 *out_hwirq);
+
+/*
+ * Unmap a single VIRQ mapping and free the VIRQ number.
+ *
+ * @virq:
+ *   VIRQ number to unmap.
+ *
+ * Return:
+ *   SBI_OK on success
+ *   SBI_EINVAL if virq is invalid or state is inconsistent
+ */
+int sbi_virq_unmap_one(u32 virq);
+
+/*
+ * Uninitialize the VIRQ mapping allocator and free all resources.
+ *
+ * Notes:
+ *   - This frees bitmap, forward vector, and reverse chunks.
+ */
+void sbi_virq_map_uninit(void);
+
+/*
+ * Reset all physical interrupt routing rules (frees the rule array).
+ *
+ * Typical usage:
+ *   - Called once at cold boot during init before parsing DT domains.
+ */
+void sbi_virq_route_reset(void);
+
+/*
+ * Add a routing rule: (chip_uid, hwirq) -> dom with channel_id.
+ *
+ * @dom:
+ *   Target domain that should receive this physical interrupt.
+ *
+ * @chip_uid:
+ *   Unique ID of the physical interrupt controller.
+ *
+ * @hwirq:
+ *   HWIRQ number to route.
+ *
+ * @hwirq_flags:
+ *   Physical interrupt flags used when activating the courier.
+ *
+ * @channel_id:
+ *   Paravirt channel ID for VIRQ mapping (MPXY channel).
+ *
+ * Return:
+ *   SBI_OK on success
+ *   SBI_EINVAL on invalid parameters
+ *   SBI_ENOMEM on allocation failure
+ *   SBI_EALREADY if the physical endpoint already has a rule
+ */
+int sbi_virq_route_add(struct sbi_domain *dom, u32 chip_uid, u32 hwirq,
+		       u32 hwirq_flags, u32 channel_id);
+
+/*
+ * Lookup destination domain for a given physical interrupt endpoint.
+ *
+ * @chip_uid:
+ *   Unique ID of the physical interrupt controller.
+ *
+ * @hwirq:
+ *   Incoming host HWIRQ number.
+ *
+ * @out_dom:
+ *   Output pointer receiving destination domain. If no rule matches, &root
+ *   is returned.
+ *
+ * @out_channel_id:
+ *   Output pointer receiving channel id if non-NULL.
+ *
+ * Return:
+ *   SBI_OK on success
+ *   SBI_EINVAL on invalid parameters
+ */
+int sbi_virq_route_lookup(u32 chip_uid, u32 hwirq,
+			  struct sbi_domain **out_dom, u32 *out_channel_id);
+
+/* Activate DT couriers after domain boot HARTs have been selected. */
+int sbi_virq_setup_couriers(void);
+
+/*
+ * Enqueue a VIRQ for the destination domain on the current hart.
+ *
+ * @c:
+ *   Courier binding containing:
+ *     - c->dom  : destination domain
+ *     - c->chip : irqchip device pointer
+ *     - c->virq : VIRQ number
+ *
+ * Return:
+ *   SBI_OK on success
+ *   SBI_EINVAL on invalid parameters
+ *   SBI_ENODEV if per-(domain,hart) state is not available
+ *   SBI_ENOMEM if queue is full
+ */
+int sbi_virq_enqueue(struct sbi_virq_courier_binding *c);
+
+/*
+ * Claim up to max_count pending VIRQs in one channel on this domain/hart.
+ * A successful claim removes pending entries but leaves their sources masked.
+ * A zero-result claim may request a return to the previous domain.
+ */
+int sbi_virq_claim_thishart(u32 channel_id, u32 *virqs, u32 max_count,
+			   u32 *out_count);
+
+/* Rearm a previously claimed source after S-mode has handled its device. */
+int sbi_virq_rearm_thishart(u32 channel_id, u32 virq);
+
+/* Register a DT-defined local RPMI SYSTEM_IRQ channel. */
+int sbi_virq_sysirq_add_channel(struct sbi_domain *dom, u32 channel_id,
+			       u32 num_irqs);
+
+/* Return to previous domain if a VIRQ-driven switch is pending. */
+void sbi_virq_return_to_prev_if_needed(void);
+
+
+/*
+ * Courier handler intended to be registered by host irqchip driver.
+ *
+ * @hwirq:
+ *   Incoming host HWIRQ number asserted on the irqchip.
+ *
+ * @opaque:
+ *   Point to a valid struct sbi_virq_courier_ctx, which provides the
+ *   irqchip device pointer used for mapping and mask/unmask.
+ *
+ * Return:
+ *   SBI_OK on success
+ *   SBI_EINVAL on invalid parameters
+ *   Other SBI_E* propagated from mapping or enqueue
+ */
+int sbi_virq_courier_handler(u32 hwirq, void *opaque);
+
+/* Register a wired courier and restore its DT-selected target hart. */
+int sbi_virq_register_courier(struct sbi_irqchip_device *chip, u32 hwirq,
+			     u32 hwirq_flags, u32 hartindex);
+
+/*
+ * Initialize per-domain VIRQ state.
+ *
+ * @dom:
+ *   Domain to initialize.
+ *
+ * Return:
+ *   SBI_OK on success
+ *   SBI_EINVAL on invalid parameters
+ *   SBI_ENOMEM on allocation failure
+ */
+int sbi_virq_domain_init(struct sbi_domain *dom);
+
+/*
+ * Free per-domain VIRQ state.
+ *
+ * @dom:
+ *   Free the per-domain VIRQ state.
+ */
+void sbi_virq_domain_exit(struct sbi_domain *dom);
+
+/*
+ * Initialize VIRQ subsystem (mapping allocator + route rules).
+ * Must be called once before parsing sysirq DT nodes.
+ *
+ * @init_virq_cap:
+ *   Initial VIRQ bitmap capacity in bits
+ *
+ * Return:
+ *   SBI_OK on success
+ *   SBI_EALREADY if called more than once
+ *   SBI_ENOMEM on allocation failure
+ *   Other SBI_E* error codes propagated from mapping init
+ */
+int sbi_virq_init(u32 init_virq_cap);
+
+/*
+ * Query whether the VIRQ subsystem is initialized.
+ */
+bool sbi_virq_is_inited(void);
+
+#endif
diff --git a/lib/sbi/objects.mk b/lib/sbi/objects.mk
index 16c141fe..cbd6f6bf 100644
--- a/lib/sbi/objects.mk
+++ b/lib/sbi/objects.mk
@@ -89,6 +89,7 @@ libsbi-objs-y += sbi_illegal_insn.o
 libsbi-objs-y += sbi_init.o
 libsbi-objs-y += sbi_ipi.o
 libsbi-objs-y += sbi_irqchip.o
+libsbi-objs-y += sbi_virq.o
 libsbi-objs-y += sbi_platform.o
 libsbi-objs-y += sbi_pmp.o
 libsbi-objs-y += sbi_pmu.o
diff --git a/lib/sbi/sbi_domain.c b/lib/sbi/sbi_domain.c
index 604c5b7a..30588678 100644
--- a/lib/sbi/sbi_domain.c
+++ b/lib/sbi/sbi_domain.c
@@ -18,6 +18,7 @@
 #include <sbi/sbi_platform.h>
 #include <sbi/sbi_scratch.h>
 #include <sbi/sbi_string.h>
+#include <sbi/sbi_virq.h>
 
 SBI_LIST_HEAD(domain_list);
 
@@ -710,6 +711,13 @@ int sbi_domain_register(struct sbi_domain *dom)
 		return rc;
 	}
 
+	/* Init per-domain wired-IRQ courier state */
+	rc = sbi_virq_domain_init(dom);
+	if (rc) {
+		sbi_list_del(&dom->node);
+		return rc;
+	}
+
 	return 0;
 }
 
diff --git a/lib/sbi/sbi_virq.c b/lib/sbi/sbi_virq.c
new file mode 100644
index 00000000..04033ce7
--- /dev/null
+++ b/lib/sbi/sbi_virq.c
@@ -0,0 +1,1235 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/*
+ * Copyright (c) 2026 RISCstar Solutions Corporation.
+ *
+ * Author: Raymond Mao <raymond.mao@riscstar.com>
+ */
+
+#include <sbi/sbi_console.h>
+#include <sbi/sbi_error.h>
+#include <sbi/sbi_heap.h>
+#include <sbi/sbi_irqchip.h>
+#include <sbi/sbi_hartmask.h>
+#include <sbi/sbi_platform.h>
+#include <sbi/sbi_scratch.h>
+#include <sbi/sbi_string.h>
+#include <sbi/sbi_virq.h>
+#include <sbi_utils/sysirq/rpmi_sysirq.h>
+#include <sbi/sbi_domain.h>
+#include <sbi/sbi_domain_context.h>
+#include <sbi/riscv_asm.h>
+#include <sbi/riscv_locks.h>
+
+struct map_node {
+	u32 chip_uid;
+	u32 hwirq;
+	u32 virq;
+};
+
+struct sbi_virq_map {
+	spinlock_t lock;
+
+	/* allocator bitmap */
+	unsigned long *bmap;
+	u32 bmap_nbits; /* virq range: [0..nbits-1] */
+
+	/* reverse table: virq -> endpoint */
+	struct virq_chunk **chunks;
+	u32 chunks_cap; /* number of chunk pointers */
+
+	/* forward table: vector of mappings, linear search */
+	struct map_node *nodes;
+	u32 nodes_cnt;
+	u32 nodes_cap;
+};
+
+struct sbi_virq_map_list {
+	u32 channel_id;
+	struct sbi_virq_map map;
+};
+
+/*
+ * (chip_uid, HWIRQ) -> Domain routing rules
+ */
+
+struct sbi_virq_router {
+	spinlock_t lock;
+	struct sbi_virq_route_rule *rules;
+	u32 cnt;
+	u32 cap;
+};
+
+static struct sbi_virq_map g_virq_map; /* channel 0 */
+static struct sbi_virq_map_list *g_virq_maps;
+static u32 g_virq_maps_cnt;
+static u32 g_virq_maps_cap;
+static spinlock_t g_virq_maps_lock;
+static struct sbi_virq_router g_router;
+static bool g_virq_inited;
+
+void sbi_virq_route_reset(void)
+{
+	spin_lock(&g_router.lock);
+	if (g_router.rules) {
+		sbi_free(g_router.rules);
+		g_router.rules = NULL;
+	}
+	g_router.cnt = 0;
+	g_router.cap = 0;
+	spin_unlock(&g_router.lock);
+}
+
+static int router_ensure_cap(u32 need)
+{
+	struct sbi_virq_route_rule *newp;
+	u32 newcap;
+
+	if (g_router.cap >= need)
+		return 0;
+
+	newcap = g_router.cap ? (g_router.cap << 1) : 8;
+	while (newcap < need)
+		newcap <<= 1;
+
+	newp = sbi_zalloc((size_t)newcap * sizeof(*newp));
+	if (!newp)
+		return SBI_ENOMEM;
+
+	if (g_router.rules) {
+		sbi_memcpy(newp, g_router.rules,
+			   (size_t)g_router.cnt * sizeof(*newp));
+		sbi_free(g_router.rules);
+	}
+
+	g_router.rules = newp;
+	g_router.cap = newcap;
+
+	return SBI_OK;
+}
+
+int sbi_virq_route_add(struct sbi_domain *dom, u32 chip_uid, u32 hwirq,
+		       u32 hwirq_flags, u32 channel_id)
+{
+	int rc;
+
+	if (!dom)
+		return SBI_EINVAL;
+
+	spin_lock(&g_router.lock);
+
+	/* Reject duplicates to keep routing unambiguous */
+	for (u32 i = 0; i < g_router.cnt; i++) {
+		if (g_router.rules[i].chip_uid == chip_uid &&
+		    g_router.rules[i].hwirq == hwirq) {
+			spin_unlock(&g_router.lock);
+			return SBI_EALREADY;
+		}
+	}
+
+	rc = router_ensure_cap(g_router.cnt + 1);
+	if (rc) {
+		spin_unlock(&g_router.lock);
+		return rc;
+	}
+
+	g_router.rules[g_router.cnt].chip_uid = chip_uid;
+	g_router.rules[g_router.cnt].hwirq = hwirq;
+	g_router.rules[g_router.cnt].hwirq_flags = hwirq_flags;
+	g_router.rules[g_router.cnt].dom   = dom;
+	g_router.rules[g_router.cnt].channel_id = channel_id;
+	g_router.cnt++;
+
+	spin_unlock(&g_router.lock);
+
+	return SBI_OK;
+}
+
+int sbi_virq_route_lookup(u32 chip_uid, u32 hwirq,
+			  struct sbi_domain **out_dom, u32 *out_channel_id)
+{
+	/* Fast path: no rules */
+	if (!g_router.cnt) {
+		if (out_dom)
+			*out_dom = &root;
+		if (out_channel_id)
+			*out_channel_id = 0;
+		return SBI_OK;
+	}
+
+	spin_lock(&g_router.lock);
+	for (u32 i = 0; i < g_router.cnt; i++) {
+		if (chip_uid == g_router.rules[i].chip_uid &&
+		    hwirq == g_router.rules[i].hwirq) {
+			struct sbi_domain *d = g_router.rules[i].dom;
+			u32 cid = g_router.rules[i].channel_id;
+
+			spin_unlock(&g_router.lock);
+			if (out_dom)
+				*out_dom = d ? d : &root;
+			if (out_channel_id)
+				*out_channel_id = cid;
+			return SBI_OK;
+		}
+	}
+	spin_unlock(&g_router.lock);
+
+	if (out_dom)
+		*out_dom = &root;
+	if (out_channel_id)
+		*out_channel_id = 0;
+	return SBI_OK;
+}
+
+static inline void virq_state_init(struct sbi_domain_virq_state *st)
+{
+	SPIN_LOCK_INIT(st->lock);
+	st->head = 0;
+	st->tail = 0;
+	st->claimed_count = 0;
+	st->return_to_prev = false;
+}
+
+static inline
+struct sbi_domain_virq_state *domain_virq_thishart(struct sbi_domain *dom)
+{
+	unsigned long hartidx = sbi_hartid_to_hartindex(current_hartid());
+	struct sbi_domain_virq_priv *p;
+
+	p = (struct sbi_domain_virq_priv *)dom->virq_priv;
+	if (!p || hartidx >= p->nharts)
+		return NULL;
+
+	return p->st_by_hart[hartidx];
+}
+
+static inline bool q_full(struct sbi_domain_virq_state *st)
+{
+	return ((st->tail + 1) % VIRQ_QSIZE) == st->head;
+}
+
+static inline bool q_empty(struct sbi_domain_virq_state *st)
+{
+	return st->head == st->tail;
+}
+
+static inline void virq_set_domain_return_flag(struct sbi_domain *dom,
+					       bool return_to_prev)
+{
+	struct sbi_domain_virq_state *st = domain_virq_thishart(dom);
+
+	if (!st)
+		return;
+
+	spin_lock(&st->lock);
+	st->return_to_prev = return_to_prev;
+	spin_unlock(&st->lock);
+}
+
+static u32 sbi_virq_platform_hart_count(void)
+{
+	struct sbi_scratch *scratch = sbi_scratch_thishart_ptr();
+	const struct sbi_platform *plat = sbi_platform_ptr(scratch);
+
+	return sbi_platform_hart_count(plat);
+}
+
+static int bmap_alloc_one(struct sbi_virq_map *m, u32 *out_virq)
+{
+	u32 v;
+
+	for (v = 0; v < m->bmap_nbits; v++) {
+		if (!bitmap_test(m->bmap, (int)v)) {
+			bitmap_set(m->bmap, (int)v, 1);
+			*out_virq = v;
+			return 0;
+		}
+	}
+
+	return SBI_ENOSPC;
+}
+
+static int bmap_alloc_specific(struct sbi_virq_map *m, u32 virq)
+{
+	if (virq >= m->bmap_nbits)
+		return SBI_EINVAL;
+	if (bitmap_test(m->bmap, (int)virq))
+		return SBI_EALREADY;
+	bitmap_set(m->bmap, (int)virq, 1);
+
+	return 0;
+}
+
+static void bmap_free_one(struct sbi_virq_map *m, u32 virq)
+{
+	if (virq < m->bmap_nbits)
+		bitmap_clear(m->bmap, (int)virq, 1);
+}
+
+static int chunks_ensure_cap(struct sbi_virq_map *m, u32 new_bmap_nbits)
+{
+	u32 new_chunks_cap =
+		(new_bmap_nbits + VIRQ_CHUNK_SIZE - 1U) >> VIRQ_CHUNK_SHIFT;
+	struct virq_chunk **newp;
+
+	if (new_chunks_cap <= m->chunks_cap)
+		return 0;
+
+	newp = sbi_zalloc((size_t)new_chunks_cap * sizeof(*newp));
+	if (!newp)
+		return SBI_ENOMEM;
+
+	if (m->chunks) {
+		sbi_memcpy(newp, m->chunks,
+			   (size_t)m->chunks_cap * sizeof(*newp));
+		sbi_free(m->chunks);
+	}
+
+	m->chunks = newp;
+	m->chunks_cap = new_chunks_cap;
+
+	return 0;
+}
+
+static int bmap_grow(struct sbi_virq_map *m, u32 new_nbits)
+{
+	unsigned long *newmap;
+
+	if (new_nbits <= m->bmap_nbits)
+		return 0;
+
+	newmap = sbi_zalloc(bitmap_estimate_size((int)new_nbits));
+	if (!newmap)
+		return SBI_ENOMEM;
+
+	bitmap_zero(newmap, (int)new_nbits);
+	bitmap_copy(newmap, m->bmap, (int)m->bmap_nbits);
+
+	sbi_free(m->bmap);
+	m->bmap = newmap;
+	m->bmap_nbits = new_nbits;
+
+	return chunks_ensure_cap(m, new_nbits);
+}
+
+static struct virq_entry *rev_get_or_alloc(struct sbi_virq_map *m, u32 virq)
+{
+	u32 ci = virq >> VIRQ_CHUNK_SHIFT;
+	u32 off = virq & VIRQ_CHUNK_MASK;
+
+	if (ci >= m->chunks_cap)
+		return NULL;
+
+	if (!m->chunks[ci]) {
+		m->chunks[ci] = sbi_zalloc(sizeof(struct virq_chunk));
+		if (!m->chunks[ci])
+			return NULL;
+	}
+	return &m->chunks[ci]->e[off];
+}
+
+static struct virq_entry *rev_get_existing(struct sbi_virq_map *m, u32 virq)
+{
+	u32 ci = virq >> VIRQ_CHUNK_SHIFT;
+	u32 off = virq & VIRQ_CHUNK_MASK;
+
+	if (ci >= m->chunks_cap || !m->chunks[ci])
+		return NULL;
+	return &m->chunks[ci]->e[off];
+}
+
+static void rev_clear(struct sbi_virq_map *m, u32 virq)
+{
+	struct virq_entry *e = rev_get_existing(m, virq);
+
+	if (e) {
+		e->chip_uid = 0;
+		e->hwirq = 0;
+	}
+}
+
+static int vec_ensure_cap(struct sbi_virq_map *m, u32 need_cnt)
+{
+	struct map_node *newp;
+	u32 newcap;
+
+	if (m->nodes_cap >= need_cnt)
+		return 0;
+
+	newcap = m->nodes_cap ? (m->nodes_cap << 1) :
+		VEC_GROW_MIN;
+	while (newcap < need_cnt)
+		newcap <<= 1;
+
+	newp = sbi_zalloc((size_t)newcap * sizeof(*newp));
+	if (!newp)
+		return SBI_ENOMEM;
+
+	if (m->nodes) {
+		sbi_memcpy(newp, m->nodes,
+			   (size_t)m->nodes_cnt * sizeof(*newp));
+		sbi_free(m->nodes);
+	}
+
+	m->nodes = newp;
+	m->nodes_cap = newcap;
+
+	return 0;
+}
+
+static int forward_find_idx(struct sbi_virq_map *m,
+			    u32 chip_uid, u32 hwirq, u32 *out_idx)
+{
+	u32 i;
+
+	for (i = 0; i < m->nodes_cnt; i++) {
+		if (m->nodes[i].chip_uid == chip_uid &&
+		    m->nodes[i].hwirq == hwirq) {
+			*out_idx = i;
+			return 0;
+		}
+	}
+
+	return SBI_ENOENT;
+}
+
+static int virq_map_init_one(struct sbi_virq_map *m, u32 init_virq_cap)
+{
+	int rc;
+
+	sbi_memset(m, 0, sizeof(*m));
+	SPIN_LOCK_INIT(m->lock);
+
+	if (init_virq_cap < 8U)
+		init_virq_cap = 8U;
+
+	m->bmap_nbits = init_virq_cap;
+	m->bmap =
+		sbi_zalloc(bitmap_estimate_size((int)m->bmap_nbits));
+	if (!m->bmap)
+		return SBI_ENOMEM;
+
+	bitmap_zero(m->bmap, (int)m->bmap_nbits);
+
+	rc = chunks_ensure_cap(m, m->bmap_nbits);
+	if (rc)
+		return rc;
+
+	return SBI_OK;
+}
+
+static struct sbi_virq_map *virq_map_get(u32 channel_id, bool create,
+					 u32 init_virq_cap)
+{
+	u32 i;
+	struct sbi_virq_map_list *newp;
+
+	if (channel_id == 0)
+		return &g_virq_map;
+
+	spin_lock(&g_virq_maps_lock);
+	for (i = 0; i < g_virq_maps_cnt; i++) {
+		if (g_virq_maps[i].channel_id == channel_id) {
+			spin_unlock(&g_virq_maps_lock);
+			return &g_virq_maps[i].map;
+		}
+	}
+	if (!create) {
+		spin_unlock(&g_virq_maps_lock);
+		return NULL;
+	}
+
+	if (g_virq_maps_cnt == g_virq_maps_cap) {
+		u32 newcap = g_virq_maps_cap ? (g_virq_maps_cap << 1) : 4;
+
+		newp = sbi_zalloc((size_t)newcap * sizeof(*newp));
+		if (!newp) {
+			spin_unlock(&g_virq_maps_lock);
+			return NULL;
+		}
+		if (g_virq_maps) {
+			sbi_memcpy(newp, g_virq_maps,
+				   (size_t)g_virq_maps_cnt * sizeof(*newp));
+			sbi_free(g_virq_maps);
+		}
+		g_virq_maps = newp;
+		g_virq_maps_cap = newcap;
+	}
+
+	g_virq_maps[g_virq_maps_cnt].channel_id = channel_id;
+	if (virq_map_init_one(&g_virq_maps[g_virq_maps_cnt].map,
+			      init_virq_cap)) {
+		spin_unlock(&g_virq_maps_lock);
+		return NULL;
+	}
+	g_virq_maps_cnt++;
+	spin_unlock(&g_virq_maps_lock);
+
+	return &g_virq_maps[g_virq_maps_cnt - 1].map;
+}
+
+int sbi_virq_map_init(u32 channel_id, u32 init_virq_cap)
+{
+	if (channel_id == 0)
+		return virq_map_init_one(&g_virq_map, init_virq_cap);
+	SPIN_LOCK_INIT(g_virq_maps_lock);
+	return virq_map_get(channel_id, true, init_virq_cap) ?
+		SBI_OK : SBI_ENOMEM;
+}
+
+int sbi_virq_map_one(u32 channel_id, u32 chip_uid, u32 hwirq,
+		     bool allow_identity, u32 identity_limit,
+		     u32 *out_virq)
+{
+	u32 idx, virq = 0;
+	int rc;
+	struct sbi_virq_map *m;
+
+	m = virq_map_get(channel_id, true, 0);
+	if (!m)
+		return SBI_ENOMEM;
+
+	spin_lock(&m->lock);
+	/* already mapped? */
+	rc = forward_find_idx(m, chip_uid, hwirq, &idx);
+	if (!rc) {
+		*out_virq = m->nodes[idx].virq;
+		spin_unlock(&m->lock);
+		return 0;
+	}
+
+	/* ensure vector capacity for new node */
+	rc = vec_ensure_cap(m, m->nodes_cnt + 1U);
+	if (rc) {
+		spin_unlock(&m->lock);
+		return rc;
+	}
+
+	/* optional identity */
+	if (allow_identity && hwirq < identity_limit) {
+		/* ensure bitmap covers this virq */
+		if (hwirq >= m->bmap_nbits) {
+			u32 new_nbits = m->bmap_nbits;
+
+			while (new_nbits <= hwirq)
+				new_nbits <<= 1;
+			rc = bmap_grow(m, new_nbits);
+			if (rc) {
+				spin_unlock(&m->lock);
+				return rc;
+			}
+		}
+
+		rc = bmap_alloc_specific(m, hwirq);
+		if (!rc)
+			virq = hwirq;
+		else if (rc != SBI_EALREADY) {
+			spin_unlock(&m->lock);
+			return rc;
+		}
+	}
+
+	/* allocate new virq if identity not taken */
+	if (!virq) {
+		rc = bmap_alloc_one(m, &virq);
+		if (rc == SBI_ENOSPC) {
+			rc = bmap_grow(m, m->bmap_nbits << 1);
+			if (rc) {
+				spin_unlock(&m->lock);
+				return rc;
+			}
+			rc = bmap_alloc_one(m, &virq);
+		}
+		if (rc) {
+			spin_unlock(&m->lock);
+			return rc;
+		}
+	}
+
+	/* install reverse mapping */
+	{
+		struct virq_entry *e = rev_get_or_alloc(m, virq);
+
+		if (!e) {
+			bmap_free_one(m, virq);
+			spin_unlock(&m->lock);
+			return SBI_ENOMEM;
+		}
+		e->chip_uid = chip_uid;
+		e->hwirq = hwirq;
+	}
+
+	/* append forward node */
+	m->nodes[m->nodes_cnt].chip_uid = chip_uid;
+	m->nodes[m->nodes_cnt].hwirq = hwirq;
+	m->nodes[m->nodes_cnt].virq = virq;
+	m->nodes_cnt++;
+
+	*out_virq = virq;
+	spin_unlock(&m->lock);
+
+	return SBI_OK;
+}
+
+int sbi_virq_map_set(u32 channel_id, u32 chip_uid, u32 hwirq, u32 virq)
+{
+	struct sbi_virq_map *m;
+	u32 idx;
+	int rc;
+
+	m = virq_map_get(channel_id, true, virq + 1U);
+	if (!m)
+		return SBI_ENOMEM;
+
+	spin_lock(&m->lock);
+	rc = forward_find_idx(m, chip_uid, hwirq, &idx);
+	if (!rc) {
+		spin_unlock(&m->lock);
+		return (m->nodes[idx].virq == virq) ? SBI_OK : SBI_EALREADY;
+	}
+
+	if (virq >= m->bmap_nbits) {
+		u32 new_nbits = m->bmap_nbits;
+
+		while (new_nbits <= virq)
+			new_nbits <<= 1;
+		rc = bmap_grow(m, new_nbits);
+		if (rc) {
+			spin_unlock(&m->lock);
+			return rc;
+		}
+	}
+
+	rc = bmap_alloc_specific(m, virq);
+	if (rc == SBI_EALREADY) {
+		struct virq_entry *e = rev_get_existing(m, virq);
+
+		if (!e || e->chip_uid != chip_uid || e->hwirq != hwirq) {
+			spin_unlock(&m->lock);
+			return SBI_EALREADY;
+		}
+
+		spin_unlock(&m->lock);
+		return SBI_OK;
+	} else if (rc) {
+		spin_unlock(&m->lock);
+		return rc;
+	}
+
+	rc = vec_ensure_cap(m, m->nodes_cnt + 1U);
+	if (rc) {
+		spin_unlock(&m->lock);
+		return rc;
+	}
+
+	{
+		struct virq_entry *e = rev_get_or_alloc(m, virq);
+
+		if (!e) {
+			bmap_free_one(m, virq);
+			spin_unlock(&m->lock);
+			return SBI_ENOMEM;
+		}
+		e->chip_uid = chip_uid;
+		e->hwirq = hwirq;
+	}
+
+	m->nodes[m->nodes_cnt].chip_uid = chip_uid;
+	m->nodes[m->nodes_cnt].hwirq = hwirq;
+	m->nodes[m->nodes_cnt].virq = virq;
+	m->nodes_cnt++;
+	spin_unlock(&m->lock);
+
+	return SBI_OK;
+}
+
+int sbi_virq_map_ensure_cap(u32 channel_id, u32 min_virq_cap)
+{
+	struct sbi_virq_map *m;
+	u32 new_nbits;
+	int rc = SBI_OK;
+
+	if (min_virq_cap < 8U)
+		min_virq_cap = 8U;
+
+	if (channel_id == 0) {
+		m = &g_virq_map;
+		if (!m->bmap)
+			return SBI_EINVAL;
+	} else {
+		m = virq_map_get(channel_id, true, min_virq_cap);
+		if (!m)
+			return SBI_ENOMEM;
+	}
+
+	if (m->bmap_nbits >= min_virq_cap)
+		return SBI_OK;
+
+	spin_lock(&m->lock);
+	new_nbits = m->bmap_nbits ? m->bmap_nbits : 8U;
+	while (new_nbits < min_virq_cap)
+		new_nbits <<= 1;
+	rc = bmap_grow(m, new_nbits);
+	spin_unlock(&m->lock);
+
+	return rc;
+}
+
+int sbi_virq_hwirq2virq(u32 channel_id, u32 chip_uid, u32 hwirq,
+			u32 *out_virq)
+{
+	u32 idx;
+	int rc;
+	struct sbi_virq_map *m;
+
+	m = virq_map_get(channel_id, false, 0);
+	if (!m)
+		return SBI_ENOENT;
+
+	spin_lock(&m->lock);
+	rc = forward_find_idx(m, chip_uid, hwirq, &idx);
+	if (!rc)
+		*out_virq = m->nodes[idx].virq;
+	spin_unlock(&m->lock);
+
+	return rc;
+}
+
+int sbi_virq_virq2hwirq(u32 channel_id, u32 virq,
+			u32 *out_chip_uid, u32 *out_hwirq)
+{
+	struct virq_entry *e;
+	struct sbi_virq_map *m;
+
+	m = virq_map_get(channel_id, false, 0);
+	if (!m)
+		return SBI_EINVAL;
+
+	spin_lock(&m->lock);
+
+	if (virq >= m->bmap_nbits ||
+	    !bitmap_test(m->bmap, (int)virq)) {
+		spin_unlock(&m->lock);
+		return SBI_EINVAL;
+	}
+
+	e = rev_get_existing(m, virq);
+	if (!e) {
+		spin_unlock(&m->lock);
+		return SBI_EINVAL;
+	}
+
+	*out_chip_uid = e->chip_uid;
+	*out_hwirq = e->hwirq;
+
+	spin_unlock(&m->lock);
+
+	return SBI_OK;
+}
+
+int sbi_virq_unmap_one(u32 virq)
+{
+	struct virq_entry *e;
+	u32 idx, last;
+	int rc;
+	struct sbi_virq_map *m = &g_virq_map;
+
+	spin_lock(&m->lock);
+
+	if (virq >= m->bmap_nbits ||
+	    !bitmap_test(m->bmap, (int)virq)) {
+		spin_unlock(&m->lock);
+		return SBI_EINVAL;
+	}
+
+	e = rev_get_existing(m, virq);
+	if (!e) {
+		spin_unlock(&m->lock);
+		return SBI_EINVAL;
+	}
+
+	/* find forward node corresponding to this virq (linear) */
+	rc = SBI_ENOENT;
+	for (idx = 0; idx < m->nodes_cnt; idx++) {
+		if (m->nodes[idx].virq == virq) {
+			/* optionally also check endpoint matches e */
+			rc = 0;
+			break;
+		}
+	}
+	if (rc) {
+		/* inconsistent state */
+		spin_unlock(&m->lock);
+		return SBI_EINVAL;
+	}
+
+	/* remove node: swap with last */
+	last = m->nodes_cnt - 1U;
+	if (idx != last)
+		m->nodes[idx] = m->nodes[last];
+	m->nodes_cnt--;
+
+	/* clear reverse + free virq id */
+	rev_clear(m, virq);
+	bmap_free_one(m, virq);
+
+	spin_unlock(&m->lock);
+
+	return SBI_OK;
+}
+
+static void virq_map_uninit_one(struct sbi_virq_map *m)
+{
+	u32 i;
+
+	spin_lock(&m->lock);
+
+	/* free reverse chunks */
+	if (m->chunks) {
+		for (i = 0; i < m->chunks_cap; i++) {
+			if (m->chunks[i])
+				sbi_free(m->chunks[i]);
+		}
+		sbi_free(m->chunks);
+		m->chunks = NULL;
+		m->chunks_cap = 0;
+	}
+
+	/* free forward vector */
+	if (m->nodes) {
+		sbi_free(m->nodes);
+		m->nodes = NULL;
+		m->nodes_cnt = 0;
+		m->nodes_cap = 0;
+	}
+
+	/* free bitmap */
+	if (m->bmap) {
+		sbi_free(m->bmap);
+		m->bmap = NULL;
+		m->bmap_nbits = 0;
+	}
+
+	spin_unlock(&m->lock);
+}
+
+void sbi_virq_map_uninit(void)
+{
+	u32 i;
+
+	virq_map_uninit_one(&g_virq_map);
+
+	spin_lock(&g_virq_maps_lock);
+	for (i = 0; i < g_virq_maps_cnt; i++)
+		virq_map_uninit_one(&g_virq_maps[i].map);
+	if (g_virq_maps) {
+		sbi_free(g_virq_maps);
+		g_virq_maps = NULL;
+		g_virq_maps_cnt = 0;
+		g_virq_maps_cap = 0;
+	}
+	spin_unlock(&g_virq_maps_lock);
+}
+
+int sbi_virq_sysirq_add_channel(struct sbi_domain *dom, u32 channel_id,
+			       u32 num_irqs)
+{
+	static const struct rpmi_sysirq_ops ops = {
+		.claim = sbi_virq_claim_thishart,
+		.complete = sbi_virq_rearm_thishart,
+		.drained = sbi_virq_return_to_prev_if_needed,
+	};
+
+	return rpmi_sysirq_register_channel(dom, channel_id, num_irqs, &ops);
+}
+
+int sbi_virq_enqueue(struct sbi_virq_courier_binding *c)
+{
+	struct sbi_domain_virq_state *st;
+
+	if (!c || !c->dom || !c->chip || c->virq == VIRQ_INVALID ||
+	    !c->hwirq || c->hwirq >= c->chip->num_hwirq)
+		return SBI_EINVAL;
+
+	st = domain_virq_thishart(c->dom);
+	if (!st)
+		return SBI_ENODEV;
+
+	spin_lock(&st->lock);
+	if (q_full(st)) {
+		spin_unlock(&st->lock);
+		return SBI_ENOSPC;
+	}
+
+	st->q[st->tail].virq = c->virq;
+	st->q[st->tail].channel_id = c->channel_id;
+	st->q[st->tail].hwirq = c->hwirq;
+	st->q[st->tail].chip = c->chip;
+	st->tail = (st->tail + 1) % VIRQ_QSIZE;
+	spin_unlock(&st->lock);
+
+	return SBI_OK;
+}
+
+int sbi_virq_claim_thishart(u32 channel_id, u32 *virqs, u32 max_count,
+			   u32 *out_count)
+{
+	struct sbi_domain *dom = sbi_domain_thishart_ptr();
+	struct sbi_domain_virq_state *st;
+	u32 count = 0, index, next;
+	bool found, drained;
+
+	if (!dom || !virqs || !max_count || !out_count)
+		return SBI_EINVAL;
+	*out_count = 0;
+	st = domain_virq_thishart(dom);
+	if (!st)
+		return SBI_ENODEV;
+
+	while (count < max_count) {
+		found = false;
+		spin_lock(&st->lock);
+		for (index = st->head; index != st->tail;
+		     index = (index + 1) % VIRQ_QSIZE) {
+			if (st->q[index].channel_id == channel_id) {
+				found = true;
+				break;
+			}
+		}
+		if (!found) {
+			spin_unlock(&st->lock);
+			break;
+		}
+
+		virqs[count++] = st->q[index].virq;
+		/* Remove this channel's entry without reordering other channels. */
+		for (next = (index + 1) % VIRQ_QSIZE; next != st->tail;
+		     next = (next + 1) % VIRQ_QSIZE) {
+			st->q[index] = st->q[next];
+			index = next;
+		}
+		st->tail = (st->tail + VIRQ_QSIZE - 1) % VIRQ_QSIZE;
+		st->claimed_count++;
+		spin_unlock(&st->lock);
+	}
+
+	spin_lock(&st->lock);
+	drained = q_empty(st);
+	spin_unlock(&st->lock);
+	if (drained) {
+		sbi_irqchip_notify_smode_clear();
+		/*
+		 * A nonempty response must reach S-mode before switching back.
+		 * The payload requests the next batch after handling this one.
+		 */
+		if (!count)
+			sbi_virq_return_to_prev_if_needed();
+	}
+
+	*out_count = count;
+	return SBI_OK;
+}
+
+int sbi_virq_rearm_thishart(u32 channel_id, u32 virq)
+{
+	struct sbi_domain *dom = sbi_domain_thishart_ptr();
+	struct sbi_domain_virq_state *st;
+	struct sbi_irqchip_device *chip;
+	u32 chip_uid, hwirq;
+	int rc;
+
+	if (!dom)
+		return SBI_EINVAL;
+	st = domain_virq_thishart(dom);
+	if (!st)
+		return SBI_ENODEV;
+	if (!st->claimed_count)
+		return SBI_EINVAL;
+	rc = sbi_virq_virq2hwirq(channel_id, virq, &chip_uid, &hwirq);
+	if (rc)
+		return rc;
+	chip = sbi_irqchip_find_device(chip_uid);
+	if (!chip)
+		return SBI_ENODEV;
+	if (chip->hwirq_eoi)
+		chip->hwirq_eoi(chip, hwirq);
+	rc = sbi_irqchip_unmask_hwirq(chip, hwirq);
+	if (rc && rc != SBI_EALREADY)
+		return rc;
+
+	spin_lock(&st->lock);
+	st->claimed_count--;
+	spin_unlock(&st->lock);
+	return SBI_OK;
+}
+
+void sbi_virq_return_to_prev_if_needed(void)
+{
+	struct sbi_domain *dom = sbi_domain_thishart_ptr();
+	struct sbi_domain_virq_state *st;
+	bool do_return = false;
+
+	if (!dom)
+		return;
+
+	st = domain_virq_thishart(dom);
+	if (!st)
+		return;
+
+	spin_lock(&st->lock);
+	if (st->return_to_prev && q_empty(st) && !st->claimed_count) {
+		st->return_to_prev = false;
+		do_return = true;
+	}
+	spin_unlock(&st->lock);
+
+	if (!do_return)
+		return;
+
+	sbi_domain_context_request_return_to_prev();
+}
+
+int sbi_virq_courier_handler(u32 hwirq, void *opaque)
+{
+	struct sbi_virq_courier_ctx *ctx =
+		(struct sbi_virq_courier_ctx *)opaque;
+	struct sbi_domain *dom;
+	struct sbi_virq_courier_binding courier;
+	u32 channel_id = 0;
+	u32 virq = 0;
+	int rc;
+	struct sbi_domain *curr_dom;
+
+	if (!ctx || !ctx->chip)
+		return SBI_EINVAL;
+
+	/* Route by the physical controller and HWIRQ from FDT. */
+	rc = sbi_virq_route_lookup(ctx->chip->id, hwirq, &dom, &channel_id);
+	if (rc || !dom)
+		return SBI_EINVAL;
+
+	curr_dom = sbi_domain_thishart_ptr();
+
+	/* Allocate/Get a stable VIRQ for (chip_uid, hwirq). */
+	rc = sbi_virq_map_one(channel_id, ctx->chip->id, hwirq,
+			      false, 0, &virq);
+	if (rc)
+		return rc;
+
+	/*
+	 * Mask to avoid level-trigger storm before S-mode clears device source.
+	 * The next SYSIRQ_CLAIM rearms it after S-mode handles the device.
+	 */
+	sbi_irqchip_mask_hwirq(ctx->chip, hwirq);
+
+	courier.dom  = dom;
+	courier.chip = ctx->chip;
+	courier.channel_id = channel_id;
+	courier.virq = virq;
+	courier.hwirq = hwirq;
+
+	rc = sbi_virq_enqueue(&courier);
+	if (rc) {
+		/* enqueue failed; re-enable to avoid deadlock */
+		sbi_irqchip_unmask_hwirq(ctx->chip, hwirq);
+		return rc;
+	}
+
+	/*
+	 * Notify S-mode on notification rising edge.
+	 *
+	 * If the target is the current domain, operate on the live CSR.
+	 * Otherwise, set the pending bit in the target domain context
+	 * before switching (covers first-entry). After switching, set the
+	 * live CSR only if needed (covers already-initialized targets).
+	 */
+	if (dom != curr_dom) {
+		sbi_domain_context_pending_notify_smode(dom, current_hartindex());
+
+		/* Mark return_to_prev for VIRQ-driven domain switch. */
+		virq_set_domain_return_flag(dom, true);
+		rc = sbi_domain_context_enter(dom);
+		if (rc) {
+			/* Switch failed; do not defer EOI */
+			sbi_irqchip_unmask_hwirq(ctx->chip, hwirq);
+			if (ctx->chip->hwirq_eoi)
+				ctx->chip->hwirq_eoi(ctx->chip, hwirq);
+			return SBI_OK;
+		}
+
+		/*
+		 * If the domain was already initialized,
+		 * sbi_domain_context_enter() returns and CSR_SIP reflect
+		 * dom_ctx->sip. For robustness, set the live notify bit if it
+		 * is still clear.
+		 */
+		if (!sbi_irqchip_notify_smode_get()) {
+			rc = sbi_irqchip_notify_smode_set();
+			if (rc) {
+				/*
+				 * notification failed; re-enable to avoid
+				 * deadlock
+				 */
+				sbi_irqchip_unmask_hwirq(ctx->chip, hwirq);
+				return rc;
+			}
+		}
+	} else if (!sbi_irqchip_notify_smode_get()) {
+		rc = sbi_irqchip_notify_smode_set();
+		if (rc) {
+			/* notification failed; re-enable to avoid deadlock */
+			sbi_irqchip_unmask_hwirq(ctx->chip, hwirq);
+			return rc;
+		}
+	}
+
+	/*
+	 * Defer EOI until a follow-up CLAIM rearms the handled source.
+	 */
+	return SBI_EALREADY;
+}
+
+int sbi_virq_register_courier(struct sbi_irqchip_device *chip, u32 hwirq,
+			     u32 hwirq_flags, u32 hartindex)
+{
+	struct sbi_virq_courier_ctx *ctx;
+	int rc;
+
+	if (!chip || !hwirq || hwirq >= chip->num_hwirq ||
+	    !sbi_hartindex_valid(hartindex))
+		return SBI_EINVAL;
+	if (!(chip->caps & SBI_IRQCHIP_CAPS_WIRED) || !chip->process_hwirqs ||
+	    !chip->hwirq_mask || !chip->hwirq_unmask || !chip->hwirq_set_affinity)
+		return SBI_ENOTSUPP;
+	if (!sbi_hartmask_test_hartindex(hartindex, &chip->target_harts))
+		return SBI_ENOTSUPP;
+
+	ctx = sbi_zalloc(sizeof(*ctx));
+	if (!ctx)
+		return SBI_ENOMEM;
+	ctx->chip = chip;
+	rc = sbi_irqchip_register_handler(chip, hwirq, 1, hwirq_flags,
+					 sbi_virq_courier_handler, ctx);
+	if (rc) {
+		sbi_free(ctx);
+		return rc;
+	}
+
+	/* Registration unmasks hardware before updating the enabled state. */
+	chip->hwirq_mask(chip, hwirq);
+	rc = sbi_irqchip_set_affinity(chip, hwirq, hartindex);
+	if (!rc)
+		rc = sbi_irqchip_unmask_hwirq(chip, hwirq);
+	if (rc) {
+		sbi_irqchip_unregister_handler(chip, hwirq, 1);
+		sbi_free(ctx);
+	}
+	return rc;
+}
+
+int sbi_virq_setup_couriers(void)
+{
+	struct sbi_virq_route_rule *rule;
+	struct sbi_irqchip_device *chip;
+	u32 i, hartindex;
+	int rc;
+
+	/* Cold-boot only: domain registration and route construction are done. */
+	for (i = 0; i < g_router.cnt; i++) {
+		rule = &g_router.rules[i];
+		chip = sbi_irqchip_find_device(rule->chip_uid);
+		if (!chip)
+			return SBI_ENODEV;
+		hartindex = sbi_hartid_to_hartindex(rule->dom->boot_hartid);
+		if (!sbi_hartindex_valid(hartindex) ||
+		    !sbi_hartmask_test_hartindex(hartindex,
+					       rule->dom->possible_harts))
+			return SBI_EINVAL;
+		rc = sbi_virq_register_courier(chip, rule->hwirq,
+					    rule->hwirq_flags, hartindex);
+		if (rc)
+			return rc;
+	}
+
+	return SBI_OK;
+}
+
+int sbi_virq_domain_init(struct sbi_domain *dom)
+{
+	struct sbi_domain_virq_priv *p;
+	u32 i, k, nharts, st_count;
+	struct sbi_domain_virq_state *st_base;
+	size_t alloc_size;
+
+	if (!dom)
+		return SBI_EINVAL;
+
+	if (dom->virq_priv)
+		return SBI_OK;
+
+	nharts = sbi_virq_platform_hart_count();
+	st_count = dom->possible_harts ?
+		   (u32)sbi_hartmask_weight(dom->possible_harts) : nharts;
+
+	alloc_size = sizeof(*p) +
+		     nharts * sizeof(p->st_by_hart[0]) +
+		     st_count * sizeof(struct sbi_domain_virq_state);
+	p = sbi_zalloc(alloc_size);
+	if (!p)
+		return SBI_ENOMEM;
+
+	p->nharts = nharts;
+	p->st_count = st_count;
+	st_base = (struct sbi_domain_virq_state *)(p->st_by_hart + nharts);
+
+	if (!dom->possible_harts) {
+		for (i = 0; i < nharts; i++) {
+			p->st_by_hart[i] = &st_base[i];
+			virq_state_init(p->st_by_hart[i]);
+		}
+	} else {
+		for (i = 0; i < nharts; i++)
+			p->st_by_hart[i] = NULL;
+		k = 0;
+		sbi_hartmask_for_each_hartindex(i, dom->possible_harts) {
+			if (k >= st_count)
+				break;
+			p->st_by_hart[i] = &st_base[k++];
+			virq_state_init(p->st_by_hart[i]);
+		}
+	}
+	dom->virq_priv = p;
+
+	return SBI_OK;
+}
+
+void sbi_virq_domain_exit(struct sbi_domain *dom)
+{
+	if (!dom || !dom->virq_priv)
+		return;
+
+	sbi_free(dom->virq_priv);
+	dom->virq_priv = NULL;
+}
+
+int sbi_virq_init(u32 init_virq_cap)
+{
+	int rc = SBI_OK;
+
+	if (g_virq_inited)
+		return SBI_EALREADY;
+
+	rc = sbi_virq_map_init(0, init_virq_cap);
+	if (rc)
+		return rc;
+
+	SPIN_LOCK_INIT(g_virq_maps_lock);
+	SPIN_LOCK_INIT(g_router.lock);
+	sbi_virq_route_reset();
+	g_virq_inited = true;
+	return rc;
+}
+
+bool sbi_virq_is_inited(void)
+{
+	return g_virq_inited;
+}
-- 
2.25.1


-- 
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 07/12] lib: sbi: domain: add domain lookup by name
  2026-10-29 15:59 [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework Raymond Mao
                   ` (5 preceding siblings ...)
  2026-10-29 15:59 ` [PATCH v2 06/12] lib: sbi: Add Virtual IRQ (VIRQ) subsystem Raymond Mao
@ 2026-10-29 15:59 ` Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 08/12] lib: utils: irqchip: derive APLIC targets from sysirq nodes Raymond Mao
                   ` (4 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Raymond Mao @ 2026-10-29 15:59 UTC (permalink / raw)
  To: opensbi
  Cc: anup, anup.patel, scott, raymond.mao, robin.randhawa,
	samuel.holland, peter.lin

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

Provide a helper to resolve a domain by its DT node name, used by
sysirq DT parsing.

Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
 include/sbi/sbi_domain.h |  3 +++
 lib/sbi/sbi_domain.c     | 15 +++++++++++++++
 2 files changed, 18 insertions(+)

diff --git a/include/sbi/sbi_domain.h b/include/sbi/sbi_domain.h
index 3e357e67..3cb49007 100644
--- a/include/sbi/sbi_domain.h
+++ b/include/sbi/sbi_domain.h
@@ -234,6 +234,9 @@ struct sbi_domain *sbi_hartindex_to_domain(u32 hartindex);
 /** Update HART local pointer to point to specified domain */
 void sbi_update_hartindex_to_domain(u32 hartindex, struct sbi_domain *dom);
 
+/** Find domain by DT node name (domain name) */
+struct sbi_domain *sbi_domain_find_by_name(const char *name);
+
 /** Get pointer to sbi_domain for current HART */
 #define sbi_domain_thishart_ptr() \
 	sbi_hartindex_to_domain(current_hartindex())
diff --git a/lib/sbi/sbi_domain.c b/lib/sbi/sbi_domain.c
index 30588678..16df5533 100644
--- a/lib/sbi/sbi_domain.c
+++ b/lib/sbi/sbi_domain.c
@@ -61,6 +61,21 @@ void sbi_update_hartindex_to_domain(u32 hartindex, struct sbi_domain *dom)
 	sbi_scratch_write_type(scratch, void *, domain_hart_ptr_offset, dom);
 }
 
+struct sbi_domain *sbi_domain_find_by_name(const char *name)
+{
+	struct sbi_domain *dom;
+
+	if (!name)
+		return NULL;
+
+	sbi_domain_for_each(dom) {
+		if (!sbi_strncmp(dom->name, name, sizeof(dom->name)))
+			return dom;
+	}
+
+	return NULL;
+}
+
 bool sbi_domain_is_assigned_hart(const struct sbi_domain *dom, u32 hartindex)
 	MUST_NOT_HOLD(&dom->assigned_harts_lock)
 {
-- 
2.25.1


-- 
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 08/12] lib: utils: irqchip: derive APLIC targets from sysirq nodes
  2026-10-29 15:59 [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework Raymond Mao
                   ` (6 preceding siblings ...)
  2026-10-29 15:59 ` [PATCH v2 07/12] lib: sbi: domain: add domain lookup by name Raymond Mao
@ 2026-10-29 15:59 ` Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 09/12] lib: utils: irqchip: Add helper to register VIRQ courier handler Raymond Mao
                   ` (3 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Raymond Mao @ 2026-10-29 15:59 UTC (permalink / raw)
  To: opensbi
  Cc: anup, anup.patel, scott, raymond.mao, robin.randhawa,
	samuel.holland, peter.lin

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

Derive a per-HWIRQ target hartindex map from each sysirq node's
target domain boot-hart, and store the result in aplic_data.
This is used for setting IDC target per HWIRQ under irqchip driver
instead of hardcoding to a specific target.

Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
 include/sbi_utils/irqchip/aplic.h     |   1 +
 lib/utils/irqchip/fdt_irqchip_aplic.c | 103 ++++++++++++++++++++++++++
 2 files changed, 104 insertions(+)

diff --git a/include/sbi_utils/irqchip/aplic.h b/include/sbi_utils/irqchip/aplic.h
index 8d6d871e..76b39099 100644
--- a/include/sbi_utils/irqchip/aplic.h
+++ b/include/sbi_utils/irqchip/aplic.h
@@ -48,6 +48,7 @@ struct aplic_data {
 	struct aplic_msicfg_data msicfg_smode;
 	struct aplic_delegate_data delegate[APLIC_MAX_DELEGATE];
 	u32 *idc_map;
+	u32 *hwirq_target_hartindex;
 };
 
 int aplic_cold_irqchip_init(struct aplic_data *aplic);
diff --git a/lib/utils/irqchip/fdt_irqchip_aplic.c b/lib/utils/irqchip/fdt_irqchip_aplic.c
index 4d7b1e77..0f9ba636 100644
--- a/lib/utils/irqchip/fdt_irqchip_aplic.c
+++ b/lib/utils/irqchip/fdt_irqchip_aplic.c
@@ -12,6 +12,7 @@
 #include <sbi/riscv_asm.h>
 #include <sbi/sbi_error.h>
 #include <sbi/sbi_heap.h>
+#include <sbi/sbi_scratch.h>
 #include <sbi_utils/fdt/fdt_helper.h>
 #include <sbi_utils/irqchip/fdt_irqchip.h>
 #include <sbi_utils/irqchip/aplic.h>
@@ -53,6 +54,88 @@ static int irqchip_aplic_update_idc_map(const void *fdt, int nodeoff,
 	return 0;
 }
 
+static u32 irqchip_aplic_domain_boot_hartindex(void *fdt, int domain_offset)
+{
+	int len, cpu_offset;
+	const fdt32_t *val;
+
+	val = fdt_getprop(fdt, domain_offset, "boot-hart", &len);
+	if (val && len >= 4) {
+		cpu_offset = fdt_node_offset_by_phandle(fdt,
+							fdt32_to_cpu(*val));
+		if (cpu_offset >= 0) {
+			u32 hartid;
+
+			if (!fdt_parse_hart_id(fdt, cpu_offset, &hartid)) {
+				u32 hidx = sbi_hartid_to_hartindex(hartid);
+
+				if (sbi_hartindex_valid(hidx))
+					return hidx;
+			}
+		}
+	}
+
+	return current_hartindex();
+}
+
+static void irqchip_aplic_fill_hwirq_targets_from_sysirq(const void *fdt,
+							 int aplic_nodeoff,
+							 struct aplic_data *pd)
+{
+	int chosen_off, nodeoff;
+	int len, rc, index;
+	const fdt32_t *val;
+	u32 boot_hartindex;
+
+	if (!fdt || aplic_nodeoff < 0 || !pd || !pd->hwirq_target_hartindex)
+		return;
+
+	chosen_off = fdt_path_offset(fdt, "/chosen/opensbi-domains");
+	if (chosen_off < 0)
+		return;
+
+	fdt_for_each_subnode(nodeoff, fdt, chosen_off) {
+		if (fdt_node_check_compatible(fdt, nodeoff,
+					      "opensbi,mpxy-sysirq"))
+			continue;
+
+		val = fdt_getprop(fdt, nodeoff, "opensbi,domain", &len);
+		if (!val || len < 4)
+			continue;
+
+		rc = fdt_node_offset_by_phandle(fdt, fdt32_to_cpu(*val));
+		if (rc < 0)
+			continue;
+
+		boot_hartindex = irqchip_aplic_domain_boot_hartindex((void *)fdt,
+								     rc);
+
+		for (index = 0; ; index++) {
+			struct fdt_phandle_args args;
+			u32 hwirq;
+
+			rc = fdt_parse_phandle_with_args(fdt, nodeoff,
+							 "interrupts-extended",
+							 "#interrupt-cells",
+							 index, &args);
+			if (rc)
+				break;
+			if (args.args_count < 1)
+				continue;
+			if (args.node_offset != aplic_nodeoff)
+				continue;
+
+			hwirq = args.args[0];
+			if (!hwirq || hwirq > pd->num_source)
+				continue;
+
+			if (pd->hwirq_target_hartindex[hwirq] == -1U)
+				pd->hwirq_target_hartindex[hwirq] =
+					boot_hartindex;
+		}
+	}
+}
+
 static int irqchip_aplic_cold_init(const void *fdt, int nodeoff,
 				   const struct fdt_match *match)
 {
@@ -79,6 +162,24 @@ static int irqchip_aplic_cold_init(const void *fdt, int nodeoff,
 			goto fail_free_idc_map;
 	}
 
+	/* Precompute target hartindex per HWIRQ from DT. */
+	if (pd->targets_mmode) {
+		u32 i;
+
+		pd->hwirq_target_hartindex =
+			sbi_zalloc(sizeof(*pd->hwirq_target_hartindex) *
+				   (pd->num_source + 1));
+		if (!pd->hwirq_target_hartindex) {
+			rc = SBI_ENOMEM;
+			goto fail_free_idc_map;
+		}
+
+		for (i = 0; i <= pd->num_source; i++)
+			pd->hwirq_target_hartindex[i] = -1U;
+
+		irqchip_aplic_fill_hwirq_targets_from_sysirq(fdt, nodeoff, pd);
+	}
+
 	rc = aplic_cold_irqchip_init(pd);
 	if (rc)
 		goto fail_free_idc_map;
@@ -86,6 +187,8 @@ static int irqchip_aplic_cold_init(const void *fdt, int nodeoff,
 	return 0;
 
 fail_free_idc_map:
+	if (pd->hwirq_target_hartindex)
+		sbi_free(pd->hwirq_target_hartindex);
 	if (pd->num_idc)
 		sbi_free(pd->idc_map);
 fail_free_data:
-- 
2.25.1


-- 
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 09/12] lib: utils: irqchip: Add helper to register VIRQ courier handler
  2026-10-29 15:59 [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework Raymond Mao
                   ` (7 preceding siblings ...)
  2026-10-29 15:59 ` [PATCH v2 08/12] lib: utils: irqchip: derive APLIC targets from sysirq nodes Raymond Mao
@ 2026-10-29 15:59 ` Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 10/12] lib: utils: fdt: parse sysirq routing from DT Raymond Mao
                   ` (2 subsequent siblings)
  11 siblings, 0 replies; 13+ messages in thread
From: Raymond Mao @ 2026-10-29 15:59 UTC (permalink / raw)
  To: opensbi
  Cc: anup, anup.patel, scott, raymond.mao, robin.randhawa,
	samuel.holland, peter.lin

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

Add an APLIC helper to register a VIRQ courier handler for a supplied
HWIRQ and IRQ type, using the precomputed per-HWIRQ target hartindex
map.

Translate OpenSBI hart indices to APLIC IDC indices when programming
targets and setting affinity. Reject targets with no matching IDC
instead of directing interrupts to an unrelated fallback IDC.
Reuse the VIRQ registration helper to preserve the selected target
after handler registration, which initially selects the calling hart.

Treat SBI_EALREADY as deferred completion rather than an error when
processing hardware interrupts.

Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
 include/sbi_utils/irqchip/aplic.h |  3 ++
 lib/utils/irqchip/aplic.c         | 57 +++++++++++++++++++++++++++++--
 2 files changed, 58 insertions(+), 2 deletions(-)

diff --git a/include/sbi_utils/irqchip/aplic.h b/include/sbi_utils/irqchip/aplic.h
index 76b39099..44bbfcf6 100644
--- a/include/sbi_utils/irqchip/aplic.h
+++ b/include/sbi_utils/irqchip/aplic.h
@@ -53,6 +53,9 @@ struct aplic_data {
 
 int aplic_cold_irqchip_init(struct aplic_data *aplic);
 
+int aplic_init_virq_courier(struct aplic_data *aplic, u32 hwirq,
+			    u32 hwirq_flags);
+
 void aplic_reinit_all(void);
 
 #endif
diff --git a/lib/utils/irqchip/aplic.c b/lib/utils/irqchip/aplic.c
index 3da9a717..b9a43115 100644
--- a/lib/utils/irqchip/aplic.c
+++ b/lib/utils/irqchip/aplic.c
@@ -13,6 +13,8 @@
 #include <sbi/sbi_domain.h>
 #include <sbi/sbi_error.h>
 #include <sbi/sbi_heap.h>
+#include <sbi/sbi_scratch.h>
+#include <sbi/sbi_virq.h>
 #include <sbi_utils/irqchip/aplic.h>
 
 #define APLIC_MAX_IDC			(1UL << 14)
@@ -426,7 +428,7 @@ static int aplic_process_hwirqs(struct sbi_irqchip_device *chip)
 			continue;
 
 		tmp = sbi_irqchip_process_hwirq(chip, hwirq);
-		if (tmp)
+		if (tmp && tmp != SBI_EALREADY)
 			rc = tmp;
 	}
 
@@ -549,6 +551,19 @@ static int aplic_setup_msi(struct aplic_data *aplic, u32 hwirq)
 	return 0;
 }
 
+static void aplic_set_target(struct sbi_irqchip_device *chip, u32 hwirq,
+			     u32 idc_index)
+{
+	struct aplic_data *aplic = container_of(chip, struct aplic_data, irqchip);
+
+	aplic_target_write(aplic, hwirq, APLIC_TARGET_HART_IDX(idc_index) |
+			   APLIC_TARGET_IPRIO(APLIC_DEFAULT_PRIORITY));
+	aplic_idc_write(aplic, idc_index, APLIC_IDC_IDELIVERY,
+			APLIC_ENABLE_IDELIVERY);
+	aplic_idc_write(aplic, idc_index, APLIC_IDC_ITHRESHOLD,
+			APLIC_ENABLE_ITHRESHOLD);
+}
+
 static int aplic_hwirq_setup(struct sbi_irqchip_device *chip,
 			     u32 hwirq, u32 hwirq_flags)
 {
@@ -637,7 +652,7 @@ static int aplic_hwirq_set_affinity(struct sbi_irqchip_device *chip,
 		return SBI_EINVAL;
 
 	aplic_target_write(aplic, hwirq,
-			   APLIC_TARGET_HART_IDX(hart_index) |
+			   APLIC_TARGET_HART_IDX(idc_index) |
 			   APLIC_TARGET_IPRIO(APLIC_DEFAULT_PRIORITY));
 
 	return 0;
@@ -676,6 +691,44 @@ static struct sbi_irqchip_device aplic_irqchip_template = {
 	.hwirq_unmask		= aplic_hwirq_unmask,
 };
 
+int aplic_init_virq_courier(struct aplic_data *aplic, u32 hwirq,
+			    u32 hwirq_flags)
+{
+	u32 irq, hartindex = current_hartindex();
+	int idc_idx;
+	int rc;
+
+	if (!aplic || !aplic->targets_mmode || !aplic_is_direct_mode(aplic))
+		return SBI_ENOTSUPP;
+	if (!hwirq || hwirq > aplic->num_source)
+		return SBI_EINVAL;
+
+	if (!sbi_virq_is_inited()) {
+		rc = sbi_virq_init(aplic->num_source);
+		if (rc)
+			return rc;
+	}
+
+	for (irq = 1; irq <= aplic->num_source; irq++) {
+		u32 hartidx;
+
+		if (!aplic->hwirq_target_hartindex)
+			break;
+		hartidx = aplic->hwirq_target_hartindex[irq];
+		if (hartidx == -1U)
+			continue;
+		idc_idx = aplic_find_idc_index(aplic, hartidx);
+		if (idc_idx < 0)
+			return SBI_EINVAL;
+		aplic_set_target(&aplic->irqchip, irq, idc_idx);
+	}
+	if (aplic->hwirq_target_hartindex &&
+	    aplic->hwirq_target_hartindex[hwirq] != -1U)
+		hartindex = aplic->hwirq_target_hartindex[hwirq];
+	return sbi_virq_register_courier(&aplic->irqchip, hwirq, hwirq_flags,
+					hartindex);
+}
+
 int aplic_cold_irqchip_init(struct aplic_data *aplic)
 {
 	int rc;
-- 
2.25.1


-- 
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 10/12] lib: utils: fdt: parse sysirq routing from DT
  2026-10-29 15:59 [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework Raymond Mao
                   ` (8 preceding siblings ...)
  2026-10-29 15:59 ` [PATCH v2 09/12] lib: utils: irqchip: Add helper to register VIRQ courier handler Raymond Mao
@ 2026-10-29 15:59 ` Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 11/12] docs: domain: document sysirq VIRQ mapping and routing rules Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 12/12] [NOT-FOR-UPSTREAM] platform: generic: Add sample dts overlays for testing hwirq/domain route rules Raymond Mao
  11 siblings, 0 replies; 13+ messages in thread
From: Raymond Mao @ 2026-10-29 15:59 UTC (permalink / raw)
  To: opensbi
  Cc: anup, anup.patel, scott, raymond.mao, robin.randhawa,
	samuel.holland, peter.lin

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

Parse mpxy-sysirq nodes under /chosen/opensbi-domains, pre-size
per-channel VIRQ maps, and add physical interrupt routes from
interrupts-extended. Mark sysirq domains to allow SEIP-based VIRQ
notification.

Initialize VIRQ on demand and register each domain-owned RPMI
SYSTEM_IRQ channel using its channel ID and interrupt entry count.
Store the physical controller, HWIRQ and IRQ flags in each route.
Activate courier handlers during domain finalization after boot HART
selection, setting affinity to the destination domain's final boot HART.
Complete mapping and channel setup before activating physical sources.

Reject malformed phandle argument lists and insufficient IRQ flag
buffers instead of truncating interrupt specifiers. Require one HWIRQ
cell and one flags cell for SYSIRQ sources, and validate scalar
channel and domain properties.

Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
 include/sbi_utils/fdt/fdt_helper.h |   8 ++
 lib/sbi/sbi_domain.c               |   3 +
 lib/utils/fdt/fdt_domain.c         | 116 ++++++++++++++++++++++++++++-
 lib/utils/fdt/fdt_helper.c         |  57 +++++++++++++-
 4 files changed, 179 insertions(+), 5 deletions(-)

diff --git a/include/sbi_utils/fdt/fdt_helper.h b/include/sbi_utils/fdt/fdt_helper.h
index 75a564d1..342cf738 100644
--- a/include/sbi_utils/fdt/fdt_helper.h
+++ b/include/sbi_utils/fdt/fdt_helper.h
@@ -12,6 +12,7 @@
 
 #include <sbi/sbi_types.h>
 #include <sbi/sbi_domain.h>
+#include <sbi/sbi_irqchip.h>
 
 struct fdt_match {
 	const char *compatible;
@@ -38,6 +39,13 @@ int fdt_parse_phandle_with_args(const void *fdt, int nodeoff,
 				const char *prop, const char *cells_prop,
 				int index, struct fdt_phandle_args *out_args);
 
+int fdt_parse_interrupts_extended_entry(const void *fdt, int nodeoff,
+					int index,
+					struct sbi_irqchip_device **out_chip,
+					u32 *out_hwirq,
+					u32 *out_flags,
+					u32 *out_flags_count);
+
 int fdt_get_node_addr_size(const void *fdt, int node, int index,
 			   uint64_t *addr, uint64_t *size);
 
diff --git a/lib/sbi/sbi_domain.c b/lib/sbi/sbi_domain.c
index 16df5533..36b64b32 100644
--- a/lib/sbi/sbi_domain.c
+++ b/lib/sbi/sbi_domain.c
@@ -930,6 +930,9 @@ int sbi_domain_finalize(struct sbi_scratch *scratch)
 
 	/* Select stable boot HARTs before programming physical IRQ targets. */
 	domain_select_boot_harts();
+	rc = sbi_virq_setup_couriers();
+	if (rc)
+		return rc;
 
 	/*
 	 * Set the finalized flag so that the root domain
diff --git a/lib/utils/fdt/fdt_domain.c b/lib/utils/fdt/fdt_domain.c
index 575fbdf0..4d38d402 100644
--- a/lib/utils/fdt/fdt_domain.c
+++ b/lib/utils/fdt/fdt_domain.c
@@ -15,7 +15,9 @@
 #include <sbi/sbi_error.h>
 #include <sbi/sbi_hartmask.h>
 #include <sbi/sbi_heap.h>
+#include <sbi/sbi_irqchip.h>
 #include <sbi/sbi_scratch.h>
+#include <sbi/sbi_virq.h>
 #include <sbi_utils/fdt/fdt_domain.h>
 #include <sbi_utils/fdt/fdt_helper.h>
 
@@ -291,7 +293,6 @@ static int __fdt_parse_region(const void *fdt, int domain_offset,
 
 	return 0;
 }
-
 static int __fdt_parse_domain(const void *fdt, int domain_offset, void *opaque)
 {
 	u32 val32;
@@ -488,12 +489,123 @@ fail_free_domain:
 	return err;
 }
 
+static int __fdt_parse_mpxy_sysirq_node(const void *fdt, int nodeoff)
+{
+	const fdt32_t *val;
+	int len, rc, doff;
+	u32 channel_id;
+	u32 index, num_irqs;
+	struct sbi_domain *dom;
+
+	if (!fdt || nodeoff < 0)
+		return SBI_EINVAL;
+
+	val = fdt_getprop(fdt, nodeoff, "opensbi,mpxy-channel-id", &len);
+	if (!val || len != sizeof(*val))
+		return SBI_EINVAL;
+	channel_id = fdt32_to_cpu(*val);
+
+	val = fdt_getprop(fdt, nodeoff, "opensbi,domain", &len);
+	if (!val || len != sizeof(*val))
+		return SBI_EINVAL;
+
+	doff = fdt_node_offset_by_phandle(fdt, fdt32_to_cpu(*val));
+	if (doff < 0)
+		return doff;
+
+	dom = sbi_domain_find_by_name(fdt_get_name(fdt, doff, NULL));
+	if (!dom)
+		return SBI_ENOENT;
+	dom->virq_seip_notify = true;
+
+	/* Pre-allocate VIRQ map based on interrupts-extended count */
+	for (index = 0; ; index++) {
+		rc = fdt_parse_interrupts_extended_entry(fdt, nodeoff, index,
+							 NULL, NULL,
+							 NULL, NULL);
+		if (rc == SBI_ENOENT)
+			break;
+		if (rc)
+			return rc;
+	}
+
+	if (!sbi_virq_is_inited()) {
+		rc = sbi_virq_init(index);
+		if (rc)
+			return rc;
+	}
+	rc = sbi_virq_map_ensure_cap(channel_id, index);
+	if (rc)
+		return rc;
+	num_irqs = index;
+
+	for (index = 0; ; index++) {
+		struct sbi_irqchip_device *chip = NULL;
+		u32 hwirq = 0, flags, flags_count = 1;
+
+		rc = fdt_parse_interrupts_extended_entry(fdt, nodeoff, index,
+							 &chip, &hwirq,
+							 &flags, &flags_count);
+		if (rc == SBI_ENOENT)
+			break;
+		if (rc)
+			return rc;
+		if (!chip)
+			return SBI_ENODEV;
+		if (flags_count != 1)
+			return SBI_ENOTSUPP;
+
+		rc = sbi_virq_map_set(channel_id, chip->id, hwirq, index);
+		if (rc)
+			return rc;
+
+		rc = sbi_virq_route_add(dom, chip->id, hwirq, flags, channel_id);
+		if (rc)
+			return rc;
+	}
+
+	return sbi_virq_sysirq_add_channel(dom, channel_id, num_irqs);
+}
+
+static int __fdt_parse_mpxy_sysirq_nodes(const void *fdt)
+{
+	int poffset, noff, rc;
+
+	if (!fdt)
+		return SBI_EINVAL;
+
+	poffset = fdt_path_offset(fdt, "/chosen");
+	if (poffset < 0)
+		return 0;
+	poffset = fdt_node_offset_by_compatible(fdt, poffset,
+						"opensbi,domain,config");
+	if (poffset < 0)
+		return 0;
+
+	fdt_for_each_subnode(noff, fdt, poffset) {
+		if (fdt_node_check_compatible(fdt, noff,
+					      "opensbi,mpxy-sysirq"))
+			continue;
+		rc = __fdt_parse_mpxy_sysirq_node(fdt, noff);
+		if (rc)
+			return rc;
+	}
+
+	return 0;
+}
+
 int fdt_domains_populate(const void *fdt)
 {
+	int err;
+
 	/* Sanity checks */
 	if (!fdt)
 		return SBI_EINVAL;
 
 	/* Iterate over each domain in FDT and populate details */
-	return fdt_iterate_each_domain_ro(fdt, NULL, __fdt_parse_domain);
+	err = fdt_iterate_each_domain_ro(fdt, NULL, __fdt_parse_domain);
+	if (err)
+		return err;
+
+	return __fdt_parse_mpxy_sysirq_nodes(fdt);
 }
diff --git a/lib/utils/fdt/fdt_helper.c b/lib/utils/fdt/fdt_helper.c
index 747ba028..21c78279 100644
--- a/lib/utils/fdt/fdt_helper.c
+++ b/lib/utils/fdt/fdt_helper.c
@@ -10,6 +10,7 @@
 #include <sbi/riscv_asm.h>
 #include <sbi/sbi_console.h>
 #include <sbi/sbi_hartmask.h>
+#include <sbi/sbi_irqchip.h>
 #include <sbi/sbi_platform.h>
 #include <sbi/sbi_scratch.h>
 #include <sbi/sbi_hart.h>
@@ -47,6 +48,8 @@ int fdt_parse_phandle_with_args(const void *fdt, int nodeoff,
 	list = fdt_getprop(fdt, nodeoff, prop, &len);
 	if (!list)
 		return SBI_ENOENT;
+	if (len % sizeof(*list))
+		return SBI_EINVAL;
 	list_end = list + (len / sizeof(*list));
 
 	while (list < list_end) {
@@ -57,13 +60,13 @@ int fdt_parse_phandle_with_args(const void *fdt, int nodeoff,
 		list++;
 
 		val = fdt_getprop(fdt, pnodeoff, cells_prop, &len);
-		if (!val)
-			return SBI_ENOENT;
+		if (!val || len != sizeof(*val))
+			return SBI_EINVAL;
 		pcells = fdt32_to_cpu(*val);
 		if (FDT_MAX_PHANDLE_ARGS < pcells)
 			return SBI_EINVAL;
 		if (list + pcells > list_end)
-			return SBI_ENOENT;
+			return SBI_EINVAL;
 
 		if (index > 0) {
 			list += pcells;
@@ -80,6 +83,54 @@ int fdt_parse_phandle_with_args(const void *fdt, int nodeoff,
 	return SBI_ENOENT;
 }
 
+int fdt_parse_interrupts_extended_entry(const void *fdt, int nodeoff,
+					int index,
+					struct sbi_irqchip_device **out_chip,
+					u32 *out_hwirq,
+					u32 *out_flags,
+					u32 *out_flags_count)
+{
+	struct fdt_phandle_args args;
+	struct sbi_irqchip_device *chip;
+	u32 flags_cap = 0, flags_cnt = 0;
+	int rc, i;
+
+	if (!fdt || nodeoff < 0)
+		return SBI_EINVAL;
+
+	rc = fdt_parse_phandle_with_args(fdt, nodeoff, "interrupts-extended",
+					 "#interrupt-cells", index, &args);
+	if (rc)
+		return rc;
+
+	if (args.args_count < 1)
+		return SBI_EINVAL;
+
+	if (out_hwirq)
+		*out_hwirq = args.args[0];
+
+	if (out_flags_count) {
+		flags_cap = *out_flags_count;
+		flags_cnt = (args.args_count > 1) ? (args.args_count - 1) : 0;
+		*out_flags_count = flags_cnt;
+		if (out_flags && flags_cap < flags_cnt)
+			return SBI_EBAD_RANGE;
+		if (out_flags) {
+			for (i = 0; i < (int)flags_cnt; i++)
+				out_flags[i] = args.args[i + 1];
+		}
+	}
+
+	if (out_chip) {
+		chip = sbi_irqchip_find_device((u32)args.node_offset);
+		if (!chip)
+			return SBI_ENODEV;
+		*out_chip = chip;
+	}
+
+	return 0;
+}
+
 static int fdt_translate_address(const void *fdt, uint64_t reg, int parent,
 				 uint64_t *addr)
 {
-- 
2.25.1


-- 
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 11/12] docs: domain: document sysirq VIRQ mapping and routing rules
  2026-10-29 15:59 [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework Raymond Mao
                   ` (9 preceding siblings ...)
  2026-10-29 15:59 ` [PATCH v2 10/12] lib: utils: fdt: parse sysirq routing from DT Raymond Mao
@ 2026-10-29 15:59 ` Raymond Mao
  2026-10-29 15:59 ` [PATCH v2 12/12] [NOT-FOR-UPSTREAM] platform: generic: Add sample dts overlays for testing hwirq/domain route rules Raymond Mao
  11 siblings, 0 replies; 13+ messages in thread
From: Raymond Mao @ 2026-10-29 15:59 UTC (permalink / raw)
  To: opensbi
  Cc: anup, anup.patel, scott, raymond.mao, robin.randhawa,
	samuel.holland, peter.lin

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

Document the DT binding semantics for opensbi,mpxy-sysirq nodes,
including how interrupts-extended entry order determines per-channel
VIRQ numbers and how opensbi,domain selects the destination OpenSBI
domain.
Add a SYSIRQ node example under /chosen/opensbi-domains.

Describe DT-driven courier registration and delivery to the owning
domain's boot hart. Summarize interrupt notification, claiming,
completion and deferred return domain switches.

Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
 docs/domain_support.md | 55 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/docs/domain_support.md b/docs/domain_support.md
index 51737218..a2d40024 100644
--- a/docs/domain_support.md
+++ b/docs/domain_support.md
@@ -325,3 +325,58 @@ be done:
         ...
     };
 ```
+
+### SYSIRQ Node
+
+The SYSIRQ node describes physical interrupts delivered to a domain through
+an RPMI SYSTEM_IRQ channel over SBI MPXY. It should be placed under the
+domain configuration node.
+
+The DT properties of a SYSIRQ node are as follows:
+
+* **compatible** (Mandatory) - The compatible string of the SYSIRQ node.
+  This DT property should have value *"opensbi,mpxy-sysirq"*.
+* **interrupt-controller** (Optional) - Marks the node as a virtual interrupt
+  controller when it is referenced by other DT nodes.
+* **#interrupt-cells** (Optional) - Must be <1> when the node is used as an
+  interrupt controller. The interrupt specifier is the VIRQ number.
+* **interrupts-extended** (Mandatory) - The physical interrupt sources,
+  specified as <&irqchip hwirq flags> entries.
+* **opensbi,mpxy-channel-id** (Mandatory) - The unique SBI MPXY channel ID.
+  Each channel has its own VIRQ namespace.
+* **opensbi,domain** (Mandatory) - The phandle of the destination domain
+  instance node.
+
+Each interrupts-extended entry maps a physical interrupt to a VIRQ number
+starting from zero, in entry order. All entries in the node belong to the
+specified domain. Physical sources are identified by the interrupt controller
+and HWIRQ number; the same HWIRQ number on different controllers denotes
+different sources. Each physical source can belong to only one channel.
+The physical specifier must contain one HWIRQ cell and one flags cell.
+Physical delivery targets the domain's finalized boot HART. The current
+registration path supports wired M-mode interrupt controllers with masking
+and affinity operations. The interrupt controller must be configured to
+deliver these sources to M-mode rather than delegate them to S-mode.
+
+OpenSBI establishes the channel mappings and routing rules, then selects
+domain boot HARTs before enabling physical delivery. It notifies the
+destination domain through SEIP and switches to that domain if required.
+The payload claims pending interrupts through SYSIRQ_CLAIM and handles the
+device sources before completing them through the next CLAIM or the optional
+Yield ECALL. Return to a previously interrupted domain is permitted only
+after pending and claimed interrupts are drained. This completion and
+return-switch convention is specific to OpenSBI's domain courier.
+
+For example, the following node maps physical interrupt 10 to VIRQ 0 in
+channel 4 and delivers it to the boot HART of tdomain:
+
+```dts
+sysirq {
+    compatible = "opensbi,mpxy-sysirq";
+    interrupt-controller;
+    #interrupt-cells = <1>;
+    opensbi,mpxy-channel-id = <4>;
+    opensbi,domain = <&tdomain>;
+    interrupts-extended = <&aplic_m 10 4>;
+};
+```
-- 
2.25.1


-- 
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi

^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 12/12] [NOT-FOR-UPSTREAM] platform: generic: Add sample dts overlays for testing hwirq/domain route rules
  2026-10-29 15:59 [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework Raymond Mao
                   ` (10 preceding siblings ...)
  2026-10-29 15:59 ` [PATCH v2 11/12] docs: domain: document sysirq VIRQ mapping and routing rules Raymond Mao
@ 2026-10-29 15:59 ` Raymond Mao
  11 siblings, 0 replies; 13+ messages in thread
From: Raymond Mao @ 2026-10-29 15:59 UTC (permalink / raw)
  To: opensbi
  Cc: anup, anup.patel, scott, raymond.mao, robin.randhawa,
	samuel.holland, peter.lin

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

Introduce rpmi_sysirq_intc nodes with interrupts-extended property
for VIRQ testing.
- Routing rules are derived from sysirq nodes by:
      interrupts-extended =
         <&aplic HWIRQx IRQ_TYPE>, // virq 0
         <&aplic HWIRQy IRQ_TYPE>, // virq 1
         ...;
- VIRQ numbers are allocated from zero, implicit from the order
  of the entries in the interrupts-extended property.

During cold boot, route rules are converted and cached for VIRQ
routing/couriering purpose, and DT IRQ entries activate courier delivery.

hwirq_bind_domain.dts is for testing with a single domain/hart.
Domain1 owns UART hwirq 10 via 'interrupts-extended'.

hwirq_bind_domain_linux_bmapp.dts is for testing with multiple
domains/harts. It models two BM domains alongside the root domain
on 'QEMU virt -smp 4'. Domain1 and domain2 share hart2 as boot hart
and each maps its own bm-app RAM region and the UART region;
domain2 owns UART hwirq 10 via 'interrupts-extended'.
Bind CPU hart2 to domain1 and set explicit init-order values to keep
hart2 in domain1 for initial execution. Load the BM payloads at
0x88000000 and 0x89000000 to exercise domain1 -> domain2 -> domain1.

Keep the configured courier sources 10, 20 and 21 in the M-mode APLIC
and delegate the remaining sources to its S-mode child. This preserves
Linux interrupt delivery without delegating the BM domains' IRQs.

Usage:
1. Compile the overlay.
   $ dtc -@ -I dts -O dtb -o <dto_file> \
         platform/generic/virt/<dts_overlay_file>
2. Dump the base dtb from QEMU via arg
   '-machine dumpdtb=<dtb_qemu_base>'
3. Apply the overlay on the base.
   $ fdtoverlay -i <dtb_qemu_base> -o <dtb_qemu_merge> <dto_file>
4. Re-run QEMU with adding argument '-dtb <dtb_qemu_merge>'

Signed-off-by: Raymond Mao <raymond.mao@riscstar.com>
---
 platform/generic/virt/hwirq_bind_domain.dts   | 107 +++++++++++++++
 .../virt/hwirq_bind_domain_linux_bmapp.dts    | 125 ++++++++++++++++++
 2 files changed, 232 insertions(+)
 create mode 100644 platform/generic/virt/hwirq_bind_domain.dts
 create mode 100644 platform/generic/virt/hwirq_bind_domain_linux_bmapp.dts

diff --git a/platform/generic/virt/hwirq_bind_domain.dts b/platform/generic/virt/hwirq_bind_domain.dts
new file mode 100644
index 00000000..93f21d0b
--- /dev/null
+++ b/platform/generic/virt/hwirq_bind_domain.dts
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/dts-v1/;
+/plugin/;
+
+/ {
+	/*
+	 * Fragment 0: OpenSBI domain configuration under /chosen
+	 */
+	fragment@0 {
+		target-path = "/chosen";
+		__overlay__ {
+			opensbi-domains {
+				compatible = "opensbi,domain,config";
+
+				/*
+				 * DRAM region for domain1 (2GB from 0x8000_0000).
+				 * base is 64-bit: <hi lo>, order is log2(size).
+				 */
+				memregion_dram_2g: memregion_dram_2g {
+					compatible = "opensbi,domain,memregion";
+					base  = <0x0 0x80000000>;
+					order = <31>;
+				};
+
+				/*
+				 * UART MMIO region so S-mode payload can access UART registers
+				 * without fault. 0x1000_0000 size 4KB.
+				 */
+				uart0_mmio: memregion_uart0 {
+					compatible = "opensbi,domain,memregion";
+					base  = <0x0 0x10000000>;
+					order = <12>;
+				};
+
+				/*
+				 * Domain instance:
+				 * - possible-harts = <cpu0 phandle> (cpu@0 phandle is 1)
+				 * - regions use label references (fdtoverlay will fixup phandles)
+				 * - IRQ routing is defined by sysirq nodes under
+				 *   /chosen/opensbi-domains
+				 */
+				domain1: domain1 {
+					compatible = "opensbi,domain,instance";
+
+					/* cpu@0 phandle in base qemu dtb is 1 */
+					possible-harts = <0x1>;
+
+					/*
+					 * Keep permissive for test.
+					 * access=0x3f: enable all M+SU R/W/X flags (test only).
+					 */
+					regions = <&memregion_dram_2g 0x3f>,
+						  <&uart0_mmio 0x3f>;
+
+					/* Route rules are supplied via sysirq nodes. */
+				};
+
+				rpmi_sysirq_intc: interrupt-controller {
+					compatible = "opensbi,mpxy-sysirq";
+					interrupt-controller;
+					#interrupt-cells = <1>;
+
+					/*
+					 * VIRQ numbers are allocated from zero, in order.
+					 * Each entry: <&irqchip hwirq flags>
+					 * flags: IRQ_TYPE_LEVEL_HIGH = 4
+					 */
+					/*
+					 * Base DTB (qemu_linux_test.dtb) phandle:
+					 * /soc/interrupt-controller@c000000 -> 0x09
+					 */
+					interrupts-extended =
+						<0x09 10 4>, /* VIRQ 0: UART RX */
+						<0x09 20 4>, /* VIRQ 1: test */
+						<0x09 21 4>; /* VIRQ 2: test */
+
+					/* MPXY channel id allocated by system designer */
+					opensbi,mpxy-channel-id = <4>;
+
+					/* Route VIRQs to domain1 */
+					opensbi,domain = <&domain1>;
+				};
+			};
+		};
+	};
+
+	/*
+	 * Fragment 1: Assign cpu0 to domain1 using label reference
+	 */
+	fragment@1 {
+		target-path = "/cpus/cpu@0";
+		__overlay__ {
+			opensbi-domain = <&domain1>;
+		};
+	};
+
+	fragment@2 {
+		target-path = "/soc/interrupt-controller@c000000";
+		__overlay__ {
+			/* Keep courier IRQs 10, 20 and 21 in the M-mode APLIC. */
+			riscv,delegate = <0x0a 1 9>, <0x0a 11 19>,
+					 <0x0a 22 96>;
+			riscv,delegation = <0x0a 1 9>, <0x0a 11 19>,
+					   <0x0a 22 96>;
+		};
+	};
+};
diff --git a/platform/generic/virt/hwirq_bind_domain_linux_bmapp.dts b/platform/generic/virt/hwirq_bind_domain_linux_bmapp.dts
new file mode 100644
index 00000000..d26e4237
--- /dev/null
+++ b/platform/generic/virt/hwirq_bind_domain_linux_bmapp.dts
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: BSD-2-Clause
+/dts-v1/;
+/plugin/;
+
+/ {
+	fragment@0 {
+		target-path = "/chosen";
+		__overlay__ {
+			opensbi-domains {
+				compatible = "opensbi,domain,config";
+
+				d1_uart_mmio: memregion_domain1_uart_mmio {
+					compatible = "opensbi,domain,memregion";
+					base  = <0x0 0x10000000>;
+					order = <12>;
+					mmio;
+				};
+
+				d1_bmapp_ram: memregion_domain1_bmapp_ram {
+					compatible = "opensbi,domain,memregion";
+					base  = <0x0 0x88000000>;
+					order = <24>;
+				};
+
+				d2_bmapp_ram: memregion_domain2_bmapp_ram {
+					compatible = "opensbi,domain,memregion";
+					base  = <0x0 0x89000000>;
+					order = <24>;
+				};
+
+				/*
+				 * Domain instance:
+				 * - possible-harts uses CPU node phandles
+				 * - regions use label references (fdtoverlay will fixup phandles)
+				 */
+				domain1: domain1 {
+					compatible = "opensbi,domain,instance";
+					init-order = <1>;
+
+					/*
+					 * QEMU virt -smp 4 dumpdtb phandles:
+					 * cpu@1 -> 0x05, cpu@2 -> 0x03
+					 */
+					possible-harts = <0x05 0x03>;
+					boot-hart      = <0x03>;
+
+					/*
+					 * Keep permissive for test.
+					 * access=0x3f: enable all M+SU R/W/X flags (test only).
+					 */
+					regions = <&d1_bmapp_ram 0x3f>,
+						  <&d1_uart_mmio 0x3f>;
+
+					next-addr = <0x0 0x88000000>;
+					next-arg1 = <0x0 0x1>;
+					next-mode = <0x1>;
+				};
+
+				domain2: domain2 {
+					compatible = "opensbi,domain,instance";
+					init-order = <2>;
+
+					/*
+					 * QEMU virt -smp 4 dumpdtb phandles:
+					 * cpu@1 -> 0x05, cpu@2 -> 0x03
+					 */
+					possible-harts = <0x05 0x03>;
+					boot-hart      = <0x03>;
+
+					regions = <&d2_bmapp_ram 0x3f>,
+						  <&d1_uart_mmio 0x3f>;
+
+					next-addr = <0x0 0x89000000>;
+					next-arg1 = <0x0 0x2>;
+					next-mode = <0x1>;
+				};
+
+				rpmi_sysirq_intc: interrupt-controller {
+					compatible = "opensbi,mpxy-sysirq";
+					interrupt-controller;
+					#interrupt-cells = <1>;
+
+					/*
+					 * VIRQ numbers are allocated from zero, in order.
+					 * Each entry: <&irqchip hwirq flags>
+					 * flags: IRQ_TYPE_LEVEL_HIGH = 4
+					 */
+					/*
+					 * Base DTB (qemu_linux_test.dtb) phandle:
+					 * /soc/interrupt-controller@c000000 -> 0x09
+					 */
+					interrupts-extended =
+						<0x09 10 4>, /* VIRQ 0: UART RX */
+						<0x09 20 4>, /* VIRQ 1: test */
+						<0x09 21 4>; /* VIRQ 2: test */
+
+					/* MPXY channel id allocated by system designer */
+					opensbi,mpxy-channel-id = <4>;
+
+					/* Route VIRQs to domain2 */
+					opensbi,domain = <&domain2>;
+				};
+			};
+		};
+	};
+
+	fragment@1 {
+		target-path = "/cpus/cpu@2";
+		__overlay__ {
+			opensbi-domain = <&domain1>;
+		};
+	};
+
+	fragment@2 {
+		target-path = "/soc/interrupt-controller@c000000";
+		__overlay__ {
+			/* Keep courier IRQs 10, 20 and 21 in the M-mode APLIC. */
+			riscv,delegate = <0x0a 1 9>, <0x0a 11 19>,
+					 <0x0a 22 96>;
+			riscv,delegation = <0x0a 1 9>, <0x0a 11 19>,
+					   <0x0a 22 96>;
+		};
+	};
+
+};
-- 
2.25.1


-- 
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi

^ permalink raw reply related	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2026-10-29 16:00 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-29 15:59 [PATCH v2 00/12] Introduce Virtual IRQ (VIRQ) framework Raymond Mao
2026-10-29 15:59 ` [PATCH v2 01/12] lib: utils: add RPMI SYSTEM_IRQ service support Raymond Mao
2026-10-29 15:59 ` [PATCH v2 02/12] lib: sbi: add experimental Yield ECALL extension Raymond Mao
2026-10-29 15:59 ` [PATCH v2 03/12] lib: irqchip: add S-mode notification helpers Raymond Mao
2026-10-29 15:59 ` [PATCH v2 04/12] lib: sbi: domain: support VIRQ couriering domain context switch Raymond Mao
2026-10-29 15:59 ` [PATCH v2 05/12] lib: sbi: domain: ensure boot_hartid is assigned Raymond Mao
2026-10-29 15:59 ` [PATCH v2 06/12] lib: sbi: Add Virtual IRQ (VIRQ) subsystem Raymond Mao
2026-10-29 15:59 ` [PATCH v2 07/12] lib: sbi: domain: add domain lookup by name Raymond Mao
2026-10-29 15:59 ` [PATCH v2 08/12] lib: utils: irqchip: derive APLIC targets from sysirq nodes Raymond Mao
2026-10-29 15:59 ` [PATCH v2 09/12] lib: utils: irqchip: Add helper to register VIRQ courier handler Raymond Mao
2026-10-29 15:59 ` [PATCH v2 10/12] lib: utils: fdt: parse sysirq routing from DT Raymond Mao
2026-10-29 15:59 ` [PATCH v2 11/12] docs: domain: document sysirq VIRQ mapping and routing rules Raymond Mao
2026-10-29 15:59 ` [PATCH v2 12/12] [NOT-FOR-UPSTREAM] platform: generic: Add sample dts overlays for testing hwirq/domain route rules Raymond Mao

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox