* [RFC PATCH v3 0/6] virt: bao: Add Bao hypervisor IPC and I/O dispatcher drivers
@ 2026-08-07 7:39 João Peixoto
2026-08-07 7:39 ` [RFC PATCH v3 1/6] dt-bindings: bao: add IPC shared-memory device João Peixoto
` (5 more replies)
0 siblings, 6 replies; 11+ messages in thread
From: João Peixoto @ 2026-08-07 7:39 UTC (permalink / raw)
To: gregkh, robh, krzk+dt, conor+dt
Cc: jose, davidmcerdeira, corbet, skhan, catalin.marinas, will, linux,
pjw, palmer, aou, alex, andrew.jones, rdunlap, linux-kernel,
devicetree, linux-doc, linux-arm-kernel, linux-riscv
This series adds guest-side drivers for the Bao static-partitioning
hypervisor: an IPC shared-memory driver and an I/O dispatcher, plus their
device-tree bindings, UAPI and MAINTAINERS entry.
Bao is a lightweight static-partitioning hypervisor for embedded and
safety-critical systems.
- The IPC shared-memory driver lets Bao guests exchange data through a
shared-memory region split into a read and a write channel.
- The I/O dispatcher lets a backend guest service paravirtualised (VirtIO)
I/O on behalf of frontend guests.
Sent as RFC: the RISC-V backend uses the SBI experimental extension space
(see "Open items" below), and the device-tree bindings were reworked in this
version and would benefit from another look.
## Changes since v2
- dt-bindings (ipcshmem): describe the two channels through reg/reg-names,
drop read-channel/write-channel and the "id" property, use a vendor
"bao,id", and use a generic node name (Krzysztof Kozlowski).
- dt-bindings (io-dispatcher): one node per backend device (single reg +
interrupt) instead of one node describing many devices, modelled on the
gunyah/Mediatek Genio bindings (Krzysztof Kozlowski).
- io dispatcher driver: one platform device per device model (/dev/bao-dmX);
removed the global DM list, the anonymous-inode fd and the dispatcher
indirection ioctl; fixed a leak and probe error-path bugs; initialise
virtio_requests_lock and the hypercall context fields.
- ipcshmem driver: derive the two regions from reg; drop the success print;
do the range arithmetic in u64.
- Kconfig: fix the help-text indentation (Randy Dunlap).
- style: unwrap the few-char line wraps; the IPC hypercall helper now takes a
single argument and fits on one line (Andrew Jones).
- riscv: use BAO_SBI_EXT_ID consistently, document that it is in the SBI
experimental extension space, and drop the redundant ecall input
constraints (Andrew Jones).
- commit messages: rewritten to kernel style; the "consolidate the IPC
hypercall ID" patch now explains why and covers the signature change
(Greg KH).
- process: sent as a fresh thread with this changelog, and the mail setup that
bounced v1/v2 is fixed (Krzysztof Kozlowski, Greg KH).
## Open items / still under discussion
- RISC-V uses the experimental SBI extension ID (0x08000ba0). A permanent
implementation ID must be registered in the RISC-V SBI spec before RISC-V
can be non-experimental; hence RFC.
- Whether the Remote I/O hypercall must follow SBI chapter-3 register rules
(Andrew Jones) — see the reply on patch 4.
- The "bao,id" property and the "bao" vendor prefix (Krzysztof Kozlowski) —
rationale is in the bindings; alternatives welcome.
Link to v2: https://lore.kernel.org/all/20260107162829.416885-1-joaopeixoto@osyx.tech/
João Peixoto (6):
dt-bindings: bao: add IPC shared-memory device
virt: bao: add IPC shared-memory driver
dt-bindings: bao: add I/O dispatcher device
virt: bao: add I/O dispatcher driver
virt: bao: consolidate the IPC hypercall ID in include/linux/bao.h
MAINTAINERS: add Bao hypervisor entry
.../bindings/bao/bao,io-dispatcher.yaml | 58 +++
.../devicetree/bindings/bao/bao,ipcshmem.yaml | 59 +++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
.../userspace-api/ioctl/ioctl-number.rst | 2 +
MAINTAINERS | 13 +
arch/arm/include/asm/bao.h | 60 +++
arch/arm64/include/asm/bao.h | 60 +++
arch/riscv/include/asm/bao.h | 66 +++
drivers/virt/Kconfig | 2 +
drivers/virt/Makefile | 2 +
drivers/virt/bao/Kconfig | 5 +
drivers/virt/bao/Makefile | 4 +
drivers/virt/bao/io-dispatcher/Kconfig | 16 +
drivers/virt/bao/io-dispatcher/Makefile | 4 +
drivers/virt/bao/io-dispatcher/bao_drv.h | 349 +++++++++++++++
drivers/virt/bao/io-dispatcher/dm.c | 316 ++++++++++++++
drivers/virt/bao/io-dispatcher/driver.c | 95 +++++
drivers/virt/bao/io-dispatcher/intc.c | 64 +++
drivers/virt/bao/io-dispatcher/io_client.c | 401 ++++++++++++++++++
.../virt/bao/io-dispatcher/io_dispatcher.c | 181 ++++++++
drivers/virt/bao/io-dispatcher/ioeventfd.c | 323 ++++++++++++++
drivers/virt/bao/io-dispatcher/irqfd.c | 314 ++++++++++++++
drivers/virt/bao/ipcshmem/Kconfig | 10 +
drivers/virt/bao/ipcshmem/Makefile | 3 +
drivers/virt/bao/ipcshmem/ipcshmem.c | 228 ++++++++++
include/linux/bao.h | 44 ++
include/uapi/linux/bao.h | 96 +++++
27 files changed, 2777 insertions(+)
create mode 100644 Documentation/devicetree/bindings/bao/bao,io-dispatcher.yaml
create mode 100644 Documentation/devicetree/bindings/bao/bao,ipcshmem.yaml
create mode 100644 arch/arm/include/asm/bao.h
create mode 100644 arch/arm64/include/asm/bao.h
create mode 100644 arch/riscv/include/asm/bao.h
create mode 100644 drivers/virt/bao/Kconfig
create mode 100644 drivers/virt/bao/Makefile
create mode 100644 drivers/virt/bao/io-dispatcher/Kconfig
create mode 100644 drivers/virt/bao/io-dispatcher/Makefile
create mode 100644 drivers/virt/bao/io-dispatcher/bao_drv.h
create mode 100644 drivers/virt/bao/io-dispatcher/dm.c
create mode 100644 drivers/virt/bao/io-dispatcher/driver.c
create mode 100644 drivers/virt/bao/io-dispatcher/intc.c
create mode 100644 drivers/virt/bao/io-dispatcher/io_client.c
create mode 100644 drivers/virt/bao/io-dispatcher/io_dispatcher.c
create mode 100644 drivers/virt/bao/io-dispatcher/ioeventfd.c
create mode 100644 drivers/virt/bao/io-dispatcher/irqfd.c
create mode 100644 drivers/virt/bao/ipcshmem/Kconfig
create mode 100644 drivers/virt/bao/ipcshmem/Makefile
create mode 100644 drivers/virt/bao/ipcshmem/ipcshmem.c
create mode 100644 include/linux/bao.h
create mode 100644 include/uapi/linux/bao.h
base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482
--
2.43.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [RFC PATCH v3 1/6] dt-bindings: bao: add IPC shared-memory device
2026-08-07 7:39 [RFC PATCH v3 0/6] virt: bao: Add Bao hypervisor IPC and I/O dispatcher drivers João Peixoto
@ 2026-08-07 7:39 ` João Peixoto
2026-08-07 7:45 ` sashiko-bot
2026-08-07 7:39 ` [RFC PATCH v3 2/6] virt: bao: add IPC shared-memory driver João Peixoto
` (4 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: João Peixoto @ 2026-08-07 7:39 UTC (permalink / raw)
To: gregkh, robh, krzk+dt, conor+dt
Cc: jose, davidmcerdeira, corbet, skhan, catalin.marinas, will, linux,
pjw, palmer, aou, alex, andrew.jones, rdunlap, linux-kernel,
devicetree, linux-doc, linux-arm-kernel, linux-riscv
Add the device-tree binding for the Bao hypervisor IPC shared-memory
device, used by a Bao guest to exchange data with a peer guest over a
shared-memory region.
Co-developed-by: José Martins <jose@osyx.tech>
Signed-off-by: José Martins <jose@osyx.tech>
Co-developed-by: David Cerdeira <davidmcerdeira@osyx.tech>
Signed-off-by: David Cerdeira <davidmcerdeira@osyx.tech>
Signed-off-by: João Peixoto <jpeixoto@osyx.tech>
---
v3:
- Describe the read and write channels through reg/reg-names instead of the
custom read-channel/write-channel properties; drop those properties and the
"id" property; add a vendor "bao,id" for the hypercall channel id; use a
generic node name (shmem@...) (Krzysztof Kozlowski).
- Clarify the "bao" vendor-prefix description.
- Subject: drop the redundant trailing "binding" (Krzysztof Kozlowski).
- Add José's and David's Co-developed-by/Signed-off-by.
.../devicetree/bindings/bao/bao,ipcshmem.yaml | 59 +++++++++++++++++++
.../devicetree/bindings/vendor-prefixes.yaml | 2 +
2 files changed, 61 insertions(+)
create mode 100644 Documentation/devicetree/bindings/bao/bao,ipcshmem.yaml
diff --git a/Documentation/devicetree/bindings/bao/bao,ipcshmem.yaml b/Documentation/devicetree/bindings/bao/bao,ipcshmem.yaml
new file mode 100644
index 000000000000..60947150d576
--- /dev/null
+++ b/Documentation/devicetree/bindings/bao/bao,ipcshmem.yaml
@@ -0,0 +1,59 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/bao/bao,ipcshmem.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Bao Hypervisor IPC Shared Memory Device
+
+maintainers:
+ - João Peixoto <jpeixoto@osyx.tech>
+ - José Martins <jose@osyx.tech>
+ - David Cerdeira <davidmcerdeira@osyx.tech>
+
+description: |
+ Shared-memory device exposed by the Bao hypervisor to a guest so that it
+ can exchange data with a peer guest. The hypervisor provides two
+ contiguous memory regions, one the guest reads from (written by the peer)
+ and one the guest writes to (read by the peer). A write is signalled to
+ the peer through a hypercall identified by bao,id.
+
+properties:
+ compatible:
+ const: bao,ipcshmem
+
+ reg:
+ items:
+ - description: region read by this guest and written by the peer
+ - description: region written by this guest and read by the peer
+
+ reg-names:
+ items:
+ - const: read
+ - const: write
+
+ bao,id:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Hypervisor-assigned identifier of this shared-memory channel. The
+ guest passes it to the Bao hypercall that notifies the peer of a
+ write, so it must match the identifier configured for the channel in
+ the hypervisor.
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - bao,id
+
+additionalProperties: false
+
+examples:
+ - |
+ shmem@f0000000 {
+ compatible = "bao,ipcshmem";
+ reg = <0xf0000000 0x2000>,
+ <0xf0002000 0x2000>;
+ reg-names = "read", "write";
+ bao,id = <0>;
+ };
diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
index 396044f368e7..b11b3dd995a5 100644
--- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
+++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
@@ -231,6 +231,8 @@ patternProperties:
description: Shenzhen AZW Technology Co., Ltd.
"^baikal,.*":
description: BAIKAL ELECTRONICS, JSC
+ "^bao,.*":
+ description: Bao Project (https://github.com/bao-project)
"^bananapi,.*":
description: BIPAI KEJI LIMITED
"^beacon,.*":
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [RFC PATCH v3 2/6] virt: bao: add IPC shared-memory driver
2026-08-07 7:39 [RFC PATCH v3 0/6] virt: bao: Add Bao hypervisor IPC and I/O dispatcher drivers João Peixoto
2026-08-07 7:39 ` [RFC PATCH v3 1/6] dt-bindings: bao: add IPC shared-memory device João Peixoto
@ 2026-08-07 7:39 ` João Peixoto
2026-08-07 7:56 ` sashiko-bot
2026-08-07 7:39 ` [RFC PATCH v3 3/6] dt-bindings: bao: add I/O dispatcher device João Peixoto
` (3 subsequent siblings)
5 siblings, 1 reply; 11+ messages in thread
From: João Peixoto @ 2026-08-07 7:39 UTC (permalink / raw)
To: gregkh, robh, krzk+dt, conor+dt
Cc: jose, davidmcerdeira, corbet, skhan, catalin.marinas, will, linux,
pjw, palmer, aou, alex, andrew.jones, rdunlap, linux-kernel,
devicetree, linux-doc, linux-arm-kernel, linux-riscv
Add a driver that lets guests running on the Bao static-partitioning
hypervisor communicate through shared memory. Each guest is assigned a
read and a write region within a shared-memory area described in the
device tree.
Userspace accesses the regions through a misc character device using
read(), write() and mmap(). A write() notifies the peer guest through an
architecture-specific hypercall (HVC on arm/arm64, SBI ecall on RISC-V).
Co-developed-by: José Martins <jose@osyx.tech>
Signed-off-by: José Martins <jose@osyx.tech>
Co-developed-by: David Cerdeira <davidmcerdeira@osyx.tech>
Signed-off-by: David Cerdeira <davidmcerdeira@osyx.tech>
Signed-off-by: João Peixoto <jpeixoto@osyx.tech>
---
v3:
- Map the read and write regions from the two reg entries (by reg-name)
instead of parsing read-channel/write-channel offsets; read the channel id
from "bao,id"; mmap() now maps per region.
- Drop the noisy dev_info() on successful probe.
- Do the shared-memory range arithmetic in u64 to avoid a u32 overflow.
- Fix the Kconfig help-text indentation (Randy Dunlap).
- Add José's and David's Co-developed-by/Signed-off-by.
arch/arm/include/asm/bao.h | 31 ++++
arch/arm64/include/asm/bao.h | 31 ++++
arch/riscv/include/asm/bao.h | 31 ++++
drivers/virt/Kconfig | 2 +
drivers/virt/Makefile | 1 +
drivers/virt/bao/Kconfig | 3 +
drivers/virt/bao/Makefile | 3 +
drivers/virt/bao/ipcshmem/Kconfig | 10 ++
drivers/virt/bao/ipcshmem/Makefile | 3 +
drivers/virt/bao/ipcshmem/ipcshmem.c | 231 +++++++++++++++++++++++++++
10 files changed, 346 insertions(+)
create mode 100644 arch/arm/include/asm/bao.h
create mode 100644 arch/arm64/include/asm/bao.h
create mode 100644 arch/riscv/include/asm/bao.h
create mode 100644 drivers/virt/bao/Kconfig
create mode 100644 drivers/virt/bao/Makefile
create mode 100644 drivers/virt/bao/ipcshmem/Kconfig
create mode 100644 drivers/virt/bao/ipcshmem/Makefile
create mode 100644 drivers/virt/bao/ipcshmem/ipcshmem.c
diff --git a/arch/arm/include/asm/bao.h b/arch/arm/include/asm/bao.h
new file mode 100644
index 000000000000..ba64d1a18f91
--- /dev/null
+++ b/arch/arm/include/asm/bao.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Bao Hypervisor Hypercall Interface
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ *
+ * Authors:
+ * João Peixoto <jpeixoto@osyx.tech>
+ * José Martins <jose@osyx.tech>
+ * David Cerdeira <davidmcerdeira@osyx.tech>
+ */
+
+#ifndef __ASM_ARM_BAO_H
+#define __ASM_ARM_BAO_H
+
+#include <linux/arm-smccc.h>
+
+static inline unsigned long bao_ipcshmem_hypercall(unsigned long hypercall_id,
+ unsigned long ipcshmem_id)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_hvc(ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,
+ ARM_SMCCC_OWNER_VENDOR_HYP,
+ hypercall_id),
+ ipcshmem_id, 0, 0, 0, 0, 0, 0, &res);
+
+ return res.a0;
+}
+
+#endif /* __ASM_ARM_BAO_H */
diff --git a/arch/arm64/include/asm/bao.h b/arch/arm64/include/asm/bao.h
new file mode 100644
index 000000000000..ab9b283168e3
--- /dev/null
+++ b/arch/arm64/include/asm/bao.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Bao Hypervisor Hypercall Interface
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ *
+ * Authors:
+ * João Peixoto <jpeixoto@osyx.tech>
+ * José Martins <jose@osyx.tech>
+ * David Cerdeira <davidmcerdeira@osyx.tech>
+ */
+
+#ifndef __ASM_ARM64_BAO_H
+#define __ASM_ARM64_BAO_H
+
+#include <linux/arm-smccc.h>
+
+static inline unsigned long bao_ipcshmem_hypercall(unsigned long hypercall_id,
+ unsigned long ipcshmem_id)
+{
+ struct arm_smccc_res res;
+
+ arm_smccc_hvc(ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64,
+ ARM_SMCCC_OWNER_VENDOR_HYP,
+ hypercall_id),
+ ipcshmem_id, 0, 0, 0, 0, 0, 0, &res);
+
+ return res.a0;
+}
+
+#endif /* __ASM_ARM64_BAO_H */
diff --git a/arch/riscv/include/asm/bao.h b/arch/riscv/include/asm/bao.h
new file mode 100644
index 000000000000..d2c79a6a4ade
--- /dev/null
+++ b/arch/riscv/include/asm/bao.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Bao Hypervisor Hypercall Interface
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ *
+ * Authors:
+ * João Peixoto <jpeixoto@osyx.tech>
+ * José Martins <jose@osyx.tech>
+ * David Cerdeira <davidmcerdeira@osyx.tech>
+ */
+
+#ifndef __ASM_RISCV_BAO_H
+#define __ASM_RISCV_BAO_H
+
+#include <asm/sbi.h>
+
+#define BAO_SBI_EXT_ID 0x08000ba0
+
+static inline unsigned long bao_ipcshmem_hypercall(unsigned long hypercall_id,
+ unsigned long ipcshmem_id)
+{
+ struct sbiret ret;
+
+ ret = sbi_ecall(BAO_SBI_EXT_ID, hypercall_id, ipcshmem_id, 0, 0, 0, 0,
+ 0);
+
+ return ret.error;
+}
+
+#endif /* __ASM_RISCV_BAO_H */
diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig
index 52eb7e4ba71f..cb98c4c52fd1 100644
--- a/drivers/virt/Kconfig
+++ b/drivers/virt/Kconfig
@@ -47,6 +47,8 @@ source "drivers/virt/nitro_enclaves/Kconfig"
source "drivers/virt/acrn/Kconfig"
+source "drivers/virt/bao/Kconfig"
+
endif
source "drivers/virt/coco/Kconfig"
diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile
index f29901bd7820..623a671f8711 100644
--- a/drivers/virt/Makefile
+++ b/drivers/virt/Makefile
@@ -10,3 +10,4 @@ obj-y += vboxguest/
obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
obj-$(CONFIG_ACRN_HSM) += acrn/
obj-y += coco/
+obj-$(CONFIG_BAO_SHMEM) += bao/
diff --git a/drivers/virt/bao/Kconfig b/drivers/virt/bao/Kconfig
new file mode 100644
index 000000000000..4f7929d57475
--- /dev/null
+++ b/drivers/virt/bao/Kconfig
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+source "drivers/virt/bao/ipcshmem/Kconfig"
diff --git a/drivers/virt/bao/Makefile b/drivers/virt/bao/Makefile
new file mode 100644
index 000000000000..68f5d3f282c4
--- /dev/null
+++ b/drivers/virt/bao/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_BAO_SHMEM) += ipcshmem/
diff --git a/drivers/virt/bao/ipcshmem/Kconfig b/drivers/virt/bao/ipcshmem/Kconfig
new file mode 100644
index 000000000000..b789e5ea1264
--- /dev/null
+++ b/drivers/virt/bao/ipcshmem/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0
+config BAO_SHMEM
+ tristate "Bao hypervisor shared memory support"
+ help
+ This enables support for Bao shared memory communication.
+ It allows the kernel to interface with guests running under
+ the Bao hypervisor, providing a character device interface
+ for exchanging data through dedicated shared-memory regions.
+
+ If unsure, say N.
diff --git a/drivers/virt/bao/ipcshmem/Makefile b/drivers/virt/bao/ipcshmem/Makefile
new file mode 100644
index 000000000000..e027dcdb06aa
--- /dev/null
+++ b/drivers/virt/bao/ipcshmem/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_BAO_SHMEM) += bao.o
+bao-objs += ipcshmem.o
diff --git a/drivers/virt/bao/ipcshmem/ipcshmem.c b/drivers/virt/bao/ipcshmem/ipcshmem.c
new file mode 100644
index 000000000000..0d46d89ee788
--- /dev/null
+++ b/drivers/virt/bao/ipcshmem/ipcshmem.c
@@ -0,0 +1,231 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Bao Hypervisor IPC Through Shared-memory Driver
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/miscdevice.h>
+#include <linux/of.h>
+#include <linux/mm.h>
+#include <linux/io.h>
+#include <asm/bao.h>
+
+#define BAO_IPCSHMEM_NAME_LEN 16
+
+/* IPC through shared-memory hypercall ID */
+#define BAO_IPCSHMEM_HYPERCALL_ID 0x1
+
+struct bao_ipcshmem {
+ struct miscdevice miscdev;
+ u32 id;
+ char label[BAO_IPCSHMEM_NAME_LEN];
+ void *read_base;
+ phys_addr_t read_phys;
+ size_t read_size;
+ void *write_base;
+ phys_addr_t write_phys;
+ size_t write_size;
+};
+
+static int bao_ipcshmem_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct bao_ipcshmem *bao = filp->private_data;
+ unsigned long vsize = vma->vm_end - vma->vm_start;
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+ phys_addr_t region_phys;
+ size_t region_size;
+
+ if (!vsize)
+ return -EINVAL;
+
+ /*
+ * The read region is exposed at offset 0 and the write region right
+ * after it. A single mapping cannot span both regions, since they are
+ * not guaranteed to be physically contiguous.
+ */
+ if (offset < bao->read_size) {
+ region_phys = bao->read_phys;
+ region_size = bao->read_size;
+ } else if (offset < bao->read_size + bao->write_size) {
+ offset -= bao->read_size;
+ region_phys = bao->write_phys;
+ region_size = bao->write_size;
+ } else {
+ return -EINVAL;
+ }
+
+ if (vsize > region_size - offset)
+ return -EINVAL;
+
+ region_phys += offset;
+ if (!PAGE_ALIGNED(region_phys))
+ return -EINVAL;
+
+ return remap_pfn_range(vma, vma->vm_start, region_phys >> PAGE_SHIFT,
+ vsize, vma->vm_page_prot);
+}
+
+static ssize_t bao_ipcshmem_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct bao_ipcshmem *bao = filp->private_data;
+ size_t available;
+
+ if (*ppos >= bao->read_size)
+ return 0;
+
+ available = bao->read_size - *ppos;
+ count = min(count, available);
+
+ if (copy_to_user(buf, bao->read_base + *ppos, count))
+ return -EFAULT;
+
+ *ppos += count;
+ return count;
+}
+
+static ssize_t bao_ipcshmem_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct bao_ipcshmem *bao = filp->private_data;
+ size_t available;
+
+ if (*ppos >= bao->write_size)
+ return 0;
+
+ available = bao->write_size - *ppos;
+ count = min(count, available);
+
+ if (copy_from_user(bao->write_base + *ppos, buf, count))
+ return -EFAULT;
+
+ *ppos += count;
+
+ /* Notify Bao hypervisor */
+ bao_ipcshmem_hypercall(BAO_IPCSHMEM_HYPERCALL_ID, bao->id);
+
+ return count;
+}
+
+static int bao_ipcshmem_open(struct inode *inode, struct file *filp)
+{
+ struct bao_ipcshmem *bao;
+
+ bao = container_of(filp->private_data, struct bao_ipcshmem, miscdev);
+ filp->private_data = bao;
+
+ return 0;
+}
+
+static int bao_ipcshmem_release(struct inode *inode, struct file *filp)
+{
+ filp->private_data = NULL;
+ return 0;
+}
+
+static const struct file_operations bao_ipcshmem_fops = {
+ .owner = THIS_MODULE,
+ .read = bao_ipcshmem_read,
+ .write = bao_ipcshmem_write,
+ .mmap = bao_ipcshmem_mmap,
+ .open = bao_ipcshmem_open,
+ .release = bao_ipcshmem_release,
+};
+
+static int bao_ipcshmem_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct resource *read_res;
+ struct resource *write_res;
+ struct bao_ipcshmem *bao;
+ u32 id;
+ int ret;
+
+ read_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "read");
+ if (!read_res) {
+ dev_err(dev, "missing 'read' shared memory region\n");
+ return -ENODEV;
+ }
+
+ write_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "write");
+ if (!write_res) {
+ dev_err(dev, "missing 'write' shared memory region\n");
+ return -ENODEV;
+ }
+
+ ret = of_property_read_u32(np, "bao,id", &id);
+ if (ret) {
+ dev_err(dev, "missing or invalid 'bao,id' property\n");
+ return ret;
+ }
+
+ bao = devm_kzalloc(dev, sizeof(*bao), GFP_KERNEL);
+ if (!bao)
+ return -ENOMEM;
+
+ bao->read_base = devm_memremap(dev, read_res->start,
+ resource_size(read_res), MEMREMAP_WB);
+ if (IS_ERR(bao->read_base))
+ return PTR_ERR(bao->read_base);
+
+ bao->write_base = devm_memremap(dev, write_res->start,
+ resource_size(write_res), MEMREMAP_WB);
+ if (IS_ERR(bao->write_base))
+ return PTR_ERR(bao->write_base);
+
+ bao->id = id;
+ bao->read_phys = read_res->start;
+ bao->read_size = resource_size(read_res);
+ bao->write_phys = write_res->start;
+ bao->write_size = resource_size(write_res);
+
+ scnprintf(bao->label, BAO_IPCSHMEM_NAME_LEN, "baoipc%u", id);
+
+ bao->miscdev.minor = MISC_DYNAMIC_MINOR;
+ bao->miscdev.name = bao->label;
+ bao->miscdev.fops = &bao_ipcshmem_fops;
+ bao->miscdev.parent = dev;
+
+ ret = misc_register(&bao->miscdev);
+ if (ret) {
+ dev_err(dev, "failed to register misc device: %d\n", ret);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, bao);
+
+ return 0;
+}
+
+static void bao_ipcshmem_remove(struct platform_device *pdev)
+{
+ struct bao_ipcshmem *bao = platform_get_drvdata(pdev);
+
+ misc_deregister(&bao->miscdev);
+}
+
+static const struct of_device_id of_bao_ipcshmem_match[] = {
+ { .compatible = "bao,ipcshmem" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, of_bao_ipcshmem_match);
+
+static struct platform_driver bao_ipcshmem_driver = {
+ .probe = bao_ipcshmem_probe,
+ .remove = bao_ipcshmem_remove,
+ .driver = {
+ .name = "baoipc",
+ .of_match_table = of_bao_ipcshmem_match,
+ },
+};
+
+module_platform_driver(bao_ipcshmem_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("David Cerdeira <davidmcerdeira@osyx.tech>");
+MODULE_AUTHOR("José Martins <jose@osyx.tech>");
+MODULE_AUTHOR("João Peixoto <jpeixoto@osyx.tech>");
+MODULE_DESCRIPTION("Bao Hypervisor IPC Through Shared-memory Driver");
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [RFC PATCH v3 3/6] dt-bindings: bao: add I/O dispatcher device
2026-08-07 7:39 [RFC PATCH v3 0/6] virt: bao: Add Bao hypervisor IPC and I/O dispatcher drivers João Peixoto
2026-08-07 7:39 ` [RFC PATCH v3 1/6] dt-bindings: bao: add IPC shared-memory device João Peixoto
2026-08-07 7:39 ` [RFC PATCH v3 2/6] virt: bao: add IPC shared-memory driver João Peixoto
@ 2026-08-07 7:39 ` João Peixoto
2026-08-07 7:39 ` [RFC PATCH v3 4/6] virt: bao: add I/O dispatcher driver João Peixoto
` (2 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: João Peixoto @ 2026-08-07 7:39 UTC (permalink / raw)
To: gregkh, robh, krzk+dt, conor+dt
Cc: jose, davidmcerdeira, corbet, skhan, catalin.marinas, will, linux,
pjw, palmer, aou, alex, andrew.jones, rdunlap, linux-kernel,
devicetree, linux-doc, linux-arm-kernel, linux-riscv
Add the device-tree binding for the Bao I/O dispatcher, present in
backend VMs that service paravirtualised (VirtIO) I/O on behalf of
frontend guests.
Co-developed-by: José Martins <jose@osyx.tech>
Signed-off-by: José Martins <jose@osyx.tech>
Co-developed-by: David Cerdeira <davidmcerdeira@osyx.tech>
Signed-off-by: David Cerdeira <davidmcerdeira@osyx.tech>
Signed-off-by: João Peixoto <jpeixoto@osyx.tech>
---
v3:
- Model one node per backend device (single reg + single interrupt +
"bao,id") instead of a single node describing many devices, following the
gunyah and Mediatek Genio bindings (Krzysztof Kozlowski).
- Add José's and David's Co-developed-by/Signed-off-by.
.../bindings/bao/bao,io-dispatcher.yaml | 58 +++++++++++++++++++
1 file changed, 58 insertions(+)
create mode 100644 Documentation/devicetree/bindings/bao/bao,io-dispatcher.yaml
diff --git a/Documentation/devicetree/bindings/bao/bao,io-dispatcher.yaml b/Documentation/devicetree/bindings/bao/bao,io-dispatcher.yaml
new file mode 100644
index 000000000000..f118c6d486a6
--- /dev/null
+++ b/Documentation/devicetree/bindings/bao/bao,io-dispatcher.yaml
@@ -0,0 +1,58 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/bao/bao,io-dispatcher.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Bao Hypervisor I/O Dispatcher Device
+
+maintainers:
+ - João Peixoto <jpeixoto@osyx.tech>
+ - José Martins <jose@osyx.tech>
+ - David Cerdeira <davidmcerdeira@osyx.tech>
+
+description:
+ Backend device exposed by the Bao hypervisor to a guest that services
+ paravirtualised (VirtIO) I/O on behalf of a frontend guest. Each device
+ owns one contiguous shared-memory region used to exchange I/O buffers
+ with its frontend, and one interrupt the hypervisor raises to signal a
+ pending I/O request. A backend guest that services several frontends
+ instantiates one node per backend device.
+
+properties:
+ compatible:
+ const: bao,io-dispatcher
+
+ reg:
+ maxItems: 1
+ description:
+ Shared-memory region used to exchange I/O buffers with the frontend.
+
+ interrupts:
+ maxItems: 1
+ description:
+ Interrupt raised by the hypervisor to signal a pending I/O request.
+
+ bao,id:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ Hypervisor-assigned identifier of the backend device model. It must
+ match the identifier configured for the device model in the
+ hypervisor.
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - bao,id
+
+additionalProperties: false
+
+examples:
+ - |
+ shmem@50000000 {
+ compatible = "bao,io-dispatcher";
+ reg = <0x50000000 0x01000000>;
+ interrupts = <0x0 0x08 0x1>;
+ bao,id = <0>;
+ };
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [RFC PATCH v3 4/6] virt: bao: add I/O dispatcher driver
2026-08-07 7:39 [RFC PATCH v3 0/6] virt: bao: Add Bao hypervisor IPC and I/O dispatcher drivers João Peixoto
` (2 preceding siblings ...)
2026-08-07 7:39 ` [RFC PATCH v3 3/6] dt-bindings: bao: add I/O dispatcher device João Peixoto
@ 2026-08-07 7:39 ` João Peixoto
2026-08-07 7:54 ` sashiko-bot
2026-08-07 7:39 ` [RFC PATCH v3 5/6] virt: bao: consolidate the IPC hypercall ID in include/linux/bao.h João Peixoto
2026-08-07 7:39 ` [RFC PATCH v3 6/6] MAINTAINERS: add Bao hypervisor entry João Peixoto
5 siblings, 1 reply; 11+ messages in thread
From: João Peixoto @ 2026-08-07 7:39 UTC (permalink / raw)
To: gregkh, robh, krzk+dt, conor+dt
Cc: jose, davidmcerdeira, corbet, skhan, catalin.marinas, will, linux,
pjw, palmer, aou, alex, andrew.jones, rdunlap, linux-kernel,
devicetree, linux-doc, linux-arm-kernel, linux-riscv
Add the Bao I/O dispatcher, used by backend VMs to service I/O on behalf
of frontend guests. It bridges Bao's Remote I/O mechanism to userspace
VirtIO backend device models.
Each backend device has a contiguous shared-memory region for exchanging
I/O buffers with its frontend and an interrupt the hypervisor uses to
signal pending requests. Userspace drives the dispatcher through a set of
ioctls on a misc character device.
Co-developed-by: José Martins <jose@osyx.tech>
Signed-off-by: José Martins <jose@osyx.tech>
Co-developed-by: David Cerdeira <davidmcerdeira@osyx.tech>
Signed-off-by: David Cerdeira <davidmcerdeira@osyx.tech>
Signed-off-by: João Peixoto <jpeixoto@osyx.tech>
---
v3:
- Bind one platform device per device model and expose one /dev/bao-dmX each;
drop the global DM list and its locking, the anonymous-inode fd, and the
dispatcher indirection ioctl (BAO_IOCTL_DM_GET_INFO now lives in the DM fops).
- Fix the DM leak on remove, free the misc-device name, and unwind the probe
error path fully; validate "bao,id" against BAO_IO_MAX_DMS.
- Initialise virtio_requests_lock and the hypercall context fields; drop the
redundant control-client dispatch loop.
- riscv: reference BAO_SBI_EXT_ID consistently, document that it uses the SBI
experimental extension space (RISC-V stays experimental until an SBI
implementation ID is registered), and drop the redundant ecall input
constraints (Andrew Jones).
- Use kzalloc_obj() instead of kzalloc(sizeof(*x)).
- Add José's and David's Co-developed-by/Signed-off-by.
.../userspace-api/ioctl/ioctl-number.rst | 2 +
arch/arm/include/asm/bao.h | 30 ++
arch/arm64/include/asm/bao.h | 30 ++
arch/riscv/include/asm/bao.h | 37 ++
drivers/virt/Makefile | 1 +
drivers/virt/bao/Kconfig | 2 +
drivers/virt/bao/Makefile | 1 +
drivers/virt/bao/io-dispatcher/Kconfig | 16 +
drivers/virt/bao/io-dispatcher/Makefile | 4 +
drivers/virt/bao/io-dispatcher/bao_drv.h | 349 +++++++++++++++
drivers/virt/bao/io-dispatcher/dm.c | 316 ++++++++++++++
drivers/virt/bao/io-dispatcher/driver.c | 95 +++++
drivers/virt/bao/io-dispatcher/intc.c | 64 +++
drivers/virt/bao/io-dispatcher/io_client.c | 401 ++++++++++++++++++
.../virt/bao/io-dispatcher/io_dispatcher.c | 181 ++++++++
drivers/virt/bao/io-dispatcher/ioeventfd.c | 323 ++++++++++++++
drivers/virt/bao/io-dispatcher/irqfd.c | 314 ++++++++++++++
include/linux/bao.h | 41 ++
include/uapi/linux/bao.h | 96 +++++
19 files changed, 2303 insertions(+)
create mode 100644 drivers/virt/bao/io-dispatcher/Kconfig
create mode 100644 drivers/virt/bao/io-dispatcher/Makefile
create mode 100644 drivers/virt/bao/io-dispatcher/bao_drv.h
create mode 100644 drivers/virt/bao/io-dispatcher/dm.c
create mode 100644 drivers/virt/bao/io-dispatcher/driver.c
create mode 100644 drivers/virt/bao/io-dispatcher/intc.c
create mode 100644 drivers/virt/bao/io-dispatcher/io_client.c
create mode 100644 drivers/virt/bao/io-dispatcher/io_dispatcher.c
create mode 100644 drivers/virt/bao/io-dispatcher/ioeventfd.c
create mode 100644 drivers/virt/bao/io-dispatcher/irqfd.c
create mode 100644 include/linux/bao.h
create mode 100644 include/uapi/linux/bao.h
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
index 3f0ef1e27eb0..3c6b0c060bab 100644
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
@@ -346,6 +346,8 @@ Code Seq# Include File Comments
<mailto:luzmaximilian@gmail.com>
0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver
<mailto:luzmaximilian@gmail.com>
+0xA6 all uapi/linux/bao.h Bao hypervisor
+ <mailto:info@bao-project.org>
0xAA 00-3F linux/uapi/linux/userfaultfd.h
0xAB 00-1F linux/nbd.h
0xAC 00-1F linux/raw.h
diff --git a/arch/arm/include/asm/bao.h b/arch/arm/include/asm/bao.h
index ba64d1a18f91..eca258cc94e3 100644
--- a/arch/arm/include/asm/bao.h
+++ b/arch/arm/include/asm/bao.h
@@ -14,6 +14,7 @@
#define __ASM_ARM_BAO_H
#include <linux/arm-smccc.h>
+#include <linux/bao.h>
static inline unsigned long bao_ipcshmem_hypercall(unsigned long hypercall_id,
unsigned long ipcshmem_id)
@@ -28,4 +29,33 @@ static inline unsigned long bao_ipcshmem_hypercall(unsigned long hypercall_id,
return res.a0;
}
+static inline unsigned long
+bao_remio_hypercall(struct bao_remio_hypercall_ctx *ctx)
+{
+ register int r0 asm("r0") =
+ ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,
+ ARM_SMCCC_OWNER_VENDOR_HYP, BAO_REMIO_HYPERCALL_ID);
+ register u32 r1 asm("r1") = ctx->dm_id;
+ register u32 r2 asm("r2") = ctx->addr;
+ register u32 r3 asm("r3") = ctx->op;
+ register u32 r4 asm("r4") = ctx->value;
+ register u32 r5 asm("r5") = ctx->request_id;
+ register u32 r6 asm("r6") = 0;
+
+ asm volatile("hvc 0\n\t"
+ : "=r"(r0), "=r"(r1), "=r"(r2), "=r"(r3), "=r"(r4),
+ "=r"(r5), "=r"(r6)
+ : "r"(r0), "r"(r1), "r"(r2), "r"(r3), "r"(r4), "r"(r5)
+ : "memory");
+
+ ctx->addr = r1;
+ ctx->op = r2;
+ ctx->value = r3;
+ ctx->access_width = r4;
+ ctx->request_id = r5;
+ ctx->npend_req = r6;
+
+ return r0;
+}
+
#endif /* __ASM_ARM_BAO_H */
diff --git a/arch/arm64/include/asm/bao.h b/arch/arm64/include/asm/bao.h
index ab9b283168e3..1dc09a2c261b 100644
--- a/arch/arm64/include/asm/bao.h
+++ b/arch/arm64/include/asm/bao.h
@@ -14,6 +14,7 @@
#define __ASM_ARM64_BAO_H
#include <linux/arm-smccc.h>
+#include <linux/bao.h>
static inline unsigned long bao_ipcshmem_hypercall(unsigned long hypercall_id,
unsigned long ipcshmem_id)
@@ -28,4 +29,33 @@ static inline unsigned long bao_ipcshmem_hypercall(unsigned long hypercall_id,
return res.a0;
}
+static inline unsigned long
+bao_remio_hypercall(struct bao_remio_hypercall_ctx *ctx)
+{
+ register int x0 asm("x0") =
+ ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64,
+ ARM_SMCCC_OWNER_VENDOR_HYP, BAO_REMIO_HYPERCALL_ID);
+ register u64 x1 asm("x1") = ctx->dm_id;
+ register u64 x2 asm("x2") = ctx->addr;
+ register u64 x3 asm("x3") = ctx->op;
+ register u64 x4 asm("x4") = ctx->value;
+ register u64 x5 asm("x5") = ctx->request_id;
+ register u64 x6 asm("x6") = 0;
+
+ asm volatile("hvc 0\n\t"
+ : "=r"(x0), "=r"(x1), "=r"(x2), "=r"(x3), "=r"(x4),
+ "=r"(x5), "=r"(x6)
+ : "r"(x0), "r"(x1), "r"(x2), "r"(x3), "r"(x4), "r"(x5)
+ : "memory");
+
+ ctx->addr = x1;
+ ctx->op = x2;
+ ctx->value = x3;
+ ctx->access_width = x4;
+ ctx->request_id = x5;
+ ctx->npend_req = x6;
+
+ return x0;
+}
+
#endif /* __ASM_ARM64_BAO_H */
diff --git a/arch/riscv/include/asm/bao.h b/arch/riscv/include/asm/bao.h
index d2c79a6a4ade..6dc0cb13c94c 100644
--- a/arch/riscv/include/asm/bao.h
+++ b/arch/riscv/include/asm/bao.h
@@ -14,7 +14,16 @@
#define __ASM_RISCV_BAO_H
#include <asm/sbi.h>
+#include <linux/bao.h>
+/*
+ * Bao SBI extension ID.
+ *
+ * This currently lives in the SBI experimental extension space
+ * (0x08000000-0x08FFFFFF). A permanent ID has to be assigned through the
+ * RISC-V SBI specification before the RISC-V support can be considered
+ * stable; until then the RISC-V backend is experimental.
+ */
#define BAO_SBI_EXT_ID 0x08000ba0
static inline unsigned long bao_ipcshmem_hypercall(unsigned long hypercall_id,
@@ -28,4 +37,32 @@ static inline unsigned long bao_ipcshmem_hypercall(unsigned long hypercall_id,
return ret.error;
}
+static inline unsigned long
+bao_remio_hypercall(struct bao_remio_hypercall_ctx *ctx)
+{
+ register uintptr_t a0 asm("a0") = (uintptr_t)(ctx->dm_id);
+ register uintptr_t a1 asm("a1") = (uintptr_t)(ctx->addr);
+ register uintptr_t a2 asm("a2") = (uintptr_t)(ctx->op);
+ register uintptr_t a3 asm("a3") = (uintptr_t)(ctx->value);
+ register uintptr_t a4 asm("a4") = (uintptr_t)(ctx->request_id);
+ register uintptr_t a5 asm("a5") = (uintptr_t)(0);
+ register uintptr_t a6 asm("a6") = (uintptr_t)(BAO_REMIO_HYPERCALL_ID);
+ register uintptr_t a7 asm("a7") = (uintptr_t)(BAO_SBI_EXT_ID);
+
+ asm volatile("ecall"
+ : "+r"(a0), "+r"(a1), "+r"(a2), "+r"(a3), "+r"(a4),
+ "+r"(a5), "+r"(a6), "+r"(a7)
+ :
+ : "memory");
+
+ ctx->addr = a2;
+ ctx->op = a3;
+ ctx->value = a4;
+ ctx->access_width = a5;
+ ctx->request_id = a6;
+ ctx->npend_req = a7;
+
+ return a0;
+}
+
#endif /* __ASM_RISCV_BAO_H */
diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile
index 623a671f8711..8bffc7ccd29e 100644
--- a/drivers/virt/Makefile
+++ b/drivers/virt/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
obj-$(CONFIG_ACRN_HSM) += acrn/
obj-y += coco/
obj-$(CONFIG_BAO_SHMEM) += bao/
+obj-$(CONFIG_BAO_IO_DISPATCHER) += bao/
diff --git a/drivers/virt/bao/Kconfig b/drivers/virt/bao/Kconfig
index 4f7929d57475..ab08a20db8c4 100644
--- a/drivers/virt/bao/Kconfig
+++ b/drivers/virt/bao/Kconfig
@@ -1,3 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
source "drivers/virt/bao/ipcshmem/Kconfig"
+
+source "drivers/virt/bao/io-dispatcher/Kconfig"
diff --git a/drivers/virt/bao/Makefile b/drivers/virt/bao/Makefile
index 68f5d3f282c4..c463f04cf206 100644
--- a/drivers/virt/bao/Makefile
+++ b/drivers/virt/bao/Makefile
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_BAO_SHMEM) += ipcshmem/
+obj-$(CONFIG_BAO_IO_DISPATCHER) += io-dispatcher/
diff --git a/drivers/virt/bao/io-dispatcher/Kconfig b/drivers/virt/bao/io-dispatcher/Kconfig
new file mode 100644
index 000000000000..cf3e8df0f2f4
--- /dev/null
+++ b/drivers/virt/bao/io-dispatcher/Kconfig
@@ -0,0 +1,16 @@
+# SPDX-License-Identifier: GPL-2.0
+config BAO_IO_DISPATCHER
+ tristate "Bao Hypervisor I/O Dispatcher"
+ select EVENTFD
+ help
+ The Bao I/O Dispatcher is a kernel module for backend Linux VMs
+ running under the Bao hypervisor. It establishes the connection
+ between the Remote I/O system (Bao's mechanism for forwarding
+ I/O requests from frontend VMs to the backend VMs) and the
+ VirtIO backend device.
+
+ This provides a unified API to support various VirtIO backends,
+ allowing Bao guests to perform I/O through the hypervisor
+ transparently.
+
+ If unsure, say N.
diff --git a/drivers/virt/bao/io-dispatcher/Makefile b/drivers/virt/bao/io-dispatcher/Makefile
new file mode 100644
index 000000000000..e18de1d1a026
--- /dev/null
+++ b/drivers/virt/bao/io-dispatcher/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_BAO_IO_DISPATCHER) += bao.o
+bao-objs += ioeventfd.o io_client.o io_dispatcher.o irqfd.o dm.o intc.o driver.o
+
diff --git a/drivers/virt/bao/io-dispatcher/bao_drv.h b/drivers/virt/bao/io-dispatcher/bao_drv.h
new file mode 100644
index 000000000000..a93386028947
--- /dev/null
+++ b/drivers/virt/bao/io-dispatcher/bao_drv.h
@@ -0,0 +1,349 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Provides some definitions for the Bao Hypervisor modules
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ *
+ * Authors:
+ * João Peixoto <jpeixoto@osyx.tech>
+ * José Martins <jose@osyx.tech>
+ * David Cerdeira <davidmcerdeira@osyx.tech>
+ */
+
+#ifndef __BAO_DRV_H
+#define __BAO_DRV_H
+
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/bao.h>
+#include <uapi/linux/bao.h>
+
+#define BAO_NAME_MAX_LEN 16
+#define BAO_IO_MAX_DMS 16
+
+#define BAO_IOEVENTFD_FLAG_DATAMATCH BIT(1)
+#define BAO_IOEVENTFD_FLAG_DEASSIGN BIT(2)
+#define BAO_IRQFD_FLAG_DEASSIGN 1U
+#define BAO_IO_CLIENT_DESTROYING 0U
+
+struct bao_dm;
+struct bao_io_client;
+
+typedef int (*bao_io_client_handler_t)(struct bao_io_client *client,
+ struct bao_virtio_request *req);
+
+/**
+ * enum bao_io_op - Bao hypervisor I/O operation types
+ * @BAO_IO_WRITE: Write operation
+ * @BAO_IO_READ: Read operation
+ * @BAO_IO_ASK: Request operation information (e.g., MMIO address)
+ * @BAO_IO_NOTIFY: Notify I/O completion
+ */
+enum bao_io_op {
+ BAO_IO_WRITE = 0,
+ BAO_IO_READ,
+ BAO_IO_ASK,
+ BAO_IO_NOTIFY,
+};
+
+/**
+ * struct bao_io_client - Bao I/O client
+ * @name: Client name
+ * @dm: The DM that the client belongs to
+ * @list: List node for this bao_io_client
+ * @is_control: If this client is the control client
+ * @flags: Flags (BAO_IO_CLIENT_*)
+ * @virtio_requests: List of free I/O requests
+ * @range_list: I/O ranges
+ * @handler: I/O request handler for this client
+ * @thread: Kernel thread executing the handler
+ * @wq: Wait queue used for thread parking
+ * @priv: Private data for the handler
+ */
+struct bao_io_client {
+ char name[BAO_NAME_MAX_LEN];
+ struct bao_dm *dm;
+ struct list_head list;
+ bool is_control;
+ unsigned long flags;
+ struct list_head virtio_requests;
+
+ /* protects virtio_requests list */
+ struct mutex virtio_requests_lock;
+
+ struct list_head range_list;
+
+ /* protects range_list */
+ struct rw_semaphore range_lock;
+
+ bao_io_client_handler_t handler;
+ struct task_struct *thread;
+ wait_queue_head_t wq;
+ void *priv;
+};
+
+/**
+ * struct bao_dm - Bao backend device model (DM)
+ * @info: DM information (id, shmem_addr, shmem_size, irq)
+ * @name: Backing storage for the misc device name
+ * @shmem_base_addr: The base address of the shared memory
+ * @ioeventfds: List of all ioeventfds
+ * @ioeventfd_client: Ioeventfd client
+ * @irqfds: List of all irqfds
+ * @irqfd_server: Workqueue responsible for irqfd handling
+ * @io_clients: List of all bao_io_client
+ * @control_client: Control client
+ * @miscdev: Character device exposing this DM to userspace
+ */
+struct bao_dm {
+ struct bao_dm_info info;
+ char name[BAO_NAME_MAX_LEN];
+ void *shmem_base_addr;
+
+ struct list_head ioeventfds;
+
+ /* protects ioeventfds list */
+ struct mutex ioeventfds_lock;
+
+ struct bao_io_client *ioeventfd_client;
+
+ struct list_head irqfds;
+
+ /* protects irqfds list */
+ struct mutex irqfds_lock;
+
+ struct workqueue_struct *irqfd_server;
+
+ /* protects io_clients list */
+ struct rw_semaphore io_clients_lock;
+
+ struct list_head io_clients;
+ struct bao_io_client *control_client;
+
+ struct miscdevice miscdev;
+};
+
+/**
+ * struct bao_io_range - Represents a range of I/O addresses
+ * @list: List node for linking multiple ranges
+ * @start: Start address of the range
+ * @end: End address of the range (inclusive)
+ */
+struct bao_io_range {
+ struct list_head list;
+ u64 start;
+ u64 end;
+};
+
+/**
+ * bao_dm_create - Create a backend device model (DM)
+ * @info: DM information (id, shmem_addr, shmem_size, irq)
+ * @dev: Parent device (the backing platform device)
+ *
+ * Return: Pointer to the created DM on success, NULL on error.
+ */
+struct bao_dm *bao_dm_create(struct bao_dm_info *info, struct device *dev);
+
+/**
+ * bao_dm_destroy - Destroy a backend device model (DM)
+ * @dm: DM to be destroyed
+ */
+void bao_dm_destroy(struct bao_dm *dm);
+
+/**
+ * bao_io_client_create - Create a backend I/O client
+ * @dm: DM this client belongs to
+ * @handler: I/O client handler for requests
+ * @data: Private data passed to the handler
+ * @is_control: True if this is the control client
+ * @name: Name of the I/O client
+ *
+ * Return: Pointer to the created I/O client, NULL on failure.
+ */
+struct bao_io_client *bao_io_client_create(struct bao_dm *dm,
+ bao_io_client_handler_t handler,
+ void *data, bool is_control,
+ const char *name);
+
+/**
+ * bao_io_clients_destroy - Destroy all I/O clients of a DM
+ * @dm: DM whose I/O clients are to be destroyed
+ */
+void bao_io_clients_destroy(struct bao_dm *dm);
+
+/**
+ * bao_io_client_attach - Attach a thread to an I/O client
+ * @client: I/O client to attach
+ *
+ * The thread will wait for I/O requests on this client.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int bao_io_client_attach(struct bao_io_client *client);
+
+/**
+ * bao_io_client_range_add - Add an I/O range to monitor in a client
+ * @client: I/O client
+ * @start: Start address of the range
+ * @end: End address of the range (inclusive)
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int bao_io_client_range_add(struct bao_io_client *client, u64 start, u64 end);
+
+/**
+ * bao_io_client_range_del - Remove an I/O range from a client
+ * @client: I/O client
+ * @start: Start address of the range
+ * @end: End address of the range (inclusive)
+ */
+void bao_io_client_range_del(struct bao_io_client *client, u64 start, u64 end);
+
+/**
+ * bao_io_client_request - Retrieve the oldest I/O request from a client
+ * @client: I/O client
+ * @req: Pointer to virtio request structure to fill
+ *
+ * Return: 0 on success, negative error code if no request is available.
+ */
+int bao_io_client_request(struct bao_io_client *client,
+ struct bao_virtio_request *req);
+
+/**
+ * bao_io_client_push_request - Push an I/O request into a client
+ * @client: I/O client
+ * @req: I/O request to push
+ *
+ * Return: True if a request was pushed, false otherwise.
+ */
+bool bao_io_client_push_request(struct bao_io_client *client,
+ struct bao_virtio_request *req);
+
+/**
+ * bao_io_client_pop_request - Pop the oldest I/O request from a client
+ * @client: I/O client
+ * @req: Buffer to store the popped request
+ *
+ * Return: True if a request was popped, false if the list was empty.
+ */
+bool bao_io_client_pop_request(struct bao_io_client *client,
+ struct bao_virtio_request *req);
+
+/**
+ * bao_io_client_find - Find the I/O client for a given request
+ * @dm: DM that the I/O request belongs to
+ * @req: I/O request to locate
+ *
+ * Return: Pointer to the I/O client handling the request, NULL if none found.
+ */
+struct bao_io_client *bao_io_client_find(struct bao_dm *dm,
+ struct bao_virtio_request *req);
+
+/**
+ * bao_ioeventfd_client_init - Initialize the Ioeventfd client for a DM
+ * @dm: DM that the Ioeventfd client belongs to
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int bao_ioeventfd_client_init(struct bao_dm *dm);
+
+/**
+ * bao_ioeventfd_client_destroy - Destroy the Ioeventfd client for a DM
+ * @dm: DM that the Ioeventfd client belongs to
+ */
+void bao_ioeventfd_client_destroy(struct bao_dm *dm);
+
+/**
+ * bao_ioeventfd_client_config - Configure an Ioeventfd client
+ * @dm: DM that the Ioeventfd client belongs to
+ * @config: Ioeventfd configuration to apply
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int bao_ioeventfd_client_config(struct bao_dm *dm,
+ struct bao_ioeventfd *config);
+
+/**
+ * bao_irqfd_server_init - Initialize the Irqfd server for a DM
+ * @dm: DM that the Irqfd server belongs to
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int bao_irqfd_server_init(struct bao_dm *dm);
+
+/**
+ * bao_irqfd_server_destroy - Destroy the Irqfd server for a DM
+ * @dm: DM that the Irqfd server belongs to
+ */
+void bao_irqfd_server_destroy(struct bao_dm *dm);
+
+/**
+ * bao_irqfd_server_config - Configure an Irqfd server
+ * @dm: DM that the Irqfd server belongs to
+ * @config: Irqfd configuration to apply
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int bao_irqfd_server_config(struct bao_dm *dm, struct bao_irqfd *config);
+
+/**
+ * bao_io_dispatcher_init - Initialize the I/O Dispatcher for a DM
+ * @dm: DM to initialize on the I/O Dispatcher
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int bao_io_dispatcher_init(struct bao_dm *dm);
+
+/**
+ * bao_io_dispatcher_destroy - Destroy the I/O Dispatcher for a DM
+ * @dm: DM to destroy on the I/O Dispatcher
+ */
+void bao_io_dispatcher_destroy(struct bao_dm *dm);
+
+/**
+ * bao_dispatch_io - Acquire and dispatch I/O requests from the Bao Hypervisor
+ * @dm: DM whose I/O clients will handle the requests
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int bao_dispatch_io(struct bao_dm *dm);
+
+/**
+ * bao_io_dispatcher_pause - Pause the I/O Dispatcher for a DM
+ * @dm: DM to pause
+ */
+void bao_io_dispatcher_pause(struct bao_dm *dm);
+
+/**
+ * bao_io_dispatcher_resume - Resume the I/O Dispatcher for a DM
+ * @dm: DM to resume
+ */
+void bao_io_dispatcher_resume(struct bao_dm *dm);
+
+/**
+ * bao_intc_init - Register the interrupt controller for a DM
+ * @dm: DM that the interrupt controller belongs to
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int bao_intc_init(struct bao_dm *dm);
+
+/**
+ * bao_intc_destroy - Unregister the interrupt controller for a DM
+ * @dm: DM that the interrupt controller belongs to
+ */
+void bao_intc_destroy(struct bao_dm *dm);
+
+/**
+ * bao_intc_setup_handler - Setup the interrupt controller handler
+ * @handler: Function pointer to the interrupt handler
+ * @dm: DM that the interrupt controller belongs to
+ */
+void bao_intc_setup_handler(void (*handler)(struct bao_dm *dm));
+
+/**
+ * bao_intc_remove_handler - Remove the interrupt controller handler
+ */
+void bao_intc_remove_handler(void);
+
+#endif /* __BAO_DRV_H */
diff --git a/drivers/virt/bao/io-dispatcher/dm.c b/drivers/virt/bao/io-dispatcher/dm.c
new file mode 100644
index 000000000000..d8e2832e1c05
--- /dev/null
+++ b/drivers/virt/bao/io-dispatcher/dm.c
@@ -0,0 +1,316 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Bao Hypervisor Backend Device Model (DM)
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ *
+ * Authors:
+ * João Peixoto <jpeixoto@osyx.tech>
+ * José Martins <jose@osyx.tech>
+ * David Cerdeira <davidmcerdeira@osyx.tech>
+ */
+
+#include "bao_drv.h"
+#include <linux/io.h>
+#include <linux/mm.h>
+#include <linux/miscdevice.h>
+#include <asm/bao.h>
+
+static int bao_dm_open(struct inode *inode, struct file *filp)
+{
+ struct bao_dm *dm = container_of(filp->private_data, struct bao_dm,
+ miscdev);
+
+ filp->private_data = dm;
+
+ return 0;
+}
+
+static int bao_dm_release(struct inode *inode, struct file *filp)
+{
+ filp->private_data = NULL;
+ return 0;
+}
+
+static long bao_dm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ struct bao_dm *dm = filp->private_data;
+ int rc;
+
+ if (WARN_ON_ONCE(!dm))
+ return -ENODEV;
+
+ switch (cmd) {
+ case BAO_IOCTL_DM_GET_INFO: {
+ struct bao_dm_info info = dm->info;
+
+ if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+ return -EFAULT;
+
+ rc = 0;
+ break;
+ }
+ case BAO_IOCTL_IO_CLIENT_ATTACH: {
+ struct bao_virtio_request *req;
+
+ req = memdup_user((void __user *)arg, sizeof(*req));
+ if (IS_ERR(req)) {
+ rc = PTR_ERR(req);
+ break;
+ }
+
+ if (!dm->control_client) {
+ rc = -ENOENT;
+ goto out_free;
+ }
+
+ rc = bao_io_client_attach(dm->control_client);
+ if (rc)
+ goto out_free;
+
+ rc = bao_io_client_request(dm->control_client, req);
+ if (rc)
+ goto out_free;
+
+ if (copy_to_user((void __user *)arg, req, sizeof(*req))) {
+ rc = -EFAULT;
+ goto out_free;
+ }
+
+ rc = 0;
+
+out_free:
+ kfree(req);
+ break;
+ }
+ case BAO_IOCTL_IO_REQUEST_COMPLETE: {
+ struct bao_virtio_request *req;
+ struct bao_remio_hypercall_ctx ctx;
+
+ req = memdup_user((void __user *)arg, sizeof(*req));
+ if (IS_ERR(req)) {
+ rc = PTR_ERR(req);
+ break;
+ }
+
+ ctx.dm_id = req->dm_id;
+ ctx.addr = req->addr;
+ ctx.op = req->op;
+ ctx.value = req->value;
+ ctx.access_width = req->access_width;
+ ctx.request_id = req->request_id;
+
+ rc = bao_remio_hypercall(&ctx);
+ kfree(req);
+
+ break;
+ }
+ case BAO_IOCTL_IOEVENTFD: {
+ struct bao_ioeventfd ioeventfd;
+
+ if (copy_from_user(&ioeventfd, (void __user *)arg,
+ sizeof(struct bao_ioeventfd)))
+ return -EFAULT;
+
+ rc = bao_ioeventfd_client_config(dm, &ioeventfd);
+ break;
+ }
+ case BAO_IOCTL_IRQFD: {
+ struct bao_irqfd irqfd;
+
+ if (copy_from_user(&irqfd, (void __user *)arg,
+ sizeof(struct bao_irqfd)))
+ return -EFAULT;
+
+ rc = bao_irqfd_server_config(dm, &irqfd);
+ break;
+ }
+ default:
+ rc = -ENOTTY;
+ break;
+ }
+
+ return rc;
+}
+
+/**
+ * bao_dm_mmap - mmap backend DM shared memory to userspace
+ * @filp: File pointer for the DM device
+ * @vma: Virtual memory area for mapping
+ *
+ * Return: 0 on success, negative errno on failure
+ */
+static int bao_dm_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct bao_dm *dm = filp->private_data;
+ unsigned long vsize;
+ unsigned long offset;
+ phys_addr_t phys;
+
+ if (WARN_ON_ONCE(!dm))
+ return -ENODEV;
+
+ vsize = vma->vm_end - vma->vm_start;
+ offset = vma->vm_pgoff << PAGE_SHIFT;
+
+ if (!vsize || offset)
+ return -EINVAL;
+
+ if (vsize > dm->info.shmem_size)
+ return -EINVAL;
+
+ phys = dm->info.shmem_addr;
+ if (!PAGE_ALIGNED(phys))
+ return -EINVAL;
+
+ if (remap_pfn_range(vma, vma->vm_start, phys >> PAGE_SHIFT, vsize,
+ vma->vm_page_prot))
+ return -EFAULT;
+
+ return 0;
+}
+
+/**
+ * bao_dm_llseek - Adjust file offset for backend DM device
+ * @file: File pointer for the DM device
+ * @offset: Offset to seek
+ * @whence: Reference point (SEEK_SET, SEEK_CUR, SEEK_END)
+ *
+ * Return: New file position on success, negative errno on failure
+ */
+static loff_t bao_dm_llseek(struct file *file, loff_t offset, int whence)
+{
+ struct bao_dm *dm = file->private_data;
+ loff_t new_pos;
+
+ if (WARN_ON_ONCE(!dm))
+ return -ENODEV;
+
+ switch (whence) {
+ case SEEK_SET:
+ new_pos = offset;
+ break;
+ case SEEK_CUR:
+ new_pos = file->f_pos + offset;
+ break;
+ case SEEK_END:
+ new_pos = dm->info.shmem_size + offset;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (new_pos < 0 || new_pos > dm->info.shmem_size)
+ return -EINVAL;
+
+ file->f_pos = new_pos;
+ return new_pos;
+}
+
+static const struct file_operations bao_dm_fops = {
+ .owner = THIS_MODULE,
+ .open = bao_dm_open,
+ .release = bao_dm_release,
+ .unlocked_ioctl = bao_dm_ioctl,
+ .llseek = bao_dm_llseek,
+ .mmap = bao_dm_mmap,
+};
+
+struct bao_dm *bao_dm_create(struct bao_dm_info *info, struct device *dev)
+{
+ struct bao_dm *dm;
+ int ret;
+
+ if (WARN_ON(!info))
+ return NULL;
+
+ dm = kzalloc_obj(*dm, GFP_KERNEL);
+ if (!dm)
+ return NULL;
+
+ INIT_LIST_HEAD(&dm->io_clients);
+ init_rwsem(&dm->io_clients_lock);
+
+ dm->info = *info;
+
+ ret = bao_io_dispatcher_init(dm);
+ if (ret) {
+ dev_err(dev, "failed to init I/O dispatcher for DM %u\n",
+ dm->info.id);
+ goto err_free;
+ }
+
+ snprintf(dm->name, sizeof(dm->name), "bao-ioctlc%u", dm->info.id);
+ dm->control_client = bao_io_client_create(dm, NULL, NULL, true,
+ dm->name);
+ if (!dm->control_client) {
+ dev_err(dev, "failed to create control client for DM %u\n",
+ dm->info.id);
+ goto err_destroy_dispatcher;
+ }
+
+ if (bao_ioeventfd_client_init(dm)) {
+ dev_err(dev, "failed to initialize ioeventfd for DM %u\n",
+ dm->info.id);
+ goto err_destroy_io_clients;
+ }
+
+ if (bao_irqfd_server_init(dm)) {
+ dev_err(dev, "failed to initialize irqfd for DM %u\n",
+ dm->info.id);
+ goto err_destroy_io_clients;
+ }
+
+ dm->shmem_base_addr = memremap(dm->info.shmem_addr, dm->info.shmem_size,
+ MEMREMAP_WB);
+ if (!dm->shmem_base_addr) {
+ dev_err(dev, "failed to map memory region for DM %u\n",
+ dm->info.id);
+ goto err_destroy_irqfd;
+ }
+
+ snprintf(dm->name, sizeof(dm->name), "bao-dm%u", dm->info.id);
+ dm->miscdev.minor = MISC_DYNAMIC_MINOR;
+ dm->miscdev.name = dm->name;
+ dm->miscdev.fops = &bao_dm_fops;
+ dm->miscdev.parent = dev;
+
+ ret = misc_register(&dm->miscdev);
+ if (ret) {
+ dev_err(dev, "failed to register misc device for DM %u: %d\n",
+ dm->info.id, ret);
+ goto err_unmap;
+ }
+
+ return dm;
+
+err_unmap:
+ memunmap(dm->shmem_base_addr);
+err_destroy_irqfd:
+ bao_irqfd_server_destroy(dm);
+err_destroy_io_clients:
+ bao_io_clients_destroy(dm);
+err_destroy_dispatcher:
+ bao_io_dispatcher_destroy(dm);
+err_free:
+ kfree(dm);
+
+ return NULL;
+}
+
+void bao_dm_destroy(struct bao_dm *dm)
+{
+ if (WARN_ON_ONCE(!dm))
+ return;
+
+ misc_deregister(&dm->miscdev);
+
+ if (dm->shmem_base_addr)
+ memunmap(dm->shmem_base_addr);
+
+ bao_irqfd_server_destroy(dm);
+ bao_io_clients_destroy(dm);
+ bao_io_dispatcher_destroy(dm);
+
+ kfree(dm);
+}
diff --git a/drivers/virt/bao/io-dispatcher/driver.c b/drivers/virt/bao/io-dispatcher/driver.c
new file mode 100644
index 000000000000..1e90737e51c7
--- /dev/null
+++ b/drivers/virt/bao/io-dispatcher/driver.c
@@ -0,0 +1,95 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Bao Hypervisor I/O Dispatcher Kernel Driver
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/of_irq.h>
+#include "bao_drv.h"
+
+static int bao_io_dispatcher_driver_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct bao_dm_info dm_info;
+ struct bao_dm *dm;
+ struct resource *r;
+ int irq;
+ u32 id;
+ int ret;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r) {
+ dev_err(dev, "missing shared memory resource\n");
+ return -ENODEV;
+ }
+
+ ret = of_property_read_u32(dev->of_node, "bao,id", &id);
+ if (ret) {
+ dev_err(dev, "missing or invalid 'bao,id' property\n");
+ return ret;
+ }
+
+ if (id >= BAO_IO_MAX_DMS) {
+ dev_err(dev, "'bao,id' %u out of range (max %u)\n", id,
+ BAO_IO_MAX_DMS - 1);
+ return -EINVAL;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ dm_info.id = id;
+ dm_info.shmem_addr = r->start;
+ dm_info.shmem_size = resource_size(r);
+ dm_info.irq = irq;
+
+ dm = bao_dm_create(&dm_info, dev);
+ if (!dm)
+ return -EINVAL;
+
+ ret = bao_intc_init(dm);
+ if (ret) {
+ dev_err(dev, "failed to register interrupt %d for DM %u\n", irq,
+ id);
+ bao_dm_destroy(dm);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, dm);
+
+ return 0;
+}
+
+static void bao_io_dispatcher_driver_remove(struct platform_device *pdev)
+{
+ struct bao_dm *dm = platform_get_drvdata(pdev);
+
+ bao_intc_destroy(dm);
+ bao_dm_destroy(dm);
+}
+
+static const struct of_device_id bao_io_dispatcher_driver_dt_ids[] = {
+ { .compatible = "bao,io-dispatcher" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, bao_io_dispatcher_driver_dt_ids);
+
+static struct platform_driver bao_io_dispatcher_driver = {
+ .probe = bao_io_dispatcher_driver_probe,
+ .remove = bao_io_dispatcher_driver_remove,
+ .driver = {
+ .name = "bao-io-dispatcher",
+ .of_match_table = bao_io_dispatcher_driver_dt_ids,
+ },
+};
+
+module_platform_driver(bao_io_dispatcher_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("João Peixoto <jpeixoto@osyx.tech>");
+MODULE_AUTHOR("David Cerdeira <davidmcerdeira@osyx.tech>");
+MODULE_AUTHOR("José Martins <jose@osyx.tech>");
+MODULE_DESCRIPTION("Bao Hypervisor I/O Dispatcher Kernel Driver");
diff --git a/drivers/virt/bao/io-dispatcher/intc.c b/drivers/virt/bao/io-dispatcher/intc.c
new file mode 100644
index 000000000000..d9d2f0861639
--- /dev/null
+++ b/drivers/virt/bao/io-dispatcher/intc.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Bao Hypervisor I/O Dispatcher Interrupt Controller
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ *
+ * Authors:
+ * João Peixoto <jpeixoto@osyx.tech>
+ * José Martins <jose@osyx.tech>
+ * David Cerdeira <davidmcerdeira@osyx.tech>
+ */
+
+#include <linux/interrupt.h>
+#include "bao_drv.h"
+
+/* Top-level handler registered by the Bao interrupt controller */
+static void (*bao_intc_handler)(struct bao_dm *dm);
+
+/**
+ * bao_interrupt_handler - Top-level interrupt handler for Bao DM
+ * @irq: Interrupt number
+ * @dev: Pointer to the Bao device model (struct bao_dm)
+ *
+ * Invokes the registered Bao interrupt controller handler, if any.
+ */
+static irqreturn_t bao_interrupt_handler(int irq, void *dev)
+{
+ struct bao_dm *dm = (struct bao_dm *)dev;
+
+ if (bao_intc_handler)
+ bao_intc_handler(dm);
+
+ return IRQ_HANDLED;
+}
+
+void bao_intc_setup_handler(void (*handler)(struct bao_dm *dm))
+{
+ bao_intc_handler = handler;
+}
+
+void bao_intc_remove_handler(void)
+{
+ bao_intc_handler = NULL;
+}
+
+int bao_intc_init(struct bao_dm *dm)
+{
+ char name[BAO_NAME_MAX_LEN];
+
+ if (WARN_ON_ONCE(!dm))
+ return -EINVAL;
+
+ scnprintf(name, sizeof(name), "bao-iodintc%d", dm->info.id);
+
+ return request_irq(dm->info.irq, bao_interrupt_handler, 0, name, dm);
+}
+
+void bao_intc_destroy(struct bao_dm *dm)
+{
+ if (WARN_ON_ONCE(!dm))
+ return;
+
+ free_irq(dm->info.irq, dm);
+}
diff --git a/drivers/virt/bao/io-dispatcher/io_client.c b/drivers/virt/bao/io-dispatcher/io_client.c
new file mode 100644
index 000000000000..849631bad2f0
--- /dev/null
+++ b/drivers/virt/bao/io-dispatcher/io_client.c
@@ -0,0 +1,401 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Bao Hypervisor I/O Client
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ *
+ * Authors:
+ * João Peixoto <jpeixoto@osyx.tech>
+ * José Martins <jose@osyx.tech>
+ * David Cerdeira <davidmcerdeira@osyx.tech>
+ */
+
+#include <linux/kthread.h>
+#include <asm/bao.h>
+#include "bao_drv.h"
+
+/**
+ * struct bao_io_request - Bao I/O request structure
+ * @list: List node linking all requests
+ * @virtio_request: The VirtIO request payload
+ *
+ * Represents a single I/O request for a Bao I/O client.
+ */
+struct bao_io_request {
+ struct list_head list;
+ struct bao_virtio_request virtio_request;
+};
+
+/**
+ * bao_io_client_has_pending_requests - Check if an I/O client has pending requests
+ * @client: The bao_io_client to check
+ *
+ * Return: True if has pending I/O requests, false otherwise.
+ */
+static inline bool
+bao_io_client_has_pending_requests(struct bao_io_client *client)
+{
+ if (WARN_ON_ONCE(!client))
+ return false;
+
+ return !list_empty(&client->virtio_requests);
+}
+
+/**
+ * bao_io_client_is_destroying - Check if an I/O client is being destroyed
+ * @client: The bao_io_client to check
+ *
+ * Return: True if the client is being destroyed, false otherwise.
+ */
+static inline bool bao_io_client_is_destroying(struct bao_io_client *client)
+{
+ if (WARN_ON_ONCE(!client))
+ return true;
+
+ return test_bit(BAO_IO_CLIENT_DESTROYING, &client->flags);
+}
+
+bool bao_io_client_push_request(struct bao_io_client *client,
+ struct bao_virtio_request *req)
+{
+ struct bao_io_request *io_req;
+
+ if (WARN_ON_ONCE(!client || !req))
+ return false;
+
+ io_req = kzalloc_obj(*io_req, GFP_KERNEL);
+ if (!io_req)
+ return false;
+
+ io_req->virtio_request = *req;
+
+ mutex_lock(&client->virtio_requests_lock);
+ list_add_tail(&io_req->list, &client->virtio_requests);
+ mutex_unlock(&client->virtio_requests_lock);
+
+ return true;
+}
+
+bool bao_io_client_pop_request(struct bao_io_client *client,
+ struct bao_virtio_request *ret)
+{
+ struct bao_io_request *req;
+
+ if (WARN_ON_ONCE(!client || !ret))
+ return false;
+
+ mutex_lock(&client->virtio_requests_lock);
+
+ req = list_first_entry_or_null(&client->virtio_requests,
+ struct bao_io_request, list);
+ if (!req) {
+ mutex_unlock(&client->virtio_requests_lock);
+ return false;
+ }
+
+ list_del(&req->list);
+ *ret = req->virtio_request;
+
+ mutex_unlock(&client->virtio_requests_lock);
+
+ kfree(req);
+
+ return true;
+}
+
+/**
+ * bao_io_client_destroy - Destroy an I/O client
+ * @client: The bao_io_client to destroy
+ */
+static void bao_io_client_destroy(struct bao_io_client *client)
+{
+ struct bao_io_client *range;
+ struct bao_io_client *next;
+ struct bao_dm *dm;
+
+ if (WARN_ON_ONCE(!client))
+ return;
+
+ dm = client->dm;
+
+ bao_io_dispatcher_pause(dm);
+
+ set_bit(BAO_IO_CLIENT_DESTROYING, &client->flags);
+
+ if (client->is_control) {
+ wake_up_interruptible(&client->wq);
+ } else {
+ bao_ioeventfd_client_destroy(dm);
+ if (client->thread)
+ kthread_stop(client->thread);
+ }
+
+ down_write(&client->range_lock);
+ list_for_each_entry_safe(range, next, &client->range_list, list) {
+ list_del(&range->list);
+ kfree(range);
+ }
+ up_write(&client->range_lock);
+
+ down_write(&dm->io_clients_lock);
+ if (client->is_control)
+ dm->control_client = NULL;
+ else
+ dm->ioeventfd_client = NULL;
+
+ list_del(&client->list);
+ up_write(&dm->io_clients_lock);
+
+ bao_io_dispatcher_resume(dm);
+
+ kfree(client);
+}
+
+void bao_io_clients_destroy(struct bao_dm *dm)
+{
+ struct bao_io_client *client, *next;
+
+ if (WARN_ON_ONCE(!dm))
+ return;
+
+ list_for_each_entry_safe(client, next, &dm->io_clients, list) {
+ bao_io_client_destroy(client);
+ }
+}
+
+int bao_io_client_attach(struct bao_io_client *client)
+{
+ if (WARN_ON_ONCE(!client))
+ return -EINVAL;
+
+ if (client->is_control) {
+ wait_event_interruptible(client->wq,
+ bao_io_client_has_pending_requests(client) ||
+ bao_io_client_is_destroying(client));
+ if (bao_io_client_is_destroying(client))
+ return -EPERM;
+ } else {
+ wait_event_interruptible(client->wq,
+ bao_io_client_has_pending_requests(client) ||
+ bao_io_client_is_destroying(client) ||
+ kthread_should_stop());
+ if (bao_io_client_is_destroying(client) ||
+ kthread_should_stop()) {
+ if (kthread_should_stop())
+ bao_io_client_destroy(client);
+ return -EPERM;
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * bao_io_client_kernel_thread - Thread for processing a kernel I/O client
+ * @data: Pointer to the bao_io_client structure
+ *
+ * Return: 0 on completion
+ */
+static int bao_io_client_kernel_thread(void *data)
+{
+ struct bao_io_client *client = data;
+ struct bao_virtio_request req;
+ struct bao_remio_hypercall_ctx ctx;
+ bool stop = false;
+ int ret;
+
+ if (WARN_ON_ONCE(!client))
+ return -EINVAL;
+
+ while (!stop && !kthread_should_stop()) {
+ ret = bao_io_client_attach(client);
+ if (ret < 0) {
+ stop = true;
+ break;
+ }
+
+ while (bao_io_client_has_pending_requests(client) && !stop) {
+ if (!bao_io_client_pop_request(client, &req)) {
+ pr_err("%s: failed to pop I/O request\n",
+ __func__);
+ stop = true;
+ break;
+ }
+
+ ret = client->handler(client, &req);
+ if (ret < 0) {
+ pr_warn("%s: client handler returned %d\n",
+ __func__, ret);
+ break;
+ }
+
+ ctx.dm_id = req.dm_id;
+ ctx.op = req.op;
+ ctx.addr = req.addr;
+ ctx.value = req.value;
+ ctx.access_width = req.access_width;
+ ctx.request_id = req.request_id;
+
+ if (bao_remio_hypercall(&ctx)) {
+ stop = true;
+ break;
+ }
+ }
+ }
+
+ return 0;
+}
+
+struct bao_io_client *bao_io_client_create(struct bao_dm *dm,
+ bao_io_client_handler_t handler,
+ void *data, bool is_control,
+ const char *name)
+{
+ struct bao_io_client *client;
+
+ if (WARN_ON_ONCE(!dm || !name))
+ return NULL;
+
+ if (!handler && !is_control)
+ return NULL;
+
+ client = kzalloc_obj(*client, GFP_KERNEL);
+ if (!client)
+ return NULL;
+
+ client->handler = handler;
+ client->dm = dm;
+ client->priv = data;
+ client->is_control = is_control;
+ if (name)
+ strscpy(client->name, name, sizeof(client->name));
+
+ INIT_LIST_HEAD(&client->virtio_requests);
+ mutex_init(&client->virtio_requests_lock);
+ init_rwsem(&client->range_lock);
+ INIT_LIST_HEAD(&client->range_list);
+ init_waitqueue_head(&client->wq);
+
+ if (client->handler) {
+ client->thread = kthread_run(bao_io_client_kernel_thread,
+ client, "%s-kthread",
+ client->name);
+ if (IS_ERR(client->thread)) {
+ kfree(client);
+ return NULL;
+ }
+ }
+
+ down_write(&dm->io_clients_lock);
+ if (is_control)
+ dm->control_client = client;
+ else
+ dm->ioeventfd_client = client;
+
+ list_add(&client->list, &dm->io_clients);
+ up_write(&dm->io_clients_lock);
+
+ return client;
+}
+
+int bao_io_client_request(struct bao_io_client *client,
+ struct bao_virtio_request *req)
+{
+ if (WARN_ON_ONCE(!client))
+ return -EINVAL;
+
+ if (!bao_io_client_pop_request(client, req))
+ return -EFAULT;
+
+ return 0;
+}
+
+int bao_io_client_range_add(struct bao_io_client *client, u64 start, u64 end)
+{
+ struct bao_io_range *range;
+
+ if (WARN_ON_ONCE(!client))
+ return -EINVAL;
+
+ if (end < start)
+ return -EINVAL;
+
+ range = kzalloc_obj(*range, GFP_KERNEL);
+ if (!range)
+ return -ENOMEM;
+
+ range->start = start;
+ range->end = end;
+
+ down_write(&client->range_lock);
+ list_add(&range->list, &client->range_list);
+ up_write(&client->range_lock);
+
+ return 0;
+}
+
+void bao_io_client_range_del(struct bao_io_client *client, u64 start, u64 end)
+{
+ struct bao_io_range *range;
+ struct bao_io_range *tmp;
+
+ if (WARN_ON_ONCE(!client))
+ return;
+
+ down_write(&client->range_lock);
+ list_for_each_entry_safe(range, tmp, &client->range_list, list) {
+ if (range->start == start && range->end == end) {
+ list_del(&range->list);
+ kfree(range);
+ break;
+ }
+ }
+ up_write(&client->range_lock);
+}
+
+/**
+ * bao_io_request_in_range - Check if the I/O request is in the range
+ * @range: The I/O request range
+ * @req: The I/O request to be checked
+ *
+ * Return: True if the I/O request is in the range, false otherwise
+ */
+static bool bao_io_request_in_range(struct bao_io_range *range,
+ struct bao_virtio_request *req)
+{
+ if (WARN_ON_ONCE(!range || !req))
+ return false;
+
+ if (req->addr >= range->start &&
+ (req->addr + req->access_width - 1) <= range->end)
+ return true;
+
+ return false;
+}
+
+struct bao_io_client *bao_io_client_find(struct bao_dm *dm,
+ struct bao_virtio_request *req)
+{
+ struct bao_io_client *client;
+ struct bao_io_client *found = NULL;
+ struct bao_io_range *range;
+
+ if (WARN_ON_ONCE(!dm || !req))
+ return NULL;
+
+ list_for_each_entry(client, &dm->io_clients, list) {
+ down_read(&client->range_lock);
+ list_for_each_entry(range, &client->range_list, list) {
+ if (bao_io_request_in_range(range, req)) {
+ found = client;
+ break;
+ }
+ }
+ up_read(&client->range_lock);
+
+ if (found)
+ break;
+ }
+
+ return found ? found : dm->control_client;
+}
diff --git a/drivers/virt/bao/io-dispatcher/io_dispatcher.c b/drivers/virt/bao/io-dispatcher/io_dispatcher.c
new file mode 100644
index 000000000000..82161ae47cb6
--- /dev/null
+++ b/drivers/virt/bao/io-dispatcher/io_dispatcher.c
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Bao Hypervisor I/O Dispatcher
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ *
+ * Authors:
+ * João Peixoto <jpeixoto@osyx.tech>
+ * José Martins <jose@osyx.tech>
+ * David Cerdeira <davidmcerdeira@osyx.tech>
+ */
+
+#include <asm/bao.h>
+#include "bao_drv.h"
+
+/**
+ * struct bao_io_dispatcher_work - Work item for I/O dispatching
+ * @work: Work struct for scheduling on workqueue
+ * @dm: Pointer to the associated Bao device model
+ *
+ * Represents a single work item that dispatches I/O requests
+ * for a specific Bao device model.
+ */
+struct bao_io_dispatcher_work {
+ struct work_struct work;
+ struct bao_dm *dm;
+};
+
+/* Array of I/O dispatcher work items, one per Bao DM */
+static struct bao_io_dispatcher_work io_dispatcher_work[BAO_IO_MAX_DMS];
+
+/* Workqueues dedicated to dispatching I/O requests for each Bao DM */
+static struct workqueue_struct *bao_io_dispatcher_wq[BAO_IO_MAX_DMS];
+
+void bao_io_dispatcher_destroy(struct bao_dm *dm)
+{
+ if (WARN_ON_ONCE(!dm))
+ return;
+
+ if (bao_io_dispatcher_wq[dm->info.id]) {
+ bao_io_dispatcher_pause(dm);
+
+ destroy_workqueue(bao_io_dispatcher_wq[dm->info.id]);
+ bao_io_dispatcher_wq[dm->info.id] = NULL;
+
+ bao_intc_remove_handler();
+ }
+}
+
+int bao_dispatch_io(struct bao_dm *dm)
+{
+ struct bao_io_client *client;
+ struct bao_remio_hypercall_ctx ctx;
+ struct bao_virtio_request req;
+
+ if (WARN_ON_ONCE(!dm))
+ return -EINVAL;
+
+ ctx.dm_id = dm->info.id;
+ ctx.op = BAO_IO_ASK;
+ ctx.addr = 0;
+ ctx.value = 0;
+ ctx.request_id = 0;
+ ctx.access_width = 0;
+ ctx.npend_req = 0;
+
+ if (bao_remio_hypercall(&ctx))
+ return -EFAULT;
+
+ req.dm_id = ctx.dm_id;
+ req.op = ctx.op;
+ req.addr = ctx.addr;
+ req.value = ctx.value;
+ req.access_width = ctx.access_width;
+ req.request_id = ctx.request_id;
+
+ down_read(&dm->io_clients_lock);
+ client = bao_io_client_find(dm, &req);
+ if (!client) {
+ up_read(&dm->io_clients_lock);
+ return -ENODEV;
+ }
+
+ if (!bao_io_client_push_request(client, &req)) {
+ up_read(&dm->io_clients_lock);
+ return -EINVAL;
+ }
+
+ wake_up_interruptible(&client->wq);
+ up_read(&dm->io_clients_lock);
+
+ return ctx.npend_req;
+}
+
+/**
+ * io_dispatcher - Workqueue handler for dispatching I/O
+ * @work: Work struct representing this dispatch operation
+ *
+ * Handles all pending I/O requests for the associated Bao DM.
+ * Executed in process context by the workqueue.
+ */
+static void io_dispatcher(struct work_struct *work)
+{
+ struct bao_io_dispatcher_work *bao_dm_work;
+ struct bao_dm *dm;
+
+ if (WARN_ON_ONCE(!work))
+ return;
+
+ bao_dm_work = container_of(work, struct bao_io_dispatcher_work, work);
+ dm = bao_dm_work->dm;
+
+ if (WARN_ON_ONCE(!dm))
+ return;
+
+ while (bao_dispatch_io(dm) > 0)
+ cpu_relax();
+}
+
+/**
+ * io_dispatcher_intc_handler - Interrupt handler for I/O requests
+ * @dm: Bao device model that triggered the interrupt
+ *
+ * Invoked by the interrupt controller when a new I/O request is available.
+ * Queues the corresponding work item onto the I/O dispatcher workqueue
+ * for processing in process context.
+ */
+static void io_dispatcher_intc_handler(struct bao_dm *dm)
+{
+ if (WARN_ON_ONCE(!dm || !bao_io_dispatcher_wq[dm->info.id]))
+ return;
+
+ queue_work(bao_io_dispatcher_wq[dm->info.id],
+ &io_dispatcher_work[dm->info.id].work);
+}
+
+void bao_io_dispatcher_pause(struct bao_dm *dm)
+{
+ if (WARN_ON_ONCE(!dm || !bao_io_dispatcher_wq[dm->info.id]))
+ return;
+
+ bao_intc_remove_handler();
+
+ drain_workqueue(bao_io_dispatcher_wq[dm->info.id]);
+}
+
+void bao_io_dispatcher_resume(struct bao_dm *dm)
+{
+ if (WARN_ON_ONCE(!dm || !bao_io_dispatcher_wq[dm->info.id]))
+ return;
+
+ bao_intc_setup_handler(io_dispatcher_intc_handler);
+
+ queue_work(bao_io_dispatcher_wq[dm->info.id],
+ &io_dispatcher_work[dm->info.id].work);
+}
+
+int bao_io_dispatcher_init(struct bao_dm *dm)
+{
+ char name[BAO_NAME_MAX_LEN];
+
+ if (WARN_ON_ONCE(!dm))
+ return -EINVAL;
+
+ snprintf(name, sizeof(name), "bao-iodwq%u", dm->info.id);
+
+ if (bao_io_dispatcher_wq[dm->info.id])
+ return -EBUSY;
+ bao_io_dispatcher_wq[dm->info.id] =
+ alloc_workqueue(name, WQ_HIGHPRI | WQ_MEM_RECLAIM, 1);
+ if (!bao_io_dispatcher_wq[dm->info.id])
+ return -ENOMEM;
+
+ io_dispatcher_work[dm->info.id].dm = dm;
+ INIT_WORK(&io_dispatcher_work[dm->info.id].work, io_dispatcher);
+
+ bao_intc_setup_handler(io_dispatcher_intc_handler);
+
+ return 0;
+}
+
diff --git a/drivers/virt/bao/io-dispatcher/ioeventfd.c b/drivers/virt/bao/io-dispatcher/ioeventfd.c
new file mode 100644
index 000000000000..8c6a69df7a51
--- /dev/null
+++ b/drivers/virt/bao/io-dispatcher/ioeventfd.c
@@ -0,0 +1,323 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Bao Hypervisor Ioeventfd Client
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ *
+ * Authors:
+ * João Peixoto <jpeixoto@osyx.tech>
+ * José Martins <jose@osyx.tech>
+ * David Cerdeira <davidmcerdeira@osyx.tech>
+ */
+
+#include <linux/eventfd.h>
+#include "bao_drv.h"
+
+/**
+ * struct ioeventfd - Properties of an I/O eventfd
+ * @list: List node linking this ioeventfd
+ * @eventfd: Associated eventfd context
+ * @addr: Start address of the I/O range
+ * @data: Data used for matching (if not wildcard)
+ * @length: Length of the I/O range
+ * @wildcard: True if data matching is not required
+ *
+ * Represents an I/O eventfd registered for a Bao device model.
+ */
+struct ioeventfd {
+ struct list_head list;
+ struct eventfd_ctx *eventfd;
+ u64 addr;
+ u64 data;
+ int length;
+ bool wildcard;
+};
+
+/**
+ * bao_ioeventfd_shutdown - Release and remove an ioeventfd
+ * @dm: Bao device model owning the ioeventfd
+ * @p: Ioeventfd to shut down
+ */
+static void bao_ioeventfd_shutdown(struct bao_dm *dm, struct ioeventfd *p)
+{
+ lockdep_assert_held(&dm->ioeventfds_lock);
+
+ if (WARN_ON_ONCE(!p))
+ return;
+
+ eventfd_ctx_put(p->eventfd);
+ list_del(&p->list);
+ kfree(p);
+}
+
+/**
+ * bao_ioeventfd_config_valid - Validate ioeventfd configuration
+ * @config: Ioeventfd configuration
+ *
+ * Return: True if config is non-NULL, address+length does not wrap,
+ * and length is 1, 2, 4, or 8 bytes.
+ */
+static bool bao_ioeventfd_config_valid(struct bao_ioeventfd *config)
+{
+ if (WARN_ON_ONCE(!config))
+ return false;
+
+ if (config->addr + config->len < config->addr)
+ return false;
+
+ if (!(config->len == 1 || config->len == 2 || config->len == 4 ||
+ config->len == 8))
+ return false;
+
+ return true;
+}
+
+/**
+ * bao_ioeventfd_is_conflict - Check if an ioeventfd conflicts with existing ones
+ * @dm: Bao device model
+ * @ioeventfd: Ioeventfd to check
+ *
+ * Return: True if an existing ioeventfd matches address, eventfd,
+ * and optionally data.
+ */
+static bool bao_ioeventfd_is_conflict(struct bao_dm *dm,
+ struct ioeventfd *ioeventfd)
+{
+ struct ioeventfd *p;
+
+ lockdep_assert_held(&dm->ioeventfds_lock);
+
+ if (WARN_ON_ONCE(!dm || !ioeventfd))
+ return true;
+
+ list_for_each_entry(p, &dm->ioeventfds, list) {
+ if (p->eventfd == ioeventfd->eventfd &&
+ p->addr == ioeventfd->addr &&
+ (p->wildcard || ioeventfd->wildcard ||
+ p->data == ioeventfd->data)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * bao_ioeventfd_match - Find ioeventfd matching an I/O request
+ * @dm: Bao device model
+ * @addr: I/O request address
+ * @data: I/O request data
+ * @len: I/O request length
+ *
+ * Return: The matching ioeventfd, NULL if none matches.
+ */
+static struct ioeventfd *bao_ioeventfd_match(struct bao_dm *dm, u64 addr,
+ u64 data, int len)
+{
+ struct ioeventfd *p;
+
+ lockdep_assert_held(&dm->ioeventfds_lock);
+
+ if (WARN_ON_ONCE(!dm))
+ return NULL;
+
+ list_for_each_entry(p, &dm->ioeventfds, list) {
+ if (p->addr == addr && p->length >= len &&
+ (p->wildcard || p->data == data)) {
+ return p;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * bao_ioeventfd_assign - Assign and create an eventfd for a DM
+ * @dm: Bao device model to assign the eventfd to
+ * @config: Configuration of the eventfd to create
+ *
+ * Creates a new ioeventfd associated with the given eventfd and
+ * adds it to the Bao DM. Validates the configuration, checks for
+ * conflicts with existing ioeventfds, and registers the corresponding
+ * I/O client address range. Supports optional data matching for
+ * virtio 1.0 notifications; if not set, wildcard matching is used.
+ *
+ * Return: 0 on success, a negative error code on failure
+ */
+static int bao_ioeventfd_assign(struct bao_dm *dm, struct bao_ioeventfd *config)
+{
+ struct eventfd_ctx *eventfd;
+ struct ioeventfd *new;
+ int rc = 0;
+
+ if (WARN_ON_ONCE(!dm || !config))
+ return -EINVAL;
+
+ if (!bao_ioeventfd_config_valid(config))
+ return -EINVAL;
+
+ eventfd = eventfd_ctx_fdget(config->fd);
+ if (IS_ERR(eventfd))
+ return PTR_ERR(eventfd);
+
+ new = kzalloc_obj(*new, GFP_KERNEL);
+ if (!new) {
+ rc = -ENOMEM;
+ goto err_put_eventfd;
+ }
+
+ INIT_LIST_HEAD(&new->list);
+ new->addr = config->addr;
+ new->length = config->len;
+ new->eventfd = eventfd;
+ new->wildcard = !(config->flags & BAO_IOEVENTFD_FLAG_DATAMATCH);
+ if (!new->wildcard)
+ new->data = config->data;
+
+ mutex_lock(&dm->ioeventfds_lock);
+
+ if (bao_ioeventfd_is_conflict(dm, new)) {
+ rc = -EEXIST;
+ goto err_unlock_free;
+ }
+
+ rc = bao_io_client_range_add(dm->ioeventfd_client, new->addr,
+ new->addr + new->length - 1);
+ if (rc < 0)
+ goto err_unlock_free;
+
+ list_add_tail(&new->list, &dm->ioeventfds);
+ mutex_unlock(&dm->ioeventfds_lock);
+
+ return 0;
+
+err_unlock_free:
+ mutex_unlock(&dm->ioeventfds_lock);
+ kfree(new);
+err_put_eventfd:
+ eventfd_ctx_put(eventfd);
+ return rc;
+}
+
+/**
+ * bao_ioeventfd_deassign - Deassign and destroy an eventfd from a DM
+ * @dm: Bao device model to deassign the eventfd from
+ * @config: Configuration of the eventfd to remove
+ *
+ * Return: 0 on success, a negative error code on failure
+ */
+static int bao_ioeventfd_deassign(struct bao_dm *dm,
+ struct bao_ioeventfd *config)
+{
+ struct ioeventfd *p;
+ struct eventfd_ctx *eventfd;
+
+ if (WARN_ON_ONCE(!dm || !config))
+ return -EINVAL;
+
+ eventfd = eventfd_ctx_fdget(config->fd);
+ if (IS_ERR(eventfd))
+ return PTR_ERR(eventfd);
+
+ mutex_lock(&dm->ioeventfds_lock);
+
+ list_for_each_entry(p, &dm->ioeventfds, list) {
+ if (p->eventfd != eventfd)
+ continue;
+
+ bao_io_client_range_del(dm->ioeventfd_client, p->addr,
+ p->addr + p->length - 1);
+
+ bao_ioeventfd_shutdown(dm, p);
+ break;
+ }
+
+ mutex_unlock(&dm->ioeventfds_lock);
+ eventfd_ctx_put(eventfd);
+
+ return 0;
+}
+
+/**
+ * bao_ioeventfd_handler - Handle an Ioeventfd client I/O request
+ * @client: Ioeventfd client associated with the request
+ * @req: I/O request to process
+ *
+ * Processes I/O requests from the Bao I/O client kernel thread
+ * (bao_io_client_kernel_thread). For READ operations, the value is
+ * ignored and set to 0 since virtio MMIO drivers only write to the
+ * `QueueNotify` field. WRITE operations are checked against the
+ * registered ioeventfds, and the corresponding eventfd is signaled
+ * if a match is found.
+ *
+ * Return: 0 on success, a negative error code on failure
+ */
+static int bao_ioeventfd_handler(struct bao_io_client *client,
+ struct bao_virtio_request *req)
+{
+ struct ioeventfd *p;
+
+ if (WARN_ON_ONCE(!client || !req))
+ return -EINVAL;
+
+ if (req->op == BAO_IO_READ) {
+ req->value = 0;
+ return 0;
+ }
+
+ mutex_lock(&client->dm->ioeventfds_lock);
+
+ p = bao_ioeventfd_match(client->dm, req->addr, req->value,
+ req->access_width);
+ if (p)
+ eventfd_signal(p->eventfd);
+
+ mutex_unlock(&client->dm->ioeventfds_lock);
+
+ return 0;
+}
+
+int bao_ioeventfd_client_config(struct bao_dm *dm, struct bao_ioeventfd *config)
+{
+ if (WARN_ON_ONCE(!dm || !config))
+ return -EINVAL;
+
+ if (config->flags & BAO_IOEVENTFD_FLAG_DEASSIGN)
+ bao_ioeventfd_deassign(dm, config);
+
+ return bao_ioeventfd_assign(dm, config);
+}
+
+int bao_ioeventfd_client_init(struct bao_dm *dm)
+{
+ char name[BAO_NAME_MAX_LEN];
+
+ if (WARN_ON_ONCE(!dm))
+ return -EINVAL;
+
+ mutex_init(&dm->ioeventfds_lock);
+ INIT_LIST_HEAD(&dm->ioeventfds);
+
+ snprintf(name, sizeof(name), "bao-ioevfdc%u", dm->info.id);
+
+ dm->ioeventfd_client = bao_io_client_create(dm, bao_ioeventfd_handler,
+ NULL, false, name);
+ if (!dm->ioeventfd_client)
+ return -ENOMEM;
+
+ return 0;
+}
+
+void bao_ioeventfd_client_destroy(struct bao_dm *dm)
+{
+ struct ioeventfd *p;
+ struct ioeventfd *next;
+
+ if (WARN_ON_ONCE(!dm))
+ return;
+
+ mutex_lock(&dm->ioeventfds_lock);
+ list_for_each_entry_safe(p, next, &dm->ioeventfds, list)
+ bao_ioeventfd_shutdown(dm, p);
+ mutex_unlock(&dm->ioeventfds_lock);
+}
diff --git a/drivers/virt/bao/io-dispatcher/irqfd.c b/drivers/virt/bao/io-dispatcher/irqfd.c
new file mode 100644
index 000000000000..07b20365ecae
--- /dev/null
+++ b/drivers/virt/bao/io-dispatcher/irqfd.c
@@ -0,0 +1,314 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Bao Hypervisor Irqfd Server
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ *
+ * Authors:
+ * João Peixoto <jpeixoto@osyx.tech>
+ * José Martins <jose@osyx.tech>
+ * David Cerdeira <davidmcerdeira@osyx.tech>
+ */
+
+#include <linux/eventfd.h>
+#include <linux/file.h>
+#include <linux/poll.h>
+#include <asm/bao.h>
+#include "bao_drv.h"
+
+/**
+ * struct irqfd - Properties of an IRQ eventfd
+ * @dm: Associated Bao device model
+ * @wait: Wait queue entry for blocking/waking
+ * @shutdown: Work struct for async shutdown
+ * @eventfd: Eventfd used to signal interrupts
+ * @list: List node within &bao_dm.irqfds
+ * @pt: Poll table for select/poll on the eventfd
+ *
+ * Represents an IRQ eventfd registered to a Bao device model.
+ */
+struct irqfd {
+ struct bao_dm *dm;
+ wait_queue_entry_t wait;
+ struct work_struct shutdown;
+ struct eventfd_ctx *eventfd;
+ struct list_head list;
+ poll_table pt;
+};
+
+/**
+ * bao_irqfd_shutdown - Release and remove an irqfd
+ * @irqfd: IRQ eventfd to shut down (lock must be held)
+ */
+static void bao_irqfd_shutdown(struct irqfd *irqfd)
+{
+ u64 cnt;
+
+ if (WARN_ON_ONCE(!irqfd || !irqfd->dm))
+ return;
+
+ lockdep_assert_held(&irqfd->dm->irqfds_lock);
+
+ list_del_init(&irqfd->list);
+
+ eventfd_ctx_remove_wait_queue(irqfd->eventfd, &irqfd->wait, &cnt);
+
+ eventfd_ctx_put(irqfd->eventfd);
+
+ kfree(irqfd);
+}
+
+/**
+ * bao_irqfd_inject - Inject a notify hypercall into the Bao hypervisor
+ * @id: Bao DM ID
+ *
+ * Return: 0 on success, -EFAULT if the hypercall fails.
+ */
+static int bao_irqfd_inject(int id)
+{
+ struct bao_remio_hypercall_ctx ctx = {
+ .dm_id = id,
+ .addr = 0,
+ .op = BAO_IO_NOTIFY,
+ .value = 0,
+ .access_width = 0,
+ .request_id = 0,
+ };
+
+ if (bao_remio_hypercall(&ctx))
+ return -EFAULT;
+
+ return 0;
+}
+
+/**
+ * bao_irqfd_wakeup - Custom wake-up handler for eventfd signaling
+ * @wait: Wait queue entry
+ * @mode: Mode flags
+ * @sync: Sync indicator
+ * @key: Poll bits (cast from void *)
+ *
+ * Called by the Linux kernel poll table when the underlying eventfd is signaled.
+ * Injects a Bao notify hypercall on POLLIN or schedules shutdown on POLLHUP.
+ *
+ * Return: 0 on success, a negative error code on failure
+ */
+static int bao_irqfd_wakeup(wait_queue_entry_t *wait, unsigned int mode,
+ int sync, void *key)
+{
+ struct irqfd *irqfd;
+ struct bao_dm *dm;
+ unsigned long poll_bits;
+
+ if (WARN_ON_ONCE(!wait || !key))
+ return -EINVAL;
+
+ irqfd = container_of(wait, struct irqfd, wait);
+ dm = irqfd->dm;
+ poll_bits = (unsigned long)key;
+
+ if (poll_bits & POLLIN)
+ bao_irqfd_inject(dm->info.id);
+
+ if (poll_bits & POLLHUP)
+ queue_work(dm->irqfd_server, &irqfd->shutdown);
+
+ return 0;
+}
+
+/**
+ * bao_irqfd_poll_func - Register an IRQFD with a poll table
+ * @file: File to poll
+ * @wqh: Wait queue head
+ * @pt: Poll table
+ *
+ * Adds the irqfd's wait queue entry to the kernel wait queue for event monitoring.
+ */
+static void bao_irqfd_poll_func(struct file *file, wait_queue_head_t *wqh,
+ poll_table *pt)
+{
+ struct irqfd *irqfd;
+
+ if (WARN_ON_ONCE(!pt || !wqh))
+ return;
+
+ irqfd = container_of(pt, struct irqfd, pt);
+ add_wait_queue(wqh, &irqfd->wait);
+}
+
+/**
+ * irqfd_shutdown_work - Workqueue handler to shutdown an irqfd
+ * @work: Work struct for the shutdown operation
+ *
+ * Removes and frees the irqfd from the DM under lock if it is still linked.
+ */
+static void irqfd_shutdown_work(struct work_struct *work)
+{
+ struct irqfd *irqfd;
+ struct bao_dm *dm;
+
+ if (WARN_ON_ONCE(!work))
+ return;
+
+ irqfd = container_of(work, struct irqfd, shutdown);
+ dm = irqfd->dm;
+
+ if (WARN_ON_ONCE(!dm))
+ return;
+
+ mutex_lock(&dm->irqfds_lock);
+ if (!list_empty(&irqfd->list))
+ bao_irqfd_shutdown(irqfd);
+ mutex_unlock(&dm->irqfds_lock);
+}
+
+/**
+ * bao_irqfd_assign - Assign an eventfd to a DM and create an irqfd
+ * @dm: Bao device model to assign the eventfd
+ * @args: Configuration of the irqfd to assign
+ *
+ * Return: 0 on success, a negative error code on failure
+ */
+static int bao_irqfd_assign(struct bao_dm *dm, struct bao_irqfd *args)
+{
+ struct eventfd_ctx *eventfd = NULL;
+ struct irqfd *irqfd;
+ struct irqfd *tmp;
+ __poll_t events;
+ struct fd f;
+ int ret = 0;
+
+ if (WARN_ON_ONCE(!dm || !args))
+ return -EINVAL;
+
+ irqfd = kzalloc_obj(*irqfd, GFP_KERNEL);
+ if (!irqfd)
+ return -ENOMEM;
+
+ irqfd->dm = dm;
+ INIT_LIST_HEAD(&irqfd->list);
+ INIT_WORK(&irqfd->shutdown, irqfd_shutdown_work);
+
+ f = fdget(args->fd);
+ if (!fd_file(f)) {
+ ret = -EBADF;
+ goto out_free_irqfd;
+ }
+
+ eventfd = eventfd_ctx_fileget(fd_file(f));
+ if (IS_ERR(eventfd)) {
+ ret = PTR_ERR(eventfd);
+ goto out_fdput;
+ }
+ irqfd->eventfd = eventfd;
+
+ init_waitqueue_func_entry(&irqfd->wait, bao_irqfd_wakeup);
+ init_poll_funcptr(&irqfd->pt, bao_irqfd_poll_func);
+
+ mutex_lock(&dm->irqfds_lock);
+ list_for_each_entry(tmp, &dm->irqfds, list) {
+ if (irqfd->eventfd == tmp->eventfd) {
+ ret = -EBUSY;
+ mutex_unlock(&dm->irqfds_lock);
+ goto out_put_eventfd;
+ }
+ }
+ list_add_tail(&irqfd->list, &dm->irqfds);
+ mutex_unlock(&dm->irqfds_lock);
+
+ events = vfs_poll(fd_file(f), &irqfd->pt);
+ if (events & EPOLLIN)
+ bao_irqfd_inject(dm->info.id);
+
+ fdput(f);
+ return 0;
+
+out_put_eventfd:
+ eventfd_ctx_put(eventfd);
+out_fdput:
+ fdput(f);
+out_free_irqfd:
+ kfree(irqfd);
+ return ret;
+}
+
+/**
+ * bao_irqfd_deassign - Deassign an eventfd and destroy the associated irqfd
+ * @dm: Bao device model to remove the irqfd from
+ * @args: Configuration of the irqfd to deassign
+ *
+ * Return: 0 on success, a negative error code on failure
+ */
+static int bao_irqfd_deassign(struct bao_dm *dm, struct bao_irqfd *args)
+{
+ struct irqfd *irqfd;
+ struct irqfd *tmp;
+ struct eventfd_ctx *eventfd;
+
+ if (WARN_ON_ONCE(!dm || !args))
+ return -EINVAL;
+
+ eventfd = eventfd_ctx_fdget(args->fd);
+ if (IS_ERR(eventfd))
+ return PTR_ERR(eventfd);
+
+ mutex_lock(&dm->irqfds_lock);
+ list_for_each_entry_safe(irqfd, tmp, &dm->irqfds, list) {
+ if (irqfd->eventfd == eventfd) {
+ bao_irqfd_shutdown(irqfd);
+ break;
+ }
+ }
+ mutex_unlock(&dm->irqfds_lock);
+
+ eventfd_ctx_put(eventfd);
+
+ return 0;
+}
+
+int bao_irqfd_server_config(struct bao_dm *dm, struct bao_irqfd *config)
+{
+ if (WARN_ON_ONCE(!dm || !config))
+ return -EINVAL;
+
+ if (config->flags & BAO_IRQFD_FLAG_DEASSIGN)
+ return bao_irqfd_deassign(dm, config);
+
+ return bao_irqfd_assign(dm, config);
+}
+
+int bao_irqfd_server_init(struct bao_dm *dm)
+{
+ char name[BAO_NAME_MAX_LEN];
+
+ if (WARN_ON_ONCE(!dm))
+ return -EINVAL;
+
+ mutex_init(&dm->irqfds_lock);
+ INIT_LIST_HEAD(&dm->irqfds);
+
+ snprintf(name, sizeof(name), "bao-ioirqfds%u", dm->info.id);
+
+ dm->irqfd_server = alloc_workqueue(name, WQ_UNBOUND | WQ_HIGHPRI, 0);
+ if (!dm->irqfd_server)
+ return -ENOMEM;
+
+ return 0;
+}
+
+void bao_irqfd_server_destroy(struct bao_dm *dm)
+{
+ struct irqfd *irqfd;
+ struct irqfd *next;
+
+ if (WARN_ON_ONCE(!dm))
+ return;
+
+ if (dm->irqfd_server)
+ destroy_workqueue(dm->irqfd_server);
+
+ mutex_lock(&dm->irqfds_lock);
+ list_for_each_entry_safe(irqfd, next, &dm->irqfds, list)
+ bao_irqfd_shutdown(irqfd);
+ mutex_unlock(&dm->irqfds_lock);
+}
diff --git a/include/linux/bao.h b/include/linux/bao.h
new file mode 100644
index 000000000000..076f071086b6
--- /dev/null
+++ b/include/linux/bao.h
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Bao Hypervisor Linux Kernel Header file
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ *
+ * Authors:
+ * João Peixoto <jpeixoto@osyx.tech>
+ * José Martins <jose@osyx.tech>
+ * David Cerdeira <davidmcerdeira@osyx.tech>
+ */
+
+#ifndef _LINUX_BAO_H
+#define _LINUX_BAO_H
+
+#include <linux/types.h>
+
+/* Remote I/O Hypercall ID */
+#define BAO_REMIO_HYPERCALL_ID 0x2
+
+/**
+ * struct bao_remio_hypercall_ctx - REMIO hypercall context
+ * @dm_id: Device model identifier
+ * @addr: Target address
+ * @op: Operation code
+ * @value: Value to read/write
+ * @access_width: Access width in bytes
+ * @request_id: Request identifier
+ * @npend_req: Number of pending requests
+ */
+struct bao_remio_hypercall_ctx {
+ u64 dm_id;
+ u64 addr;
+ u64 op;
+ u64 value;
+ u64 access_width;
+ u64 request_id;
+ u64 npend_req;
+};
+
+#endif /* _LINUX_BAO_H */
diff --git a/include/uapi/linux/bao.h b/include/uapi/linux/bao.h
new file mode 100644
index 000000000000..5c97595637e6
--- /dev/null
+++ b/include/uapi/linux/bao.h
@@ -0,0 +1,96 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Provides the Bao Hypervisor IOCTLs and global structures
+ *
+ * Copyright (c) Bao Project and Contributors. All rights reserved.
+ *
+ * Authors:
+ * João Peixoto <jpeixoto@osyx.tech>
+ * José Martins <jose@osyx.tech>
+ * David Cerdeira <davidmcerdeira@osyx.tech>
+ */
+
+#ifndef _UAPI_BAO_H
+#define _UAPI_BAO_H
+
+#include <linux/types.h>
+
+/**
+ * struct bao_virtio_request - Parameters of a Bao VirtIO request
+ * @dm_id: Device model ID
+ * @addr: MMIO register address accessed
+ * @op: Operation type (WRITE = 0, READ, ASK, NOTIFY)
+ * @value: Value to write or read
+ * @access_width: Access width (VirtIO MMIO supports 4-byte aligned accesses)
+ * @request_id: Request ID of the I/O request
+ */
+struct bao_virtio_request {
+ __u64 dm_id;
+ __u64 addr;
+ __u64 op;
+ __u64 value;
+ __u64 access_width;
+ __u64 request_id;
+};
+
+/**
+ * struct bao_ioeventfd - Parameters of an ioeventfd request
+ * @fd: Eventfd file descriptor associated with the I/O request
+ * @flags: Logical OR of BAO_IOEVENTFD_FLAG_*
+ * @addr: Start address of the I/O range
+ * @len: Length of the I/O range
+ * @reserved: Reserved, must be 0
+ * @data: Data for matching (used if data matching is enabled)
+ */
+struct bao_ioeventfd {
+ __u32 fd;
+ __u32 flags;
+ __u64 addr;
+ __u32 len;
+ __u32 reserved;
+ __u64 data;
+};
+
+/**
+ * struct bao_irqfd - Parameters of an IRQFD request
+ * @fd: File descriptor of the eventfd
+ * @flags: Flags associated with the eventfd
+ */
+struct bao_irqfd {
+ __s32 fd;
+ __u32 flags;
+};
+
+/**
+ * struct bao_dm_info - Parameters of a Bao device model
+ * @id: Virtual ID of the DM
+ * @shmem_addr: Base address of the shared memory
+ * @shmem_size: Size of the shared memory
+ * @irq: IRQ number
+ */
+struct bao_dm_info {
+ __u32 id;
+ __u64 shmem_addr;
+ __u64 shmem_size;
+ __u32 irq;
+};
+
+/*
+ * The ioctl type for Bao, documented in
+ * Documentation/userspace-api/ioctl/ioctl-number.rst
+ */
+#define BAO_IOCTL_TYPE 0xA6
+
+/*
+ * Bao userspace IOCTL commands
+ * Follows Linux kernel convention, see Documentation/driver-api/ioctl.rst
+ */
+#define BAO_IOCTL_DM_GET_INFO _IOWR(BAO_IOCTL_TYPE, 0x01, struct bao_dm_info)
+#define BAO_IOCTL_IO_CLIENT_ATTACH \
+ _IOWR(BAO_IOCTL_TYPE, 0x02, struct bao_virtio_request)
+#define BAO_IOCTL_IO_REQUEST_COMPLETE \
+ _IOW(BAO_IOCTL_TYPE, 0x03, struct bao_virtio_request)
+#define BAO_IOCTL_IOEVENTFD _IOW(BAO_IOCTL_TYPE, 0x04, struct bao_ioeventfd)
+#define BAO_IOCTL_IRQFD _IOW(BAO_IOCTL_TYPE, 0x05, struct bao_irqfd)
+
+#endif /* _UAPI_BAO_H */
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [RFC PATCH v3 5/6] virt: bao: consolidate the IPC hypercall ID in include/linux/bao.h
2026-08-07 7:39 [RFC PATCH v3 0/6] virt: bao: Add Bao hypervisor IPC and I/O dispatcher drivers João Peixoto
` (3 preceding siblings ...)
2026-08-07 7:39 ` [RFC PATCH v3 4/6] virt: bao: add I/O dispatcher driver João Peixoto
@ 2026-08-07 7:39 ` João Peixoto
2026-08-07 7:50 ` sashiko-bot
2026-08-07 7:39 ` [RFC PATCH v3 6/6] MAINTAINERS: add Bao hypervisor entry João Peixoto
5 siblings, 1 reply; 11+ messages in thread
From: João Peixoto @ 2026-08-07 7:39 UTC (permalink / raw)
To: gregkh, robh, krzk+dt, conor+dt
Cc: jose, davidmcerdeira, corbet, skhan, catalin.marinas, will, linux,
pjw, palmer, aou, alex, andrew.jones, rdunlap, linux-kernel,
devicetree, linux-doc, linux-arm-kernel, linux-riscv
The IPC shared-memory hypercall ID was passed as a parameter through each
architecture's bao_ipcshmem_hypercall() and defined locally in ipcshmem.c.
Define it once in include/linux/bao.h, next to the Remote I/O hypercall
ID, and reference it directly from the arch helpers. As there is only one
IPC hypercall, this also lets bao_ipcshmem_hypercall() drop its
hypercall_id parameter.
Signed-off-by: João Peixoto <jpeixoto@osyx.tech>
---
v3:
- Rewrite the commit message to explain why the ID is consolidated and to
describe the bao_ipcshmem_hypercall() parameter removal it performs
(Greg KH).
- The helper now takes a single argument and fits on one line; unwrap the
remaining few-char line wraps (Andrew Jones).
arch/arm/include/asm/bao.h | 5 ++---
arch/arm64/include/asm/bao.h | 5 ++---
arch/riscv/include/asm/bao.h | 6 ++----
drivers/virt/bao/ipcshmem/ipcshmem.c | 5 +----
include/linux/bao.h | 3 +++
5 files changed, 10 insertions(+), 14 deletions(-)
diff --git a/arch/arm/include/asm/bao.h b/arch/arm/include/asm/bao.h
index eca258cc94e3..a7d608493723 100644
--- a/arch/arm/include/asm/bao.h
+++ b/arch/arm/include/asm/bao.h
@@ -16,14 +16,13 @@
#include <linux/arm-smccc.h>
#include <linux/bao.h>
-static inline unsigned long bao_ipcshmem_hypercall(unsigned long hypercall_id,
- unsigned long ipcshmem_id)
+static inline unsigned long bao_ipcshmem_hypercall(unsigned long ipcshmem_id)
{
struct arm_smccc_res res;
arm_smccc_hvc(ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_32,
ARM_SMCCC_OWNER_VENDOR_HYP,
- hypercall_id),
+ BAO_IPCSHMEM_HYPERCALL_ID),
ipcshmem_id, 0, 0, 0, 0, 0, 0, &res);
return res.a0;
diff --git a/arch/arm64/include/asm/bao.h b/arch/arm64/include/asm/bao.h
index 1dc09a2c261b..5bd2a8c9905d 100644
--- a/arch/arm64/include/asm/bao.h
+++ b/arch/arm64/include/asm/bao.h
@@ -16,14 +16,13 @@
#include <linux/arm-smccc.h>
#include <linux/bao.h>
-static inline unsigned long bao_ipcshmem_hypercall(unsigned long hypercall_id,
- unsigned long ipcshmem_id)
+static inline unsigned long bao_ipcshmem_hypercall(unsigned long ipcshmem_id)
{
struct arm_smccc_res res;
arm_smccc_hvc(ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64,
ARM_SMCCC_OWNER_VENDOR_HYP,
- hypercall_id),
+ BAO_IPCSHMEM_HYPERCALL_ID),
ipcshmem_id, 0, 0, 0, 0, 0, 0, &res);
return res.a0;
diff --git a/arch/riscv/include/asm/bao.h b/arch/riscv/include/asm/bao.h
index 6dc0cb13c94c..a587a6abd54e 100644
--- a/arch/riscv/include/asm/bao.h
+++ b/arch/riscv/include/asm/bao.h
@@ -26,13 +26,11 @@
*/
#define BAO_SBI_EXT_ID 0x08000ba0
-static inline unsigned long bao_ipcshmem_hypercall(unsigned long hypercall_id,
- unsigned long ipcshmem_id)
+static inline unsigned long bao_ipcshmem_hypercall(unsigned long ipcshmem_id)
{
struct sbiret ret;
- ret = sbi_ecall(BAO_SBI_EXT_ID, hypercall_id, ipcshmem_id, 0, 0, 0, 0,
- 0);
+ ret = sbi_ecall(BAO_SBI_EXT_ID, BAO_IPCSHMEM_HYPERCALL_ID, ipcshmem_id, 0, 0, 0, 0, 0);
return ret.error;
}
diff --git a/drivers/virt/bao/ipcshmem/ipcshmem.c b/drivers/virt/bao/ipcshmem/ipcshmem.c
index 0d46d89ee788..5bf37c1c7933 100644
--- a/drivers/virt/bao/ipcshmem/ipcshmem.c
+++ b/drivers/virt/bao/ipcshmem/ipcshmem.c
@@ -14,9 +14,6 @@
#define BAO_IPCSHMEM_NAME_LEN 16
-/* IPC through shared-memory hypercall ID */
-#define BAO_IPCSHMEM_HYPERCALL_ID 0x1
-
struct bao_ipcshmem {
struct miscdevice miscdev;
u32 id;
@@ -104,7 +101,7 @@ static ssize_t bao_ipcshmem_write(struct file *filp, const char __user *buf,
*ppos += count;
/* Notify Bao hypervisor */
- bao_ipcshmem_hypercall(BAO_IPCSHMEM_HYPERCALL_ID, bao->id);
+ bao_ipcshmem_hypercall(bao->id);
return count;
}
diff --git a/include/linux/bao.h b/include/linux/bao.h
index 076f071086b6..17d6ea472521 100644
--- a/include/linux/bao.h
+++ b/include/linux/bao.h
@@ -15,6 +15,9 @@
#include <linux/types.h>
+/* IPC through shared-memory hypercall ID */
+#define BAO_IPCSHMEM_HYPERCALL_ID 0x1
+
/* Remote I/O Hypercall ID */
#define BAO_REMIO_HYPERCALL_ID 0x2
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [RFC PATCH v3 6/6] MAINTAINERS: add Bao hypervisor entry
2026-08-07 7:39 [RFC PATCH v3 0/6] virt: bao: Add Bao hypervisor IPC and I/O dispatcher drivers João Peixoto
` (4 preceding siblings ...)
2026-08-07 7:39 ` [RFC PATCH v3 5/6] virt: bao: consolidate the IPC hypercall ID in include/linux/bao.h João Peixoto
@ 2026-08-07 7:39 ` João Peixoto
5 siblings, 0 replies; 11+ messages in thread
From: João Peixoto @ 2026-08-07 7:39 UTC (permalink / raw)
To: gregkh, robh, krzk+dt, conor+dt
Cc: jose, davidmcerdeira, corbet, skhan, catalin.marinas, will, linux,
pjw, palmer, aou, alex, andrew.jones, rdunlap, linux-kernel,
devicetree, linux-doc, linux-arm-kernel, linux-riscv
Add a MAINTAINERS entry covering the Bao hypervisor drivers, the
architecture headers, the in-kernel and UAPI headers, and the
device-tree bindings.
Signed-off-by: João Peixoto <jpeixoto@osyx.tech>
---
v3:
- Concise commit message (Krzysztof Kozlowski).
MAINTAINERS | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a9..6a81a8da58f1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4519,6 +4519,19 @@ F: drivers/video/backlight/
F: include/linux/backlight.h
F: include/linux/pwm_backlight.h
+BAO HYPERVISOR
+M: José Martins <jose@osyx.tech>
+M: David Cerdeira <davidmcerdeira@osyx.tech>
+M: João Peixoto <jpeixoto@osyx.tech>
+S: Maintained
+F: Documentation/devicetree/bindings/bao/
+F: arch/arm/include/asm/bao.h
+F: arch/arm64/include/asm/bao.h
+F: arch/riscv/include/asm/bao.h
+F: drivers/virt/bao
+F: include/linux/bao.h
+F: include/uapi/linux/bao.h
+
BARCO P50 GPIO DRIVER
M: Santosh Kumar Yadav <santoshkumar.yadav@barco.com>
M: Peter Korsgaard <peter.korsgaard@barco.com>
--
2.43.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [RFC PATCH v3 1/6] dt-bindings: bao: add IPC shared-memory device
2026-08-07 7:39 ` [RFC PATCH v3 1/6] dt-bindings: bao: add IPC shared-memory device João Peixoto
@ 2026-08-07 7:45 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-08-07 7:45 UTC (permalink / raw)
To: João Peixoto; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] The new vendor prefix '^bao,.*' is inserted out of alphabetical order in vendor-prefixes.yaml.
--
--- Patch [1]: [RFC PATCH v3 1/6] dt-bindings: bao: add IPC shared-memory device ---
commit 41886074b8a6e75abf127210a8dc6414ebf6935a
Author: João Peixoto <jpeixoto@osyx.tech>
dt-bindings: bao: add IPC shared-memory device
Add the device-tree binding for the Bao hypervisor IPC shared-memory
device, used by a Bao guest to exchange data with a peer guest over a
shared-memory region.
[ ... ]
> diff --git a/Documentation/devicetree/bindings/vendor-prefixes.yaml b/Documentation/devicetree/bindings/vendor-prefixes.yaml
> index 396044f368e7c..b11b3dd995a53 100644
> --- a/Documentation/devicetree/bindings/vendor-prefixes.yaml
> +++ b/Documentation/devicetree/bindings/vendor-prefixes.yaml
> @@ -231,6 +231,8 @@ patternProperties:
> description: Shenzhen AZW Technology Co., Ltd.
> "^baikal,.*":
> description: BAIKAL ELECTRONICS, JSC
> + "^bao,.*":
> + description: Bao Project (https://github.com/bao-project)
> "^bananapi,.*":
[Severity: Low]
This isn't a bug, but should the bao prefix be placed after bananapi to
maintain alphabetical sorting?
> description: BIPAI KEJI LIMITED
> "^beacon,.*":
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1786010512.git.jpeixoto@osyx.tech?part=1
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [RFC PATCH v3 5/6] virt: bao: consolidate the IPC hypercall ID in include/linux/bao.h
2026-08-07 7:39 ` [RFC PATCH v3 5/6] virt: bao: consolidate the IPC hypercall ID in include/linux/bao.h João Peixoto
@ 2026-08-07 7:50 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-08-07 7:50 UTC (permalink / raw)
To: João Peixoto; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] Missing memory barrier (e.g., `wmb()`) before notifying the hypervisor of newly written data, potentially causing the hypervisor or the receiving VM to read stale memory.
--
--- Patch [5]: [RFC PATCH v3 5/6] virt: bao: consolidate the IPC hypercall ID in include/linux/bao.h ---
commit 392291f754c5a86346cd59911eed878c78654f9d
Author: João Peixoto <jpeixoto@osyx.tech>
virt: bao: consolidate the IPC hypercall ID in include/linux/bao.h
This commit consolidates the IPC shared-memory hypercall ID into a common
header. It also simplifies the architecture-specific bao_ipcshmem_hypercall()
helpers by dropping the hypercall_id parameter.
> diff --git a/drivers/virt/bao/ipcshmem/ipcshmem.c b/drivers/virt/bao/ipcshmem/ipcshmem.c
> index 0d46d89ee788b..5bf37c1c7933d 100644
> --- a/drivers/virt/bao/ipcshmem/ipcshmem.c
> +++ b/drivers/virt/bao/ipcshmem/ipcshmem.c
[ ... ]
> @@ -104,7 +101,7 @@ static ssize_t bao_ipcshmem_write(struct file *filp, const char __user *buf,
> *ppos += count;
>
> /* Notify Bao hypervisor */
> - bao_ipcshmem_hypercall(BAO_IPCSHMEM_HYPERCALL_ID, bao->id);
> + bao_ipcshmem_hypercall(bao->id);
[Severity: High]
This isn't a bug introduced by this patch, but is there a missing memory
barrier before notifying the hypervisor here in bao_ipcshmem_write()?
Data is written to the shared memory via copy_from_user() just before this,
but no memory barrier ensures global visibility of the write prior to the
notification.
The ARM SMCCC explicitly states that callers must ensure memory updates are
visible before invoking SMC/HVC, meaning an explicit memory barrier (like
wmb()) is architecturally required to prevent stale reads.
Without it, could the hypervisor or the receiving VM read stale memory before
the stores from copy_from_user() become globally visible across PEs?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1786010512.git.jpeixoto@osyx.tech?part=5
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [RFC PATCH v3 4/6] virt: bao: add I/O dispatcher driver
2026-08-07 7:39 ` [RFC PATCH v3 4/6] virt: bao: add I/O dispatcher driver João Peixoto
@ 2026-08-07 7:54 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-08-07 7:54 UTC (permalink / raw)
To: João Peixoto; +Cc: devicetree, robh, conor+dt
Thank you for your contribution! Sashiko AI review found 16 potential issue(s) to consider:
- [High] Userspace can spoof the `dm_id` in `BAO_IOCTL_IO_REQUEST_COMPLETE`, allowing one device model instance to interfere with others.
- [High] Unrecoverable self-deadlock in the I/O client kernel thread during teardown.
- [High] Global interrupt handler pointer breaks multi-DM support by starving active DMs of interrupts.
- [High] TOCTOU Use-After-Free race in `bao_irqfd_assign()` when polling the eventfd.
- [High] Missing workqueue cancellation during irqfd shutdown causes Use-After-Free on asynchronous POLLHUP.
- [High] Device unbind frees `struct bao_dm` while userspace holds open file descriptors.
- [High] Calling `destroy_workqueue` before removing eventfd wait queues creates a Use-After-Free on the irqfd workqueue.
- [High] Passing a stack-allocated string as the 'name' argument to request_irq() leads to a Use-After-Free.
- [High] A missing return statement in `bao_ioeventfd_client_config` silently breaks ioeventfd deassignment.
- [High] The I/O dispatcher workqueue queues `bao_io_request` structures unboundedly, allowing a malicious frontend VM to cause a kernel OOM.
- [Medium] The `struct bao_dm_info` UAPI structure contains uninitialized implicit padding holes, leaking kernel stack memory to userspace.
- [Medium] Ioeventfd deassignment matches only by eventfd, breaking targeted range deregistration.
- [Medium] Signals are incorrectly handled in `bao_io_client_attach`, translating normal signal interruptions into spurious `-EFAULT` errors in userspace.
- [Medium] The `bao_io_client_destroy` function fails to free pending I/O requests.
- [Medium] Unused kernel memory mapping of backend shared memory wastes vmalloc space.
- [Medium] Silent discard of I/O requests when `copy_to_user` fails or memory allocation fails.
--
--- Patch [4]: [RFC PATCH v3 4/6] virt: bao: add I/O dispatcher driver ---
commit fc6ea3f8ec16168d50252a311b2bf53f0409c312
Author: João Peixoto <jpeixoto@osyx.tech>
Subject: virt: bao: add I/O dispatcher driver
Add the Bao I/O dispatcher, used by backend VMs to service I/O on behalf
of frontend guests. It bridges Bao's Remote I/O mechanism to userspace
VirtIO backend device models.
> --- a/include/uapi/linux/bao.h
> +++ b/include/uapi/linux/bao.h
> @@ -68,6 +68,13 @@ struct bao_irqfd {
> };
>
> +struct bao_dm_info {
> + __u32 id;
> + __u64 shmem_addr;
> + __u64 shmem_size;
> + __u32 irq;
> +};
[Severity: Medium]
Does this UAPI structure leak kernel stack memory to userspace? The structure
contains implicit padding holes after the 32-bit fields.
When it is allocated on the stack in bao_io_dispatcher_driver_probe() and
copied to userspace via bao_dm_ioctl() using copy_to_user(), the uninitialized
4-byte holes are leaked.
> --- a/drivers/virt/bao/io-dispatcher/dm.c
> +++ b/drivers/virt/bao/io-dispatcher/dm.c
> @@ -34,6 +34,107 @@ static int bao_dm_release(struct inode *inode, struct file *filp)
> +static long bao_dm_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> +{
> + struct bao_dm *dm = filp->private_data;
[ ... ]
> + case BAO_IOCTL_IO_CLIENT_ATTACH: {
> + struct bao_virtio_request *req;
> +
> + req = memdup_user((void __user *)arg, sizeof(*req));
> + if (IS_ERR(req)) {
> + rc = PTR_ERR(req);
> + break;
> + }
> +
> + if (!dm->control_client) {
> + rc = -ENOENT;
> + goto out_free;
> + }
> +
> + rc = bao_io_client_attach(dm->control_client);
> + if (rc)
> + goto out_free;
> +
> + rc = bao_io_client_request(dm->control_client, req);
> + if (rc)
> + goto out_free;
> +
> + if (copy_to_user((void __user *)arg, req, sizeof(*req))) {
> + rc = -EFAULT;
> + goto out_free;
> + }
[Severity: Medium]
What happens to the I/O request if copy_to_user() fails here? The request
is permanently popped from the queue in bao_io_client_request().
If copying it to userspace fails, the request appears to be silently discarded,
which would leave the frontend guest VM hanging indefinitely.
> + case BAO_IOCTL_IO_REQUEST_COMPLETE: {
> + struct bao_virtio_request *req;
> + struct bao_remio_hypercall_ctx ctx;
> +
> + req = memdup_user((void __user *)arg, sizeof(*req));
> + if (IS_ERR(req)) {
> + rc = PTR_ERR(req);
> + break;
> + }
> +
> + ctx.dm_id = req->dm_id;
> + ctx.addr = req->addr;
> + ctx.op = req->op;
> + ctx.value = req->value;
> + ctx.access_width = req->access_width;
> + ctx.request_id = req->request_id;
> +
> + rc = bao_remio_hypercall(&ctx);
[Severity: High]
Can userspace spoof the dm_id in this hypercall context? The request's dm_id
is blindly copied from userspace instead of being validated against the dm
associated with the file descriptor (dm->info.id).
Could this allow an unprivileged frontend process to complete or corrupt
requests belonging to other device models?
[ ... ]
> +struct bao_dm *bao_dm_create(struct bao_dm_info *info, struct device *dev)
> +{
> + struct bao_dm *dm;
> + int ret;
[ ... ]
> + dm->shmem_base_addr = memremap(dm->info.shmem_addr, dm->info.shmem_size,
> + MEMREMAP_WB);
[Severity: Medium]
Is this kernel memory mapping necessary? The mapped physical memory pointer
dm->shmem_base_addr is never dereferenced by the kernel for reading or writing.
Since userspace maps the memory directly via remap_pfn_range() using the
physical address, does this just waste kernel virtual address space?
[ ... ]
> +void bao_dm_destroy(struct bao_dm *dm)
> +{
> + if (WARN_ON_ONCE(!dm))
> + return;
> +
> + misc_deregister(&dm->miscdev);
> +
> + if (dm->shmem_base_addr)
> + memunmap(dm->shmem_base_addr);
> +
> + bao_irqfd_server_destroy(dm);
> + bao_io_clients_destroy(dm);
> + bao_io_dispatcher_destroy(dm);
> +
> + kfree(dm);
> +}
[Severity: High]
Is it safe to unconditionally free the dm structure here? This runs during the
driver's remove callback, but misc_deregister() does not revoke existing open
file descriptors.
If userspace holds the device open and issues an ioctl() or mmap() after the
driver unbinds, the VFS callbacks will dereference the freed dm pointer,
leading to a use-after-free.
> --- a/drivers/virt/bao/io-dispatcher/intc.c
> +++ b/drivers/virt/bao/io-dispatcher/intc.c
> @@ -13,6 +13,52 @@
> +/* Top-level handler registered by the Bao interrupt controller */
> +static void (*bao_intc_handler)(struct bao_dm *dm);
[Severity: High]
Does this global function pointer break multi-DM support? When any single DM
is paused or destroyed, bao_intc_remove_handler() sets this global pointer
to NULL.
This would silently disable I/O interrupt dispatching for all other active DMs
on the system.
[ ... ]
> +int bao_intc_init(struct bao_dm *dm)
> +{
> + char name[BAO_NAME_MAX_LEN];
> +
> + if (WARN_ON_ONCE(!dm))
> + return -EINVAL;
> +
> + scnprintf(name, sizeof(name), "bao-iodintc%d", dm->info.id);
> +
> + return request_irq(dm->info.irq, bao_interrupt_handler, 0, name, dm);
> +}
[Severity: High]
Can this lead to a use-after-free when reading /proc/interrupts? The character
array name is allocated on the stack and passed to request_irq().
The IRQ subsystem stores this pointer directly without copying the string. When
this function returns and the stack frame is destroyed, any subsequent access
to the interrupt name will dereference a stale stack pointer.
> --- a/drivers/virt/bao/io-dispatcher/io_client.c
> +++ b/drivers/virt/bao/io-dispatcher/io_client.c
> @@ -45,6 +45,356 @@ static inline bool bao_io_client_is_destroying(struct bao_io_client *client)
> +bool bao_io_client_push_request(struct bao_io_client *client,
> + struct bao_virtio_request *req)
> +{
> + struct bao_io_request *io_req;
> +
> + if (WARN_ON_ONCE(!client || !req))
> + return false;
> +
> + io_req = kzalloc_obj(*io_req, GFP_KERNEL);
[Severity: High]
Could this allow a malicious frontend VM to cause a host kernel OOM? The
io_dispatcher workqueue continually pulls pending I/O requests from the
hypervisor and pushes them here, which allocates memory with GFP_KERNEL.
There doesn't appear to be any limit or backpressure mechanism if userspace
processes requests slower than they are produced.
[ ... ]
> +static void bao_io_client_destroy(struct bao_io_client *client)
> +{
> + struct bao_io_client *range;
> + struct bao_io_client *next;
> + struct bao_dm *dm;
> +
> + if (WARN_ON_ONCE(!client))
> + return;
> +
> + dm = client->dm;
> +
> + bao_io_dispatcher_pause(dm);
> +
> + set_bit(BAO_IO_CLIENT_DESTROYING, &client->flags);
> +
> + if (client->is_control) {
> + wake_up_interruptible(&client->wq);
> + } else {
> + bao_ioeventfd_client_destroy(dm);
> + if (client->thread)
> + kthread_stop(client->thread);
> + }
[Severity: High]
Could this cause an unrecoverable self-deadlock? In bao_io_client_attach(),
when kthread_should_stop() is true, the kthread recursively calls
bao_io_client_destroy() on itself.
Inside this call, it invokes kthread_stop() on its own thread context, which
blocks waiting for the thread to exit.
[ ... ]
> + down_write(&dm->io_clients_lock);
> + if (client->is_control)
> + dm->control_client = NULL;
> + else
> + dm->ioeventfd_client = NULL;
> +
> + list_del(&client->list);
> + up_write(&dm->io_clients_lock);
> +
> + bao_io_dispatcher_resume(dm);
> +
> + kfree(client);
> +}
[Severity: Medium]
Is there a memory leak here? When the client is destroyed, its range_list is
cleaned up, but it does not iterate over the virtio_requests list to free
pending struct bao_io_request items before calling kfree(client).
> +int bao_io_client_attach(struct bao_io_client *client)
> +{
> + if (WARN_ON_ONCE(!client))
> + return -EINVAL;
> +
> + if (client->is_control) {
> + wait_event_interruptible(client->wq,
> + bao_io_client_has_pending_requests(client) ||
> + bao_io_client_is_destroying(client));
> + if (bao_io_client_is_destroying(client))
> + return -EPERM;
[Severity: Medium]
Does this mishandle signals? The wait_event_interruptible() macro returns
-ERESTARTSYS if interrupted by a signal, but this return value is ignored.
As a result, normal signal interruptions cause bao_dm_ioctl() to attempt to
pop an empty queue, failing and returning a spurious -EFAULT to userspace.
> --- a/drivers/virt/bao/io-dispatcher/io_dispatcher.c
> +++ b/drivers/virt/bao/io-dispatcher/io_dispatcher.c
> @@ -25,6 +25,156 @@ struct bao_io_dispatcher_work {
> +int bao_dispatch_io(struct bao_dm *dm)
> +{
> + struct bao_io_client *client;
> + struct bao_remio_hypercall_ctx ctx;
> + struct bao_virtio_request req;
[ ... ]
> + if (!bao_io_client_push_request(client, &req)) {
> + up_read(&dm->io_clients_lock);
> + return -EINVAL;
> + }
[Severity: Medium]
Are requests silently discarded here if bao_io_client_push_request() fails?
If memory exhaustion prevents the allocation, the request fetched from the
hypervisor is dropped, permanently hanging the frontend guest waiting for
I/O completion.
> --- a/drivers/virt/bao/io-dispatcher/ioeventfd.c
> +++ b/drivers/virt/bao/io-dispatcher/ioeventfd.c
> @@ -201,6 +201,122 @@ static int bao_ioeventfd_deassign(struct bao_dm *dm,
> + list_for_each_entry(p, &dm->ioeventfds, list) {
> + if (p->eventfd != eventfd)
> + continue;
> +
> + bao_io_client_range_del(dm->ioeventfd_client, p->addr,
> + p->addr + p->length - 1);
> +
> + bao_ioeventfd_shutdown(dm, p);
> + break;
> + }
[Severity: Medium]
Does this deassignment loop match solely on the eventfd context and ignore
the requested memory address range? If userspace registers multiple memory
ranges to trigger the same eventfd, this will arbitrarily delete the first
matching entry rather than the specific range requested by userspace.
[ ... ]
> +int bao_ioeventfd_client_config(struct bao_dm *dm, struct bao_ioeventfd *config)
> +{
> + if (WARN_ON_ONCE(!dm || !config))
> + return -EINVAL;
> +
> + if (config->flags & BAO_IOEVENTFD_FLAG_DEASSIGN)
> + bao_ioeventfd_deassign(dm, config);
> +
> + return bao_ioeventfd_assign(dm, config);
> +}
[Severity: High]
Is a return statement missing here? Because there is no return after calling
bao_ioeventfd_deassign(), execution falls through to bao_ioeventfd_assign(),
immediately recreating the ioeventfd that was just destroyed.
> --- a/drivers/virt/bao/io-dispatcher/irqfd.c
> +++ b/drivers/virt/bao/io-dispatcher/irqfd.c
> @@ -34,6 +34,280 @@ struct irqfd {
> +static void bao_irqfd_shutdown(struct irqfd *irqfd)
> +{
> + u64 cnt;
> +
> + if (WARN_ON_ONCE(!irqfd || !irqfd->dm))
> + return;
> +
> + lockdep_assert_held(&irqfd->dm->irqfds_lock);
> +
> + list_del_init(&irqfd->list);
> +
> + eventfd_ctx_remove_wait_queue(irqfd->eventfd, &irqfd->wait, &cnt);
> +
> + eventfd_ctx_put(irqfd->eventfd);
> +
> + kfree(irqfd);
> +}
[Severity: High]
Is it safe to free the irqfd here without cancelling the asynchronous shutdown
work? If userspace closes the eventfd concurrently, a POLLHUP event queues
the shutdown work.
The irqfd is then freed, and when the workqueue executes irqfd_shutdown_work(),
it dereferences the freed memory to access irqfd->dm.
[ ... ]
> +static int bao_irqfd_assign(struct bao_dm *dm, struct bao_irqfd *args)
> +{
> + struct eventfd_ctx *eventfd = NULL;
> + struct irqfd *irqfd;
[ ... ]
> + list_add_tail(&irqfd->list, &dm->irqfds);
> + mutex_unlock(&dm->irqfds_lock);
> +
> + events = vfs_poll(fd_file(f), &irqfd->pt);
[Severity: High]
Could this cause a use-after-free race? The newly created irqfd is published
to the globally visible dm->irqfds list, and the lock is dropped before
calling vfs_poll().
A concurrent thread calling deassign can remove and free this irqfd while
vfs_poll() proceeds to access the freed &irqfd->pt.
[ ... ]
> +void bao_irqfd_server_destroy(struct bao_dm *dm)
> +{
> + struct irqfd *irqfd;
> + struct irqfd *next;
> +
> + if (WARN_ON_ONCE(!dm))
> + return;
> +
> + if (dm->irqfd_server)
> + destroy_workqueue(dm->irqfd_server);
> +
> + mutex_lock(&dm->irqfds_lock);
> + list_for_each_entry_safe(irqfd, next, &dm->irqfds, list)
> + bao_irqfd_shutdown(irqfd);
> + mutex_unlock(&dm->irqfds_lock);
> +}
[Severity: High]
Can this lead to a use-after-free on the irqfd workqueue? Calling
destroy_workqueue() before shutting down the active eventfds leaves their
wait queues active.
If an eventfd receives an event in this window, bao_irqfd_wakeup() will execute
queue_work() on the destroyed and freed workqueue pointer.
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1786010512.git.jpeixoto@osyx.tech?part=4
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [RFC PATCH v3 2/6] virt: bao: add IPC shared-memory driver
2026-08-07 7:39 ` [RFC PATCH v3 2/6] virt: bao: add IPC shared-memory driver João Peixoto
@ 2026-08-07 7:56 ` sashiko-bot
0 siblings, 0 replies; 11+ messages in thread
From: sashiko-bot @ 2026-08-07 7:56 UTC (permalink / raw)
To: João Peixoto; +Cc: robh, conor+dt, devicetree
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [High] Unbinding the device while the misc character device is open causes a Use-After-Free of the driver context and unmapped memory access.
- [High] Missing memory barrier before notifying the peer guest via hypercall, causing potential data corruption due to stale memory observation.
- [High] Missing write-protection enforcement allows userspace to mmap the read region with write permissions, triggering a hypervisor stage-2 fault.
- [Medium] The mmap offset calculation uses `unsigned long`, which overflows on 32-bit architectures, contradicting the author's intent to fix u32 overflows.
- [Medium] The mmap logic fails entirely for the write region if the read region's size is not page-aligned.
- [Medium] Missing `.llseek` operation prevents userspace from resetting the file position, allowing the device to be read or written only once per open.
- [Low] The device label string buffer is too small to hold the maximum device ID, leading to truncation and potential naming collisions.
- [Low] Missing architecture dependencies in Kconfig will cause build failures on unsupported architectures.
--
--- Patch [2]: [RFC PATCH v3 2/6] virt: bao: add IPC shared-memory driver ---
commit 79e4d2a70a45b49aa525420ee0e59987417ec11f
Author: João Peixoto <jpeixoto@osyx.tech>
Subject: virt: bao: add IPC shared-memory driver
Adds a driver for guests running on the Bao static-partitioning hypervisor
to communicate through shared memory. Userspace accesses the regions through
a misc character device using read(), write(), and mmap(), where a write()
notifies the peer guest through an architecture-specific hypercall.
> diff --git a/drivers/virt/bao/ipcshmem/Kconfig b/drivers/virt/bao/ipcshmem/Kconfig
> --- /dev/null
> +++ b/drivers/virt/bao/ipcshmem/Kconfig
> @@ -0,0 +1,10 @@
> +# SPDX-License-Identifier: GPL-2.0
> +config BAO_SHMEM
> + tristate "Bao hypervisor shared memory support"
[Severity: Low]
Does this config need an architecture dependency?
Building this on an unsupported architecture like x86 (e.g., via
allmodconfig) might fail because it unconditionally includes <asm/bao.h>
which only exists for ARM, ARM64, and RISC-V.
[ ... ]
> diff --git a/drivers/virt/bao/ipcshmem/ipcshmem.c b/drivers/virt/bao/ipcshmem/ipcshmem.c
> --- /dev/null
> +++ b/drivers/virt/bao/ipcshmem/ipcshmem.c
[ ... ]
> +static int bao_ipcshmem_mmap(struct file *filp, struct vm_area_struct *vma)
> +{
> + struct bao_ipcshmem *bao = filp->private_data;
> + unsigned long vsize = vma->vm_end - vma->vm_start;
> + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
[Severity: Medium]
Will this offset calculation overflow on 32-bit architectures?
Since unsigned long is 32 bits there, an offset larger than 4GB might cause
an overflow. This seems to contradict the commit message's goal to avoid u32
overflows.
> + phys_addr_t region_phys;
> + size_t region_size;
> +
> + if (!vsize)
> + return -EINVAL;
> +
> + /*
> + * The read region is exposed at offset 0 and the write region right
> + * after it. A single mapping cannot span both regions, since they are
> + * not guaranteed to be physically contiguous.
> + */
> + if (offset < bao->read_size) {
> + region_phys = bao->read_phys;
> + region_size = bao->read_size;
[Severity: High]
Can a user map the read region with write permissions here?
If userspace opens the device O_RDWR and maps this region with PROT_WRITE,
the kernel will create writable PTEs because vma->vm_page_prot is used
directly during remap_pfn_range().
If the hypervisor maps the read region as read-only at Stage-2, a subsequent
write from userspace could trigger a Stage-2 permission fault and crash the
guest VM. Does this need a check to enforce !(vma->vm_flags & VM_WRITE)?
> + } else if (offset < bao->read_size + bao->write_size) {
> + offset -= bao->read_size;
[Severity: Medium]
What happens if the device tree specifies a read_size that is not
page-aligned?
Subtracting an unaligned read_size from a page-aligned offset would result
in an unaligned offset. This would then cause the PAGE_ALIGNED(region_phys)
check below to fail unconditionally, preventing userspace from mapping the
write region.
> + region_phys = bao->write_phys;
> + region_size = bao->write_size;
> + } else {
> + return -EINVAL;
> + }
[ ... ]
> +static ssize_t bao_ipcshmem_write(struct file *filp, const char __user *buf,
> + size_t count, loff_t *ppos)
> +{
[ ... ]
> + if (copy_from_user(bao->write_base + *ppos, buf, count))
> + return -EFAULT;
> +
> + *ppos += count;
> +
> + /* Notify Bao hypervisor */
> + bao_ipcshmem_hypercall(BAO_IPCSHMEM_HYPERCALL_ID, bao->id);
[Severity: High]
Is a memory barrier needed here before notifying the peer guest?
The hypercall provides a Context Synchronization Event, but it does not
guarantee that prior memory stores to the MEMREMAP_WB (cacheable) memory are
globally visible.
Could the peer guest observe stale data if the local CPU's store buffer
hasn't drained before the hypervisor wakes it up?
> + return count;
> +}
[ ... ]
> +static const struct file_operations bao_ipcshmem_fops = {
> + .owner = THIS_MODULE,
> + .read = bao_ipcshmem_read,
> + .write = bao_ipcshmem_write,
> + .mmap = bao_ipcshmem_mmap,
> + .open = bao_ipcshmem_open,
> + .release = bao_ipcshmem_release,
> +};
[Severity: Medium]
Does this file operations structure need an .llseek callback (such as
default_llseek)?
Without it, the VFS will clear the FMODE_LSEEK flag during open(). Any
subsequent lseek() calls will return -ESPIPE. Once a read() or write() hits
the end of the shared memory region, userspace might be completely unable to
rewind the offset without closing and reopening the character device.
> +static int bao_ipcshmem_probe(struct platform_device *pdev)
> +{
[ ... ]
> + bao = devm_kzalloc(dev, sizeof(*bao), GFP_KERNEL);
> + if (!bao)
> + return -ENOMEM;
> +
> + bao->read_base = devm_memremap(dev, read_res->start,
> + resource_size(read_res), MEMREMAP_WB);
> + if (IS_ERR(bao->read_base))
> + return PTR_ERR(bao->read_base);
[ ... ]
> + bao->id = id;
> + bao->read_phys = read_res->start;
> + bao->read_size = resource_size(read_res);
[Severity: Medium]
Will resource_size() be truncated here on 32-bit systems with LPAE?
The resource_size() helper returns a 64-bit resource_size_t, but read_size
(and write_size) are defined as size_t in the bao_ipcshmem struct, which
will be 32 bits on those platforms.
> + bao->write_phys = write_res->start;
> + bao->write_size = resource_size(write_res);
> +
> + scnprintf(bao->label, BAO_IPCSHMEM_NAME_LEN, "baoipc%u", id);
[Severity: Low]
Can this label string be truncated?
If the bao,id from the device tree is a 10-digit number (e.g., 4294967295),
the formatted string requires 17 bytes including the null terminator. Since
BAO_IPCSHMEM_NAME_LEN is 16, the last digit could be truncated, potentially
leading to naming collisions.
[ ... ]
> +static void bao_ipcshmem_remove(struct platform_device *pdev)
> +{
> + struct bao_ipcshmem *bao = platform_get_drvdata(pdev);
> +
> + misc_deregister(&bao->miscdev);
> +}
[Severity: High]
Can this result in a use-after-free if the driver is unbound while the
character device is still open?
While misc_deregister() removes the device, it does not revoke existing open
file descriptors. When bao_ipcshmem_remove() returns, devres will
automatically free the bao structure and unmap the MEMREMAP_WB regions.
If a user process subsequently calls read, write, or mmap on the open file
descriptor, could it dereference the freed bao structure and access unmapped
memory?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1786010512.git.jpeixoto@osyx.tech?part=2
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-08-07 7:56 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-07 7:39 [RFC PATCH v3 0/6] virt: bao: Add Bao hypervisor IPC and I/O dispatcher drivers João Peixoto
2026-08-07 7:39 ` [RFC PATCH v3 1/6] dt-bindings: bao: add IPC shared-memory device João Peixoto
2026-08-07 7:45 ` sashiko-bot
2026-08-07 7:39 ` [RFC PATCH v3 2/6] virt: bao: add IPC shared-memory driver João Peixoto
2026-08-07 7:56 ` sashiko-bot
2026-08-07 7:39 ` [RFC PATCH v3 3/6] dt-bindings: bao: add I/O dispatcher device João Peixoto
2026-08-07 7:39 ` [RFC PATCH v3 4/6] virt: bao: add I/O dispatcher driver João Peixoto
2026-08-07 7:54 ` sashiko-bot
2026-08-07 7:39 ` [RFC PATCH v3 5/6] virt: bao: consolidate the IPC hypercall ID in include/linux/bao.h João Peixoto
2026-08-07 7:50 ` sashiko-bot
2026-08-07 7:39 ` [RFC PATCH v3 6/6] MAINTAINERS: add Bao hypervisor entry João Peixoto
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox