* [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator
@ 2025-08-28 23:53 dmukhin
2025-08-28 23:53 ` [PATCH v5 01/15] emul/vuart: introduce framework for UART emulators dmukhin
` (14 more replies)
0 siblings, 15 replies; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:53 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
x86 port of Xen lacks vUART facility similar to Arm's vpl011 to support x86
guest OS bring up in the embedded setups.
This patch series introduces initial in-hypervisor emulator for
NS8250/NS16x50-compatible UARTs under CONFIG_VUART_NS16X50.
In parallel domain creation scenario (hyperlaunch), NS16550 emulator helps
early guest firmware and OS bringup debugging, because it eliminates
dependency on the external emulator (qemu) being operational by the time
domains are created.
The emulator also allows to forward the physical console input to the x86
domain which is useful when a system has only one physical UART for early
debugging and this UART is owned by Xen.
By default, CONFIG_VUART_NS16X50 enables emulation of NS16550 at I/O port
0x2f8, IRQ#3 in guest OS (legacy COM2). Legacy COM resources cannot be
selected at built-time or via per-domain xl configuration in this initial
submission.
CONFIG_VUART_NS16X50_DEBUG enables some extra debugging facilities useful
for NS16550 emulator development/debugging (disabled by default).
The NS16550 emulator is disabled in default x86 configuration and goes under
CONFIG_EXPERT in Kconfig.
Limitations
===========
- Only x86;
- Only HVM domains support (build-time), PVH domains are not supported yet;
- Only legacy COM{1,2,3,4} resources via Kconfig, custom I/O ports/IRQs
are not supported;
- Only Xen console as a backend, no inter-domain communication (similar to
vpl011 on Arm);
- Only 8n1 emulation (8-bit data, no parity, 1 stop bit);
- No toolstack integration;
- No baud rate emulation (reports 115200 baud to the guest OS);
- No FIFO-less mode emulation;
- No RX FIFO interrupt moderation (FCR) emulation;
- No integration w/ VM snapshotting (HVM_REGISTER_SAVE_RESTORE() and
friends);
- No ISA IRQ sharing allowed;
- No MMIO-based UART emulation.
Series
======
Patch 1 introduces the new vUART framework, that is the code originally
posted here:
https://lore.kernel.org/xen-devel/20250624035443.344099-16-dmukhin@ford.com/
Required for emulator.
Patch 2 adds missing NS16550 definitions, required for emulator.
Patch 3 introduces the basic emulator skeleton - state machine
initialization stubs, I/O port handler stub, logging, etc.
Patches 4-10 incrementally populate the minimal NS16550 register emulation.
Patch 11 hooks vUART to console driver and enables physical console intput
forwarding.
Patch 12 hooks vUART state debugging (disabled by default).
Pathes 13-15 introduce necessary changes to enable NS16550 on dom0 (and PVH).
Link to CI: https://gitlab.com/xen-project/people/dmukhin/xen/-/pipelines/2009685553
Link to branch: https://gitlab.com/xen-project/people/dmukhin/xen/-/tree/vuart-ns8250-v5?ref_type=heads
Testing
=======
```shell
echo CONFIG_EXPERT=y >> .config
echo CONFIG_VUART_NS16X50=y >> .config
make olddefconfig
```
COM2 (0x2f8) resources are used by default.
To test w/ virtual COM2, the guest kernel parameters should contain
something like the following:
earlycon=uart,io,0x2f8,115200n8 console=uart,io,0x2f8,115200n8
HVM
---
Tested only boot of HVM linux guest with OVMF as the virtual firmware.
SeaBIOS as a virtual firmware is not tested.
PVH (dom0)
----------
Xen is able to forward physical console input to the domain with virtual
NS16550. To switch the console focus press Ctrl+aaa.
Switch is limited on x86 to dom0 and Xen (fixes pending).
With COM2, Linux kernel complains nobody cared about IRQ#3. Likely that's
because guest DSDT needs patching, which is a follow on change, but not
critical for demonstration; the code is disabled in default config.
Changes since v4:
- Split the series to make it simpler to review.
- Addressed feedback from v4.
- Dropped xl changes, which I will submit separately.
- Link to v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-1-dmukhin@ford.com/
Changes since v3:
- Reduced the blast radius of the series, thanks to reviews, individual
aspects (like console focus) touched in v3 moved to separate threads.
- Kept the UART emulator framework since I need to redo some of emulator code
and there's more-or-less agreement on it (where to place, naming, scope).
- Applied the feedback from
https://lore.kernel.org/xen-devel/20250624035443.344099-1-dmukhin@ford.com/
- Link to v3: https://lore.kernel.org/xen-devel/20250103-vuart-ns8250-v3-v1-0-c5d36b31d66c@ford.com/
Changes since v2:
- renamed emulator s/NS8250/NS16550/g
- reduced the patch series after addressing v2 feedback
- introduced driver framework for UART emulators
- unified guest OS printouts across all available UART emulators
- Link to v2: https://lore.kernel.org/xen-devel/20241205-vuart-ns8250-v1-0-e9aa923127eb@ford.com/
Changes since v1:
- dropped kmalloc/kfree aliases
- fixed ECLAIR jobs (thanks Andrew Cooper)
- addressed console forwarding on arm32 and arm64 (thanks to Luca Fancellu)
- moved NS8250 debugging stubs into its own patch
- added fix for https://gitlab.com/xen-project/xen/-/issues/184
- Link to v1: https://lore.kernel.org/r/20241126-vuart-ns8250-v1-v1-0-87b9a8375b7a@ford.com
Denis Mukhin (15):
emul/vuart: introduce framework for UART emulators
xen/8250-uart: update definitions
emul/ns16x50: implement emulator stub
emul/ns16x50: implement DLL/DLM registers
emul/ns16x50: implement EIR/IIR registers
emul/ns16x50: implement THR/RBR registers
emul/ns16x50: implement FCR register (write-only)
emul/ns16x50: implement LCR/LSR registers
emul/ns16x50: implement MCR/MSR registers
emul/ns16x50: implement SCR register
emul/ns16x50: implement put_rx() hook
emul/ns16550: implement dump_state() hook
x86/domain: enable per-domain I/O port bitmaps
xen/domain: allocate d->irq_caps before arch-specific initialization
emul/ns16x50: implement IRQ emulation via vIOAPIC
xen/arch/arm/xen.lds.S | 1 +
xen/arch/ppc/xen.lds.S | 1 +
xen/arch/riscv/xen.lds.S | 1 +
xen/arch/x86/Makefile | 1 +
xen/arch/x86/dom0_build.c | 112 +--
xen/arch/x86/domain.c | 2 +-
xen/arch/x86/hvm/dom0_build.c | 7 +
xen/arch/x86/hvm/hvm.c | 55 +-
xen/arch/x86/hvm/nestedhvm.c | 8 +-
xen/arch/x86/hvm/quirks.c | 3 -
xen/arch/x86/hvm/svm/nestedsvm.c | 2 +-
xen/arch/x86/hvm/vioapic.c | 10 +
xen/arch/x86/hvm/vmx/vvmx.c | 4 +-
xen/arch/x86/include/asm/hvm/nestedhvm.h | 3 +-
xen/arch/x86/include/asm/hvm/support.h | 2 -
xen/arch/x86/include/asm/iocap.h | 2 +
xen/arch/x86/include/asm/irq.h | 5 +-
xen/arch/x86/ioport.c | 163 ++++
xen/arch/x86/irq.c | 12 +-
xen/arch/x86/physdev.c | 8 +-
xen/arch/x86/pv/dom0_build.c | 7 +
xen/arch/x86/xen.lds.S | 1 +
xen/common/Kconfig | 2 +
xen/common/Makefile | 1 +
xen/common/domain.c | 8 +-
xen/common/emul/Kconfig | 6 +
xen/common/emul/Makefile | 1 +
xen/common/emul/vuart/Kconfig | 24 +
xen/common/emul/vuart/Makefile | 2 +
xen/common/emul/vuart/ns16x50.c | 976 +++++++++++++++++++++++
xen/common/emul/vuart/vuart.c | 156 ++++
xen/common/keyhandler.c | 3 +
xen/drivers/char/console.c | 6 +-
xen/drivers/char/ns16550.c | 16 +-
xen/drivers/passthrough/x86/hvm.c | 13 +-
xen/include/xen/8250-uart.h | 50 +-
xen/include/xen/sched.h | 4 +
xen/include/xen/serial.h | 3 +
xen/include/xen/vuart.h | 116 +++
xen/include/xen/xen.lds.h | 10 +
40 files changed, 1624 insertions(+), 183 deletions(-)
create mode 100644 xen/arch/x86/ioport.c
create mode 100644 xen/common/emul/Kconfig
create mode 100644 xen/common/emul/Makefile
create mode 100644 xen/common/emul/vuart/Kconfig
create mode 100644 xen/common/emul/vuart/Makefile
create mode 100644 xen/common/emul/vuart/ns16x50.c
create mode 100644 xen/common/emul/vuart/vuart.c
create mode 100644 xen/include/xen/vuart.h
--
2.51.0
^ permalink raw reply [flat|nested] 46+ messages in thread
* [PATCH v5 01/15] emul/vuart: introduce framework for UART emulators
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
@ 2025-08-28 23:53 ` dmukhin
2025-08-29 19:27 ` Stefano Stabellini
2025-08-28 23:53 ` [PATCH v5 02/15] xen/8250-uart: update definitions dmukhin
` (13 subsequent siblings)
14 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:53 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
Introduce a driver framework to abstract UART emulators in the hypervisor.
That allows for architecture-independent handling of virtual UARTs in the
console driver and simplifies enabling new UART emulators.
The framework is built under CONFIG_VUART_FRAMEWORK, which will be
automatically enabled once the user enables any UART emulator.
Current implementation supports maximum of one vUART of each kind per domain.
Use new domain_has_vuart() in the console driver code to check whether to
forward console input to the domain using vUART.
Enable console forwarding over vUART for hardware domains with a vUART. That
enables console forwarding to dom0 on x86, since console can be forwarded only
to Xen, dom0 and pvshim on x86 as of now.
Note: existing vUARTs are deliberately *not* hooked to the new framework to
minimize the scope of the patch: vpl011 (i.e. SBSA) emulator and "vuart" (i.e.
minimalistic MMIO-mapped dtuart for hwdoms on Arm) are kept unmodified.
No functional changes for non-x86 architectures.
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- addressed feedback
- Link to v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-3-dmukhin@ford.com/
---
xen/arch/arm/xen.lds.S | 1 +
xen/arch/ppc/xen.lds.S | 1 +
xen/arch/riscv/xen.lds.S | 1 +
xen/arch/x86/xen.lds.S | 1 +
xen/common/Kconfig | 2 +
xen/common/Makefile | 1 +
xen/common/emul/Kconfig | 6 ++
xen/common/emul/Makefile | 1 +
xen/common/emul/vuart/Kconfig | 6 ++
xen/common/emul/vuart/Makefile | 1 +
xen/common/emul/vuart/vuart.c | 156 +++++++++++++++++++++++++++++++++
xen/common/keyhandler.c | 3 +
xen/drivers/char/console.c | 6 +-
xen/include/xen/serial.h | 3 +
xen/include/xen/vuart.h | 116 ++++++++++++++++++++++++
xen/include/xen/xen.lds.h | 10 +++
16 files changed, 314 insertions(+), 1 deletion(-)
create mode 100644 xen/common/emul/Kconfig
create mode 100644 xen/common/emul/Makefile
create mode 100644 xen/common/emul/vuart/Kconfig
create mode 100644 xen/common/emul/vuart/Makefile
create mode 100644 xen/common/emul/vuart/vuart.c
create mode 100644 xen/include/xen/vuart.h
diff --git a/xen/arch/arm/xen.lds.S b/xen/arch/arm/xen.lds.S
index db17ff1efa98..cd05b18770f4 100644
--- a/xen/arch/arm/xen.lds.S
+++ b/xen/arch/arm/xen.lds.S
@@ -58,6 +58,7 @@ SECTIONS
*(.rodata)
*(.rodata.*)
VPCI_ARRAY
+ VUART_ARRAY
*(.data.rel.ro)
*(.data.rel.ro.*)
diff --git a/xen/arch/ppc/xen.lds.S b/xen/arch/ppc/xen.lds.S
index 1de0b77fc6b9..f9d4e5b0dcd8 100644
--- a/xen/arch/ppc/xen.lds.S
+++ b/xen/arch/ppc/xen.lds.S
@@ -52,6 +52,7 @@ SECTIONS
*(.rodata)
*(.rodata.*)
VPCI_ARRAY
+ VUART_ARRAY
*(.data.rel.ro)
*(.data.rel.ro.*)
diff --git a/xen/arch/riscv/xen.lds.S b/xen/arch/riscv/xen.lds.S
index edcadff90bfe..59dcaa5fef9a 100644
--- a/xen/arch/riscv/xen.lds.S
+++ b/xen/arch/riscv/xen.lds.S
@@ -47,6 +47,7 @@ SECTIONS
*(.rodata)
*(.rodata.*)
VPCI_ARRAY
+ VUART_ARRAY
*(.data.rel.ro)
*(.data.rel.ro.*)
diff --git a/xen/arch/x86/xen.lds.S b/xen/arch/x86/xen.lds.S
index 966e514f2034..d877b93a6964 100644
--- a/xen/arch/x86/xen.lds.S
+++ b/xen/arch/x86/xen.lds.S
@@ -132,6 +132,7 @@ SECTIONS
*(.rodata)
*(.rodata.*)
VPCI_ARRAY
+ VUART_ARRAY
*(.data.rel.ro)
*(.data.rel.ro.*)
diff --git a/xen/common/Kconfig b/xen/common/Kconfig
index 76f9ce705f7a..78a32b69e2b2 100644
--- a/xen/common/Kconfig
+++ b/xen/common/Kconfig
@@ -676,4 +676,6 @@ config PM_STATS
Enable collection of performance management statistics to aid in
analyzing and tuning power/performance characteristics of the system
+source "common/emul/Kconfig"
+
endmenu
diff --git a/xen/common/Makefile b/xen/common/Makefile
index c316957fcb36..c0734480ee4b 100644
--- a/xen/common/Makefile
+++ b/xen/common/Makefile
@@ -11,6 +11,7 @@ obj-$(filter-out $(CONFIG_X86),$(CONFIG_ACPI)) += device.o
obj-$(CONFIG_DEVICE_TREE_PARSE) += device-tree/
obj-$(CONFIG_IOREQ_SERVER) += dm.o
obj-y += domain.o
+obj-y += emul/
obj-y += event_2l.o
obj-y += event_channel.o
obj-$(CONFIG_EVTCHN_FIFO) += event_fifo.o
diff --git a/xen/common/emul/Kconfig b/xen/common/emul/Kconfig
new file mode 100644
index 000000000000..7c6764d1756b
--- /dev/null
+++ b/xen/common/emul/Kconfig
@@ -0,0 +1,6 @@
+menu "Domain Emulation Features"
+ visible if EXPERT
+
+source "common/emul/vuart/Kconfig"
+
+endmenu
diff --git a/xen/common/emul/Makefile b/xen/common/emul/Makefile
new file mode 100644
index 000000000000..ae0b575c3901
--- /dev/null
+++ b/xen/common/emul/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_VUART_FRAMEWORK) += vuart/
diff --git a/xen/common/emul/vuart/Kconfig b/xen/common/emul/vuart/Kconfig
new file mode 100644
index 000000000000..ce1b976b7da7
--- /dev/null
+++ b/xen/common/emul/vuart/Kconfig
@@ -0,0 +1,6 @@
+config VUART_FRAMEWORK
+ bool
+
+menu "UART Emulation"
+
+endmenu
diff --git a/xen/common/emul/vuart/Makefile b/xen/common/emul/vuart/Makefile
new file mode 100644
index 000000000000..97f792dc6641
--- /dev/null
+++ b/xen/common/emul/vuart/Makefile
@@ -0,0 +1 @@
+obj-y += vuart.o
diff --git a/xen/common/emul/vuart/vuart.c b/xen/common/emul/vuart/vuart.c
new file mode 100644
index 000000000000..7b277d00d5c7
--- /dev/null
+++ b/xen/common/emul/vuart/vuart.c
@@ -0,0 +1,156 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * UART emulator framework.
+ *
+ * Copyright 2025 Ford Motor Company
+ */
+
+#include <xen/err.h>
+#include <xen/sched.h>
+#include <xen/vuart.h>
+#include <xen/xvmalloc.h>
+
+#define for_each_emulator(e) \
+ for ( e = vuart_array_start; e < vuart_array_end; e++ )
+
+extern const struct vuart_emulator vuart_array_start[];
+extern const struct vuart_emulator vuart_array_end[];
+
+static const struct vuart_emulator *
+vuart_match_by_compatible(struct domain *d, const char *compat)
+{
+ const struct vuart_emulator *emulator;
+
+ if ( d->console.vuart )
+ return NULL;
+
+ for_each_emulator(emulator)
+ if ( emulator->compatible &&
+ !strncmp(emulator->compatible, compat,
+ strlen(emulator->compatible)) )
+ return emulator;
+
+ return NULL;
+}
+
+static struct vuart *vuart_find_by_console_permission(const struct domain *d)
+{
+ struct vuart *vuart = d->console.vuart;
+
+ ASSERT(d->console.input_allowed);
+
+ if ( !vuart || !vuart->emulator || !vuart->emulator->put_rx ||
+ !(vuart->flags & VUART_CONSOLE_INPUT))
+ return NULL;
+
+ return vuart;
+}
+
+struct vuart *vuart_find_by_io_range(struct domain *d, unsigned long addr,
+ unsigned long size)
+{
+ struct vuart *vuart = d->console.vuart;
+
+ if ( !vuart || !vuart->info )
+ return NULL;
+
+ if ( addr >= vuart->info->base_addr &&
+ addr + size - 1 <= vuart->info->base_addr + vuart->info->size - 1 )
+ return vuart;
+
+ return NULL;
+}
+
+int vuart_init(struct domain *d, struct vuart_info *info)
+{
+ const struct vuart_emulator *emulator;
+ struct vuart *vuart;
+ int rc;
+
+ emulator = vuart_match_by_compatible(d, info->compatible);
+ if ( !emulator )
+ return -ENODEV;
+
+ vuart = xzalloc(typeof(*vuart));
+ if ( !vuart )
+ return -ENOMEM;
+
+ vuart->info = xvzalloc(typeof(*info));
+ if ( !vuart->info )
+ {
+ rc = -ENOMEM;
+ goto err_out;
+ }
+ memcpy(vuart->info, info, sizeof(*info));
+
+ vuart->vdev = emulator->alloc(d, vuart->info);
+ if ( IS_ERR(vuart->vdev) )
+ {
+ rc = PTR_ERR(vuart->vdev);
+ goto err_out;
+ }
+
+ vuart->emulator = emulator;
+ vuart->owner = d;
+ vuart->flags |= VUART_CONSOLE_INPUT;
+
+ d->console.input_allowed = true;
+ d->console.vuart = vuart;
+
+ return 0;
+
+ err_out:
+ XVFREE(vuart);
+ return rc;
+}
+
+/*
+ * Release any resources taken by UART emulators.
+ *
+ * NB: no flags are cleared, since currently exit() is called only during
+ * domain destroy.
+ */
+void vuart_deinit(struct domain *d)
+{
+ struct vuart *vuart = d->console.vuart;
+
+ if ( vuart )
+ {
+ vuart->emulator->free(vuart);
+ XVFREE(vuart->info);
+ }
+
+ XVFREE(d->console.vuart);
+}
+
+void vuart_dump_state(const struct domain *d)
+{
+ struct vuart *vuart = d->console.vuart;
+
+ if ( vuart )
+ vuart->emulator->dump_state(vuart);
+}
+
+/*
+ * Put character to the *first* suitable emulated UART's FIFO.
+ */
+int vuart_put_rx(struct domain *d, char c)
+{
+ struct vuart *vuart = vuart_find_by_console_permission(d);
+
+ return vuart ? vuart->emulator->put_rx(vuart, c) : -ENODEV;
+}
+
+bool domain_has_vuart(const struct domain *d)
+{
+ return vuart_find_by_console_permission(d);
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/common/keyhandler.c b/xen/common/keyhandler.c
index cb6df2823b00..156e64d9eb58 100644
--- a/xen/common/keyhandler.c
+++ b/xen/common/keyhandler.c
@@ -22,6 +22,7 @@
#include <xen/mm.h>
#include <xen/watchdog.h>
#include <xen/init.h>
+#include <xen/vuart.h>
#include <asm/div64.h>
static unsigned char keypress_key;
@@ -352,6 +353,8 @@ static void cf_check dump_domains(unsigned char key)
v->periodic_period / 1000000);
}
}
+
+ vuart_dump_state(d);
}
for_each_domain ( d )
diff --git a/xen/drivers/char/console.c b/xen/drivers/char/console.c
index 9bd5b4825da6..d5164897a776 100644
--- a/xen/drivers/char/console.c
+++ b/xen/drivers/char/console.c
@@ -33,6 +33,7 @@
#include <asm/setup.h>
#include <xen/sections.h>
#include <xen/consoled.h>
+#include <xen/vuart.h>
#ifdef CONFIG_X86
#include <asm/guest.h>
@@ -596,11 +597,12 @@ static void __serial_rx(char c)
if ( !d )
return;
- if ( is_hardware_domain(d) )
+ if ( is_hardware_domain(d) && !domain_has_vuart(d) )
{
/*
* Deliver input to the hardware domain buffer, unless it is
* already full.
+ * NB: must be the first check: hardware domain may have emulated UART.
*/
if ( (serial_rx_prod - serial_rx_cons) != SERIAL_RX_SIZE )
serial_rx_ring[SERIAL_RX_MASK(serial_rx_prod++)] = c;
@@ -611,6 +613,8 @@ static void __serial_rx(char c)
*/
send_global_virq(VIRQ_CONSOLE);
}
+ else if ( domain_has_vuart(d) )
+ rc = vuart_put_rx(d, c);
#ifdef CONFIG_SBSA_VUART_CONSOLE
else
/* Deliver input to the emulated UART. */
diff --git a/xen/include/xen/serial.h b/xen/include/xen/serial.h
index 8e1844555208..d7e81f098359 100644
--- a/xen/include/xen/serial.h
+++ b/xen/include/xen/serial.h
@@ -36,6 +36,9 @@ struct vuart_info {
unsigned long data_off; /* Data register offset */
unsigned long status_off; /* Status register offset */
unsigned long status; /* Ready status value */
+ unsigned int irq; /* Interrupt */
+ const char *compatible; /* Compatible string */
+ const char *name; /* User-friendly name */
};
struct serial_port {
diff --git a/xen/include/xen/vuart.h b/xen/include/xen/vuart.h
new file mode 100644
index 000000000000..ca025b4179be
--- /dev/null
+++ b/xen/include/xen/vuart.h
@@ -0,0 +1,116 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * UART emulator framework.
+ *
+ * Copyright 2025 Ford Motor Company
+ */
+
+#ifndef XEN_VUART_H
+#define XEN_VUART_H
+
+#include <xen/serial.h>
+#include <public/xen.h>
+
+struct vuart_emulator;
+
+enum {
+ VUART_CONSOLE_INPUT = 0U << 1, /** Physical console input forwarding. */
+};
+
+
+/*
+ * FIXME: #ifdef is temporary to avoid clash with
+ * arch/arm/include/asm/domain.h
+ */
+#ifdef CONFIG_VUART_FRAMEWORK
+struct vuart {
+ const struct vuart_emulator *emulator;
+ struct vuart_info *info;
+ struct domain *owner;
+ uint32_t flags;
+ void *vdev;
+};
+#endif
+
+struct vuart_emulator {
+ /* UART compatible string. Cannot be NULL or empty. */
+ const char *compatible;
+
+ /*
+ * Allocate emulated UART state (RX/TX FIFOs, locks, initialize registers,
+ * hook I/O handlers, etc.)
+ * Cannot be NULL.
+ */
+ void *(*alloc)(struct domain *d, const struct vuart_info *info);
+
+ /*
+ * Release resources used to emulate UART state (flush RX/TX FIFOs, unhook
+ * I/O handlers, etc.).
+ * Cannot be NULL.
+ */
+ void (*free)(void *arg);
+
+ /*
+ * Print emulated UART state, including registers, on the console.
+ * Can be NULL.
+ */
+ void (*dump_state)(void *arg);
+
+ /*
+ * Place character to the emulated RX FIFO.
+ * Used to forward physical console input to the guest OS.
+ * Can be NULL.
+ */
+ int (*put_rx)(void *arg, char c);
+};
+
+#define VUART_REGISTER(name, x) \
+ static const struct vuart_emulator name##_entry \
+ __used_section(".data.rel.ro.vuart") = x
+
+struct vuart *vuart_find_by_io_range(struct domain *d,
+ unsigned long base_addr,
+ unsigned long size);
+
+int vuart_put_rx(struct domain *d, char c);
+
+#ifdef CONFIG_VUART_FRAMEWORK
+
+int vuart_init(struct domain *d, struct vuart_info *info);
+void vuart_deinit(struct domain *d);
+void vuart_dump_state(const struct domain *d);
+bool domain_has_vuart(const struct domain *d);
+
+#else
+
+static inline int vuart_init(struct domain *d, struct vuart_info *info)
+{
+ return 0;
+}
+
+static inline void vuart_deinit(struct domain *d)
+{
+}
+
+static inline void vuart_dump_state(const struct domain *d)
+{
+}
+
+static inline bool domain_has_vuart(const struct domain *d)
+{
+ return false;
+}
+
+#endif /* CONFIG_VUART_FRAMEWORK */
+
+#endif /* XEN_VUART_H */
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
+
diff --git a/xen/include/xen/xen.lds.h b/xen/include/xen/xen.lds.h
index b126dfe88792..2d65f32ddad3 100644
--- a/xen/include/xen/xen.lds.h
+++ b/xen/include/xen/xen.lds.h
@@ -194,4 +194,14 @@
#define VPCI_ARRAY
#endif
+#ifdef CONFIG_VUART_FRAMEWORK
+#define VUART_ARRAY \
+ . = ALIGN(POINTER_ALIGN); \
+ vuart_array_start = .; \
+ *(.data.rel.ro.vuart) \
+ vuart_array_end = .;
+#else
+#define VUART_ARRAY
+#endif
+
#endif /* __XEN_LDS_H__ */
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 02/15] xen/8250-uart: update definitions
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
2025-08-28 23:53 ` [PATCH v5 01/15] emul/vuart: introduce framework for UART emulators dmukhin
@ 2025-08-28 23:53 ` dmukhin
2025-08-29 19:32 ` Stefano Stabellini
2025-08-28 23:53 ` [PATCH v5 03/15] emul/ns16x50: implement emulator stub dmukhin
` (12 subsequent siblings)
14 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:53 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
Added missing definitions needed for NS16550 UART emulator.
Newly introduced MSR definitions re-used in the existing ns16550 driver.
Also, corrected FCR DMA definition bit#3 (0x08) as per:
https://www.ti.com/lit/ds/symlink/tl16c550c.pdf
See "7.7.2 FIFO Control Register (FCR)".
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- reused newly introduced UART_IIR and UART_IER bits in ns16550 driver
- Link to v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-5-dmukhin@ford.com/
---
xen/drivers/char/ns16550.c | 16 ++++++------
xen/include/xen/8250-uart.h | 50 ++++++++++++++++++++++++++++++-------
2 files changed, 49 insertions(+), 17 deletions(-)
diff --git a/xen/drivers/char/ns16550.c b/xen/drivers/char/ns16550.c
index df7fff7f81df..0e80fadbb894 100644
--- a/xen/drivers/char/ns16550.c
+++ b/xen/drivers/char/ns16550.c
@@ -388,7 +388,7 @@ static void __init cf_check ns16550_init_preirq(struct serial_port *port)
/* Check this really is a 16550+. Otherwise we have no FIFOs. */
if ( uart->fifo_size <= 1 &&
- ((ns_read_reg(uart, UART_IIR) & 0xc0) == 0xc0) &&
+ ((ns_read_reg(uart, UART_IIR) & UART_IIR_FE) == UART_IIR_FE) &&
((ns_read_reg(uart, UART_FCR) & UART_FCR_TRG14) == UART_FCR_TRG14) )
uart->fifo_size = 16;
}
@@ -728,20 +728,20 @@ static int __init check_existence(struct ns16550 *uart)
* Mask out IER[7:4] bits for test as some UARTs (e.g. TL
* 16C754B) allow only to modify them if an EFR bit is set.
*/
- scratch2 = ns_read_reg(uart, UART_IER) & 0x0f;
- ns_write_reg(uart,UART_IER, 0x0F);
- scratch3 = ns_read_reg(uart, UART_IER) & 0x0f;
+ scratch2 = ns_read_reg(uart, UART_IER) & UART_IER_MASK;
+ ns_write_reg(uart, UART_IER, UART_IER_MASK);
+ scratch3 = ns_read_reg(uart, UART_IER) & UART_IER_MASK;
ns_write_reg(uart, UART_IER, scratch);
- if ( (scratch2 != 0) || (scratch3 != 0x0F) )
+ if ( (scratch2 != 0) || (scratch3 != UART_IER_MASK) )
return 0;
/*
* Check to see if a UART is really there.
* Use loopback test mode.
*/
- ns_write_reg(uart, UART_MCR, UART_MCR_LOOP | 0x0A);
- status = ns_read_reg(uart, UART_MSR) & 0xF0;
- return (status == 0x90);
+ ns_write_reg(uart, UART_MCR, UART_MCR_LOOP | UART_MCR_RTS | UART_MCR_OUT2);
+ status = ns_read_reg(uart, UART_MSR) & UART_MSR_STATUS;
+ return (status == (UART_MSR_CTS | UART_MSR_DCD));
}
#ifdef CONFIG_HAS_PCI
diff --git a/xen/include/xen/8250-uart.h b/xen/include/xen/8250-uart.h
index d13352940c13..bc11cdc376c9 100644
--- a/xen/include/xen/8250-uart.h
+++ b/xen/include/xen/8250-uart.h
@@ -32,6 +32,7 @@
#define UART_MCR 0x04 /* Modem control */
#define UART_LSR 0x05 /* line status */
#define UART_MSR 0x06 /* Modem status */
+#define UART_SCR 0x07 /* Scratch pad */
#define UART_USR 0x1f /* Status register (DW) */
#define UART_DLL 0x00 /* divisor latch (ls) (DLAB=1) */
#define UART_DLM 0x01 /* divisor latch (ms) (DLAB=1) */
@@ -42,6 +43,8 @@
#define UART_IER_ETHREI 0x02 /* tx reg. empty */
#define UART_IER_ELSI 0x04 /* rx line status */
#define UART_IER_EMSI 0x08 /* MODEM status */
+#define UART_IER_MASK \
+ (UART_IER_ERDAI | UART_IER_ETHREI | UART_IER_ELSI | UART_IER_EMSI)
/* Interrupt Identification Register */
#define UART_IIR_NOINT 0x01 /* no interrupt pending */
@@ -51,12 +54,19 @@
#define UART_IIR_THR 0x02 /* - tx reg. empty */
#define UART_IIR_MSI 0x00 /* - MODEM status */
#define UART_IIR_BSY 0x07 /* - busy detect (DW) */
+#define UART_IIR_FE 0xc0 /* FIFO enabled (2 bits) */
/* FIFO Control Register */
-#define UART_FCR_ENABLE 0x01 /* enable FIFO */
-#define UART_FCR_CLRX 0x02 /* clear Rx FIFO */
-#define UART_FCR_CLTX 0x04 /* clear Tx FIFO */
-#define UART_FCR_DMA 0x10 /* enter DMA mode */
+#define UART_FCR_ENABLE BIT(0, U) /* enable FIFO */
+#define UART_FCR_CLRX BIT(1, U) /* clear Rx FIFO */
+#define UART_FCR_CLTX BIT(2, U) /* clear Tx FIFO */
+#define UART_FCR_DMA BIT(3, U) /* enter DMA mode */
+#define UART_FCR_RESERVED0 BIT(4, U) /* reserved; always 0 */
+#define UART_FCR_RESERVED1 BIT(5, U) /* reserved; always 0 */
+#define UART_FCR_RTB0 BIT(6, U) /* receiver trigger bit #0 */
+#define UART_FCR_RTB1 BIT(7, U) /* receiver trigger bit #1 */
+#define UART_FCR_TRG_MASK (UART_FCR_RTB0 | UART_FCR_RTB1)
+
#define UART_FCR_TRG1 0x00 /* Rx FIFO trig lev 1 */
#define UART_FCR_TRG4 0x40 /* Rx FIFO trig lev 4 */
#define UART_FCR_TRG8 0x80 /* Rx FIFO trig lev 8 */
@@ -96,11 +106,32 @@
#define UART_LCR_CONF_MODE_B 0xBF /* Configuration mode B */
/* Modem Control Register */
-#define UART_MCR_DTR 0x01 /* Data Terminal Ready */
-#define UART_MCR_RTS 0x02 /* Request to Send */
-#define UART_MCR_OUT2 0x08 /* OUT2: interrupt mask */
-#define UART_MCR_LOOP 0x10 /* Enable loopback test mode */
-#define UART_MCR_TCRTLR 0x40 /* Access TCR/TLR (TI16C752, EFR[4]=1) */
+#define UART_MCR_DTR BIT(0, U) /* Data Terminal Ready */
+#define UART_MCR_RTS BIT(1, U) /* Request to Send */
+#define UART_MCR_OUT1 BIT(2, U) /* OUT1: interrupt mask */
+#define UART_MCR_OUT2 BIT(3, U) /* OUT2: interrupt mask */
+#define UART_MCR_LOOP BIT(4, U) /* Enable loopback test mode */
+#define UART_MCR_RESERVED0 BIT(5, U) /* Reserved #0 */
+#define UART_MCR_TCRTLR BIT(6, U) /* Access TCR/TLR (TI16C752, EFR[4]=1) */
+#define UART_MCR_RESERVED1 BIT(7, U) /* Reserved #1 */
+#define UART_MCR_MASK \
+ (UART_MCR_DTR | UART_MCR_RTS | \
+ UART_MCR_OUT1 | UART_MCR_OUT2 | \
+ UART_MCR_LOOP | UART_MCR_TCRTLR)
+
+/* Modem Status Register */
+#define UART_MSR_DCTS BIT(0, U) /* Change in CTS */
+#define UART_MSR_DDSR BIT(1, U) /* Change in DSR */
+#define UART_MSR_TERI BIT(2, U) /* Change in RI */
+#define UART_MSR_DDCD BIT(3, U) /* Change in CTS */
+#define UART_MSR_CTS BIT(4, U)
+#define UART_MSR_DSR BIT(5, U)
+#define UART_MSR_RI BIT(6, U)
+#define UART_MSR_DCD BIT(7, U)
+#define UART_MSR_CHANGE \
+ (UART_MSR_DCTS | UART_MSR_DDSR | UART_MSR_TERI | UART_MSR_DDCD)
+#define UART_MSR_STATUS \
+ (UART_MSR_CTS | UART_MSR_DSR | UART_MSR_RI | UART_MSR_DCD)
/* Line Status Register */
#define UART_LSR_DR 0x01 /* Data ready */
@@ -111,6 +142,7 @@
#define UART_LSR_THRE 0x20 /* Xmit hold reg empty */
#define UART_LSR_TEMT 0x40 /* Xmitter empty */
#define UART_LSR_ERR 0x80 /* Error */
+#define UART_LSR_MASK (UART_LSR_OE | UART_LSR_BI)
/* These parity settings can be ORed directly into the LCR. */
#define UART_PARITY_NONE (0<<3)
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 03/15] emul/ns16x50: implement emulator stub
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
2025-08-28 23:53 ` [PATCH v5 01/15] emul/vuart: introduce framework for UART emulators dmukhin
2025-08-28 23:53 ` [PATCH v5 02/15] xen/8250-uart: update definitions dmukhin
@ 2025-08-28 23:53 ` dmukhin
2025-08-29 19:57 ` Stefano Stabellini
2025-08-28 23:53 ` [PATCH v5 04/15] emul/ns16x50: implement DLL/DLM registers dmukhin
` (11 subsequent siblings)
14 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:53 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
The change is the first on the way on introducing minimally functional
NS16550-compatible UART emulator.
Define UART state and a set of emulated registers.
Implement alloc/free vUART hooks.
Stub out I/O port handler.
Add initialization of the NS16x50-compatible UART emulator state machine.
Plumb debug logging.
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- new patch
---
xen/arch/x86/hvm/hvm.c | 20 ++
xen/common/emul/vuart/Kconfig | 18 ++
xen/common/emul/vuart/Makefile | 1 +
xen/common/emul/vuart/ns16x50.c | 362 ++++++++++++++++++++++++++++++++
xen/include/xen/sched.h | 4 +
5 files changed, 405 insertions(+)
create mode 100644 xen/common/emul/vuart/ns16x50.c
diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c
index 23bd7f078a1d..26760cf995df 100644
--- a/xen/arch/x86/hvm/hvm.c
+++ b/xen/arch/x86/hvm/hvm.c
@@ -28,6 +28,7 @@
#include <xen/softirq.h>
#include <xen/trace.h>
#include <xen/vm_event.h>
+#include <xen/vuart.h>
#include <xen/vpci.h>
#include <xen/wait.h>
#include <xen/warning.h>
@@ -689,6 +690,21 @@ int hvm_domain_initialise(struct domain *d,
if ( rc != 0 )
goto fail1;
+ if ( IS_ENABLED(CONFIG_VUART_NS16X50) )
+ {
+ struct vuart_info info = {
+ .name = "COM2",
+ .compatible = "ns16550",
+ .base_addr = 0x2f8,
+ .size = 8,
+ .irq = 3,
+ };
+
+ rc = vuart_init(d, &info);
+ if ( rc )
+ goto out_vioapic_deinit;
+ }
+
stdvga_init(d);
rtc_init(d);
@@ -712,6 +728,8 @@ int hvm_domain_initialise(struct domain *d,
return 0;
fail2:
+ vuart_deinit(d);
+ out_vioapic_deinit:
vioapic_deinit(d);
fail1:
if ( is_hardware_domain(d) )
@@ -774,6 +792,8 @@ void hvm_domain_destroy(struct domain *d)
if ( hvm_funcs.domain_destroy )
alternative_vcall(hvm_funcs.domain_destroy, d);
+ vuart_deinit(d);
+
vioapic_deinit(d);
XFREE(d->arch.hvm.pl_time);
diff --git a/xen/common/emul/vuart/Kconfig b/xen/common/emul/vuart/Kconfig
index ce1b976b7da7..539e6d5e4fc7 100644
--- a/xen/common/emul/vuart/Kconfig
+++ b/xen/common/emul/vuart/Kconfig
@@ -3,4 +3,22 @@ config VUART_FRAMEWORK
menu "UART Emulation"
+config VUART_NS16X50
+ bool "NS16550-compatible UART Emulator" if EXPERT
+ depends on X86 && HVM
+ select VUART_FRAMEWORK
+ help
+ In-hypervisor NS16x50 UART emulation.
+
+ Only legacy PC COM2 port is emulated.
+
+ This is strictly for testing purposes (such as early HVM guest console),
+ and not appropriate for use in production.
+
+config VUART_NS16X50_DEBUG
+ bool "NS16550-compatible UART Emulator Debugging"
+ depends on VUART_NS16X50 && DEBUG
+ help
+ Enable development debugging.
+
endmenu
diff --git a/xen/common/emul/vuart/Makefile b/xen/common/emul/vuart/Makefile
index 97f792dc6641..fe904f6cb65d 100644
--- a/xen/common/emul/vuart/Makefile
+++ b/xen/common/emul/vuart/Makefile
@@ -1 +1,2 @@
obj-y += vuart.o
+obj-$(CONFIG_VUART_NS16X50) += ns16x50.o
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
new file mode 100644
index 000000000000..f0479e1022fb
--- /dev/null
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -0,0 +1,362 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * NS16550-compatible UART Emulator.
+ *
+ * See:
+ * - Serial and UART Tutorial:
+ * https://download.freebsd.org/doc/en/articles/serial-uart/serial-uart_en.pdf
+ * - UART w/ 16 byte FIFO:
+ * https://www.ti.com/lit/ds/symlink/tl16c550c.pdf
+ * - UART w/ 64 byte FIFO:
+ * https://www.ti.com/lit/ds/symlink/tl16c750.pdf
+ *
+ * Limitations:
+ * - Only x86;
+ * - Only Xen console as a backend, no inter-domain communication (similar to
+ * vpl011 on Arm);
+ * - Only 8n1 emulation (8-bit data, no parity, 1 stop bit);
+ * - No baud rate emulation (reports 115200 baud to the guest OS);
+ * - No FIFO-less mode emulation;
+ * - No RX FIFO interrupt moderation (FCR) emulation;
+ * - No integration w/ VM snapshotting (HVM_REGISTER_SAVE_RESTORE() and
+ * friends);
+ * - No ISA IRQ sharing allowed;
+ * - No MMIO-based UART emulation.
+ */
+
+#define pr_prefix "ns16x50"
+#define pr_fmt(fmt) pr_prefix ": " fmt
+
+#ifdef CONFIG_VUART_NS16X50_DEBUG
+#define guest_prefix "FROM GUEST "
+#define ns16x50_log_level 2
+#else
+#define guest_prefix ""
+#define ns16x50_log_level 0
+#endif
+
+#include <xen/8250-uart.h>
+#include <xen/console.h>
+#include <xen/err.h>
+#include <xen/iocap.h>
+#include <xen/vuart.h>
+#include <xen/xvmalloc.h>
+
+#include <public/io/console.h>
+
+#define ns16x50_log(n, lvl, vdev, fmt, args...) do { \
+ if ( ns16x50_log_level >= n) \
+ gprintk(lvl, pr_fmt("%s: " fmt), (vdev)->name, ## args); \
+} while (0)
+
+#define ns16x50_err(vdev, fmt, args...) \
+ ns16x50_log(0, KERN_ERR, vdev, fmt, ## args)
+#define ns16x50_warn(vdev, fmt, args...) \
+ ns16x50_log(1, KERN_WARNING, vdev, fmt, ## args)
+#define ns16x50_info(vdev, fmt, args...) \
+ ns16x50_log(2, KERN_INFO, vdev, fmt, ## args)
+#define ns16x50_debug(vdev, fmt, args...) \
+ ns16x50_log(3, KERN_DEBUG, vdev, fmt, ## args)
+
+/*
+ * Number of supported registers in the UART.
+ */
+#define NS16X50_REGS_NUM (UART_SCR + 1)
+
+/*
+ * Number of emulated registers.
+ *
+ * - Emulated registers [0..NS16X50_REGS_NUM] are R/W registers for DLAB=0.
+ * - DLAB=1, R/W, DLL = NS16X50_REGS_NUM + 0
+ * - DLAB=1, R/W, DLM = NS16X50_REGS_NUM + 1
+ * - R/O, IIR (IIR_THR) = NS16X50_REGS_NUM + 2
+ */
+#define NS16X50_EMU_REGS_NUM (NS16X50_REGS_NUM + 3)
+
+/*
+ * Virtual ns16x50 device state.
+ */
+struct vuart_ns16x50 {
+ struct xencons_interface cons; /* Emulated RX/TX FIFOs */
+ uint8_t regs[NS16X50_EMU_REGS_NUM]; /* Emulated registers */
+ const char *name; /* Device name */
+ struct domain *owner; /* Owner domain */
+ const struct vuart_info *info; /* UART description */
+ spinlock_t lock; /* Protection */
+};
+
+/*
+ * Emulate 8-bit write access to ns16x50 register.
+ */
+static int ns16x50_io_write8(
+ struct vuart_ns16x50 *vdev, uint32_t reg, uint8_t *data)
+{
+ int rc = 0;
+
+ return rc;
+}
+
+/*
+ * Emulate 16-bit write access to ns16x50 register.
+ * NB: some guest OSes use outw() to access UART_DLL.
+ */
+static int ns16x50_io_write16(
+ struct vuart_ns16x50 *vdev, uint32_t reg, uint16_t *data)
+{
+ int rc = -EINVAL;
+
+ return rc;
+}
+
+/*
+ * Emulate write access to ns16x50 register.
+ */
+static int ns16x50_io_write(
+ struct vuart_ns16x50 *vdev, uint8_t reg, uint32_t size, uint32_t *data)
+{
+ int rc;
+
+ switch ( size )
+ {
+ case 1:
+ rc = ns16x50_io_write8(vdev, reg, (uint8_t *)data);
+ break;
+
+ case 2:
+ rc = ns16x50_io_write16(vdev, reg, (uint16_t *)data);
+ break;
+
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * Emulate 8-bit read access to ns16x50 register.
+ */
+static int ns16x50_io_read8(
+ struct vuart_ns16x50 *vdev, uint32_t reg, uint8_t *data)
+{
+ uint8_t val = 0xff;
+ int rc = 0;
+
+ *data = val;
+
+ return rc;
+}
+
+/*
+ * Emulate 16-bit read access to ns16x50 register.
+ */
+static int ns16x50_io_read16(
+ struct vuart_ns16x50 *vdev, uint32_t reg, uint16_t *data)
+{
+ uint16_t val = 0xffff;
+ int rc = -EINVAL;
+
+ *data = val;
+
+ return rc;
+}
+
+/*
+ * Emulate read access to ns16x50 register.
+ */
+static int ns16x50_io_read(
+ struct vuart_ns16x50 *vdev, uint8_t reg, uint32_t size, uint32_t *data)
+{
+ int rc;
+
+ switch ( size )
+ {
+ case 1:
+ rc = ns16x50_io_read8(vdev, reg, (uint8_t *)data);
+ break;
+
+ case 2:
+ rc = ns16x50_io_read16(vdev, reg, (uint16_t *)data);
+ break;
+
+ default:
+ *data = 0xffffffff;
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+/*
+ * Emulate I/O access to ns16x50 register.
+ * Note, emulation always returns X86EMUL_OKAY, once I/O port trap is enabled.
+ */
+static int cf_check ns16x50_io_handle(
+ int dir, unsigned int addr, unsigned int size, uint32_t *data)
+{
+#define op(dir) (((dir) == IOREQ_WRITE) ? 'W' : 'R')
+ struct domain *d = rcu_lock_current_domain();
+ struct vuart *vuart = vuart_find_by_io_range(d, addr, size);
+ struct vuart_ns16x50 *vdev;
+ const struct domain *owner;
+ const struct vuart_info *info;
+ uint32_t reg;
+ unsigned dlab;
+ int rc;
+
+ if ( !vuart || !vuart->vdev )
+ {
+ printk(KERN_ERR "%c io 0x%04x %d: not initialized\n",
+ op(dir), addr, size);
+
+ ASSERT_UNREACHABLE();
+ goto out;
+ }
+ vdev = vuart->vdev;
+
+ owner = vuart->owner;
+ ASSERT(owner);
+ if ( d != owner )
+ {
+ ns16x50_err(vdev, "%c io 0x%04x %d: does not match current domain %pv\n",
+ op(dir), addr, size, d);
+
+ ASSERT_UNREACHABLE();
+ goto out;
+ }
+
+ info = vuart->info;
+ ASSERT(info);
+ reg = addr - info->base_addr;
+ if ( !IS_ALIGNED(reg, size) )
+ {
+ ns16x50_err(vdev, "%c 0x%04x %d: unaligned access\n",
+ op(dir), addr, size);
+ goto out;
+ }
+
+ dlab = 0;
+ if ( reg >= NS16X50_REGS_NUM )
+ {
+ ns16x50_err(vdev, "%c io 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32": not implemented\n",
+ op(dir), addr, size, dlab, reg, *data);
+ goto out;
+ }
+
+ spin_lock(&vdev->lock);
+
+ if ( dir == IOREQ_WRITE )
+ {
+ ns16x50_debug(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32"\n",
+ op(dir), addr, size, dlab, reg, *data);
+ rc = ns16x50_io_write(vdev, reg, size, data);
+ }
+ else
+ {
+ rc = ns16x50_io_read(vdev, reg, size, data);
+ ns16x50_debug(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32"\n",
+ op(dir), addr, size, dlab, reg, *data);
+ }
+ if ( rc < 0 )
+ ns16x50_err(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32": unsupported access\n",
+ op(dir), addr, size, dlab, reg, *data);
+
+ spin_unlock(&vdev->lock);
+
+out:
+ rcu_unlock_domain(d);
+
+ return X86EMUL_OKAY;
+#undef op
+}
+
+static int ns16x50_init(void *arg)
+{
+ struct vuart_ns16x50 *vdev = arg;
+ const struct vuart_info *info = vdev->info;
+ struct domain *d = vdev->owner;
+
+ ASSERT(vdev);
+
+ register_portio_handler(d, info->base_addr, info->size, ns16x50_io_handle);
+
+ return 0;
+}
+
+static void cf_check ns16x50_deinit(void *arg)
+{
+ struct vuart_ns16x50 *vdev = arg;
+
+ ASSERT(vdev);
+}
+
+static void * cf_check ns16x50_alloc(struct domain *d, const struct vuart_info *info)
+{
+ struct vuart_ns16x50 *vdev;
+ int rc;
+
+ if ( !info )
+ return ERR_PTR(-EINVAL);
+
+ if ( vuart_find_by_io_range(d, info->base_addr, info->size) )
+ {
+ ns16x50_err(info, "already registered\n");
+ return ERR_PTR(-EBUSY);
+ }
+
+ if ( !is_hvm_domain(d) )
+ {
+ ns16x50_err(info, "not an HVM domain\n");
+ return ERR_PTR(-ENOSYS);
+ }
+
+ vdev = xvzalloc(typeof(*vdev));
+ if ( !vdev )
+ {
+ ns16x50_err(info, "failed to allocate memory\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ /* Save convenience pointer. */
+ vdev->name = info->name;
+ vdev->owner = d;
+ vdev->info = info;
+
+ rc = ns16x50_init(vdev);
+ if ( rc )
+ return ERR_PTR(rc);
+
+ return vdev;
+}
+
+static void cf_check ns16x50_free(void *arg)
+{
+ struct vuart_ns16x50 *vdev = arg;
+
+ if ( vdev )
+ ns16x50_deinit(vdev);
+
+ XVFREE(vdev);
+}
+
+#define ns16x50_emulator \
+{ \
+ .compatible = "ns16550", \
+ .alloc = ns16x50_alloc, \
+ .free = ns16x50_free, \
+ .dump_state = NULL, \
+ .put_rx = NULL, \
+}
+
+VUART_REGISTER(ns16x50, ns16x50_emulator);
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/include/xen/sched.h b/xen/include/xen/sched.h
index 02bdc256ce37..613f4596e33d 100644
--- a/xen/include/xen/sched.h
+++ b/xen/include/xen/sched.h
@@ -23,6 +23,7 @@
#include <asm/atomic.h>
#include <asm/current.h>
#include <xen/vpci.h>
+#include <xen/vuart.h>
#include <xen/wait.h>
#include <public/xen.h>
#include <public/domctl.h>
@@ -660,6 +661,9 @@ struct domain
struct {
/* Permission to take ownership of the physical console input. */
bool input_allowed;
+#ifdef CONFIG_VUART_FRAMEWORK
+ struct vuart *vuart;
+#endif
} console;
} __aligned(PAGE_SIZE);
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 04/15] emul/ns16x50: implement DLL/DLM registers
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
` (2 preceding siblings ...)
2025-08-28 23:53 ` [PATCH v5 03/15] emul/ns16x50: implement emulator stub dmukhin
@ 2025-08-28 23:53 ` dmukhin
2025-08-28 23:53 ` [PATCH v5 05/15] emul/ns16x50: implement EIR/IIR registers dmukhin
` (10 subsequent siblings)
14 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:53 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
Add DLL/DLM registers emulation.
DLL/DLM registers report hardcoded 115200 baud rate to the guest OS.
Add stub for ns16x50_dlab_get() helper.
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- new patch
---
xen/common/emul/vuart/ns16x50.c | 36 ++++++++++++++++++++++++++++++++-
1 file changed, 35 insertions(+), 1 deletion(-)
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index f0479e1022fb..f9f307a4ad24 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -85,14 +85,24 @@ struct vuart_ns16x50 {
spinlock_t lock; /* Protection */
};
+static inline uint8_t cf_check ns16x50_dlab_get(const struct vuart_ns16x50 *vdev)
+{
+ return 0;
+}
+
/*
* Emulate 8-bit write access to ns16x50 register.
*/
static int ns16x50_io_write8(
struct vuart_ns16x50 *vdev, uint32_t reg, uint8_t *data)
{
+ uint8_t *regs = vdev->regs;
+ uint8_t val = *data;
int rc = 0;
+ if ( ns16x50_dlab_get(vdev) && (reg == UART_DLL || reg == UART_DLM) )
+ regs[NS16X50_REGS_NUM + reg] = val;
+
return rc;
}
@@ -103,8 +113,16 @@ static int ns16x50_io_write8(
static int ns16x50_io_write16(
struct vuart_ns16x50 *vdev, uint32_t reg, uint16_t *data)
{
+ uint16_t val = *data;
int rc = -EINVAL;
+ if ( ns16x50_dlab_get(vdev) && reg == UART_DLL )
+ {
+ vdev->regs[NS16X50_REGS_NUM + UART_DLL] = val & 0xff;
+ vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (val >> 8) & 0xff;
+ rc = 0;
+ }
+
return rc;
}
@@ -140,9 +158,13 @@ static int ns16x50_io_write(
static int ns16x50_io_read8(
struct vuart_ns16x50 *vdev, uint32_t reg, uint8_t *data)
{
+ uint8_t *regs = vdev->regs;
uint8_t val = 0xff;
int rc = 0;
+ if ( ns16x50_dlab_get(vdev) && (reg == UART_DLL || reg == UART_DLM) )
+ val = regs[NS16X50_REGS_NUM + reg];
+
*data = val;
return rc;
@@ -157,6 +179,13 @@ static int ns16x50_io_read16(
uint16_t val = 0xffff;
int rc = -EINVAL;
+ if ( ns16x50_dlab_get(vdev) && reg == UART_DLL )
+ {
+ val = vdev->regs[NS16X50_REGS_NUM + UART_DLM] << 8 |
+ vdev->regs[NS16X50_REGS_NUM + UART_DLL];
+ rc = 0;
+ }
+
*data = val;
return rc;
@@ -237,7 +266,7 @@ static int cf_check ns16x50_io_handle(
goto out;
}
- dlab = 0;
+ dlab = ns16x50_dlab_get(vdev);
if ( reg >= NS16X50_REGS_NUM )
{
ns16x50_err(vdev, "%c io 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32": not implemented\n",
@@ -274,12 +303,17 @@ out:
static int ns16x50_init(void *arg)
{
+ const uint16_t divisor = (UART_CLOCK_HZ / 115200) >> 4;
struct vuart_ns16x50 *vdev = arg;
const struct vuart_info *info = vdev->info;
struct domain *d = vdev->owner;
ASSERT(vdev);
+ /* NB: report 115200 baud rate. */
+ vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
+ vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
+
register_portio_handler(d, info->base_addr, info->size, ns16x50_io_handle);
return 0;
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 05/15] emul/ns16x50: implement EIR/IIR registers
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
` (3 preceding siblings ...)
2025-08-28 23:53 ` [PATCH v5 04/15] emul/ns16x50: implement DLL/DLM registers dmukhin
@ 2025-08-28 23:53 ` dmukhin
2025-08-29 20:14 ` Stefano Stabellini
2025-08-28 23:54 ` [PATCH v5 06/15] emul/ns16x50: implement THR/RBR registers dmukhin
` (9 subsequent siblings)
14 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:53 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
Add interrupt enable register emulation (EIR) and interrupt identity reason
(IIR) register emulation to the I/O port handler.
Also add routines for asserting/deasserting the virtual ns16x50 interrupt
line as a dependent on IIR code.
Poke ns16x50_irq_check() on every I/O register access because the emulator
does not have clock emulation anyway (e.g. for baud rate emulation).
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- new patch
---
xen/common/emul/vuart/ns16x50.c | 177 +++++++++++++++++++++++++++++++-
1 file changed, 176 insertions(+), 1 deletion(-)
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index f9f307a4ad24..20597cc36b35 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -85,9 +85,131 @@ struct vuart_ns16x50 {
spinlock_t lock; /* Protection */
};
+static bool ns16x50_fifo_rx_empty(const struct vuart_ns16x50 *vdev)
+{
+ const struct xencons_interface *cons = &vdev->cons;
+
+ return cons->in_prod == cons->in_cons;
+}
+
static inline uint8_t cf_check ns16x50_dlab_get(const struct vuart_ns16x50 *vdev)
{
- return 0;
+ return vdev->regs[UART_LCR] & UART_LCR_DLAB ? 1 : 0;
+}
+
+static bool cf_check ns16x50_iir_check_lsi(const struct vuart_ns16x50 *vdev)
+{
+ return vdev->regs[UART_LSR] & UART_LSR_MASK;
+}
+
+static bool cf_check ns16x50_iir_check_rda(const struct vuart_ns16x50 *vdev)
+{
+ return !ns16x50_fifo_rx_empty(vdev);
+}
+
+static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
+{
+ return vdev->regs[NS16X50_REGS_NUM + UART_IIR] & UART_IIR_THR;
+}
+
+static bool cf_check ns16x50_iir_check_msi(const struct vuart_ns16x50 *vdev)
+{
+ return vdev->regs[UART_MSR] & UART_MSR_CHANGE;
+}
+
+/*
+ * Get the interrupt identity reason.
+ *
+ * IIR is re-calculated once called, because ns16x50 always reports high
+ * priority events first.
+ * regs[NS16X50_REGS_NUM + UART_IIR] is used to store THR reason only.
+ */
+static uint8_t ns16x50_iir_get(const struct vuart_ns16x50 *vdev)
+{
+ /*
+ * Interrupt identity reasons by priority.
+ * NB: high priority are at lower indexes below.
+ */
+ static const struct {
+ bool (*check)(const struct vuart_ns16x50 *vdev);
+ uint8_t ier;
+ uint8_t iir;
+ } iir_by_prio[] = {
+ [0] = { ns16x50_iir_check_lsi, UART_IER_ELSI, UART_IIR_LSI },
+ [1] = { ns16x50_iir_check_rda, UART_IER_ERDAI, UART_IIR_RDA },
+ [2] = { ns16x50_iir_check_thr, UART_IER_ETHREI, UART_IIR_THR },
+ [3] = { ns16x50_iir_check_msi, UART_IER_EMSI, UART_IIR_MSI },
+ };
+ const uint8_t *regs = vdev->regs;
+ uint8_t iir = 0;
+ unsigned int i;
+
+ /*
+ * NB: every interaction w/ ns16x50 registers (except DLAB=1) goes
+ * through that call.
+ */
+ ASSERT(spin_is_locked(&vdev->lock));
+
+ for ( i = 0; i < ARRAY_SIZE(iir_by_prio); i++ )
+ {
+ if ( (regs[UART_IER] & iir_by_prio[i].ier) &&
+ iir_by_prio[i].check(vdev) )
+ break;
+
+ }
+ if ( i == ARRAY_SIZE(iir_by_prio) )
+ iir |= UART_IIR_NOINT;
+ else
+ iir |= iir_by_prio[i].iir;
+
+ if ( regs[UART_FCR] & UART_FCR_ENABLE )
+ iir |= UART_IIR_FE;
+
+ return iir;
+}
+
+static void ns16x50_irq_assert(const struct vuart_ns16x50 *vdev)
+{
+ struct domain *d = vdev->owner;
+ const struct vuart_info *info = vdev->info;
+ int vector;
+
+ if ( has_vpic(d) ) /* HVM */
+ vector = hvm_isa_irq_assert(d, info->irq, vioapic_get_vector);
+ else
+ ASSERT_UNREACHABLE();
+
+ ns16x50_debug(vdev, "IRQ#%d vector %d assert\n", info->irq, vector);
+}
+
+static void ns16x50_irq_deassert(const struct vuart_ns16x50 *vdev)
+{
+ struct domain *d = vdev->owner;
+ const struct vuart_info *info = vdev->info;
+
+ if ( has_vpic(d) ) /* HVM */
+ hvm_isa_irq_deassert(d, info->irq);
+ else
+ ASSERT_UNREACHABLE();
+
+ ns16x50_debug(vdev, "IRQ#%d deassert\n", info->irq);
+}
+
+/*
+ * Assert/deassert virtual ns16x50 interrupt line.
+ */
+static void ns16x50_irq_check(const struct vuart_ns16x50 *vdev)
+{
+ uint8_t iir = ns16x50_iir_get(vdev);
+ const struct vuart_info *info = vdev->info;
+
+ if ( iir & UART_IIR_NOINT )
+ ns16x50_irq_assert(vdev);
+ else
+ ns16x50_irq_deassert(vdev);
+
+ ns16x50_debug(vdev, "IRQ#%d IIR 0x%02x %s\n", info->irq, iir,
+ (iir & UART_IIR_NOINT) ? "deassert" : "assert");
}
/*
@@ -102,6 +224,29 @@ static int ns16x50_io_write8(
if ( ns16x50_dlab_get(vdev) && (reg == UART_DLL || reg == UART_DLM) )
regs[NS16X50_REGS_NUM + reg] = val;
+ else
+ {
+ switch ( reg )
+ {
+ case UART_IER:
+ /*
+ * NB: Make sure THR interrupt is re-triggered once guest OS
+ * re-enabled ETHREI in EIR.
+ */
+ if ( val & regs[UART_IER] & UART_IER_ETHREI )
+ regs[NS16X50_REGS_NUM + UART_IIR] |= UART_IIR_THR;
+
+ regs[UART_IER] = val & UART_IER_MASK;
+
+ break;
+
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ ns16x50_irq_check(vdev);
+ }
return rc;
}
@@ -164,6 +309,29 @@ static int ns16x50_io_read8(
if ( ns16x50_dlab_get(vdev) && (reg == UART_DLL || reg == UART_DLM) )
val = regs[NS16X50_REGS_NUM + reg];
+ else {
+ switch ( reg )
+ {
+ case UART_IER:
+ val = regs[UART_IER];
+ break;
+
+ case UART_IIR: /* RO */
+ val = ns16x50_iir_get(vdev);
+
+ /* NB: clear IIR scratch location */
+ if ( val & UART_IIR_THR )
+ regs[NS16X50_REGS_NUM + UART_IIR] &= ~UART_IIR_THR;
+
+ break;
+
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ ns16x50_irq_check(vdev);
+ }
*data = val;
@@ -314,8 +482,15 @@ static int ns16x50_init(void *arg)
vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
+ /* ns16x50 shall assert UART_IIR_THR whenever transmitter is empty. */
+ vdev->regs[NS16X50_REGS_NUM + UART_IIR] = UART_IIR_THR;
+
register_portio_handler(d, info->base_addr, info->size, ns16x50_io_handle);
+ spin_lock(&vdev->lock);
+ ns16x50_irq_check(vdev);
+ spin_unlock(&vdev->lock);
+
return 0;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 06/15] emul/ns16x50: implement THR/RBR registers
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
` (4 preceding siblings ...)
2025-08-28 23:53 ` [PATCH v5 05/15] emul/ns16x50: implement EIR/IIR registers dmukhin
@ 2025-08-28 23:54 ` dmukhin
2025-08-29 20:28 ` Stefano Stabellini
2025-08-28 23:54 ` [PATCH v5 07/15] emul/ns16x50: implement FCR register (write-only) dmukhin
` (8 subsequent siblings)
14 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:54 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
Add RBR/THR registers emulation to the I/O port handlder.
Also, add RX/TX FIFO management code since RBR depends on RX FIFO and
THR depends on TX FIFO.
FIFOs are not emulated as per UART specs for simplicity (not need to emulate
baud rate). Emulator does not emulate NS8250 (no FIFO), NS16550a (16 bytes) or
NS16750 (64 bytes).
FIFOs are emulated by means of using xencons_interface which conveniently
provides primitives for buffer management and later can be used for
inter-domain communication similarly to vpl011.
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- new patch
---
xen/common/emul/vuart/ns16x50.c | 135 ++++++++++++++++++++++++++++++++
1 file changed, 135 insertions(+)
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 20597cc36b35..efb2f4c6441c 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -92,6 +92,119 @@ static bool ns16x50_fifo_rx_empty(const struct vuart_ns16x50 *vdev)
return cons->in_prod == cons->in_cons;
}
+static bool ns16x50_fifo_rx_full(const struct vuart_ns16x50 *vdev)
+{
+ const struct xencons_interface *cons = &vdev->cons;
+
+ return cons->in_prod - cons->in_cons == ARRAY_SIZE(cons->in);
+}
+
+static void ns16x50_fifo_rx_reset(struct vuart_ns16x50 *vdev)
+{
+ struct xencons_interface *cons = &vdev->cons;
+
+ cons->in_cons = cons->in_prod;
+}
+
+static int ns16x50_fifo_rx_getchar(struct vuart_ns16x50 *vdev)
+{
+ struct xencons_interface *cons = &vdev->cons;
+ int rc;
+
+ if ( ns16x50_fifo_rx_empty(vdev) )
+ {
+ ns16x50_debug(vdev, "RX FIFO empty\n");
+ rc = -ENODATA;
+ }
+ else
+ {
+ rc = cons->in[MASK_XENCONS_IDX(cons->in_cons, cons->in)];
+ cons->in_cons++;
+ }
+
+ return rc;
+}
+
+static int ns16x50_fifo_rx_putchar(struct vuart_ns16x50 *vdev, char c)
+{
+ struct xencons_interface *cons = &vdev->cons;
+ int rc;
+
+ /*
+ * FIFO-less 8250/16450 UARTs: newly arrived word overwrites the contents
+ * of the THR.
+ */
+ if ( ns16x50_fifo_rx_full(vdev) )
+ {
+ ns16x50_debug(vdev, "RX FIFO full; resetting\n");
+ ns16x50_fifo_rx_reset(vdev);
+ rc = -ENOSPC;
+ }
+ else
+ rc = 0;
+
+ cons->in[MASK_XENCONS_IDX(cons->in_prod, cons->in)] = c;
+ cons->in_prod++;
+
+ return rc;
+}
+
+static bool ns16x50_fifo_tx_empty(const struct vuart_ns16x50 *vdev)
+{
+ const struct xencons_interface *cons = &vdev->cons;
+
+ return cons->out_prod == cons->out_cons;
+}
+
+static void ns16x50_fifo_tx_reset(struct vuart_ns16x50 *vdev)
+{
+ struct xencons_interface *cons = &vdev->cons;
+
+ cons->out_prod = 0;
+ ASSERT(cons->out_cons == cons->out_prod);
+}
+
+/*
+ * Flush cached output to Xen console.
+ */
+static void ns16x50_fifo_tx_flush(struct vuart_ns16x50 *vdev)
+{
+ struct xencons_interface *cons = &vdev->cons;
+ struct domain *d = vdev->owner;
+
+ if ( ns16x50_fifo_tx_empty(vdev) )
+ return;
+
+ ASSERT(cons->out_prod < ARRAY_SIZE(cons->out));
+ cons->out[cons->out_prod] = '\0';
+ cons->out_prod++;
+
+ guest_printk(d, guest_prefix "%s", cons->out);
+
+ ns16x50_fifo_tx_reset(vdev);
+}
+
+/*
+ * Accumulate guest OS output before sending to Xen console.
+ */
+static void ns16x50_fifo_tx_putchar(struct vuart_ns16x50 *vdev, char ch)
+{
+ struct xencons_interface *cons = &vdev->cons;
+
+ if ( !is_console_printable(ch) )
+ return;
+
+ if ( ch != '\0' )
+ {
+ cons->out[cons->out_prod] = ch;
+ cons->out_prod++;
+ }
+
+ if ( cons->out_prod == ARRAY_SIZE(cons->out) - 1 ||
+ ch == '\n' || ch == '\0' )
+ ns16x50_fifo_tx_flush(vdev);
+}
+
static inline uint8_t cf_check ns16x50_dlab_get(const struct vuart_ns16x50 *vdev)
{
return vdev->regs[UART_LCR] & UART_LCR_DLAB ? 1 : 0;
@@ -228,6 +341,16 @@ static int ns16x50_io_write8(
{
switch ( reg )
{
+ case UART_THR:
+ if ( regs[UART_MCR] & UART_MCR_LOOP )
+ rc = ns16x50_fifo_rx_putchar(vdev, val);
+ else
+ ns16x50_fifo_tx_putchar(vdev, val);
+
+ rc = 0;
+
+ break;
+
case UART_IER:
/*
* NB: Make sure THR interrupt is re-triggered once guest OS
@@ -312,6 +435,14 @@ static int ns16x50_io_read8(
else {
switch ( reg )
{
+ case UART_RBR:
+ rc = ns16x50_fifo_rx_getchar(vdev);
+ if ( rc >= 0 )
+ val = (uint8_t)rc;
+
+ rc = 0;
+ break;
+
case UART_IER:
val = regs[UART_IER];
break;
@@ -499,6 +630,10 @@ static void cf_check ns16x50_deinit(void *arg)
struct vuart_ns16x50 *vdev = arg;
ASSERT(vdev);
+
+ spin_lock(&vdev->lock);
+ ns16x50_fifo_tx_flush(vdev);
+ spin_unlock(&vdev->lock);
}
static void * cf_check ns16x50_alloc(struct domain *d, const struct vuart_info *info)
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 07/15] emul/ns16x50: implement FCR register (write-only)
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
` (5 preceding siblings ...)
2025-08-28 23:54 ` [PATCH v5 06/15] emul/ns16x50: implement THR/RBR registers dmukhin
@ 2025-08-28 23:54 ` dmukhin
2025-08-29 20:38 ` Stefano Stabellini
2025-08-28 23:54 ` [PATCH v5 08/15] emul/ns16x50: implement LCR/LSR registers dmukhin
` (7 subsequent siblings)
14 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:54 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
Add emulation logic for FCR register.
Note, that does not hooks FIFO interrupt moderation to the FIFO management
code.
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- new patch
---
xen/common/emul/vuart/ns16x50.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index efb2f4c6441c..65ca96dd8bd3 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -363,6 +363,30 @@ static int ns16x50_io_write8(
break;
+ case UART_FCR: /* WO */
+ if ( val & UART_FCR_RESERVED0 )
+ ns16x50_warn(vdev, "FCR: attempt to set reserved bit: %x\n",
+ UART_FCR_RESERVED0);
+
+ if ( val & UART_FCR_RESERVED1 )
+ ns16x50_warn(vdev, "FCR: attempt to set reserved bit: %x\n",
+ UART_FCR_RESERVED1);
+
+ if ( val & UART_FCR_CLRX )
+ ns16x50_fifo_rx_reset(vdev);
+
+ if ( val & UART_FCR_CLTX )
+ ns16x50_fifo_tx_flush(vdev);
+
+ if ( val & UART_FCR_ENABLE )
+ val &= UART_FCR_ENABLE | UART_FCR_DMA | UART_FCR_TRG_MASK;
+ else
+ val = 0;
+
+ regs[UART_FCR] = val;
+
+ break;
+
default:
rc = -EINVAL;
break;
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 08/15] emul/ns16x50: implement LCR/LSR registers
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
` (6 preceding siblings ...)
2025-08-28 23:54 ` [PATCH v5 07/15] emul/ns16x50: implement FCR register (write-only) dmukhin
@ 2025-08-28 23:54 ` dmukhin
2025-08-28 23:54 ` [PATCH v5 09/15] emul/ns16x50: implement MCR/MSR registers dmukhin
` (6 subsequent siblings)
14 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:54 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
Add LCR/LSR registers implementation to the I/O port handler.
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- new patch
---
xen/common/emul/vuart/ns16x50.c | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 65ca96dd8bd3..687a7ea2fabe 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -347,6 +347,9 @@ static int ns16x50_io_write8(
else
ns16x50_fifo_tx_putchar(vdev, val);
+ if ( rc == -ENOSPC )
+ regs[UART_LSR] |= UART_LSR_OE;
+
rc = 0;
break;
@@ -387,6 +390,11 @@ static int ns16x50_io_write8(
break;
+ case UART_LCR:
+ regs[UART_LCR] = val;
+ break;
+
+ case UART_LSR: /* RO */
default:
rc = -EINVAL;
break;
@@ -460,6 +468,9 @@ static int ns16x50_io_read8(
switch ( reg )
{
case UART_RBR:
+ /* NB: do not forget to clear overrun condition */
+ regs[UART_LSR] &= ~UART_LSR_OE;
+
rc = ns16x50_fifo_rx_getchar(vdev);
if ( rc >= 0 )
val = (uint8_t)rc;
@@ -480,6 +491,21 @@ static int ns16x50_io_read8(
break;
+ case UART_LCR:
+ val = regs[UART_LCR];
+ break;
+
+ case UART_LSR:
+ val = regs[UART_LSR] | UART_LSR_THRE | UART_LSR_TEMT;
+ if ( ns16x50_fifo_rx_empty(vdev) )
+ val &= ~UART_LSR_DR;
+ else
+ val |= UART_LSR_DR;
+
+ regs[UART_LSR] = val & ~UART_LSR_MASK;
+
+ break;
+
default:
rc = -EINVAL;
break;
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 09/15] emul/ns16x50: implement MCR/MSR registers
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
` (7 preceding siblings ...)
2025-08-28 23:54 ` [PATCH v5 08/15] emul/ns16x50: implement LCR/LSR registers dmukhin
@ 2025-08-28 23:54 ` dmukhin
2025-08-28 23:54 ` [PATCH v5 10/15] emul/ns16x50: implement SCR register dmukhin
` (5 subsequent siblings)
14 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:54 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
Add MCR/MCR registers emulation to the I/O port handler.
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- new patch
---
xen/common/emul/vuart/ns16x50.c | 60 +++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 687a7ea2fabe..670c3c6dba9d 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -394,7 +394,58 @@ static int ns16x50_io_write8(
regs[UART_LCR] = val;
break;
+ case UART_MCR: {
+ uint8_t msr_curr, msr_next, msr_delta;
+
+ msr_curr = regs[UART_MSR];
+ msr_next = 0;
+ msr_delta = 0;
+
+ if ( val & UART_MCR_RESERVED0 )
+ ns16x50_warn(vdev, "MCR: attempt to set reserved bit: %x\n",
+ UART_MCR_RESERVED0);
+
+ if ( val & UART_MCR_TCRTLR )
+ ns16x50_warn(vdev, "MCR: not supported: %x\n",
+ UART_MCR_TCRTLR);
+
+ if ( val & UART_MCR_RESERVED1 )
+ ns16x50_warn(vdev, "MCR: attempt to set reserved bit: %x\n",
+ UART_MCR_RESERVED1);
+
+ /* Set modem status */
+ if ( val & UART_MCR_LOOP )
+ {
+ if ( val & UART_MCR_DTR )
+ msr_next |= UART_MSR_DSR;
+ if ( val & UART_MCR_RTS )
+ msr_next |= UART_MSR_CTS;
+ if ( val & UART_MCR_OUT1 )
+ msr_next |= UART_MSR_RI;
+ if ( val & UART_MCR_OUT2 )
+ msr_next |= UART_MSR_DCD;
+ }
+ else
+ msr_next |= UART_MSR_DCD | UART_MSR_DSR | UART_MSR_CTS;
+
+ /* Calculate changes in modem status */
+ if ( (msr_curr & UART_MSR_CTS) ^ (msr_next & UART_MSR_CTS) )
+ msr_delta |= UART_MSR_DCTS;
+ if ( (msr_curr & UART_MSR_DSR) ^ (msr_next & UART_MSR_DSR) )
+ msr_delta |= UART_MSR_DDSR;
+ if ( (msr_curr & UART_MSR_RI) & (msr_next & UART_MSR_RI) )
+ msr_delta |= UART_MSR_TERI;
+ if ( (msr_curr & UART_MSR_DCD) ^ (msr_next & UART_MSR_DCD) )
+ msr_delta |= UART_MSR_DDCD;
+
+ regs[UART_MCR] = val & UART_MCR_MASK;
+ regs[UART_MSR] = msr_next | msr_delta;
+
+ break;
+ }
+
case UART_LSR: /* RO */
+ case UART_MSR: /* RO */
default:
rc = -EINVAL;
break;
@@ -495,6 +546,10 @@ static int ns16x50_io_read8(
val = regs[UART_LCR];
break;
+ case UART_MCR:
+ val = regs[UART_MCR];
+ break;
+
case UART_LSR:
val = regs[UART_LSR] | UART_LSR_THRE | UART_LSR_TEMT;
if ( ns16x50_fifo_rx_empty(vdev) )
@@ -506,6 +561,11 @@ static int ns16x50_io_read8(
break;
+ case UART_MSR:
+ val = regs[UART_MSR];
+ regs[UART_MSR] &= ~UART_MSR_CHANGE;
+ break;
+
default:
rc = -EINVAL;
break;
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 10/15] emul/ns16x50: implement SCR register
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
` (8 preceding siblings ...)
2025-08-28 23:54 ` [PATCH v5 09/15] emul/ns16x50: implement MCR/MSR registers dmukhin
@ 2025-08-28 23:54 ` dmukhin
2025-08-28 23:54 ` [PATCH v5 11/15] emul/ns16x50: implement put_rx() hook dmukhin
` (4 subsequent siblings)
14 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:54 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
Add SCR register emulation to the I/O port handler.
Firmware (e.g. OVMF) may use SCR during the guest OS boot.
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- new patch
---
xen/common/emul/vuart/ns16x50.c | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 670c3c6dba9d..d57695564329 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -444,6 +444,11 @@ static int ns16x50_io_write8(
break;
}
+ /* NB: Firmware (e.g. OVMF) may rely on SCR presence. */
+ case UART_SCR:
+ regs[UART_SCR] = val;
+ break;
+
case UART_LSR: /* RO */
case UART_MSR: /* RO */
default:
@@ -566,6 +571,10 @@ static int ns16x50_io_read8(
regs[UART_MSR] &= ~UART_MSR_CHANGE;
break;
+ case UART_SCR:
+ val = regs[UART_SCR];
+ break;
+
default:
rc = -EINVAL;
break;
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 11/15] emul/ns16x50: implement put_rx() hook
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
` (9 preceding siblings ...)
2025-08-28 23:54 ` [PATCH v5 10/15] emul/ns16x50: implement SCR register dmukhin
@ 2025-08-28 23:54 ` dmukhin
2025-08-28 23:54 ` [PATCH v5 12/15] emul/ns16550: implement dump_state() hook dmukhin
` (3 subsequent siblings)
14 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:54 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
Implement put_rx() vUART hook for placing a character into the emulated RX
FIFO from console driver.
That implements physical console forwarding to the guest OS over emulated
NS16550.
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- new patch
---
xen/common/emul/vuart/ns16x50.c | 48 ++++++++++++++++++++++++++++++++-
1 file changed, 47 insertions(+), 1 deletion(-)
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index d57695564329..1fb65370d464 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -804,13 +804,59 @@ static void cf_check ns16x50_free(void *arg)
XVFREE(vdev);
}
+static int cf_check ns16x50_put_rx(void *arg, char ch)
+{
+ struct vuart_ns16x50 *vdev = arg;
+ uint8_t *regs;
+ uint8_t dlab;
+ int rc = -EBUSY;
+
+ spin_lock(&vdev->lock);
+
+ dlab = ns16x50_dlab_get(vdev);
+ regs = vdev->regs;
+
+ if ( dlab )
+ ns16x50_debug(vdev, "THR/RBR access disabled: DLAB=1\n");
+ else if ( regs[UART_MCR] & UART_MCR_LOOP )
+ ns16x50_debug(vdev, "THR/RBR access disabled: loopback mode\n");
+ else
+ {
+ struct vuart *vuart = container_of(arg, struct vuart, vdev);
+ struct domain *d = vuart->owner;
+ uint8_t val = 0;
+
+ rc = ns16x50_fifo_rx_putchar(vdev, ch);
+ if ( rc == -ENOSPC )
+ val |= UART_LSR_OE;
+
+ /* NB: UART_LSR_DR is also set when UART_LSR is accessed. */
+ regs[UART_LSR] |= UART_LSR_DR | val;
+
+ /*
+ * Echo the user input on Xen console iff Xen console input is owned
+ * by ns16x50 domain.
+ * NB: use 'console_timestamps=none' to disable Xen timestamps.
+ */
+ if ( is_console_printable(ch) )
+ guest_printk(d, "%c", ch);
+
+ /* FIXME: check FCR when to fire an interrupt */
+ ns16x50_irq_check(vdev);
+ }
+
+ spin_unlock(&vdev->lock);
+
+ return rc;
+}
+
#define ns16x50_emulator \
{ \
.compatible = "ns16550", \
.alloc = ns16x50_alloc, \
.free = ns16x50_free, \
.dump_state = NULL, \
- .put_rx = NULL, \
+ .put_rx = ns16x50_put_rx, \
}
VUART_REGISTER(ns16x50, ns16x50_emulator);
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 12/15] emul/ns16550: implement dump_state() hook
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
` (10 preceding siblings ...)
2025-08-28 23:54 ` [PATCH v5 11/15] emul/ns16x50: implement put_rx() hook dmukhin
@ 2025-08-28 23:54 ` dmukhin
2025-08-28 23:54 ` [PATCH v5 13/15] x86/domain: enable per-domain I/O port bitmaps dmukhin
` (2 subsequent siblings)
14 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:54 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
Implement dump_state() vUART hook for debugging UART state machine over Xen
console. dump_state() prints state of all emulated registers (including
state-less IIR) and state of RX/TX FIFOs.
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- new patch
---
xen/common/emul/vuart/ns16x50.c | 59 ++++++++++++++++++++++++++++++++-
1 file changed, 58 insertions(+), 1 deletion(-)
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 1fb65370d464..5c1be854b544 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -636,6 +636,58 @@ static int ns16x50_io_read(
return rc;
}
+static void cf_check ns16x50_dump_state(void *arg)
+{
+#ifdef CONFIG_VUART_NS16X50_DEBUG
+ struct vuart_ns16x50 *vdev = arg;
+ const struct domain *d = vdev->owner;
+ const struct vuart_info *info = vdev->info;
+ const struct xencons_interface *cons;
+ const uint8_t *regs;
+
+ if ( !vdev )
+ return;
+
+ /* Allow printing state in case of a deadlock. */
+ if ( !spin_trylock(&vdev->lock) )
+ return;
+
+ cons = &vdev->cons;
+ regs = &vdev->regs[0];
+
+ printk("Virtual " pr_prefix " (%s) I/O port 0x%04"PRIx64" IRQ#%d owner %pd\n",
+ vdev->name, info->base_addr, info->irq, d);
+
+ printk(" RX FIFO size %ld in_prod %d in_cons %d used %d\n",
+ ARRAY_SIZE(cons->in), cons->in_prod, cons->in_cons,
+ cons->in_prod - cons->in_cons);
+
+ printk(" TX FIFO size %ld out_prod %d out_cons %d used %d\n",
+ ARRAY_SIZE(cons->out), cons->out_prod, cons->out_cons,
+ cons->out_prod - cons->out_cons);
+
+ printk(" %02"PRIx8" RBR %02"PRIx8" THR %02"PRIx8" DLL %02"PRIx8" DLM %02"PRIx8"\n",
+ UART_RBR,
+ cons->in[MASK_XENCONS_IDX(cons->in_prod, cons)],
+ cons->out[MASK_XENCONS_IDX(cons->out_prod, cons)],
+ regs[NS16X50_REGS_NUM + UART_DLL],
+ regs[NS16X50_REGS_NUM + UART_DLM]);
+
+ printk(" %02"PRIx8" IER %02"PRIx8"\n", UART_IER, regs[UART_IER]);
+
+ printk(" %02"PRIx8" FCR %02"PRIx8" IIR %02"PRIx8"\n",
+ UART_FCR, regs[UART_FCR], ns16x50_iir_get(vdev));
+
+ printk(" %02"PRIx8" LCR %02"PRIx8"\n", UART_LCR, regs[UART_LCR]);
+ printk(" %02"PRIx8" MCR %02"PRIx8"\n", UART_MCR, regs[UART_MCR]);
+ printk(" %02"PRIx8" LSR %02"PRIx8"\n", UART_LSR, regs[UART_LSR]);
+ printk(" %02"PRIx8" MSR %02"PRIx8"\n", UART_MSR, regs[UART_MSR]);
+ printk(" %02"PRIx8" SCR %02"PRIx8"\n", UART_SCR, regs[UART_SCR]);
+
+ spin_unlock(&vdev->lock);
+#endif /* CONFIG_VUART_NS16X50_DEBUG */
+}
+
/*
* Emulate I/O access to ns16x50 register.
* Note, emulation always returns X86EMUL_OKAY, once I/O port trap is enabled.
@@ -712,6 +764,9 @@ static int cf_check ns16x50_io_handle(
spin_unlock(&vdev->lock);
+ if ( ns16x50_log_level >= 3 )
+ ns16x50_dump_state(vdev);
+
out:
rcu_unlock_domain(d);
@@ -846,6 +901,8 @@ static int cf_check ns16x50_put_rx(void *arg, char ch)
}
spin_unlock(&vdev->lock);
+ if ( ns16x50_log_level >= 3 )
+ ns16x50_dump_state(vdev);
return rc;
}
@@ -855,7 +912,7 @@ static int cf_check ns16x50_put_rx(void *arg, char ch)
.compatible = "ns16550", \
.alloc = ns16x50_alloc, \
.free = ns16x50_free, \
- .dump_state = NULL, \
+ .dump_state = ns16x50_dump_state, \
.put_rx = ns16x50_put_rx, \
}
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 13/15] x86/domain: enable per-domain I/O port bitmaps
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
` (11 preceding siblings ...)
2025-08-28 23:54 ` [PATCH v5 12/15] emul/ns16550: implement dump_state() hook dmukhin
@ 2025-08-28 23:54 ` dmukhin
2025-08-29 21:43 ` Stefano Stabellini
2025-08-28 23:54 ` [PATCH v5 14/15] xen/domain: allocate d->irq_caps before arch-specific initialization dmukhin
2025-08-28 23:54 ` [PATCH v5 15/15] emul/ns16x50: implement IRQ emulation via vIOAPIC dmukhin
14 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:54 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
Current design enables all HVM domains share the same I/O port bitmap.
It is necessary for domains crafting its own I/O port address space depending
on the user configuration.
Ensure NS16550 emulator does not share I/O ports with the physical I/O ports,
which is essential for emulation in PVH hwdom case (dom0).
Not a functional change.
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- new patch
- Link tp v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-4-dmukhin@ford.com/
---
xen/arch/x86/Makefile | 1 +
xen/arch/x86/dom0_build.c | 111 +--------------
xen/arch/x86/hvm/hvm.c | 35 +----
xen/arch/x86/hvm/nestedhvm.c | 8 +-
xen/arch/x86/hvm/quirks.c | 3 -
xen/arch/x86/hvm/svm/nestedsvm.c | 2 +-
xen/arch/x86/hvm/vmx/vvmx.c | 4 +-
xen/arch/x86/include/asm/hvm/nestedhvm.h | 3 +-
xen/arch/x86/include/asm/hvm/support.h | 2 -
xen/arch/x86/include/asm/iocap.h | 2 +
xen/arch/x86/ioport.c | 163 +++++++++++++++++++++++
xen/arch/x86/pv/dom0_build.c | 4 +
xen/common/emul/vuart/ns16x50.c | 11 ++
13 files changed, 200 insertions(+), 149 deletions(-)
create mode 100644 xen/arch/x86/ioport.c
diff --git a/xen/arch/x86/Makefile b/xen/arch/x86/Makefile
index 9d67ea7cd4a8..5726ecc180eb 100644
--- a/xen/arch/x86/Makefile
+++ b/xen/arch/x86/Makefile
@@ -44,6 +44,7 @@ obj-y += msi.o
obj-y += msr.o
obj-$(CONFIG_INDIRECT_THUNK) += indirect-thunk.o
obj-$(CONFIG_RETURN_THUNK) += indirect-thunk.o
+obj-y += ioport.o
obj-$(CONFIG_PV) += ioport_emulate.o
obj-y += irq.o
obj-$(CONFIG_KEXEC) += machine_kexec.o
diff --git a/xen/arch/x86/dom0_build.c b/xen/arch/x86/dom0_build.c
index 0b467fd4a4fc..26202b33345c 100644
--- a/xen/arch/x86/dom0_build.c
+++ b/xen/arch/x86/dom0_build.c
@@ -298,9 +298,6 @@ int __init parse_arch_dom0_param(const char *s, const char *e)
return 0;
}
-static char __initdata opt_dom0_ioports_disable[200] = "";
-string_param("dom0_ioports_disable", opt_dom0_ioports_disable);
-
static bool __initdata ro_hpet = true;
boolean_param("ro-hpet", ro_hpet);
@@ -433,122 +430,20 @@ unsigned long __init dom0_compute_nr_pages(
return nr_pages;
}
-static void __init process_dom0_ioports_disable(struct domain *dom0)
-{
- unsigned long io_from, io_to;
- char *t, *s = opt_dom0_ioports_disable;
- const char *u;
-
- if ( *s == '\0' )
- return;
-
- while ( (t = strsep(&s, ",")) != NULL )
- {
- io_from = simple_strtoul(t, &u, 16);
- if ( u == t )
- {
- parse_error:
- printk("Invalid ioport range <%s> "
- "in dom0_ioports_disable, skipping\n", t);
- continue;
- }
-
- if ( *u == '\0' )
- io_to = io_from;
- else if ( *u == '-' )
- io_to = simple_strtoul(u + 1, &u, 16);
- else
- goto parse_error;
-
- if ( (*u != '\0') || (io_to < io_from) || (io_to >= 65536) )
- goto parse_error;
-
- printk("Disabling dom0 access to ioport range %04lx-%04lx\n",
- io_from, io_to);
-
- if ( ioports_deny_access(dom0, io_from, io_to) != 0 )
- BUG();
- }
-}
-
+/* Modify I/O memory access permissions. */
int __init dom0_setup_permissions(struct domain *d)
{
unsigned long mfn;
- unsigned int i, offs;
- int rc;
+ unsigned int i;
+ int rc = 0;
if ( pv_shim )
return 0;
- /* The hardware domain is initially permitted full I/O capabilities. */
- rc = ioports_permit_access(d, 0, 0xFFFF);
rc |= iomem_permit_access(d, 0UL,
PFN_DOWN(1UL << domain_max_paddr_bits(d)) - 1);
rc |= irqs_permit_access(d, 1, nr_irqs_gsi - 1);
- /* Modify I/O port access permissions. */
-
- for ( offs = 0, i = ISOLATE_LSB(i8259A_alias_mask) ?: 2;
- offs <= i8259A_alias_mask; offs += i )
- {
- if ( offs & ~i8259A_alias_mask )
- continue;
- /* Master Interrupt Controller (PIC). */
- rc |= ioports_deny_access(d, 0x20 + offs, 0x21 + offs);
- /* Slave Interrupt Controller (PIC). */
- rc |= ioports_deny_access(d, 0xA0 + offs, 0xA1 + offs);
- }
-
- /* ELCR of both PICs. */
- rc |= ioports_deny_access(d, 0x4D0, 0x4D1);
-
- /* Interval Timer (PIT). */
- for ( offs = 0, i = ISOLATE_LSB(pit_alias_mask) ?: 4;
- offs <= pit_alias_mask; offs += i )
- if ( !(offs & ~pit_alias_mask) )
- rc |= ioports_deny_access(d, PIT_CH0 + offs, PIT_MODE + offs);
-
- /* PIT Channel 2 / PC Speaker Control. */
- rc |= ioports_deny_access(d, 0x61, 0x61);
-
- /* INIT# and alternative A20M# control. */
- rc |= ioports_deny_access(d, 0x92, 0x92);
-
- /* IGNNE# control. */
- rc |= ioports_deny_access(d, 0xF0, 0xF0);
-
- /* ACPI PM Timer. */
- if ( pmtmr_ioport )
- rc |= ioports_deny_access(d, pmtmr_ioport, pmtmr_ioport + 3);
-
- /* Reset control. */
- rc |= ioports_deny_access(d, 0xCF9, 0xCF9);
-
- /* PCI configuration space (NB. 0xCF8 has special treatment). */
- rc |= ioports_deny_access(d, 0xCFC, 0xCFF);
-
-#ifdef CONFIG_HVM
- if ( is_hvm_domain(d) )
- {
- /* ISA DMA controller, channels 0-3 (incl possible aliases). */
- rc |= ioports_deny_access(d, 0x00, 0x1F);
- /* ISA DMA controller, page registers (incl various reserved ones). */
- rc |= ioports_deny_access(d, 0x80 + !!hvm_port80_allowed, 0x8F);
- /* ISA DMA controller, channels 4-7 (incl usual aliases). */
- rc |= ioports_deny_access(d, 0xC0, 0xDF);
-
- /* HVM debug console IO port. */
- rc |= ioports_deny_access(d, XEN_HVM_DEBUGCONS_IOPORT,
- XEN_HVM_DEBUGCONS_IOPORT);
- if ( amd_acpi_c1e_quirk )
- rc |= ioports_deny_access(d, acpi_smi_cmd, acpi_smi_cmd);
- }
-#endif
- /* Command-line I/O ranges. */
- process_dom0_ioports_disable(d);
-
- /* Modify I/O memory access permissions. */
-
/* Local APIC. */
if ( mp_lapic_addr != 0 )
{
diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c
index 26760cf995df..12736fc61c11 100644
--- a/xen/arch/x86/hvm/hvm.c
+++ b/xen/arch/x86/hvm/hvm.c
@@ -51,6 +51,7 @@
#include <asm/hvm/vm_event.h>
#include <asm/hvm/vpt.h>
#include <asm/i387.h>
+#include <asm/iocap.h>
#include <asm/mc146818rtc.h>
#include <asm/mce.h>
#include <asm/monitor.h>
@@ -81,14 +82,6 @@ integer_param("hvm_debug", opt_hvm_debug_level);
struct hvm_function_table __ro_after_init hvm_funcs;
-/*
- * The I/O permission bitmap is globally shared by all HVM guests except
- * the hardware domain which needs a more permissive one.
- */
-#define HVM_IOBITMAP_SIZE (3 * PAGE_SIZE)
-unsigned long __section(".bss.page_aligned") __aligned(PAGE_SIZE)
- hvm_io_bitmap[HVM_IOBITMAP_SIZE / BYTES_PER_LONG];
-
/* Xen command-line option to enable HAP */
static bool __initdata opt_hap_enabled = true;
boolean_param("hap", opt_hap_enabled);
@@ -205,15 +198,6 @@ static int __init cf_check hvm_enable(void)
if ( opt_hvm_fep )
warning_add(warning_hvm_fep);
- /*
- * Allow direct access to the PC debug ports 0x80 and 0xed (they are
- * often used for I/O delays, but the vmexits simply slow things down).
- */
- memset(hvm_io_bitmap, ~0, sizeof(hvm_io_bitmap));
- if ( hvm_port80_allowed )
- __clear_bit(0x80, hvm_io_bitmap);
- __clear_bit(0xed, hvm_io_bitmap);
-
register_cpu_notifier(&cpu_nfb);
return 0;
@@ -645,19 +629,12 @@ int hvm_domain_initialise(struct domain *d,
rwlock_init(&d->arch.hvm.pl_time->pt_migrate);
- /* Set the default IO Bitmap. */
- if ( is_hardware_domain(d) )
+ rc = ioports_setup_access(d);
+ if ( rc )
{
- d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);
- if ( d->arch.hvm.io_bitmap == NULL )
- {
- rc = -ENOMEM;
- goto fail1;
- }
- memset(d->arch.hvm.io_bitmap, ~0, HVM_IOBITMAP_SIZE);
+ printk("%pd failed to setup I/O bitmap: %d\n", d, rc);
+ goto fail1;
}
- else
- d->arch.hvm.io_bitmap = hvm_io_bitmap;
register_g2m_portio_handler(d);
register_vpci_portio_handler(d);
@@ -684,6 +661,8 @@ int hvm_domain_initialise(struct domain *d,
break;
}
+ BUG_ON(!d->arch.ioport_caps);
+
vpic_init(d);
rc = vioapic_init(d);
diff --git a/xen/arch/x86/hvm/nestedhvm.c b/xen/arch/x86/hvm/nestedhvm.c
index bddd77d8109b..d4e03123d910 100644
--- a/xen/arch/x86/hvm/nestedhvm.c
+++ b/xen/arch/x86/hvm/nestedhvm.c
@@ -107,7 +107,7 @@ nestedhvm_vmcx_flushtlb(struct p2m_domain *p2m)
* The users of the bitmap patterns are in SVM/VMX specific code.
*
* bitmap port 0x80 port 0xed
- * hvm_io_bitmap cleared cleared
+ * hvm.io_bitmap cleared cleared
* iomap[0] cleared set
* iomap[1] set cleared
* iomap[2] set set
@@ -115,7 +115,7 @@ nestedhvm_vmcx_flushtlb(struct p2m_domain *p2m)
static int __init cf_check nestedhvm_setup(void)
{
- /* Same format and size as hvm_io_bitmap (Intel needs only 2 pages). */
+ /* Same format and size as hvm.io_bitmap (Intel needs only 2 pages). */
unsigned nr = cpu_has_vmx ? 2 : 3;
unsigned int i, order = get_order_from_pages(nr);
@@ -165,7 +165,7 @@ static int __init cf_check nestedhvm_setup(void)
__initcall(nestedhvm_setup);
unsigned long *
-nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed)
+nestedhvm_vcpu_iomap_get(struct vcpu *v, bool ioport_80, bool ioport_ed)
{
int i;
@@ -174,7 +174,7 @@ nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed)
if (ioport_80 == 0) {
if (ioport_ed == 0)
- return hvm_io_bitmap;
+ return v->domain->arch.hvm.io_bitmap;
i = 0;
} else {
if (ioport_ed == 0)
diff --git a/xen/arch/x86/hvm/quirks.c b/xen/arch/x86/hvm/quirks.c
index 9202f5a47fe9..f4d95441fcff 100644
--- a/xen/arch/x86/hvm/quirks.c
+++ b/xen/arch/x86/hvm/quirks.c
@@ -73,9 +73,6 @@ static int __init cf_check check_port80(void)
dmi_check_system(hvm_no_port80_dmi_table);
- if ( !hvm_port80_allowed )
- __set_bit(0x80, hvm_io_bitmap);
-
return 0;
}
__initcall(check_port80);
diff --git a/xen/arch/x86/hvm/svm/nestedsvm.c b/xen/arch/x86/hvm/svm/nestedsvm.c
index dc2b6a42534a..cc8500b61665 100644
--- a/xen/arch/x86/hvm/svm/nestedsvm.c
+++ b/xen/arch/x86/hvm/svm/nestedsvm.c
@@ -381,7 +381,7 @@ static int nsvm_vmrun_permissionmap(struct vcpu *v, bool viopm)
hvm_unmap_guest_frame(ns_viomap, 0);
}
- svm->ns_iomap = nestedhvm_vcpu_iomap_get(ioport_80, ioport_ed);
+ svm->ns_iomap = nestedhvm_vcpu_iomap_get(v, ioport_80, ioport_ed);
nv->nv_ioport80 = ioport_80;
nv->nv_ioportED = ioport_ed;
diff --git a/xen/arch/x86/hvm/vmx/vvmx.c b/xen/arch/x86/hvm/vmx/vvmx.c
index e4f3a5fe4c71..4da3e6e90e6c 100644
--- a/xen/arch/x86/hvm/vmx/vvmx.c
+++ b/xen/arch/x86/hvm/vmx/vvmx.c
@@ -554,7 +554,7 @@ unsigned long *_shadow_io_bitmap(struct vcpu *v)
port80 = bitmap[0x80 >> 3] & (1 << (0x80 & 0x7)) ? 1 : 0;
portED = bitmap[0xed >> 3] & (1 << (0xed & 0x7)) ? 1 : 0;
- return nestedhvm_vcpu_iomap_get(port80, portED);
+ return nestedhvm_vcpu_iomap_get(v, port80, portED);
}
static void update_msrbitmap(struct vcpu *v, uint32_t shadow_ctrl)
@@ -622,7 +622,7 @@ void nvmx_update_exec_control(struct vcpu *v, u32 host_cntrl)
* L1 VMM doesn't intercept IO instruction.
* Use host configuration and reset IO_BITMAP
*/
- bitmap = hvm_io_bitmap;
+ bitmap = v->domain->arch.hvm.io_bitmap;
}
else {
/* use IO bitmap */
diff --git a/xen/arch/x86/include/asm/hvm/nestedhvm.h b/xen/arch/x86/include/asm/hvm/nestedhvm.h
index ea2c1bc328c7..d691ccb07dd6 100644
--- a/xen/arch/x86/include/asm/hvm/nestedhvm.h
+++ b/xen/arch/x86/include/asm/hvm/nestedhvm.h
@@ -50,7 +50,8 @@ int nestedhvm_hap_nested_page_fault(struct vcpu *v, paddr_t *L2_gpa,
struct npfec npfec);
/* IO permission map */
-unsigned long *nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed);
+unsigned long *nestedhvm_vcpu_iomap_get(struct vcpu *v,
+ bool ioport_80, bool ioport_ed);
/* Misc */
#define nestedhvm_paging_mode_hap(v) (!!nhvm_vmcx_hap_enabled(v))
diff --git a/xen/arch/x86/include/asm/hvm/support.h b/xen/arch/x86/include/asm/hvm/support.h
index 2a7ba36af06f..7e36d00cc188 100644
--- a/xen/arch/x86/include/asm/hvm/support.h
+++ b/xen/arch/x86/include/asm/hvm/support.h
@@ -41,8 +41,6 @@ extern unsigned int opt_hvm_debug_level;
#define HVM_DBG_LOG(level, _f, _a...) do {} while (0)
#endif
-extern unsigned long hvm_io_bitmap[];
-
enum hvm_translation_result {
HVMTRANS_okay,
HVMTRANS_bad_linear_to_gfn,
diff --git a/xen/arch/x86/include/asm/iocap.h b/xen/arch/x86/include/asm/iocap.h
index f948b7186e95..1083f6171cf7 100644
--- a/xen/arch/x86/include/asm/iocap.h
+++ b/xen/arch/x86/include/asm/iocap.h
@@ -22,6 +22,8 @@
#define cache_flush_permitted(d) \
(has_arch_io_resources(d) || has_arch_pdevs(d))
+int ioports_setup_access(struct domain *d);
+
static inline int ioports_permit_access(struct domain *d, unsigned long s,
unsigned long e)
{
diff --git a/xen/arch/x86/ioport.c b/xen/arch/x86/ioport.c
new file mode 100644
index 000000000000..dbcd52d37a4f
--- /dev/null
+++ b/xen/arch/x86/ioport.c
@@ -0,0 +1,163 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Guest I/O port address space configuration.
+ *
+ * Copyright 2025 Ford Motor Company
+ */
+
+#include <xen/domain.h>
+#include <xen/param.h>
+
+#include <asm/amd.h>
+#include <asm/acpi.h>
+#include <asm/io-ports.h>
+#include <asm/iocap.h>
+#include <asm/pv/shim.h>
+#include <asm/setup.h>
+
+static char __initdata opt_dom0_ioports_disable[200] = "";
+string_param("dom0_ioports_disable", opt_dom0_ioports_disable);
+
+/*
+ * The I/O permission bitmap size.
+ * See: comment in nestedhvm_setup()
+ */
+#define HVM_IOBITMAP_SIZE (3 * PAGE_SIZE)
+
+/* Hide user-defined I/O ports from the guest OS. */
+static void process_dom0_ioports_disable(struct domain *dom0)
+{
+ unsigned long io_from, io_to;
+ char *t, *s = opt_dom0_ioports_disable;
+ const char *u;
+
+ if ( *s == '\0' )
+ return;
+
+ while ( (t = strsep(&s, ",")) != NULL )
+ {
+ io_from = simple_strtoul(t, &u, 16);
+ if ( u == t )
+ {
+ parse_error:
+ printk("Invalid ioport range <%s> "
+ "in dom0_ioports_disable, skipping\n", t);
+ continue;
+ }
+
+ if ( *u == '\0' )
+ io_to = io_from;
+ else if ( *u == '-' )
+ io_to = simple_strtoul(u + 1, &u, 16);
+ else
+ goto parse_error;
+
+ if ( (*u != '\0') || (io_to < io_from) || (io_to >= 65536) )
+ goto parse_error;
+
+ printk("Disabling dom0 access to ioport range %04lx-%04lx\n",
+ io_from, io_to);
+
+ if ( ioports_deny_access(dom0, io_from, io_to) != 0 )
+ BUG();
+ }
+}
+
+/* Set the default IO Bitmap. */
+int ioports_setup_access(struct domain *d)
+{
+ unsigned int i, offs;
+ int rc;
+
+ if ( pv_shim )
+ return 0;
+
+#ifdef CONFIG_HVM
+ d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);
+ if ( d->arch.hvm.io_bitmap == NULL )
+ return -ENOMEM;
+
+ memset(d->arch.hvm.io_bitmap, ~0, HVM_IOBITMAP_SIZE);
+
+ if ( !is_hardware_domain(d) )
+ {
+ /*
+ * Allow direct access to the PC debug ports 0x80 and 0xed (they are
+ * often used for I/O delays, but the vmexits simply slow things down).
+ */
+ if ( hvm_port80_allowed )
+ __clear_bit(0x80, d->arch.hvm.io_bitmap);
+
+ __clear_bit(0xed, d->arch.hvm.io_bitmap);
+
+ return 0;
+ }
+#endif
+
+ /* The hardware domain is initially permitted full I/O capabilities. */
+ rc = ioports_permit_access(d, 0, 0xFFFF);
+
+ /* Modify I/O port access permissions. */
+
+ for ( offs = 0, i = ISOLATE_LSB(i8259A_alias_mask) ?: 2;
+ offs <= i8259A_alias_mask; offs += i )
+ {
+ if ( offs & ~i8259A_alias_mask )
+ continue;
+ /* Master Interrupt Controller (PIC). */
+ rc |= ioports_deny_access(d, 0x20 + offs, 0x21 + offs);
+ /* Slave Interrupt Controller (PIC). */
+ rc |= ioports_deny_access(d, 0xA0 + offs, 0xA1 + offs);
+ }
+
+ /* ELCR of both PICs. */
+ rc |= ioports_deny_access(d, 0x4D0, 0x4D1);
+
+ /* Interval Timer (PIT). */
+ for ( offs = 0, i = ISOLATE_LSB(pit_alias_mask) ?: 4;
+ offs <= pit_alias_mask; offs += i )
+ if ( !(offs & ~pit_alias_mask) )
+ rc |= ioports_deny_access(d, PIT_CH0 + offs, PIT_MODE + offs);
+
+ /* PIT Channel 2 / PC Speaker Control. */
+ rc |= ioports_deny_access(d, 0x61, 0x61);
+
+ /* INIT# and alternative A20M# control. */
+ rc |= ioports_deny_access(d, 0x92, 0x92);
+
+ /* IGNNE# control. */
+ rc |= ioports_deny_access(d, 0xF0, 0xF0);
+
+ /* ACPI PM Timer. */
+ if ( pmtmr_ioport )
+ rc |= ioports_deny_access(d, pmtmr_ioport, pmtmr_ioport + 3);
+
+ /* Reset control. */
+ rc |= ioports_deny_access(d, 0xCF9, 0xCF9);
+
+ /* PCI configuration space (NB. 0xCF8 has special treatment). */
+ rc |= ioports_deny_access(d, 0xCFC, 0xCFF);
+
+#ifdef CONFIG_HVM
+ if ( is_hvm_domain(d) )
+ {
+ /* ISA DMA controller, channels 0-3 (incl possible aliases). */
+ rc |= ioports_deny_access(d, 0x00, 0x1F);
+ /* ISA DMA controller, page registers (incl various reserved ones). */
+ rc |= ioports_deny_access(d, 0x80 + !!hvm_port80_allowed, 0x8F);
+ /* ISA DMA controller, channels 4-7 (incl usual aliases). */
+ rc |= ioports_deny_access(d, 0xC0, 0xDF);
+
+ /* HVM debug console IO port. */
+ rc |= ioports_deny_access(d, XEN_HVM_DEBUGCONS_IOPORT,
+ XEN_HVM_DEBUGCONS_IOPORT);
+ if ( amd_acpi_c1e_quirk )
+ rc |= ioports_deny_access(d, acpi_smi_cmd, acpi_smi_cmd);
+ }
+#endif
+
+ /* Command-line I/O ranges. */
+ process_dom0_ioports_disable(d);
+
+ return rc;
+}
diff --git a/xen/arch/x86/pv/dom0_build.c b/xen/arch/x86/pv/dom0_build.c
index 21158ce1812e..2b8b4d869ee7 100644
--- a/xen/arch/x86/pv/dom0_build.c
+++ b/xen/arch/x86/pv/dom0_build.c
@@ -17,6 +17,7 @@
#include <asm/bootinfo.h>
#include <asm/bzimage.h>
#include <asm/dom0_build.h>
+#include <asm/iocap.h>
#include <asm/guest.h>
#include <asm/page.h>
#include <asm/pv/mm.h>
@@ -1033,6 +1034,9 @@ static int __init dom0_construct(const struct boot_domain *bd)
if ( test_bit(XENFEAT_supervisor_mode_kernel, parms.f_required) )
panic("Dom0 requires supervisor-mode execution\n");
+ rc = ioports_setup_access(d);
+ BUG_ON(rc != 0);
+
rc = dom0_setup_permissions(d);
BUG_ON(rc != 0);
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 5c1be854b544..8860f25ffdeb 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -780,9 +780,20 @@ static int ns16x50_init(void *arg)
struct vuart_ns16x50 *vdev = arg;
const struct vuart_info *info = vdev->info;
struct domain *d = vdev->owner;
+ int rc;
ASSERT(vdev);
+ /* Disallow sharing physical I/O port */
+ rc = ioports_deny_access(d, info->base_addr,
+ info->base_addr + info->size - 1);
+ if ( rc )
+ {
+ ns16x50_err(info, " virtual I/O port range [0x%04lx"PRIx64"..0x%04lx"PRIx64"]: conflict w/ physical range\n",
+ info->base_addr, info->base_addr + info->size - 1);
+ return rc;
+ }
+
/* NB: report 115200 baud rate. */
vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 14/15] xen/domain: allocate d->irq_caps before arch-specific initialization
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
` (12 preceding siblings ...)
2025-08-28 23:54 ` [PATCH v5 13/15] x86/domain: enable per-domain I/O port bitmaps dmukhin
@ 2025-08-28 23:54 ` dmukhin
2025-08-29 21:46 ` Stefano Stabellini
2025-08-28 23:54 ` [PATCH v5 15/15] emul/ns16x50: implement IRQ emulation via vIOAPIC dmukhin
14 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:54 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
Make sure that NS16550 emulator does not share virtual device IRQ with the
physical one. This is needed for enabling NS16550 emulator for PVH hwdom
(dom0).
To do that, move per-domain interrupt rangeset allocation before arch-specific
code. Add irqs_setup_access() to setup the initial rangeset.
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- new patch
- Link to original patch in v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-4-dmukhin@ford.com/
---
xen/arch/x86/dom0_build.c | 1 -
xen/arch/x86/hvm/dom0_build.c | 7 +++++++
xen/arch/x86/include/asm/irq.h | 2 ++
xen/arch/x86/irq.c | 8 ++++++++
xen/arch/x86/pv/dom0_build.c | 3 +++
xen/common/domain.c | 8 ++++++--
xen/common/emul/vuart/ns16x50.c | 9 +++++++++
7 files changed, 35 insertions(+), 3 deletions(-)
diff --git a/xen/arch/x86/dom0_build.c b/xen/arch/x86/dom0_build.c
index 26202b33345c..9dc87efbf3e8 100644
--- a/xen/arch/x86/dom0_build.c
+++ b/xen/arch/x86/dom0_build.c
@@ -442,7 +442,6 @@ int __init dom0_setup_permissions(struct domain *d)
rc |= iomem_permit_access(d, 0UL,
PFN_DOWN(1UL << domain_max_paddr_bits(d)) - 1);
- rc |= irqs_permit_access(d, 1, nr_irqs_gsi - 1);
/* Local APIC. */
if ( mp_lapic_addr != 0 )
diff --git a/xen/arch/x86/hvm/dom0_build.c b/xen/arch/x86/hvm/dom0_build.c
index 5551f9044836..245a42dec9aa 100644
--- a/xen/arch/x86/hvm/dom0_build.c
+++ b/xen/arch/x86/hvm/dom0_build.c
@@ -1348,6 +1348,13 @@ int __init dom0_construct_pvh(const struct boot_domain *bd)
*/
pvh_setup_mmcfg(d);
+ rc = irqs_setup_access(d);
+ if ( rc )
+ {
+ printk("%pd unable to setup IRQ rangeset: %d\n", d, rc);
+ return rc;
+ }
+
/*
* Setup permissions early so that calls to add MMIO regions to the
* p2m as part of vPCI setup don't fail due to permission checks.
diff --git a/xen/arch/x86/include/asm/irq.h b/xen/arch/x86/include/asm/irq.h
index 8c81f66434a8..8bffec3bbfee 100644
--- a/xen/arch/x86/include/asm/irq.h
+++ b/xen/arch/x86/include/asm/irq.h
@@ -231,4 +231,6 @@ int allocate_and_map_gsi_pirq(struct domain *d, int index, int *pirq_p);
int allocate_and_map_msi_pirq(struct domain *d, int index, int *pirq_p,
int type, struct msi_info *msi);
+int irqs_setup_access(struct domain *d);
+
#endif /* _ASM_HW_IRQ_H */
diff --git a/xen/arch/x86/irq.c b/xen/arch/x86/irq.c
index 556134f85aa0..079277be719d 100644
--- a/xen/arch/x86/irq.c
+++ b/xen/arch/x86/irq.c
@@ -3046,3 +3046,11 @@ int allocate_and_map_msi_pirq(struct domain *d, int index, int *pirq_p,
return ret;
}
+
+int irqs_setup_access(struct domain *d)
+{
+ if ( is_hardware_domain(d) )
+ return irqs_permit_access(d, 1, nr_irqs_gsi - 1);
+
+ return 0;
+}
diff --git a/xen/arch/x86/pv/dom0_build.c b/xen/arch/x86/pv/dom0_build.c
index 2b8b4d869ee7..1a092b802833 100644
--- a/xen/arch/x86/pv/dom0_build.c
+++ b/xen/arch/x86/pv/dom0_build.c
@@ -1037,6 +1037,9 @@ static int __init dom0_construct(const struct boot_domain *bd)
rc = ioports_setup_access(d);
BUG_ON(rc != 0);
+ rc = irqs_setup_access(d);
+ BUG_ON(rc != 0);
+
rc = dom0_setup_permissions(d);
BUG_ON(rc != 0);
diff --git a/xen/common/domain.c b/xen/common/domain.c
index 104e917f07e3..eb83e3198f37 100644
--- a/xen/common/domain.c
+++ b/xen/common/domain.c
@@ -950,6 +950,11 @@ struct domain *domain_create(domid_t domid,
radix_tree_init(&d->pirq_tree);
#endif
+ err = -ENOMEM;
+ d->irq_caps = rangeset_new(d, "Interrupts", 0);
+ if ( !d->irq_caps )
+ goto fail;
+
if ( (err = arch_domain_create(d, config, flags)) != 0 )
goto fail;
init_status |= INIT_arch;
@@ -959,8 +964,7 @@ struct domain *domain_create(domid_t domid,
err = -ENOMEM;
d->iomem_caps = rangeset_new(d, "I/O Memory", RANGESETF_prettyprint_hex);
- d->irq_caps = rangeset_new(d, "Interrupts", 0);
- if ( !d->iomem_caps || !d->irq_caps )
+ if ( !d->iomem_caps )
goto fail;
if ( (err = xsm_domain_create(XSM_HOOK, d, config->ssidref)) != 0 )
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 8860f25ffdeb..aea38304b60c 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -794,6 +794,15 @@ static int ns16x50_init(void *arg)
return rc;
}
+ /* Disallow sharing physical IRQ */
+ rc = irq_deny_access(d, info->irq);
+ if ( rc )
+ {
+ ns16x50_err(info, "virtual IRQ#%d: conflict w/ physical IRQ: %d\n",
+ info->irq, rc);
+ return rc;
+ }
+
/* NB: report 115200 baud rate. */
vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* [PATCH v5 15/15] emul/ns16x50: implement IRQ emulation via vIOAPIC
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
` (13 preceding siblings ...)
2025-08-28 23:54 ` [PATCH v5 14/15] xen/domain: allocate d->irq_caps before arch-specific initialization dmukhin
@ 2025-08-28 23:54 ` dmukhin
2025-08-29 22:21 ` Stefano Stabellini
14 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-08-28 23:54 UTC (permalink / raw)
To: xen-devel
Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
roger.pau, sstabellini, dmukhin
From: Denis Mukhin <dmukhin@ford.com>
PVH domains use vIOAPIC, not vPIC and NS16550 emulates ISA IRQs which cannot
be asserted on vIOAPIC.
{map,unmap}_domain_emuirq_pirq() infrastructure is modified by adding new
type of interrupt resources 'IRQ_EMU' which means 'emulated device IRQ'
(similarly to IRQ_MSI_EMU).
This is necessary to for IOAPIC emulation code to skip IRQ->PIRQ mapping
(vioapic_hwdom_map_gsi()) when guest OS unmasks vIOAPIC pin corresponding to
virtual device's IRQ.
Also, hvm_gsi_eoi() is modified to trigger assertion in hvm_gsi_deassert()
path for ISA IRQs.
Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v4:
- dropped xl bits
- cosmetic renames
- fix hvm_gsi_eoi()
- Link to v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-8-dmukhin@ford.com/
---
xen/arch/x86/domain.c | 2 +-
xen/arch/x86/hvm/vioapic.c | 10 ++++++++++
xen/arch/x86/include/asm/irq.h | 3 ++-
xen/arch/x86/irq.c | 4 ++--
xen/arch/x86/physdev.c | 8 ++++----
xen/common/emul/vuart/ns16x50.c | 32 +++++++++++++++++++++++++++++--
xen/drivers/passthrough/x86/hvm.c | 13 ++++++++-----
7 files changed, 57 insertions(+), 15 deletions(-)
diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c
index 19fd86ce88d2..0815d0b31827 100644
--- a/xen/arch/x86/domain.c
+++ b/xen/arch/x86/domain.c
@@ -1048,7 +1048,7 @@ int arch_domain_soft_reset(struct domain *d)
write_lock(&d->event_lock);
for ( i = 0; i < d->nr_pirqs ; i++ )
{
- if ( domain_pirq_to_emuirq(d, i) != IRQ_UNBOUND )
+ if ( domain_irq_to_emuirq(d, i) != IRQ_UNBOUND )
{
ret = unmap_domain_pirq_emuirq(d, i);
if ( ret )
diff --git a/xen/arch/x86/hvm/vioapic.c b/xen/arch/x86/hvm/vioapic.c
index 7c725f9e471f..fd073f6fba4b 100644
--- a/xen/arch/x86/hvm/vioapic.c
+++ b/xen/arch/x86/hvm/vioapic.c
@@ -177,6 +177,16 @@ static int vioapic_hwdom_map_gsi(unsigned int gsi, unsigned int trig,
ASSERT(is_hardware_domain(currd));
+ /*
+ * Interrupt is claimed by one of the platform virtual devices (e.g.
+ * NS16550); do nothing.
+ */
+ write_lock(&currd->event_lock);
+ ret = domain_irq_to_emuirq(currd, gsi);
+ write_unlock(&currd->event_lock);
+ if ( ret != IRQ_UNBOUND )
+ return 0;
+
/* Interrupt has been unmasked, bind it now. */
ret = mp_register_gsi(gsi, trig, pol);
if ( ret == -EEXIST )
diff --git a/xen/arch/x86/include/asm/irq.h b/xen/arch/x86/include/asm/irq.h
index 8bffec3bbfee..3b24decb05e4 100644
--- a/xen/arch/x86/include/asm/irq.h
+++ b/xen/arch/x86/include/asm/irq.h
@@ -212,7 +212,7 @@ void cleanup_domain_irq_mapping(struct domain *d);
__ret ? radix_tree_ptr_to_int(__ret) : 0; \
})
#define PIRQ_ALLOCATED (-1)
-#define domain_pirq_to_emuirq(d, pirq) pirq_field(d, pirq, \
+#define domain_irq_to_emuirq(d, pirq) pirq_field(d, pirq, \
arch.hvm.emuirq, IRQ_UNBOUND)
#define domain_emuirq_to_pirq(d, emuirq) ({ \
void *__ret = radix_tree_lookup(&(d)->arch.hvm.emuirq_pirq, emuirq);\
@@ -221,6 +221,7 @@ void cleanup_domain_irq_mapping(struct domain *d);
#define IRQ_UNBOUND (-1)
#define IRQ_PT (-2)
#define IRQ_MSI_EMU (-3)
+#define IRQ_EMU (-4)
bool cpu_has_pending_apic_eoi(void);
diff --git a/xen/arch/x86/irq.c b/xen/arch/x86/irq.c
index 079277be719d..7a8093cd3238 100644
--- a/xen/arch/x86/irq.c
+++ b/xen/arch/x86/irq.c
@@ -2790,7 +2790,7 @@ int map_domain_emuirq_pirq(struct domain *d, int pirq, int emuirq)
return -EINVAL;
}
- old_emuirq = domain_pirq_to_emuirq(d, pirq);
+ old_emuirq = domain_irq_to_emuirq(d, pirq);
if ( emuirq != IRQ_PT )
old_pirq = domain_emuirq_to_pirq(d, emuirq);
@@ -2845,7 +2845,7 @@ int unmap_domain_pirq_emuirq(struct domain *d, int pirq)
ASSERT(rw_is_write_locked(&d->event_lock));
- emuirq = domain_pirq_to_emuirq(d, pirq);
+ emuirq = domain_irq_to_emuirq(d, pirq);
if ( emuirq == IRQ_UNBOUND )
{
dprintk(XENLOG_G_ERR, "dom%d: pirq %d not mapped\n",
diff --git a/xen/arch/x86/physdev.c b/xen/arch/x86/physdev.c
index 4dfa1c019105..90a9e7d2f120 100644
--- a/xen/arch/x86/physdev.c
+++ b/xen/arch/x86/physdev.c
@@ -145,7 +145,7 @@ int physdev_unmap_pirq(struct domain *d, int pirq)
if ( is_hvm_domain(d) && has_pirq(d) )
{
write_lock(&d->event_lock);
- if ( domain_pirq_to_emuirq(d, pirq) != IRQ_UNBOUND )
+ if ( domain_irq_to_emuirq(d, pirq) != IRQ_UNBOUND )
ret = unmap_domain_pirq_emuirq(d, pirq);
write_unlock(&d->event_lock);
if ( d == current->domain || ret )
@@ -191,10 +191,10 @@ ret_t do_physdev_op(int cmd, XEN_GUEST_HANDLE_PARAM(void) arg)
if ( is_pv_domain(currd) || domain_pirq_to_irq(currd, eoi.irq) > 0 )
pirq_guest_eoi(pirq);
if ( is_hvm_domain(currd) &&
- domain_pirq_to_emuirq(currd, eoi.irq) > 0 )
+ domain_irq_to_emuirq(currd, eoi.irq) > 0 )
{
struct hvm_irq *hvm_irq = hvm_domain_irq(currd);
- int gsi = domain_pirq_to_emuirq(currd, eoi.irq);
+ int gsi = domain_irq_to_emuirq(currd, eoi.irq);
/* if this is a level irq and count > 0, send another
* notification */
@@ -267,7 +267,7 @@ ret_t do_physdev_op(int cmd, XEN_GUEST_HANDLE_PARAM(void) arg)
irq_status_query.flags = 0;
if ( is_hvm_domain(currd) &&
domain_pirq_to_irq(currd, irq) <= 0 &&
- domain_pirq_to_emuirq(currd, irq) == IRQ_UNBOUND )
+ domain_irq_to_emuirq(currd, irq) == IRQ_UNBOUND )
{
ret = -EINVAL;
break;
diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index aea38304b60c..1126b53c30df 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -287,7 +287,9 @@ static void ns16x50_irq_assert(const struct vuart_ns16x50 *vdev)
const struct vuart_info *info = vdev->info;
int vector;
- if ( has_vpic(d) ) /* HVM */
+ if ( has_vioapic(d) && !has_vpic(d) ) /* PVH */
+ vector = hvm_ioapic_assert(d, info->irq, false);
+ else if ( has_vpic(d) ) /* HVM */
vector = hvm_isa_irq_assert(d, info->irq, vioapic_get_vector);
else
ASSERT_UNREACHABLE();
@@ -300,7 +302,9 @@ static void ns16x50_irq_deassert(const struct vuart_ns16x50 *vdev)
struct domain *d = vdev->owner;
const struct vuart_info *info = vdev->info;
- if ( has_vpic(d) ) /* HVM */
+ if ( has_vioapic(d) && !has_vpic(d) ) /* PVH */
+ hvm_ioapic_deassert(d, info->irq);
+ else if ( has_vpic(d) ) /* HVM */
hvm_isa_irq_deassert(d, info->irq);
else
ASSERT_UNREACHABLE();
@@ -803,6 +807,17 @@ static int ns16x50_init(void *arg)
return rc;
}
+ /* Claim virtual IRQ */
+ write_lock(&d->event_lock);
+ rc = map_domain_emuirq_pirq(d, info->irq, IRQ_EMU);
+ write_unlock(&d->event_lock);
+ if ( rc )
+ {
+ ns16x50_err(info, "virtual IRQ#%d: cannot claim: %d\n",
+ info->irq, rc);
+ return rc;
+ }
+
/* NB: report 115200 baud rate. */
vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
@@ -822,9 +837,22 @@ static int ns16x50_init(void *arg)
static void cf_check ns16x50_deinit(void *arg)
{
struct vuart_ns16x50 *vdev = arg;
+ const struct vuart_info *info;
+ struct domain *d;
+ int rc;
ASSERT(vdev);
+ d = vdev->owner;
+ info = vdev->info;
+
+ write_lock(&d->event_lock);
+ rc = unmap_domain_pirq_emuirq(d, info->irq);
+ write_unlock(&d->event_lock);
+ if ( rc )
+ ns16x50_err(vdev, "virtual IRQ#%d: cannot unclaim: %d\n",
+ info->irq, rc);
+
spin_lock(&vdev->lock);
ns16x50_fifo_tx_flush(vdev);
spin_unlock(&vdev->lock);
diff --git a/xen/drivers/passthrough/x86/hvm.c b/xen/drivers/passthrough/x86/hvm.c
index a2ca7e0e570c..11711d20a7ea 100644
--- a/xen/drivers/passthrough/x86/hvm.c
+++ b/xen/drivers/passthrough/x86/hvm.c
@@ -923,13 +923,16 @@ static void __hvm_dpci_eoi(struct domain *d,
static void hvm_gsi_eoi(struct domain *d, unsigned int gsi)
{
struct pirq *pirq = pirq_info(d, gsi);
+ int rc;
+
+ /* Check if GSI is claimed by one of the virtual devices. */
+ rc = domain_irq_to_emuirq(d, gsi);
+ if ( rc != IRQ_UNBOUND )
+ hvm_gsi_deassert(d, gsi);
/* Check if GSI is actually mapped. */
- if ( !pirq_dpci(pirq) )
- return;
-
- hvm_gsi_deassert(d, gsi);
- hvm_pirq_eoi(pirq);
+ if ( pirq_dpci(pirq) )
+ hvm_pirq_eoi(pirq);
}
static int cf_check _hvm_dpci_isairq_eoi(
--
2.51.0
^ permalink raw reply related [flat|nested] 46+ messages in thread
* Re: [PATCH v5 01/15] emul/vuart: introduce framework for UART emulators
2025-08-28 23:53 ` [PATCH v5 01/15] emul/vuart: introduce framework for UART emulators dmukhin
@ 2025-08-29 19:27 ` Stefano Stabellini
2025-09-01 8:14 ` Jan Beulich
2025-09-01 22:11 ` dmukhin
0 siblings, 2 replies; 46+ messages in thread
From: Stefano Stabellini @ 2025-08-29 19:27 UTC (permalink / raw)
To: dmukhin
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, sstabellini, dmukhin
On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> From: Denis Mukhin <dmukhin@ford.com>
>
> Introduce a driver framework to abstract UART emulators in the hypervisor.
>
> That allows for architecture-independent handling of virtual UARTs in the
> console driver and simplifies enabling new UART emulators.
>
> The framework is built under CONFIG_VUART_FRAMEWORK, which will be
> automatically enabled once the user enables any UART emulator.
>
> Current implementation supports maximum of one vUART of each kind per domain.
>
> Use new domain_has_vuart() in the console driver code to check whether to
> forward console input to the domain using vUART.
>
> Enable console forwarding over vUART for hardware domains with a vUART. That
> enables console forwarding to dom0 on x86, since console can be forwarded only
> to Xen, dom0 and pvshim on x86 as of now.
>
> Note: existing vUARTs are deliberately *not* hooked to the new framework to
> minimize the scope of the patch: vpl011 (i.e. SBSA) emulator and "vuart" (i.e.
> minimalistic MMIO-mapped dtuart for hwdoms on Arm) are kept unmodified.
>
> No functional changes for non-x86 architectures.
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v4:
> - addressed feedback
> - Link to v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-3-dmukhin@ford.com/
> ---
> xen/arch/arm/xen.lds.S | 1 +
> xen/arch/ppc/xen.lds.S | 1 +
> xen/arch/riscv/xen.lds.S | 1 +
> xen/arch/x86/xen.lds.S | 1 +
> xen/common/Kconfig | 2 +
> xen/common/Makefile | 1 +
> xen/common/emul/Kconfig | 6 ++
> xen/common/emul/Makefile | 1 +
> xen/common/emul/vuart/Kconfig | 6 ++
> xen/common/emul/vuart/Makefile | 1 +
> xen/common/emul/vuart/vuart.c | 156 +++++++++++++++++++++++++++++++++
> xen/common/keyhandler.c | 3 +
> xen/drivers/char/console.c | 6 +-
> xen/include/xen/serial.h | 3 +
> xen/include/xen/vuart.h | 116 ++++++++++++++++++++++++
> xen/include/xen/xen.lds.h | 10 +++
> 16 files changed, 314 insertions(+), 1 deletion(-)
> create mode 100644 xen/common/emul/Kconfig
> create mode 100644 xen/common/emul/Makefile
> create mode 100644 xen/common/emul/vuart/Kconfig
> create mode 100644 xen/common/emul/vuart/Makefile
> create mode 100644 xen/common/emul/vuart/vuart.c
> create mode 100644 xen/include/xen/vuart.h
>
> diff --git a/xen/arch/arm/xen.lds.S b/xen/arch/arm/xen.lds.S
> index db17ff1efa98..cd05b18770f4 100644
> --- a/xen/arch/arm/xen.lds.S
> +++ b/xen/arch/arm/xen.lds.S
> @@ -58,6 +58,7 @@ SECTIONS
> *(.rodata)
> *(.rodata.*)
> VPCI_ARRAY
> + VUART_ARRAY
> *(.data.rel.ro)
> *(.data.rel.ro.*)
>
> diff --git a/xen/arch/ppc/xen.lds.S b/xen/arch/ppc/xen.lds.S
> index 1de0b77fc6b9..f9d4e5b0dcd8 100644
> --- a/xen/arch/ppc/xen.lds.S
> +++ b/xen/arch/ppc/xen.lds.S
> @@ -52,6 +52,7 @@ SECTIONS
> *(.rodata)
> *(.rodata.*)
> VPCI_ARRAY
> + VUART_ARRAY
> *(.data.rel.ro)
> *(.data.rel.ro.*)
>
> diff --git a/xen/arch/riscv/xen.lds.S b/xen/arch/riscv/xen.lds.S
> index edcadff90bfe..59dcaa5fef9a 100644
> --- a/xen/arch/riscv/xen.lds.S
> +++ b/xen/arch/riscv/xen.lds.S
> @@ -47,6 +47,7 @@ SECTIONS
> *(.rodata)
> *(.rodata.*)
> VPCI_ARRAY
> + VUART_ARRAY
> *(.data.rel.ro)
> *(.data.rel.ro.*)
>
> diff --git a/xen/arch/x86/xen.lds.S b/xen/arch/x86/xen.lds.S
> index 966e514f2034..d877b93a6964 100644
> --- a/xen/arch/x86/xen.lds.S
> +++ b/xen/arch/x86/xen.lds.S
> @@ -132,6 +132,7 @@ SECTIONS
> *(.rodata)
> *(.rodata.*)
> VPCI_ARRAY
> + VUART_ARRAY
> *(.data.rel.ro)
> *(.data.rel.ro.*)
>
> diff --git a/xen/common/Kconfig b/xen/common/Kconfig
> index 76f9ce705f7a..78a32b69e2b2 100644
> --- a/xen/common/Kconfig
> +++ b/xen/common/Kconfig
> @@ -676,4 +676,6 @@ config PM_STATS
> Enable collection of performance management statistics to aid in
> analyzing and tuning power/performance characteristics of the system
>
> +source "common/emul/Kconfig"
> +
> endmenu
> diff --git a/xen/common/Makefile b/xen/common/Makefile
> index c316957fcb36..c0734480ee4b 100644
> --- a/xen/common/Makefile
> +++ b/xen/common/Makefile
> @@ -11,6 +11,7 @@ obj-$(filter-out $(CONFIG_X86),$(CONFIG_ACPI)) += device.o
> obj-$(CONFIG_DEVICE_TREE_PARSE) += device-tree/
> obj-$(CONFIG_IOREQ_SERVER) += dm.o
> obj-y += domain.o
> +obj-y += emul/
> obj-y += event_2l.o
> obj-y += event_channel.o
> obj-$(CONFIG_EVTCHN_FIFO) += event_fifo.o
> diff --git a/xen/common/emul/Kconfig b/xen/common/emul/Kconfig
> new file mode 100644
> index 000000000000..7c6764d1756b
> --- /dev/null
> +++ b/xen/common/emul/Kconfig
> @@ -0,0 +1,6 @@
> +menu "Domain Emulation Features"
> + visible if EXPERT
> +
> +source "common/emul/vuart/Kconfig"
> +
> +endmenu
> diff --git a/xen/common/emul/Makefile b/xen/common/emul/Makefile
> new file mode 100644
> index 000000000000..ae0b575c3901
> --- /dev/null
> +++ b/xen/common/emul/Makefile
> @@ -0,0 +1 @@
> +obj-$(CONFIG_VUART_FRAMEWORK) += vuart/
> diff --git a/xen/common/emul/vuart/Kconfig b/xen/common/emul/vuart/Kconfig
> new file mode 100644
> index 000000000000..ce1b976b7da7
> --- /dev/null
> +++ b/xen/common/emul/vuart/Kconfig
> @@ -0,0 +1,6 @@
> +config VUART_FRAMEWORK
> + bool
> +
> +menu "UART Emulation"
> +
> +endmenu
> diff --git a/xen/common/emul/vuart/Makefile b/xen/common/emul/vuart/Makefile
> new file mode 100644
> index 000000000000..97f792dc6641
> --- /dev/null
> +++ b/xen/common/emul/vuart/Makefile
> @@ -0,0 +1 @@
> +obj-y += vuart.o
> diff --git a/xen/common/emul/vuart/vuart.c b/xen/common/emul/vuart/vuart.c
> new file mode 100644
> index 000000000000..7b277d00d5c7
> --- /dev/null
> +++ b/xen/common/emul/vuart/vuart.c
> @@ -0,0 +1,156 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * UART emulator framework.
> + *
> + * Copyright 2025 Ford Motor Company
> + */
> +
> +#include <xen/err.h>
> +#include <xen/sched.h>
> +#include <xen/vuart.h>
> +#include <xen/xvmalloc.h>
> +
> +#define for_each_emulator(e) \
> + for ( e = vuart_array_start; e < vuart_array_end; e++ )
> +
> +extern const struct vuart_emulator vuart_array_start[];
> +extern const struct vuart_emulator vuart_array_end[];
> +
> +static const struct vuart_emulator *
> +vuart_match_by_compatible(struct domain *d, const char *compat)
> +{
> + const struct vuart_emulator *emulator;
> +
> + if ( d->console.vuart )
> + return NULL;
> +
> + for_each_emulator(emulator)
> + if ( emulator->compatible &&
> + !strncmp(emulator->compatible, compat,
> + strlen(emulator->compatible)) )
strncmp will continue until the given count even if compat is shorter
> + return emulator;
> +
> + return NULL;
> +}
> +
> +static struct vuart *vuart_find_by_console_permission(const struct domain *d)
> +{
> + struct vuart *vuart = d->console.vuart;
> +
> + ASSERT(d->console.input_allowed);
This ASSERT looks suspicious as we haven't even checked that vuart!=NULL
yet. I would remove it
> + if ( !vuart || !vuart->emulator || !vuart->emulator->put_rx ||
> + !(vuart->flags & VUART_CONSOLE_INPUT))
> + return NULL;
> +
> + return vuart;
> +}
> +
> +struct vuart *vuart_find_by_io_range(struct domain *d, unsigned long addr,
> + unsigned long size)
> +{
> + struct vuart *vuart = d->console.vuart;
> +
> + if ( !vuart || !vuart->info )
> + return NULL;
You might as well call vuart_find_by_console_permission that has all the
checks already
> +
> + if ( addr >= vuart->info->base_addr &&
> + addr + size - 1 <= vuart->info->base_addr + vuart->info->size - 1 )
> + return vuart;
> +
> + return NULL;
> +}
> +
> +int vuart_init(struct domain *d, struct vuart_info *info)
> +{
> + const struct vuart_emulator *emulator;
> + struct vuart *vuart;
> + int rc;
> +
> + emulator = vuart_match_by_compatible(d, info->compatible);
> + if ( !emulator )
> + return -ENODEV;
> +
> + vuart = xzalloc(typeof(*vuart));
> + if ( !vuart )
> + return -ENOMEM;
> +
> + vuart->info = xvzalloc(typeof(*info));
> + if ( !vuart->info )
> + {
> + rc = -ENOMEM;
> + goto err_out;
> + }
> + memcpy(vuart->info, info, sizeof(*info));
one thing to note is that the fields (strings) compatible and name are
copied by address, I am not if it is OK but FYI
> + vuart->vdev = emulator->alloc(d, vuart->info);
> + if ( IS_ERR(vuart->vdev) )
> + {
> + rc = PTR_ERR(vuart->vdev);
> + goto err_out;
this path leads to vuart->info not being freed
> + }
> +
> + vuart->emulator = emulator;
> + vuart->owner = d;
> + vuart->flags |= VUART_CONSOLE_INPUT;
> +
> + d->console.input_allowed = true;
> + d->console.vuart = vuart;
> +
> + return 0;
> +
> + err_out:
> + XVFREE(vuart);
> + return rc;
> +}
> +
> +/*
> + * Release any resources taken by UART emulators.
> + *
> + * NB: no flags are cleared, since currently exit() is called only during
> + * domain destroy.
> + */
> +void vuart_deinit(struct domain *d)
> +{
> + struct vuart *vuart = d->console.vuart;
> +
> + if ( vuart )
> + {
> + vuart->emulator->free(vuart);
should we pass vuart or vuart->vdev here? The emulator state is supposed
to be vuart->vdev?
> + XVFREE(vuart->info);
> + }
> +
> + XVFREE(d->console.vuart);
> +}
> +
> +void vuart_dump_state(const struct domain *d)
> +{
> + struct vuart *vuart = d->console.vuart;
> +
> + if ( vuart )
> + vuart->emulator->dump_state(vuart);
also here vuart->vdev?
> +}
> +
> +/*
> + * Put character to the *first* suitable emulated UART's FIFO.
> + */
> +int vuart_put_rx(struct domain *d, char c)
> +{
> + struct vuart *vuart = vuart_find_by_console_permission(d);
> +
> + return vuart ? vuart->emulator->put_rx(vuart, c) : -ENODEV;
and here?
> +}
> +
> +bool domain_has_vuart(const struct domain *d)
> +{
> + return vuart_find_by_console_permission(d);
> +}
> +
> +/*
> + * Local variables:
> + * mode: C
> + * c-file-style: "BSD"
> + * c-basic-offset: 4
> + * indent-tabs-mode: nil
> + * End:
> + */
> diff --git a/xen/common/keyhandler.c b/xen/common/keyhandler.c
> index cb6df2823b00..156e64d9eb58 100644
> --- a/xen/common/keyhandler.c
> +++ b/xen/common/keyhandler.c
> @@ -22,6 +22,7 @@
> #include <xen/mm.h>
> #include <xen/watchdog.h>
> #include <xen/init.h>
> +#include <xen/vuart.h>
> #include <asm/div64.h>
>
> static unsigned char keypress_key;
> @@ -352,6 +353,8 @@ static void cf_check dump_domains(unsigned char key)
> v->periodic_period / 1000000);
> }
> }
> +
> + vuart_dump_state(d);
> }
>
> for_each_domain ( d )
> diff --git a/xen/drivers/char/console.c b/xen/drivers/char/console.c
> index 9bd5b4825da6..d5164897a776 100644
> --- a/xen/drivers/char/console.c
> +++ b/xen/drivers/char/console.c
> @@ -33,6 +33,7 @@
> #include <asm/setup.h>
> #include <xen/sections.h>
> #include <xen/consoled.h>
> +#include <xen/vuart.h>
>
> #ifdef CONFIG_X86
> #include <asm/guest.h>
> @@ -596,11 +597,12 @@ static void __serial_rx(char c)
> if ( !d )
> return;
>
> - if ( is_hardware_domain(d) )
> + if ( is_hardware_domain(d) && !domain_has_vuart(d) )
> {
> /*
> * Deliver input to the hardware domain buffer, unless it is
> * already full.
> + * NB: must be the first check: hardware domain may have emulated UART.
> */
> if ( (serial_rx_prod - serial_rx_cons) != SERIAL_RX_SIZE )
> serial_rx_ring[SERIAL_RX_MASK(serial_rx_prod++)] = c;
> @@ -611,6 +613,8 @@ static void __serial_rx(char c)
> */
> send_global_virq(VIRQ_CONSOLE);
> }
> + else if ( domain_has_vuart(d) )
> + rc = vuart_put_rx(d, c);
> #ifdef CONFIG_SBSA_VUART_CONSOLE
> else
> /* Deliver input to the emulated UART. */
> diff --git a/xen/include/xen/serial.h b/xen/include/xen/serial.h
> index 8e1844555208..d7e81f098359 100644
> --- a/xen/include/xen/serial.h
> +++ b/xen/include/xen/serial.h
> @@ -36,6 +36,9 @@ struct vuart_info {
> unsigned long data_off; /* Data register offset */
> unsigned long status_off; /* Status register offset */
> unsigned long status; /* Ready status value */
> + unsigned int irq; /* Interrupt */
> + const char *compatible; /* Compatible string */
> + const char *name; /* User-friendly name */
> };
>
> struct serial_port {
> diff --git a/xen/include/xen/vuart.h b/xen/include/xen/vuart.h
> new file mode 100644
> index 000000000000..ca025b4179be
> --- /dev/null
> +++ b/xen/include/xen/vuart.h
> @@ -0,0 +1,116 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * UART emulator framework.
> + *
> + * Copyright 2025 Ford Motor Company
> + */
> +
> +#ifndef XEN_VUART_H
> +#define XEN_VUART_H
> +
> +#include <xen/serial.h>
> +#include <public/xen.h>
> +
> +struct vuart_emulator;
> +
> +enum {
> + VUART_CONSOLE_INPUT = 0U << 1, /** Physical console input forwarding. */
code style: single *
This flag is zero, is that intended?
> +};
> +
> +
> +/*
> + * FIXME: #ifdef is temporary to avoid clash with
> + * arch/arm/include/asm/domain.h
> + */
> +#ifdef CONFIG_VUART_FRAMEWORK
> +struct vuart {
> + const struct vuart_emulator *emulator;
> + struct vuart_info *info;
> + struct domain *owner;
> + uint32_t flags;
> + void *vdev;
> +};
> +#endif
> +
> +struct vuart_emulator {
> + /* UART compatible string. Cannot be NULL or empty. */
> + const char *compatible;
> +
> + /*
> + * Allocate emulated UART state (RX/TX FIFOs, locks, initialize registers,
> + * hook I/O handlers, etc.)
> + * Cannot be NULL.
> + */
> + void *(*alloc)(struct domain *d, const struct vuart_info *info);
> +
> + /*
> + * Release resources used to emulate UART state (flush RX/TX FIFOs, unhook
> + * I/O handlers, etc.).
> + * Cannot be NULL.
> + */
> + void (*free)(void *arg);
> +
> + /*
> + * Print emulated UART state, including registers, on the console.
> + * Can be NULL.
> + */
> + void (*dump_state)(void *arg);
> +
> + /*
> + * Place character to the emulated RX FIFO.
> + * Used to forward physical console input to the guest OS.
> + * Can be NULL.
> + */
> + int (*put_rx)(void *arg, char c);
> +};
> +
> +#define VUART_REGISTER(name, x) \
> + static const struct vuart_emulator name##_entry \
> + __used_section(".data.rel.ro.vuart") = x
> +
> +struct vuart *vuart_find_by_io_range(struct domain *d,
> + unsigned long base_addr,
> + unsigned long size);
> +
> +int vuart_put_rx(struct domain *d, char c);
> +
> +#ifdef CONFIG_VUART_FRAMEWORK
> +
> +int vuart_init(struct domain *d, struct vuart_info *info);
> +void vuart_deinit(struct domain *d);
> +void vuart_dump_state(const struct domain *d);
> +bool domain_has_vuart(const struct domain *d);
> +
> +#else
> +
> +static inline int vuart_init(struct domain *d, struct vuart_info *info)
> +{
> + return 0;
> +}
> +
> +static inline void vuart_deinit(struct domain *d)
> +{
> +}
> +
> +static inline void vuart_dump_state(const struct domain *d)
> +{
> +}
> +
> +static inline bool domain_has_vuart(const struct domain *d)
> +{
> + return false;
> +}
> +
> +#endif /* CONFIG_VUART_FRAMEWORK */
> +
> +#endif /* XEN_VUART_H */
> +
> +/*
> + * Local variables:
> + * mode: C
> + * c-file-style: "BSD"
> + * c-basic-offset: 4
> + * indent-tabs-mode: nil
> + * End:
> + */
> +
> diff --git a/xen/include/xen/xen.lds.h b/xen/include/xen/xen.lds.h
> index b126dfe88792..2d65f32ddad3 100644
> --- a/xen/include/xen/xen.lds.h
> +++ b/xen/include/xen/xen.lds.h
> @@ -194,4 +194,14 @@
> #define VPCI_ARRAY
> #endif
>
> +#ifdef CONFIG_VUART_FRAMEWORK
> +#define VUART_ARRAY \
> + . = ALIGN(POINTER_ALIGN); \
> + vuart_array_start = .; \
> + *(.data.rel.ro.vuart) \
> + vuart_array_end = .;
> +#else
> +#define VUART_ARRAY
> +#endif
> +
> #endif /* __XEN_LDS_H__ */
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 02/15] xen/8250-uart: update definitions
2025-08-28 23:53 ` [PATCH v5 02/15] xen/8250-uart: update definitions dmukhin
@ 2025-08-29 19:32 ` Stefano Stabellini
2025-09-01 22:28 ` dmukhin
0 siblings, 1 reply; 46+ messages in thread
From: Stefano Stabellini @ 2025-08-29 19:32 UTC (permalink / raw)
To: dmukhin
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, sstabellini, dmukhin
On Thu, 27 Aug 2025, dmukhin@xen.org wrote:
> From: Denis Mukhin <dmukhin@ford.com>
>
> Added missing definitions needed for NS16550 UART emulator.
>
> Newly introduced MSR definitions re-used in the existing ns16550 driver.
>
> Also, corrected FCR DMA definition bit#3 (0x08) as per:
> https://www.ti.com/lit/ds/symlink/tl16c550c.pdf
> See "7.7.2 FIFO Control Register (FCR)".
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v4:
> - reused newly introduced UART_IIR and UART_IER bits in ns16550 driver
> - Link to v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-5-dmukhin@ford.com/
> ---
> xen/drivers/char/ns16550.c | 16 ++++++------
> xen/include/xen/8250-uart.h | 50 ++++++++++++++++++++++++++++++-------
> 2 files changed, 49 insertions(+), 17 deletions(-)
>
> diff --git a/xen/drivers/char/ns16550.c b/xen/drivers/char/ns16550.c
> index df7fff7f81df..0e80fadbb894 100644
> --- a/xen/drivers/char/ns16550.c
> +++ b/xen/drivers/char/ns16550.c
> @@ -388,7 +388,7 @@ static void __init cf_check ns16550_init_preirq(struct serial_port *port)
>
> /* Check this really is a 16550+. Otherwise we have no FIFOs. */
> if ( uart->fifo_size <= 1 &&
> - ((ns_read_reg(uart, UART_IIR) & 0xc0) == 0xc0) &&
> + ((ns_read_reg(uart, UART_IIR) & UART_IIR_FE) == UART_IIR_FE) &&
> ((ns_read_reg(uart, UART_FCR) & UART_FCR_TRG14) == UART_FCR_TRG14) )
> uart->fifo_size = 16;
> }
> @@ -728,20 +728,20 @@ static int __init check_existence(struct ns16550 *uart)
> * Mask out IER[7:4] bits for test as some UARTs (e.g. TL
> * 16C754B) allow only to modify them if an EFR bit is set.
> */
> - scratch2 = ns_read_reg(uart, UART_IER) & 0x0f;
> - ns_write_reg(uart,UART_IER, 0x0F);
> - scratch3 = ns_read_reg(uart, UART_IER) & 0x0f;
> + scratch2 = ns_read_reg(uart, UART_IER) & UART_IER_MASK;
> + ns_write_reg(uart, UART_IER, UART_IER_MASK);
> + scratch3 = ns_read_reg(uart, UART_IER) & UART_IER_MASK;
> ns_write_reg(uart, UART_IER, scratch);
> - if ( (scratch2 != 0) || (scratch3 != 0x0F) )
> + if ( (scratch2 != 0) || (scratch3 != UART_IER_MASK) )
> return 0;
>
> /*
> * Check to see if a UART is really there.
> * Use loopback test mode.
> */
> - ns_write_reg(uart, UART_MCR, UART_MCR_LOOP | 0x0A);
> - status = ns_read_reg(uart, UART_MSR) & 0xF0;
> - return (status == 0x90);
> + ns_write_reg(uart, UART_MCR, UART_MCR_LOOP | UART_MCR_RTS | UART_MCR_OUT2);
> + status = ns_read_reg(uart, UART_MSR) & UART_MSR_STATUS;
> + return (status == (UART_MSR_CTS | UART_MSR_DCD));
> }
>
> #ifdef CONFIG_HAS_PCI
> diff --git a/xen/include/xen/8250-uart.h b/xen/include/xen/8250-uart.h
> index d13352940c13..bc11cdc376c9 100644
> --- a/xen/include/xen/8250-uart.h
> +++ b/xen/include/xen/8250-uart.h
> @@ -32,6 +32,7 @@
> #define UART_MCR 0x04 /* Modem control */
> #define UART_LSR 0x05 /* line status */
> #define UART_MSR 0x06 /* Modem status */
> +#define UART_SCR 0x07 /* Scratch pad */
> #define UART_USR 0x1f /* Status register (DW) */
> #define UART_DLL 0x00 /* divisor latch (ls) (DLAB=1) */
> #define UART_DLM 0x01 /* divisor latch (ms) (DLAB=1) */
> @@ -42,6 +43,8 @@
> #define UART_IER_ETHREI 0x02 /* tx reg. empty */
> #define UART_IER_ELSI 0x04 /* rx line status */
> #define UART_IER_EMSI 0x08 /* MODEM status */
> +#define UART_IER_MASK \
> + (UART_IER_ERDAI | UART_IER_ETHREI | UART_IER_ELSI | UART_IER_EMSI)
>
> /* Interrupt Identification Register */
> #define UART_IIR_NOINT 0x01 /* no interrupt pending */
> @@ -51,12 +54,19 @@
> #define UART_IIR_THR 0x02 /* - tx reg. empty */
> #define UART_IIR_MSI 0x00 /* - MODEM status */
> #define UART_IIR_BSY 0x07 /* - busy detect (DW) */
> +#define UART_IIR_FE 0xc0 /* FIFO enabled (2 bits) */
>
> /* FIFO Control Register */
> -#define UART_FCR_ENABLE 0x01 /* enable FIFO */
> -#define UART_FCR_CLRX 0x02 /* clear Rx FIFO */
> -#define UART_FCR_CLTX 0x04 /* clear Tx FIFO */
> -#define UART_FCR_DMA 0x10 /* enter DMA mode */
> +#define UART_FCR_ENABLE BIT(0, U) /* enable FIFO */
> +#define UART_FCR_CLRX BIT(1, U) /* clear Rx FIFO */
> +#define UART_FCR_CLTX BIT(2, U) /* clear Tx FIFO */
> +#define UART_FCR_DMA BIT(3, U) /* enter DMA mode */
> +#define UART_FCR_RESERVED0 BIT(4, U) /* reserved; always 0 */
> +#define UART_FCR_RESERVED1 BIT(5, U) /* reserved; always 0 */
> +#define UART_FCR_RTB0 BIT(6, U) /* receiver trigger bit #0 */
> +#define UART_FCR_RTB1 BIT(7, U) /* receiver trigger bit #1 */
> +#define UART_FCR_TRG_MASK (UART_FCR_RTB0 | UART_FCR_RTB1)
> +
> #define UART_FCR_TRG1 0x00 /* Rx FIFO trig lev 1 */
> #define UART_FCR_TRG4 0x40 /* Rx FIFO trig lev 4 */
> #define UART_FCR_TRG8 0x80 /* Rx FIFO trig lev 8 */
> @@ -96,11 +106,32 @@
> #define UART_LCR_CONF_MODE_B 0xBF /* Configuration mode B */
>
> /* Modem Control Register */
> -#define UART_MCR_DTR 0x01 /* Data Terminal Ready */
> -#define UART_MCR_RTS 0x02 /* Request to Send */
> -#define UART_MCR_OUT2 0x08 /* OUT2: interrupt mask */
> -#define UART_MCR_LOOP 0x10 /* Enable loopback test mode */
> -#define UART_MCR_TCRTLR 0x40 /* Access TCR/TLR (TI16C752, EFR[4]=1) */
> +#define UART_MCR_DTR BIT(0, U) /* Data Terminal Ready */
> +#define UART_MCR_RTS BIT(1, U) /* Request to Send */
> +#define UART_MCR_OUT1 BIT(2, U) /* OUT1: interrupt mask */
is OUT1 an interrupt mask actually?
> +#define UART_MCR_OUT2 BIT(3, U) /* OUT2: interrupt mask */
> +#define UART_MCR_LOOP BIT(4, U) /* Enable loopback test mode */
> +#define UART_MCR_RESERVED0 BIT(5, U) /* Reserved #0 */
> +#define UART_MCR_TCRTLR BIT(6, U) /* Access TCR/TLR (TI16C752, EFR[4]=1) */
> +#define UART_MCR_RESERVED1 BIT(7, U) /* Reserved #1 */
> +#define UART_MCR_MASK \
> + (UART_MCR_DTR | UART_MCR_RTS | \
> + UART_MCR_OUT1 | UART_MCR_OUT2 | \
> + UART_MCR_LOOP | UART_MCR_TCRTLR)
> +
> +/* Modem Status Register */
> +#define UART_MSR_DCTS BIT(0, U) /* Change in CTS */
> +#define UART_MSR_DDSR BIT(1, U) /* Change in DSR */
> +#define UART_MSR_TERI BIT(2, U) /* Change in RI */
> +#define UART_MSR_DDCD BIT(3, U) /* Change in CTS */
Changes in DCD, can be done on commit
> +#define UART_MSR_CTS BIT(4, U)
> +#define UART_MSR_DSR BIT(5, U)
> +#define UART_MSR_RI BIT(6, U)
> +#define UART_MSR_DCD BIT(7, U)
> +#define UART_MSR_CHANGE \
> + (UART_MSR_DCTS | UART_MSR_DDSR | UART_MSR_TERI | UART_MSR_DDCD)
> +#define UART_MSR_STATUS \
> + (UART_MSR_CTS | UART_MSR_DSR | UART_MSR_RI | UART_MSR_DCD)
>
> /* Line Status Register */
> #define UART_LSR_DR 0x01 /* Data ready */
> @@ -111,6 +142,7 @@
> #define UART_LSR_THRE 0x20 /* Xmit hold reg empty */
> #define UART_LSR_TEMT 0x40 /* Xmitter empty */
> #define UART_LSR_ERR 0x80 /* Error */
> +#define UART_LSR_MASK (UART_LSR_OE | UART_LSR_BI)
>
> /* These parity settings can be ORed directly into the LCR. */
> #define UART_PARITY_NONE (0<<3)
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 03/15] emul/ns16x50: implement emulator stub
2025-08-28 23:53 ` [PATCH v5 03/15] emul/ns16x50: implement emulator stub dmukhin
@ 2025-08-29 19:57 ` Stefano Stabellini
2025-09-01 23:11 ` dmukhin
2025-09-02 9:36 ` Jan Beulich
0 siblings, 2 replies; 46+ messages in thread
From: Stefano Stabellini @ 2025-08-29 19:57 UTC (permalink / raw)
To: dmukhin
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, sstabellini, dmukhin
On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> From: Denis Mukhin <dmukhin@ford.com>
>
> The change is the first on the way on introducing minimally functional
> NS16550-compatible UART emulator.
>
> Define UART state and a set of emulated registers.
>
> Implement alloc/free vUART hooks.
>
> Stub out I/O port handler.
>
> Add initialization of the NS16x50-compatible UART emulator state machine.
>
> Plumb debug logging.
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v4:
> - new patch
> ---
> xen/arch/x86/hvm/hvm.c | 20 ++
> xen/common/emul/vuart/Kconfig | 18 ++
> xen/common/emul/vuart/Makefile | 1 +
> xen/common/emul/vuart/ns16x50.c | 362 ++++++++++++++++++++++++++++++++
> xen/include/xen/sched.h | 4 +
> 5 files changed, 405 insertions(+)
> create mode 100644 xen/common/emul/vuart/ns16x50.c
>
> diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c
> index 23bd7f078a1d..26760cf995df 100644
> --- a/xen/arch/x86/hvm/hvm.c
> +++ b/xen/arch/x86/hvm/hvm.c
> @@ -28,6 +28,7 @@
> #include <xen/softirq.h>
> #include <xen/trace.h>
> #include <xen/vm_event.h>
> +#include <xen/vuart.h>
> #include <xen/vpci.h>
> #include <xen/wait.h>
> #include <xen/warning.h>
> @@ -689,6 +690,21 @@ int hvm_domain_initialise(struct domain *d,
> if ( rc != 0 )
> goto fail1;
>
> + if ( IS_ENABLED(CONFIG_VUART_NS16X50) )
maybe only for the hardware domain?
or only input_allowed = true only for the hardware domain?
> + {
> + struct vuart_info info = {
> + .name = "COM2",
> + .compatible = "ns16550",
> + .base_addr = 0x2f8,
> + .size = 8,
> + .irq = 3,
> + };
> +
> + rc = vuart_init(d, &info);
> + if ( rc )
> + goto out_vioapic_deinit;
> + }
> +
> stdvga_init(d);
>
> rtc_init(d);
> @@ -712,6 +728,8 @@ int hvm_domain_initialise(struct domain *d,
> return 0;
>
> fail2:
> + vuart_deinit(d);
> + out_vioapic_deinit:
> vioapic_deinit(d);
> fail1:
> if ( is_hardware_domain(d) )
> @@ -774,6 +792,8 @@ void hvm_domain_destroy(struct domain *d)
> if ( hvm_funcs.domain_destroy )
> alternative_vcall(hvm_funcs.domain_destroy, d);
>
> + vuart_deinit(d);
> +
> vioapic_deinit(d);
>
> XFREE(d->arch.hvm.pl_time);
> diff --git a/xen/common/emul/vuart/Kconfig b/xen/common/emul/vuart/Kconfig
> index ce1b976b7da7..539e6d5e4fc7 100644
> --- a/xen/common/emul/vuart/Kconfig
> +++ b/xen/common/emul/vuart/Kconfig
> @@ -3,4 +3,22 @@ config VUART_FRAMEWORK
>
> menu "UART Emulation"
>
> +config VUART_NS16X50
> + bool "NS16550-compatible UART Emulator" if EXPERT
> + depends on X86 && HVM
> + select VUART_FRAMEWORK
default n
> + help
> + In-hypervisor NS16x50 UART emulation.
> +
> + Only legacy PC COM2 port is emulated.
> +
> + This is strictly for testing purposes (such as early HVM guest console),
> + and not appropriate for use in production.
> +
> +config VUART_NS16X50_DEBUG
> + bool "NS16550-compatible UART Emulator Debugging"
> + depends on VUART_NS16X50 && DEBUG
> + help
> + Enable development debugging.
> +
> endmenu
> diff --git a/xen/common/emul/vuart/Makefile b/xen/common/emul/vuart/Makefile
> index 97f792dc6641..fe904f6cb65d 100644
> --- a/xen/common/emul/vuart/Makefile
> +++ b/xen/common/emul/vuart/Makefile
> @@ -1 +1,2 @@
> obj-y += vuart.o
> +obj-$(CONFIG_VUART_NS16X50) += ns16x50.o
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> new file mode 100644
> index 000000000000..f0479e1022fb
> --- /dev/null
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -0,0 +1,362 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * NS16550-compatible UART Emulator.
> + *
> + * See:
> + * - Serial and UART Tutorial:
> + * https://download.freebsd.org/doc/en/articles/serial-uart/serial-uart_en.pdf
> + * - UART w/ 16 byte FIFO:
> + * https://www.ti.com/lit/ds/symlink/tl16c550c.pdf
> + * - UART w/ 64 byte FIFO:
> + * https://www.ti.com/lit/ds/symlink/tl16c750.pdf
> + *
> + * Limitations:
> + * - Only x86;
> + * - Only Xen console as a backend, no inter-domain communication (similar to
> + * vpl011 on Arm);
> + * - Only 8n1 emulation (8-bit data, no parity, 1 stop bit);
> + * - No baud rate emulation (reports 115200 baud to the guest OS);
> + * - No FIFO-less mode emulation;
> + * - No RX FIFO interrupt moderation (FCR) emulation;
> + * - No integration w/ VM snapshotting (HVM_REGISTER_SAVE_RESTORE() and
> + * friends);
> + * - No ISA IRQ sharing allowed;
> + * - No MMIO-based UART emulation.
> + */
> +
> +#define pr_prefix "ns16x50"
> +#define pr_fmt(fmt) pr_prefix ": " fmt
> +
> +#ifdef CONFIG_VUART_NS16X50_DEBUG
> +#define guest_prefix "FROM GUEST "
> +#define ns16x50_log_level 2
> +#else
> +#define guest_prefix ""
> +#define ns16x50_log_level 0
> +#endif
> +
> +#include <xen/8250-uart.h>
> +#include <xen/console.h>
> +#include <xen/err.h>
> +#include <xen/iocap.h>
> +#include <xen/vuart.h>
> +#include <xen/xvmalloc.h>
> +
> +#include <public/io/console.h>
> +
> +#define ns16x50_log(n, lvl, vdev, fmt, args...) do { \
> + if ( ns16x50_log_level >= n) \
> + gprintk(lvl, pr_fmt("%s: " fmt), (vdev)->name, ## args); \
> +} while (0)
> +
> +#define ns16x50_err(vdev, fmt, args...) \
> + ns16x50_log(0, KERN_ERR, vdev, fmt, ## args)
> +#define ns16x50_warn(vdev, fmt, args...) \
> + ns16x50_log(1, KERN_WARNING, vdev, fmt, ## args)
> +#define ns16x50_info(vdev, fmt, args...) \
> + ns16x50_log(2, KERN_INFO, vdev, fmt, ## args)
> +#define ns16x50_debug(vdev, fmt, args...) \
> + ns16x50_log(3, KERN_DEBUG, vdev, fmt, ## args)
> +
> +/*
> + * Number of supported registers in the UART.
> + */
> +#define NS16X50_REGS_NUM (UART_SCR + 1)
> +
> +/*
> + * Number of emulated registers.
> + *
> + * - Emulated registers [0..NS16X50_REGS_NUM] are R/W registers for DLAB=0.
> + * - DLAB=1, R/W, DLL = NS16X50_REGS_NUM + 0
> + * - DLAB=1, R/W, DLM = NS16X50_REGS_NUM + 1
> + * - R/O, IIR (IIR_THR) = NS16X50_REGS_NUM + 2
> + */
> +#define NS16X50_EMU_REGS_NUM (NS16X50_REGS_NUM + 3)
> +
> +/*
> + * Virtual ns16x50 device state.
> + */
> +struct vuart_ns16x50 {
> + struct xencons_interface cons; /* Emulated RX/TX FIFOs */
> + uint8_t regs[NS16X50_EMU_REGS_NUM]; /* Emulated registers */
> + const char *name; /* Device name */
> + struct domain *owner; /* Owner domain */
> + const struct vuart_info *info; /* UART description */
> + spinlock_t lock; /* Protection */
> +};
> +
> +/*
> + * Emulate 8-bit write access to ns16x50 register.
> + */
> +static int ns16x50_io_write8(
> + struct vuart_ns16x50 *vdev, uint32_t reg, uint8_t *data)
> +{
> + int rc = 0;
> +
> + return rc;
> +}
> +
> +/*
> + * Emulate 16-bit write access to ns16x50 register.
> + * NB: some guest OSes use outw() to access UART_DLL.
> + */
> +static int ns16x50_io_write16(
> + struct vuart_ns16x50 *vdev, uint32_t reg, uint16_t *data)
> +{
> + int rc = -EINVAL;
> +
> + return rc;
> +}
> +
> +/*
> + * Emulate write access to ns16x50 register.
> + */
> +static int ns16x50_io_write(
> + struct vuart_ns16x50 *vdev, uint8_t reg, uint32_t size, uint32_t *data)
> +{
> + int rc;
> +
> + switch ( size )
> + {
> + case 1:
> + rc = ns16x50_io_write8(vdev, reg, (uint8_t *)data);
> + break;
> +
> + case 2:
> + rc = ns16x50_io_write16(vdev, reg, (uint16_t *)data);
> + break;
> +
> + default:
> + rc = -EINVAL;
> + break;
> + }
> +
> + return rc;
> +}
> +
> +/*
> + * Emulate 8-bit read access to ns16x50 register.
> + */
> +static int ns16x50_io_read8(
> + struct vuart_ns16x50 *vdev, uint32_t reg, uint8_t *data)
> +{
> + uint8_t val = 0xff;
> + int rc = 0;
> +
> + *data = val;
> +
> + return rc;
> +}
> +
> +/*
> + * Emulate 16-bit read access to ns16x50 register.
> + */
> +static int ns16x50_io_read16(
> + struct vuart_ns16x50 *vdev, uint32_t reg, uint16_t *data)
> +{
> + uint16_t val = 0xffff;
> + int rc = -EINVAL;
> +
> + *data = val;
> +
> + return rc;
> +}
> +
> +/*
> + * Emulate read access to ns16x50 register.
> + */
> +static int ns16x50_io_read(
> + struct vuart_ns16x50 *vdev, uint8_t reg, uint32_t size, uint32_t *data)
> +{
> + int rc;
> +
> + switch ( size )
> + {
> + case 1:
> + rc = ns16x50_io_read8(vdev, reg, (uint8_t *)data);
> + break;
> +
> + case 2:
> + rc = ns16x50_io_read16(vdev, reg, (uint16_t *)data);
> + break;
> +
> + default:
> + *data = 0xffffffff;
> + rc = -EINVAL;
> + break;
> + }
> +
> + return rc;
> +}
> +
> +/*
> + * Emulate I/O access to ns16x50 register.
> + * Note, emulation always returns X86EMUL_OKAY, once I/O port trap is enabled.
> + */
> +static int cf_check ns16x50_io_handle(
> + int dir, unsigned int addr, unsigned int size, uint32_t *data)
> +{
> +#define op(dir) (((dir) == IOREQ_WRITE) ? 'W' : 'R')
> + struct domain *d = rcu_lock_current_domain();
> + struct vuart *vuart = vuart_find_by_io_range(d, addr, size);
> + struct vuart_ns16x50 *vdev;
> + const struct domain *owner;
> + const struct vuart_info *info;
> + uint32_t reg;
> + unsigned dlab;
> + int rc;
> +
> + if ( !vuart || !vuart->vdev )
> + {
> + printk(KERN_ERR "%c io 0x%04x %d: not initialized\n",
XENLOG_ERR
> + op(dir), addr, size);
> +
> + ASSERT_UNREACHABLE();
> + goto out;
> + }
> + vdev = vuart->vdev;
> +
> + owner = vuart->owner;
> + ASSERT(owner);
> + if ( d != owner )
> + {
> + ns16x50_err(vdev, "%c io 0x%04x %d: does not match current domain %pv\n",
> + op(dir), addr, size, d);
> +
> + ASSERT_UNREACHABLE();
> + goto out;
> + }
> +
> + info = vuart->info;
> + ASSERT(info);
> + reg = addr - info->base_addr;
> + if ( !IS_ALIGNED(reg, size) )
> + {
> + ns16x50_err(vdev, "%c 0x%04x %d: unaligned access\n",
> + op(dir), addr, size);
For this one we could consider returning X86EMUL_UNHANDLEABLE but I am not sure
as it would crash the guest.
> + goto out;
> + }
> +
> + dlab = 0;
> + if ( reg >= NS16X50_REGS_NUM )
> + {
> + ns16x50_err(vdev, "%c io 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32": not implemented\n",
> + op(dir), addr, size, dlab, reg, *data);
> + goto out;
> + }
> +
> + spin_lock(&vdev->lock);
> +
> + if ( dir == IOREQ_WRITE )
> + {
> + ns16x50_debug(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32"\n",
> + op(dir), addr, size, dlab, reg, *data);
> + rc = ns16x50_io_write(vdev, reg, size, data);
> + }
> + else
> + {
> + rc = ns16x50_io_read(vdev, reg, size, data);
> + ns16x50_debug(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32"\n",
> + op(dir), addr, size, dlab, reg, *data);
> + }
> + if ( rc < 0 )
> + ns16x50_err(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32": unsupported access\n",
> + op(dir), addr, size, dlab, reg, *data);
> +
> + spin_unlock(&vdev->lock);
> +
> +out:
> + rcu_unlock_domain(d);
> +
> + return X86EMUL_OKAY;
> +#undef op
> +}
> +
> +static int ns16x50_init(void *arg)
> +{
> + struct vuart_ns16x50 *vdev = arg;
> + const struct vuart_info *info = vdev->info;
> + struct domain *d = vdev->owner;
> +
> + ASSERT(vdev);
> +
> + register_portio_handler(d, info->base_addr, info->size, ns16x50_io_handle);
> +
> + return 0;
> +}
> +
> +static void cf_check ns16x50_deinit(void *arg)
> +{
> + struct vuart_ns16x50 *vdev = arg;
> +
> + ASSERT(vdev);
it should unregister the handlers
> +}
> +
> +static void * cf_check ns16x50_alloc(struct domain *d, const struct vuart_info *info)
> +{
> + struct vuart_ns16x50 *vdev;
> + int rc;
> +
> + if ( !info )
> + return ERR_PTR(-EINVAL);
> +
> + if ( vuart_find_by_io_range(d, info->base_addr, info->size) )
> + {
> + ns16x50_err(info, "already registered\n");
> + return ERR_PTR(-EBUSY);
> + }
> +
> + if ( !is_hvm_domain(d) )
> + {
> + ns16x50_err(info, "not an HVM domain\n");
> + return ERR_PTR(-ENOSYS);
> + }
> +
> + vdev = xvzalloc(typeof(*vdev));
> + if ( !vdev )
> + {
> + ns16x50_err(info, "failed to allocate memory\n");
> + return ERR_PTR(-ENOMEM);
> + }
> +
> + /* Save convenience pointer. */
> + vdev->name = info->name;
> + vdev->owner = d;
> + vdev->info = info;
the spinlock should be initialized
> + rc = ns16x50_init(vdev);
> + if ( rc )
> + return ERR_PTR(rc);
> +
> + return vdev;
> +}
> +
> +static void cf_check ns16x50_free(void *arg)
> +{
> + struct vuart_ns16x50 *vdev = arg;
> +
> + if ( vdev )
> + ns16x50_deinit(vdev);
> +
> + XVFREE(vdev);
XVFREE should only be called if ( vdev )
> +}
> +
> +#define ns16x50_emulator \
> +{ \
> + .compatible = "ns16550", \
> + .alloc = ns16x50_alloc, \
> + .free = ns16x50_free, \
> + .dump_state = NULL, \
> + .put_rx = NULL, \
> +}
> +
> +VUART_REGISTER(ns16x50, ns16x50_emulator);
> +
> +/*
> + * Local variables:
> + * mode: C
> + * c-file-style: "BSD"
> + * c-basic-offset: 4
> + * indent-tabs-mode: nil
> + * End:
> + */
> diff --git a/xen/include/xen/sched.h b/xen/include/xen/sched.h
> index 02bdc256ce37..613f4596e33d 100644
> --- a/xen/include/xen/sched.h
> +++ b/xen/include/xen/sched.h
> @@ -23,6 +23,7 @@
> #include <asm/atomic.h>
> #include <asm/current.h>
> #include <xen/vpci.h>
> +#include <xen/vuart.h>
> #include <xen/wait.h>
> #include <public/xen.h>
> #include <public/domctl.h>
> @@ -660,6 +661,9 @@ struct domain
> struct {
> /* Permission to take ownership of the physical console input. */
> bool input_allowed;
> +#ifdef CONFIG_VUART_FRAMEWORK
> + struct vuart *vuart;
> +#endif
this should be in patch #1, as patch #1 does:
d->console.vuart = vuart;
> } console;
> } __aligned(PAGE_SIZE);
>
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 05/15] emul/ns16x50: implement EIR/IIR registers
2025-08-28 23:53 ` [PATCH v5 05/15] emul/ns16x50: implement EIR/IIR registers dmukhin
@ 2025-08-29 20:14 ` Stefano Stabellini
2025-09-05 22:05 ` dmukhin
0 siblings, 1 reply; 46+ messages in thread
From: Stefano Stabellini @ 2025-08-29 20:14 UTC (permalink / raw)
To: dmukhin
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, sstabellini, dmukhin
On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> From: Denis Mukhin <dmukhin@ford.com>
>
> Add interrupt enable register emulation (EIR) and interrupt identity reason
EIR->IER
> (IIR) register emulation to the I/O port handler.
>
> Also add routines for asserting/deasserting the virtual ns16x50 interrupt
> line as a dependent on IIR code.
>
> Poke ns16x50_irq_check() on every I/O register access because the emulator
> does not have clock emulation anyway (e.g. for baud rate emulation).
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v4:
> - new patch
> ---
> xen/common/emul/vuart/ns16x50.c | 177 +++++++++++++++++++++++++++++++-
> 1 file changed, 176 insertions(+), 1 deletion(-)
>
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index f9f307a4ad24..20597cc36b35 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -85,9 +85,131 @@ struct vuart_ns16x50 {
> spinlock_t lock; /* Protection */
> };
>
> +static bool ns16x50_fifo_rx_empty(const struct vuart_ns16x50 *vdev)
> +{
> + const struct xencons_interface *cons = &vdev->cons;
> +
> + return cons->in_prod == cons->in_cons;
> +}
there is no ring so far so I would not add ns16x50_fifo_rx_empty for now
> static inline uint8_t cf_check ns16x50_dlab_get(const struct vuart_ns16x50 *vdev)
> {
> - return 0;
> + return vdev->regs[UART_LCR] & UART_LCR_DLAB ? 1 : 0;
> +}
> +
> +static bool cf_check ns16x50_iir_check_lsi(const struct vuart_ns16x50 *vdev)
> +{
> + return vdev->regs[UART_LSR] & UART_LSR_MASK;
> +}
> +
> +static bool cf_check ns16x50_iir_check_rda(const struct vuart_ns16x50 *vdev)
> +{
> + return !ns16x50_fifo_rx_empty(vdev);
> +}
> +
> +static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
> +{
> + return vdev->regs[NS16X50_REGS_NUM + UART_IIR] & UART_IIR_THR;
> +}
> +
> +static bool cf_check ns16x50_iir_check_msi(const struct vuart_ns16x50 *vdev)
> +{
> + return vdev->regs[UART_MSR] & UART_MSR_CHANGE;
> +}
> +
> +/*
> + * Get the interrupt identity reason.
> + *
> + * IIR is re-calculated once called, because ns16x50 always reports high
> + * priority events first.
> + * regs[NS16X50_REGS_NUM + UART_IIR] is used to store THR reason only.
> + */
> +static uint8_t ns16x50_iir_get(const struct vuart_ns16x50 *vdev)
> +{
> + /*
> + * Interrupt identity reasons by priority.
> + * NB: high priority are at lower indexes below.
> + */
> + static const struct {
> + bool (*check)(const struct vuart_ns16x50 *vdev);
> + uint8_t ier;
> + uint8_t iir;
> + } iir_by_prio[] = {
> + [0] = { ns16x50_iir_check_lsi, UART_IER_ELSI, UART_IIR_LSI },
> + [1] = { ns16x50_iir_check_rda, UART_IER_ERDAI, UART_IIR_RDA },
> + [2] = { ns16x50_iir_check_thr, UART_IER_ETHREI, UART_IIR_THR },
> + [3] = { ns16x50_iir_check_msi, UART_IER_EMSI, UART_IIR_MSI },
> + };
> + const uint8_t *regs = vdev->regs;
> + uint8_t iir = 0;
> + unsigned int i;
> +
> + /*
> + * NB: every interaction w/ ns16x50 registers (except DLAB=1) goes
> + * through that call.
> + */
> + ASSERT(spin_is_locked(&vdev->lock));
> +
> + for ( i = 0; i < ARRAY_SIZE(iir_by_prio); i++ )
> + {
> + if ( (regs[UART_IER] & iir_by_prio[i].ier) &&
> + iir_by_prio[i].check(vdev) )
> + break;
> +
> + }
> + if ( i == ARRAY_SIZE(iir_by_prio) )
> + iir |= UART_IIR_NOINT;
> + else
> + iir |= iir_by_prio[i].iir;
> +
> + if ( regs[UART_FCR] & UART_FCR_ENABLE )
> + iir |= UART_IIR_FE;
> +
> + return iir;
> +}
> +
> +static void ns16x50_irq_assert(const struct vuart_ns16x50 *vdev)
> +{
> + struct domain *d = vdev->owner;
> + const struct vuart_info *info = vdev->info;
> + int vector;
> +
> + if ( has_vpic(d) ) /* HVM */
> + vector = hvm_isa_irq_assert(d, info->irq, vioapic_get_vector);
> + else
> + ASSERT_UNREACHABLE();
> +
> + ns16x50_debug(vdev, "IRQ#%d vector %d assert\n", info->irq, vector);
> +}
> +
> +static void ns16x50_irq_deassert(const struct vuart_ns16x50 *vdev)
> +{
> + struct domain *d = vdev->owner;
> + const struct vuart_info *info = vdev->info;
> +
> + if ( has_vpic(d) ) /* HVM */
> + hvm_isa_irq_deassert(d, info->irq);
> + else
> + ASSERT_UNREACHABLE();
> +
> + ns16x50_debug(vdev, "IRQ#%d deassert\n", info->irq);
> +}
> +
> +/*
> + * Assert/deassert virtual ns16x50 interrupt line.
> + */
> +static void ns16x50_irq_check(const struct vuart_ns16x50 *vdev)
> +{
> + uint8_t iir = ns16x50_iir_get(vdev);
> + const struct vuart_info *info = vdev->info;
> +
> + if ( iir & UART_IIR_NOINT )
> + ns16x50_irq_assert(vdev);
It is a bit strange that if "NOINT" is set, we raise the interrupt
> + else
> + ns16x50_irq_deassert(vdev);
> +
> + ns16x50_debug(vdev, "IRQ#%d IIR 0x%02x %s\n", info->irq, iir,
> + (iir & UART_IIR_NOINT) ? "deassert" : "assert");
> }
>
> /*
> @@ -102,6 +224,29 @@ static int ns16x50_io_write8(
>
> if ( ns16x50_dlab_get(vdev) && (reg == UART_DLL || reg == UART_DLM) )
> regs[NS16X50_REGS_NUM + reg] = val;
> + else
> + {
> + switch ( reg )
> + {
> + case UART_IER:
> + /*
> + * NB: Make sure THR interrupt is re-triggered once guest OS
> + * re-enabled ETHREI in EIR.
EIR->IER
> + */
> + if ( val & regs[UART_IER] & UART_IER_ETHREI )
> + regs[NS16X50_REGS_NUM + UART_IIR] |= UART_IIR_THR;
I am confused by this. Shouldn't it be :
if ( (val & UART_IER_ETHREI) && !(regs[UART_IER] & UART_IER_ETHREI) )
Meaning set UART_IIR_THR if ETHREI goes 0->1 ?
> + regs[UART_IER] = val & UART_IER_MASK;
> +
> + break;
> +
> + default:
> + rc = -EINVAL;
> + break;
> + }
> +
> + ns16x50_irq_check(vdev);
> + }
>
> return rc;
> }
> @@ -164,6 +309,29 @@ static int ns16x50_io_read8(
>
> if ( ns16x50_dlab_get(vdev) && (reg == UART_DLL || reg == UART_DLM) )
> val = regs[NS16X50_REGS_NUM + reg];
> + else {
> + switch ( reg )
> + {
> + case UART_IER:
> + val = regs[UART_IER];
> + break;
> +
> + case UART_IIR: /* RO */
> + val = ns16x50_iir_get(vdev);
> +
> + /* NB: clear IIR scratch location */
> + if ( val & UART_IIR_THR )
> + regs[NS16X50_REGS_NUM + UART_IIR] &= ~UART_IIR_THR;
Maybe add an in-code comment why it is a good idea to clear THR here
> +
> + break;
> +
> + default:
> + rc = -EINVAL;
> + break;
> + }
> +
> + ns16x50_irq_check(vdev);
> + }
>
> *data = val;
>
> @@ -314,8 +482,15 @@ static int ns16x50_init(void *arg)
> vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
> vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
>
> + /* ns16x50 shall assert UART_IIR_THR whenever transmitter is empty. */
> + vdev->regs[NS16X50_REGS_NUM + UART_IIR] = UART_IIR_THR;
> +
> register_portio_handler(d, info->base_addr, info->size, ns16x50_io_handle);
>
> + spin_lock(&vdev->lock);
> + ns16x50_irq_check(vdev);
> + spin_unlock(&vdev->lock);
> +
> return 0;
> }
>
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 06/15] emul/ns16x50: implement THR/RBR registers
2025-08-28 23:54 ` [PATCH v5 06/15] emul/ns16x50: implement THR/RBR registers dmukhin
@ 2025-08-29 20:28 ` Stefano Stabellini
2025-08-29 20:34 ` Stefano Stabellini
2025-09-05 22:20 ` dmukhin
0 siblings, 2 replies; 46+ messages in thread
From: Stefano Stabellini @ 2025-08-29 20:28 UTC (permalink / raw)
To: dmukhin
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, sstabellini, dmukhin
On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> From: Denis Mukhin <dmukhin@ford.com>
>
> Add RBR/THR registers emulation to the I/O port handlder.
>
> Also, add RX/TX FIFO management code since RBR depends on RX FIFO and
> THR depends on TX FIFO.
>
> FIFOs are not emulated as per UART specs for simplicity (not need to emulate
> baud rate). Emulator does not emulate NS8250 (no FIFO), NS16550a (16 bytes) or
> NS16750 (64 bytes).
>
> FIFOs are emulated by means of using xencons_interface which conveniently
> provides primitives for buffer management and later can be used for
> inter-domain communication similarly to vpl011.
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v4:
> - new patch
> ---
> xen/common/emul/vuart/ns16x50.c | 135 ++++++++++++++++++++++++++++++++
> 1 file changed, 135 insertions(+)
>
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index 20597cc36b35..efb2f4c6441c 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -92,6 +92,119 @@ static bool ns16x50_fifo_rx_empty(const struct vuart_ns16x50 *vdev)
> return cons->in_prod == cons->in_cons;
> }
>
> +static bool ns16x50_fifo_rx_full(const struct vuart_ns16x50 *vdev)
> +{
> + const struct xencons_interface *cons = &vdev->cons;
> +
> + return cons->in_prod - cons->in_cons == ARRAY_SIZE(cons->in);
> +}
> +
> +static void ns16x50_fifo_rx_reset(struct vuart_ns16x50 *vdev)
> +{
> + struct xencons_interface *cons = &vdev->cons;
> +
> + cons->in_cons = cons->in_prod;
> +}
> +
> +static int ns16x50_fifo_rx_getchar(struct vuart_ns16x50 *vdev)
> +{
> + struct xencons_interface *cons = &vdev->cons;
> + int rc;
> +
> + if ( ns16x50_fifo_rx_empty(vdev) )
> + {
> + ns16x50_debug(vdev, "RX FIFO empty\n");
> + rc = -ENODATA;
> + }
> + else
> + {
> + rc = cons->in[MASK_XENCONS_IDX(cons->in_cons, cons->in)];
> + cons->in_cons++;
> + }
> +
> + return rc;
The signed integer to char conversion here is not great from a MISRA
perspective. I think it would be better to keep rc as success/failure
return value and take the read char as a pointer parameter.
> +}
> +
> +static int ns16x50_fifo_rx_putchar(struct vuart_ns16x50 *vdev, char c)
> +{
> + struct xencons_interface *cons = &vdev->cons;
> + int rc;
> +
> + /*
> + * FIFO-less 8250/16450 UARTs: newly arrived word overwrites the contents
> + * of the THR.
> + */
> + if ( ns16x50_fifo_rx_full(vdev) )
> + {
> + ns16x50_debug(vdev, "RX FIFO full; resetting\n");
> + ns16x50_fifo_rx_reset(vdev);
> + rc = -ENOSPC;
> + }
> + else
> + rc = 0;
> +
> + cons->in[MASK_XENCONS_IDX(cons->in_prod, cons->in)] = c;
> + cons->in_prod++;
> +
> + return rc;
> +}
> +
> +static bool ns16x50_fifo_tx_empty(const struct vuart_ns16x50 *vdev)
> +{
> + const struct xencons_interface *cons = &vdev->cons;
> +
> + return cons->out_prod == cons->out_cons;
> +}
> +
> +static void ns16x50_fifo_tx_reset(struct vuart_ns16x50 *vdev)
> +{
> + struct xencons_interface *cons = &vdev->cons;
> +
> + cons->out_prod = 0;
> + ASSERT(cons->out_cons == cons->out_prod);
I am not sure about this.. why resetting the out_prod index? In theory
it could keep increasing until wrap around and still go forward thanks
to the mask
> +}
> +
> +/*
> + * Flush cached output to Xen console.
> + */
> +static void ns16x50_fifo_tx_flush(struct vuart_ns16x50 *vdev)
> +{
> + struct xencons_interface *cons = &vdev->cons;
> + struct domain *d = vdev->owner;
> +
> + if ( ns16x50_fifo_tx_empty(vdev) )
> + return;
> +
> + UART_IIR_THR ASSERT(cons->out_prod < ARRAY_SIZE(cons->out));
> + cons->out[cons->out_prod] = '\0';
should use MASK_XENCONS_IDX to access the array
> + cons->out_prod++;
> +
> + guest_printk(d, guest_prefix "%s", cons->out);
> +
> + ns16x50_fifo_tx_reset(vdev);
set UART_IIR_THR and call ns16x50_irq_check ?
> +}
> +
> +/*
> + * Accumulate guest OS output before sending to Xen console.
> + */
> +static void ns16x50_fifo_tx_putchar(struct vuart_ns16x50 *vdev, char ch)
> +{
> + struct xencons_interface *cons = &vdev->cons;
> +
> + if ( !is_console_printable(ch) )
> + return;
> +
> + if ( ch != '\0' )
> + {
> + cons->out[cons->out_prod] = ch;
should use MASK_XENCONS_IDX to access the array
> + cons->out_prod++;
> + }
> +
> + if ( cons->out_prod == ARRAY_SIZE(cons->out) - 1 ||
> + ch == '\n' || ch == '\0' )
> + ns16x50_fifo_tx_flush(vdev);
> +}
> +
> static inline uint8_t cf_check ns16x50_dlab_get(const struct vuart_ns16x50 *vdev)
> {
> return vdev->regs[UART_LCR] & UART_LCR_DLAB ? 1 : 0;
> @@ -228,6 +341,16 @@ static int ns16x50_io_write8(
> {
> switch ( reg )
> {
> + case UART_THR:
> + if ( regs[UART_MCR] & UART_MCR_LOOP )
> + rc = ns16x50_fifo_rx_putchar(vdev, val);
> + else
> + ns16x50_fifo_tx_putchar(vdev, val);
should UART_IIR_THR be cleared here?
> + rc = 0;
> +
> + break;
> +
> case UART_IER:
> /*
> * NB: Make sure THR interrupt is re-triggered once guest OS
> @@ -312,6 +435,14 @@ static int ns16x50_io_read8(
> else {
> switch ( reg )
> {
> + case UART_RBR:
> + rc = ns16x50_fifo_rx_getchar(vdev);
> + if ( rc >= 0 )
> + val = (uint8_t)rc;
Empty fifo check?
> + rc = 0;
> + break;
> +
> case UART_IER:
> val = regs[UART_IER];
> break;
> @@ -499,6 +630,10 @@ static void cf_check ns16x50_deinit(void *arg)
> struct vuart_ns16x50 *vdev = arg;
>
> ASSERT(vdev);
> +
> + spin_lock(&vdev->lock);
> + ns16x50_fifo_tx_flush(vdev);
> + spin_unlock(&vdev->lock);
> }
>
> static void * cf_check ns16x50_alloc(struct domain *d, const struct vuart_info *info)
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 06/15] emul/ns16x50: implement THR/RBR registers
2025-08-29 20:28 ` Stefano Stabellini
@ 2025-08-29 20:34 ` Stefano Stabellini
2025-09-05 22:29 ` dmukhin
2025-09-05 22:20 ` dmukhin
1 sibling, 1 reply; 46+ messages in thread
From: Stefano Stabellini @ 2025-08-29 20:34 UTC (permalink / raw)
To: Stefano Stabellini
Cc: dmukhin, xen-devel, andrew.cooper3, anthony.perard, jbeulich,
julien, michal.orzel, roger.pau, dmukhin
On Fri, 29 Aug 2025, Stefano Stabellini wrote:
> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > From: Denis Mukhin <dmukhin@ford.com>
> >
> > Add RBR/THR registers emulation to the I/O port handlder.
> >
> > Also, add RX/TX FIFO management code since RBR depends on RX FIFO and
> > THR depends on TX FIFO.
> >
> > FIFOs are not emulated as per UART specs for simplicity (not need to emulate
> > baud rate). Emulator does not emulate NS8250 (no FIFO), NS16550a (16 bytes) or
> > NS16750 (64 bytes).
> >
> > FIFOs are emulated by means of using xencons_interface which conveniently
> > provides primitives for buffer management and later can be used for
> > inter-domain communication similarly to vpl011.
> >
> > Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> > ---
> > Changes since v4:
> > - new patch
> > ---
> > xen/common/emul/vuart/ns16x50.c | 135 ++++++++++++++++++++++++++++++++
> > 1 file changed, 135 insertions(+)
> >
> > diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> > index 20597cc36b35..efb2f4c6441c 100644
> > --- a/xen/common/emul/vuart/ns16x50.c
> > +++ b/xen/common/emul/vuart/ns16x50.c
> > @@ -92,6 +92,119 @@ static bool ns16x50_fifo_rx_empty(const struct vuart_ns16x50 *vdev)
> > return cons->in_prod == cons->in_cons;
> > }
> >
> > +static bool ns16x50_fifo_rx_full(const struct vuart_ns16x50 *vdev)
> > +{
> > + const struct xencons_interface *cons = &vdev->cons;
> > +
> > + return cons->in_prod - cons->in_cons == ARRAY_SIZE(cons->in);
> > +}
> > +
> > +static void ns16x50_fifo_rx_reset(struct vuart_ns16x50 *vdev)
> > +{
> > + struct xencons_interface *cons = &vdev->cons;
> > +
> > + cons->in_cons = cons->in_prod;
> > +}
> > +
> > +static int ns16x50_fifo_rx_getchar(struct vuart_ns16x50 *vdev)
> > +{
> > + struct xencons_interface *cons = &vdev->cons;
> > + int rc;
> > +
> > + if ( ns16x50_fifo_rx_empty(vdev) )
> > + {
> > + ns16x50_debug(vdev, "RX FIFO empty\n");
> > + rc = -ENODATA;
> > + }
> > + else
> > + {
> > + rc = cons->in[MASK_XENCONS_IDX(cons->in_cons, cons->in)];
> > + cons->in_cons++;
> > + }
> > +
> > + return rc;
>
> The signed integer to char conversion here is not great from a MISRA
> perspective. I think it would be better to keep rc as success/failure
> return value and take the read char as a pointer parameter.
>
>
> > +}
> > +
> > +static int ns16x50_fifo_rx_putchar(struct vuart_ns16x50 *vdev, char c)
> > +{
> > + struct xencons_interface *cons = &vdev->cons;
> > + int rc;
> > +
> > + /*
> > + * FIFO-less 8250/16450 UARTs: newly arrived word overwrites the contents
> > + * of the THR.
> > + */
> > + if ( ns16x50_fifo_rx_full(vdev) )
> > + {
> > + ns16x50_debug(vdev, "RX FIFO full; resetting\n");
> > + ns16x50_fifo_rx_reset(vdev);
> > + rc = -ENOSPC;
> > + }
> > + else
> > + rc = 0;
> > +
> > + cons->in[MASK_XENCONS_IDX(cons->in_prod, cons->in)] = c;
> > + cons->in_prod++;
> > +
> > + return rc;
> > +}
> > +
> > +static bool ns16x50_fifo_tx_empty(const struct vuart_ns16x50 *vdev)
> > +{
> > + const struct xencons_interface *cons = &vdev->cons;
> > +
> > + return cons->out_prod == cons->out_cons;
> > +}
> > +
> > +static void ns16x50_fifo_tx_reset(struct vuart_ns16x50 *vdev)
> > +{
> > + struct xencons_interface *cons = &vdev->cons;
> > +
> > + cons->out_prod = 0;
> > + ASSERT(cons->out_cons == cons->out_prod);
>
> I am not sure about this.. why resetting the out_prod index? In theory
> it could keep increasing until wrap around and still go forward thanks
> to the mask
I get it now .. it is because it is called from ns16x50_fifo_tx_flush
right after printing to the real console.
I think it is better to simply do:
cons->out_cons = cons->out_prod;
which effectively clears out the whole buffer. It is dangerous to do:
cons->out_prod = 0;
without also doing:
cons->out_cons = 0;
Also, if ns16x50_fifo_tx_flush is the only caller of
ns16x50_fifo_tx_reset, I would open code the implementation directly
inside ns16x50_fifo_tx_flush to make it more obvious.
> > +}
> > +
> > +/*
> > + * Flush cached output to Xen console.
> > + */
> > +static void ns16x50_fifo_tx_flush(struct vuart_ns16x50 *vdev)
> > +{
> > + struct xencons_interface *cons = &vdev->cons;
> > + struct domain *d = vdev->owner;
> > +
> > + if ( ns16x50_fifo_tx_empty(vdev) )
> > + return;
> > +
> > + UART_IIR_THR ASSERT(cons->out_prod < ARRAY_SIZE(cons->out));
> > + cons->out[cons->out_prod] = '\0';
>
> should use MASK_XENCONS_IDX to access the array
>
>
> > + cons->out_prod++;
> > +
> > + guest_printk(d, guest_prefix "%s", cons->out);
> > +
> > + ns16x50_fifo_tx_reset(vdev);
>
> set UART_IIR_THR and call ns16x50_irq_check ?
>
>
> > +}
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 07/15] emul/ns16x50: implement FCR register (write-only)
2025-08-28 23:54 ` [PATCH v5 07/15] emul/ns16x50: implement FCR register (write-only) dmukhin
@ 2025-08-29 20:38 ` Stefano Stabellini
2025-09-05 22:33 ` dmukhin
0 siblings, 1 reply; 46+ messages in thread
From: Stefano Stabellini @ 2025-08-29 20:38 UTC (permalink / raw)
To: dmukhin
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, sstabellini, dmukhin
On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> From: Denis Mukhin <dmukhin@ford.com>
>
> Add emulation logic for FCR register.
>
> Note, that does not hooks FIFO interrupt moderation to the FIFO management
> code.
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v4:
> - new patch
> ---
> xen/common/emul/vuart/ns16x50.c | 24 ++++++++++++++++++++++++
> 1 file changed, 24 insertions(+)
>
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index efb2f4c6441c..65ca96dd8bd3 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -363,6 +363,30 @@ static int ns16x50_io_write8(
>
> break;
>
> + case UART_FCR: /* WO */
> + if ( val & UART_FCR_RESERVED0 )
> + ns16x50_warn(vdev, "FCR: attempt to set reserved bit: %x\n",
> + UART_FCR_RESERVED0);
> +
> + if ( val & UART_FCR_RESERVED1 )
> + ns16x50_warn(vdev, "FCR: attempt to set reserved bit: %x\n",
> + UART_FCR_RESERVED1);
> +
> + if ( val & UART_FCR_CLRX )
> + ns16x50_fifo_rx_reset(vdev);
> +
> + if ( val & UART_FCR_CLTX )
> + ns16x50_fifo_tx_flush(vdev);
Should UART_FCR_CLTX actually emit data or only clear the buffer?
set UART_IIR_THR ?
> +
> + if ( val & UART_FCR_ENABLE )
> + val &= UART_FCR_ENABLE | UART_FCR_DMA | UART_FCR_TRG_MASK;
> + else
> + val = 0;
> +
> + regs[UART_FCR] = val;
ns16x50_irq_check ?
> + break;
> +
> default:
> rc = -EINVAL;
> break;
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 13/15] x86/domain: enable per-domain I/O port bitmaps
2025-08-28 23:54 ` [PATCH v5 13/15] x86/domain: enable per-domain I/O port bitmaps dmukhin
@ 2025-08-29 21:43 ` Stefano Stabellini
2025-09-05 22:38 ` dmukhin
0 siblings, 1 reply; 46+ messages in thread
From: Stefano Stabellini @ 2025-08-29 21:43 UTC (permalink / raw)
To: dmukhin
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, sstabellini, dmukhin
On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> From: Denis Mukhin <dmukhin@ford.com>
>
> Current design enables all HVM domains share the same I/O port bitmap.
>
> It is necessary for domains crafting its own I/O port address space depending
> on the user configuration.
>
> Ensure NS16550 emulator does not share I/O ports with the physical I/O ports,
> which is essential for emulation in PVH hwdom case (dom0).
>
> Not a functional change.
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v4:
> - new patch
> - Link tp v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-4-dmukhin@ford.com/
> ---
> xen/arch/x86/Makefile | 1 +
> xen/arch/x86/dom0_build.c | 111 +--------------
> xen/arch/x86/hvm/hvm.c | 35 +----
> xen/arch/x86/hvm/nestedhvm.c | 8 +-
> xen/arch/x86/hvm/quirks.c | 3 -
> xen/arch/x86/hvm/svm/nestedsvm.c | 2 +-
> xen/arch/x86/hvm/vmx/vvmx.c | 4 +-
> xen/arch/x86/include/asm/hvm/nestedhvm.h | 3 +-
> xen/arch/x86/include/asm/hvm/support.h | 2 -
> xen/arch/x86/include/asm/iocap.h | 2 +
> xen/arch/x86/ioport.c | 163 +++++++++++++++++++++++
> xen/arch/x86/pv/dom0_build.c | 4 +
> xen/common/emul/vuart/ns16x50.c | 11 ++
> 13 files changed, 200 insertions(+), 149 deletions(-)
> create mode 100644 xen/arch/x86/ioport.c
>
> diff --git a/xen/arch/x86/Makefile b/xen/arch/x86/Makefile
> index 9d67ea7cd4a8..5726ecc180eb 100644
> --- a/xen/arch/x86/Makefile
> +++ b/xen/arch/x86/Makefile
> @@ -44,6 +44,7 @@ obj-y += msi.o
> obj-y += msr.o
> obj-$(CONFIG_INDIRECT_THUNK) += indirect-thunk.o
> obj-$(CONFIG_RETURN_THUNK) += indirect-thunk.o
> +obj-y += ioport.o
> obj-$(CONFIG_PV) += ioport_emulate.o
> obj-y += irq.o
> obj-$(CONFIG_KEXEC) += machine_kexec.o
> diff --git a/xen/arch/x86/dom0_build.c b/xen/arch/x86/dom0_build.c
> index 0b467fd4a4fc..26202b33345c 100644
> --- a/xen/arch/x86/dom0_build.c
> +++ b/xen/arch/x86/dom0_build.c
> @@ -298,9 +298,6 @@ int __init parse_arch_dom0_param(const char *s, const char *e)
> return 0;
> }
>
> -static char __initdata opt_dom0_ioports_disable[200] = "";
> -string_param("dom0_ioports_disable", opt_dom0_ioports_disable);
> -
> static bool __initdata ro_hpet = true;
> boolean_param("ro-hpet", ro_hpet);
>
> @@ -433,122 +430,20 @@ unsigned long __init dom0_compute_nr_pages(
> return nr_pages;
> }
>
> -static void __init process_dom0_ioports_disable(struct domain *dom0)
> -{
> - unsigned long io_from, io_to;
> - char *t, *s = opt_dom0_ioports_disable;
> - const char *u;
> -
> - if ( *s == '\0' )
> - return;
> -
> - while ( (t = strsep(&s, ",")) != NULL )
> - {
> - io_from = simple_strtoul(t, &u, 16);
> - if ( u == t )
> - {
> - parse_error:
> - printk("Invalid ioport range <%s> "
> - "in dom0_ioports_disable, skipping\n", t);
> - continue;
> - }
> -
> - if ( *u == '\0' )
> - io_to = io_from;
> - else if ( *u == '-' )
> - io_to = simple_strtoul(u + 1, &u, 16);
> - else
> - goto parse_error;
> -
> - if ( (*u != '\0') || (io_to < io_from) || (io_to >= 65536) )
> - goto parse_error;
> -
> - printk("Disabling dom0 access to ioport range %04lx-%04lx\n",
> - io_from, io_to);
> -
> - if ( ioports_deny_access(dom0, io_from, io_to) != 0 )
> - BUG();
> - }
> -}
> -
> +/* Modify I/O memory access permissions. */
> int __init dom0_setup_permissions(struct domain *d)
> {
> unsigned long mfn;
> - unsigned int i, offs;
> - int rc;
> + unsigned int i;
> + int rc = 0;
>
> if ( pv_shim )
> return 0;
>
> - /* The hardware domain is initially permitted full I/O capabilities. */
> - rc = ioports_permit_access(d, 0, 0xFFFF);
> rc |= iomem_permit_access(d, 0UL,
> PFN_DOWN(1UL << domain_max_paddr_bits(d)) - 1);
> rc |= irqs_permit_access(d, 1, nr_irqs_gsi - 1);
>
> - /* Modify I/O port access permissions. */
> -
> - for ( offs = 0, i = ISOLATE_LSB(i8259A_alias_mask) ?: 2;
> - offs <= i8259A_alias_mask; offs += i )
> - {
> - if ( offs & ~i8259A_alias_mask )
> - continue;
> - /* Master Interrupt Controller (PIC). */
> - rc |= ioports_deny_access(d, 0x20 + offs, 0x21 + offs);
> - /* Slave Interrupt Controller (PIC). */
> - rc |= ioports_deny_access(d, 0xA0 + offs, 0xA1 + offs);
> - }
> -
> - /* ELCR of both PICs. */
> - rc |= ioports_deny_access(d, 0x4D0, 0x4D1);
> -
> - /* Interval Timer (PIT). */
> - for ( offs = 0, i = ISOLATE_LSB(pit_alias_mask) ?: 4;
> - offs <= pit_alias_mask; offs += i )
> - if ( !(offs & ~pit_alias_mask) )
> - rc |= ioports_deny_access(d, PIT_CH0 + offs, PIT_MODE + offs);
> -
> - /* PIT Channel 2 / PC Speaker Control. */
> - rc |= ioports_deny_access(d, 0x61, 0x61);
> -
> - /* INIT# and alternative A20M# control. */
> - rc |= ioports_deny_access(d, 0x92, 0x92);
> -
> - /* IGNNE# control. */
> - rc |= ioports_deny_access(d, 0xF0, 0xF0);
> -
> - /* ACPI PM Timer. */
> - if ( pmtmr_ioport )
> - rc |= ioports_deny_access(d, pmtmr_ioport, pmtmr_ioport + 3);
> -
> - /* Reset control. */
> - rc |= ioports_deny_access(d, 0xCF9, 0xCF9);
> -
> - /* PCI configuration space (NB. 0xCF8 has special treatment). */
> - rc |= ioports_deny_access(d, 0xCFC, 0xCFF);
> -
> -#ifdef CONFIG_HVM
> - if ( is_hvm_domain(d) )
> - {
> - /* ISA DMA controller, channels 0-3 (incl possible aliases). */
> - rc |= ioports_deny_access(d, 0x00, 0x1F);
> - /* ISA DMA controller, page registers (incl various reserved ones). */
> - rc |= ioports_deny_access(d, 0x80 + !!hvm_port80_allowed, 0x8F);
> - /* ISA DMA controller, channels 4-7 (incl usual aliases). */
> - rc |= ioports_deny_access(d, 0xC0, 0xDF);
> -
> - /* HVM debug console IO port. */
> - rc |= ioports_deny_access(d, XEN_HVM_DEBUGCONS_IOPORT,
> - XEN_HVM_DEBUGCONS_IOPORT);
> - if ( amd_acpi_c1e_quirk )
> - rc |= ioports_deny_access(d, acpi_smi_cmd, acpi_smi_cmd);
> - }
> -#endif
> - /* Command-line I/O ranges. */
> - process_dom0_ioports_disable(d);
> -
> - /* Modify I/O memory access permissions. */
> -
> /* Local APIC. */
> if ( mp_lapic_addr != 0 )
> {
> diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c
> index 26760cf995df..12736fc61c11 100644
> --- a/xen/arch/x86/hvm/hvm.c
> +++ b/xen/arch/x86/hvm/hvm.c
> @@ -51,6 +51,7 @@
> #include <asm/hvm/vm_event.h>
> #include <asm/hvm/vpt.h>
> #include <asm/i387.h>
> +#include <asm/iocap.h>
> #include <asm/mc146818rtc.h>
> #include <asm/mce.h>
> #include <asm/monitor.h>
> @@ -81,14 +82,6 @@ integer_param("hvm_debug", opt_hvm_debug_level);
>
> struct hvm_function_table __ro_after_init hvm_funcs;
>
> -/*
> - * The I/O permission bitmap is globally shared by all HVM guests except
> - * the hardware domain which needs a more permissive one.
> - */
> -#define HVM_IOBITMAP_SIZE (3 * PAGE_SIZE)
> -unsigned long __section(".bss.page_aligned") __aligned(PAGE_SIZE)
> - hvm_io_bitmap[HVM_IOBITMAP_SIZE / BYTES_PER_LONG];
> -
> /* Xen command-line option to enable HAP */
> static bool __initdata opt_hap_enabled = true;
> boolean_param("hap", opt_hap_enabled);
> @@ -205,15 +198,6 @@ static int __init cf_check hvm_enable(void)
> if ( opt_hvm_fep )
> warning_add(warning_hvm_fep);
>
> - /*
> - * Allow direct access to the PC debug ports 0x80 and 0xed (they are
> - * often used for I/O delays, but the vmexits simply slow things down).
> - */
> - memset(hvm_io_bitmap, ~0, sizeof(hvm_io_bitmap));
> - if ( hvm_port80_allowed )
> - __clear_bit(0x80, hvm_io_bitmap);
> - __clear_bit(0xed, hvm_io_bitmap);
> -
> register_cpu_notifier(&cpu_nfb);
>
> return 0;
> @@ -645,19 +629,12 @@ int hvm_domain_initialise(struct domain *d,
>
> rwlock_init(&d->arch.hvm.pl_time->pt_migrate);
>
> - /* Set the default IO Bitmap. */
> - if ( is_hardware_domain(d) )
> + rc = ioports_setup_access(d);
> + if ( rc )
> {
> - d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);
> - if ( d->arch.hvm.io_bitmap == NULL )
> - {
> - rc = -ENOMEM;
> - goto fail1;
> - }
> - memset(d->arch.hvm.io_bitmap, ~0, HVM_IOBITMAP_SIZE);
> + printk("%pd failed to setup I/O bitmap: %d\n", d, rc);
> + goto fail1;
> }
> - else
> - d->arch.hvm.io_bitmap = hvm_io_bitmap;
>
> register_g2m_portio_handler(d);
> register_vpci_portio_handler(d);
> @@ -684,6 +661,8 @@ int hvm_domain_initialise(struct domain *d,
> break;
> }
>
> + BUG_ON(!d->arch.ioport_caps);
> +
> vpic_init(d);
>
> rc = vioapic_init(d);
> diff --git a/xen/arch/x86/hvm/nestedhvm.c b/xen/arch/x86/hvm/nestedhvm.c
> index bddd77d8109b..d4e03123d910 100644
> --- a/xen/arch/x86/hvm/nestedhvm.c
> +++ b/xen/arch/x86/hvm/nestedhvm.c
> @@ -107,7 +107,7 @@ nestedhvm_vmcx_flushtlb(struct p2m_domain *p2m)
> * The users of the bitmap patterns are in SVM/VMX specific code.
> *
> * bitmap port 0x80 port 0xed
> - * hvm_io_bitmap cleared cleared
> + * hvm.io_bitmap cleared cleared
> * iomap[0] cleared set
> * iomap[1] set cleared
> * iomap[2] set set
> @@ -115,7 +115,7 @@ nestedhvm_vmcx_flushtlb(struct p2m_domain *p2m)
>
> static int __init cf_check nestedhvm_setup(void)
> {
> - /* Same format and size as hvm_io_bitmap (Intel needs only 2 pages). */
> + /* Same format and size as hvm.io_bitmap (Intel needs only 2 pages). */
> unsigned nr = cpu_has_vmx ? 2 : 3;
> unsigned int i, order = get_order_from_pages(nr);
>
> @@ -165,7 +165,7 @@ static int __init cf_check nestedhvm_setup(void)
> __initcall(nestedhvm_setup);
>
> unsigned long *
> -nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed)
> +nestedhvm_vcpu_iomap_get(struct vcpu *v, bool ioport_80, bool ioport_ed)
> {
> int i;
>
> @@ -174,7 +174,7 @@ nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed)
>
> if (ioport_80 == 0) {
> if (ioport_ed == 0)
> - return hvm_io_bitmap;
> + return v->domain->arch.hvm.io_bitmap;
> i = 0;
> } else {
> if (ioport_ed == 0)
> diff --git a/xen/arch/x86/hvm/quirks.c b/xen/arch/x86/hvm/quirks.c
> index 9202f5a47fe9..f4d95441fcff 100644
> --- a/xen/arch/x86/hvm/quirks.c
> +++ b/xen/arch/x86/hvm/quirks.c
> @@ -73,9 +73,6 @@ static int __init cf_check check_port80(void)
>
> dmi_check_system(hvm_no_port80_dmi_table);
>
> - if ( !hvm_port80_allowed )
> - __set_bit(0x80, hvm_io_bitmap);
> -
> return 0;
> }
> __initcall(check_port80);
> diff --git a/xen/arch/x86/hvm/svm/nestedsvm.c b/xen/arch/x86/hvm/svm/nestedsvm.c
> index dc2b6a42534a..cc8500b61665 100644
> --- a/xen/arch/x86/hvm/svm/nestedsvm.c
> +++ b/xen/arch/x86/hvm/svm/nestedsvm.c
> @@ -381,7 +381,7 @@ static int nsvm_vmrun_permissionmap(struct vcpu *v, bool viopm)
> hvm_unmap_guest_frame(ns_viomap, 0);
> }
>
> - svm->ns_iomap = nestedhvm_vcpu_iomap_get(ioport_80, ioport_ed);
> + svm->ns_iomap = nestedhvm_vcpu_iomap_get(v, ioport_80, ioport_ed);
>
> nv->nv_ioport80 = ioport_80;
> nv->nv_ioportED = ioport_ed;
> diff --git a/xen/arch/x86/hvm/vmx/vvmx.c b/xen/arch/x86/hvm/vmx/vvmx.c
> index e4f3a5fe4c71..4da3e6e90e6c 100644
> --- a/xen/arch/x86/hvm/vmx/vvmx.c
> +++ b/xen/arch/x86/hvm/vmx/vvmx.c
> @@ -554,7 +554,7 @@ unsigned long *_shadow_io_bitmap(struct vcpu *v)
> port80 = bitmap[0x80 >> 3] & (1 << (0x80 & 0x7)) ? 1 : 0;
> portED = bitmap[0xed >> 3] & (1 << (0xed & 0x7)) ? 1 : 0;
>
> - return nestedhvm_vcpu_iomap_get(port80, portED);
> + return nestedhvm_vcpu_iomap_get(v, port80, portED);
> }
>
> static void update_msrbitmap(struct vcpu *v, uint32_t shadow_ctrl)
> @@ -622,7 +622,7 @@ void nvmx_update_exec_control(struct vcpu *v, u32 host_cntrl)
> * L1 VMM doesn't intercept IO instruction.
> * Use host configuration and reset IO_BITMAP
> */
> - bitmap = hvm_io_bitmap;
> + bitmap = v->domain->arch.hvm.io_bitmap;
> }
> else {
> /* use IO bitmap */
> diff --git a/xen/arch/x86/include/asm/hvm/nestedhvm.h b/xen/arch/x86/include/asm/hvm/nestedhvm.h
> index ea2c1bc328c7..d691ccb07dd6 100644
> --- a/xen/arch/x86/include/asm/hvm/nestedhvm.h
> +++ b/xen/arch/x86/include/asm/hvm/nestedhvm.h
> @@ -50,7 +50,8 @@ int nestedhvm_hap_nested_page_fault(struct vcpu *v, paddr_t *L2_gpa,
> struct npfec npfec);
>
> /* IO permission map */
> -unsigned long *nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed);
> +unsigned long *nestedhvm_vcpu_iomap_get(struct vcpu *v,
> + bool ioport_80, bool ioport_ed);
>
> /* Misc */
> #define nestedhvm_paging_mode_hap(v) (!!nhvm_vmcx_hap_enabled(v))
> diff --git a/xen/arch/x86/include/asm/hvm/support.h b/xen/arch/x86/include/asm/hvm/support.h
> index 2a7ba36af06f..7e36d00cc188 100644
> --- a/xen/arch/x86/include/asm/hvm/support.h
> +++ b/xen/arch/x86/include/asm/hvm/support.h
> @@ -41,8 +41,6 @@ extern unsigned int opt_hvm_debug_level;
> #define HVM_DBG_LOG(level, _f, _a...) do {} while (0)
> #endif
>
> -extern unsigned long hvm_io_bitmap[];
> -
> enum hvm_translation_result {
> HVMTRANS_okay,
> HVMTRANS_bad_linear_to_gfn,
> diff --git a/xen/arch/x86/include/asm/iocap.h b/xen/arch/x86/include/asm/iocap.h
> index f948b7186e95..1083f6171cf7 100644
> --- a/xen/arch/x86/include/asm/iocap.h
> +++ b/xen/arch/x86/include/asm/iocap.h
> @@ -22,6 +22,8 @@
> #define cache_flush_permitted(d) \
> (has_arch_io_resources(d) || has_arch_pdevs(d))
>
> +int ioports_setup_access(struct domain *d);
> +
> static inline int ioports_permit_access(struct domain *d, unsigned long s,
> unsigned long e)
> {
> diff --git a/xen/arch/x86/ioport.c b/xen/arch/x86/ioport.c
> new file mode 100644
> index 000000000000..dbcd52d37a4f
> --- /dev/null
> +++ b/xen/arch/x86/ioport.c
> @@ -0,0 +1,163 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Guest I/O port address space configuration.
> + *
> + * Copyright 2025 Ford Motor Company
> + */
> +
> +#include <xen/domain.h>
> +#include <xen/param.h>
> +
> +#include <asm/amd.h>
> +#include <asm/acpi.h>
> +#include <asm/io-ports.h>
> +#include <asm/iocap.h>
> +#include <asm/pv/shim.h>
> +#include <asm/setup.h>
> +
> +static char __initdata opt_dom0_ioports_disable[200] = "";
> +string_param("dom0_ioports_disable", opt_dom0_ioports_disable);
> +
> +/*
> + * The I/O permission bitmap size.
> + * See: comment in nestedhvm_setup()
> + */
> +#define HVM_IOBITMAP_SIZE (3 * PAGE_SIZE)
> +
> +/* Hide user-defined I/O ports from the guest OS. */
> +static void process_dom0_ioports_disable(struct domain *dom0)
> +{
> + unsigned long io_from, io_to;
> + char *t, *s = opt_dom0_ioports_disable;
> + const char *u;
> +
> + if ( *s == '\0' )
> + return;
> +
> + while ( (t = strsep(&s, ",")) != NULL )
> + {
> + io_from = simple_strtoul(t, &u, 16);
> + if ( u == t )
> + {
> + parse_error:
> + printk("Invalid ioport range <%s> "
> + "in dom0_ioports_disable, skipping\n", t);
> + continue;
> + }
> +
> + if ( *u == '\0' )
> + io_to = io_from;
> + else if ( *u == '-' )
> + io_to = simple_strtoul(u + 1, &u, 16);
> + else
> + goto parse_error;
> +
> + if ( (*u != '\0') || (io_to < io_from) || (io_to >= 65536) )
> + goto parse_error;
> +
> + printk("Disabling dom0 access to ioport range %04lx-%04lx\n",
> + io_from, io_to);
> +
> + if ( ioports_deny_access(dom0, io_from, io_to) != 0 )
> + BUG();
> + }
> +}
> +
> +/* Set the default IO Bitmap. */
> +int ioports_setup_access(struct domain *d)
> +{
> + unsigned int i, offs;
> + int rc;
> +
> + if ( pv_shim )
> + return 0;
> +
> +#ifdef CONFIG_HVM
> + d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);
> + if ( d->arch.hvm.io_bitmap == NULL )
> + return -ENOMEM;
> +
> + memset(d->arch.hvm.io_bitmap, ~0, HVM_IOBITMAP_SIZE);
> +
> + if ( !is_hardware_domain(d) )
> + {
> + /*
> + * Allow direct access to the PC debug ports 0x80 and 0xed (they are
> + * often used for I/O delays, but the vmexits simply slow things down).
> + */
> + if ( hvm_port80_allowed )
> + __clear_bit(0x80, d->arch.hvm.io_bitmap);
> +
> + __clear_bit(0xed, d->arch.hvm.io_bitmap);
> +
> + return 0;
> + }
> +#endif
> +
> + /* The hardware domain is initially permitted full I/O capabilities. */
> + rc = ioports_permit_access(d, 0, 0xFFFF);
> +
> + /* Modify I/O port access permissions. */
> +
> + for ( offs = 0, i = ISOLATE_LSB(i8259A_alias_mask) ?: 2;
> + offs <= i8259A_alias_mask; offs += i )
> + {
> + if ( offs & ~i8259A_alias_mask )
> + continue;
> + /* Master Interrupt Controller (PIC). */
> + rc |= ioports_deny_access(d, 0x20 + offs, 0x21 + offs);
> + /* Slave Interrupt Controller (PIC). */
> + rc |= ioports_deny_access(d, 0xA0 + offs, 0xA1 + offs);
> + }
> +
> + /* ELCR of both PICs. */
> + rc |= ioports_deny_access(d, 0x4D0, 0x4D1);
> +
> + /* Interval Timer (PIT). */
> + for ( offs = 0, i = ISOLATE_LSB(pit_alias_mask) ?: 4;
> + offs <= pit_alias_mask; offs += i )
> + if ( !(offs & ~pit_alias_mask) )
> + rc |= ioports_deny_access(d, PIT_CH0 + offs, PIT_MODE + offs);
> +
> + /* PIT Channel 2 / PC Speaker Control. */
> + rc |= ioports_deny_access(d, 0x61, 0x61);
> +
> + /* INIT# and alternative A20M# control. */
> + rc |= ioports_deny_access(d, 0x92, 0x92);
> +
> + /* IGNNE# control. */
> + rc |= ioports_deny_access(d, 0xF0, 0xF0);
> +
> + /* ACPI PM Timer. */
> + if ( pmtmr_ioport )
> + rc |= ioports_deny_access(d, pmtmr_ioport, pmtmr_ioport + 3);
> +
> + /* Reset control. */
> + rc |= ioports_deny_access(d, 0xCF9, 0xCF9);
> +
> + /* PCI configuration space (NB. 0xCF8 has special treatment). */
> + rc |= ioports_deny_access(d, 0xCFC, 0xCFF);
> +
> +#ifdef CONFIG_HVM
> + if ( is_hvm_domain(d) )
> + {
> + /* ISA DMA controller, channels 0-3 (incl possible aliases). */
> + rc |= ioports_deny_access(d, 0x00, 0x1F);
> + /* ISA DMA controller, page registers (incl various reserved ones). */
> + rc |= ioports_deny_access(d, 0x80 + !!hvm_port80_allowed, 0x8F);
> + /* ISA DMA controller, channels 4-7 (incl usual aliases). */
> + rc |= ioports_deny_access(d, 0xC0, 0xDF);
> +
> + /* HVM debug console IO port. */
> + rc |= ioports_deny_access(d, XEN_HVM_DEBUGCONS_IOPORT,
> + XEN_HVM_DEBUGCONS_IOPORT);
> + if ( amd_acpi_c1e_quirk )
> + rc |= ioports_deny_access(d, acpi_smi_cmd, acpi_smi_cmd);
> + }
> +#endif
> +
> + /* Command-line I/O ranges. */
> + process_dom0_ioports_disable(d);
> +
> + return rc;
> +}
> diff --git a/xen/arch/x86/pv/dom0_build.c b/xen/arch/x86/pv/dom0_build.c
> index 21158ce1812e..2b8b4d869ee7 100644
> --- a/xen/arch/x86/pv/dom0_build.c
> +++ b/xen/arch/x86/pv/dom0_build.c
> @@ -17,6 +17,7 @@
> #include <asm/bootinfo.h>
> #include <asm/bzimage.h>
> #include <asm/dom0_build.h>
> +#include <asm/iocap.h>
> #include <asm/guest.h>
> #include <asm/page.h>
> #include <asm/pv/mm.h>
> @@ -1033,6 +1034,9 @@ static int __init dom0_construct(const struct boot_domain *bd)
> if ( test_bit(XENFEAT_supervisor_mode_kernel, parms.f_required) )
> panic("Dom0 requires supervisor-mode execution\n");
>
> + rc = ioports_setup_access(d);
> + BUG_ON(rc != 0);
> +
> rc = dom0_setup_permissions(d);
> BUG_ON(rc != 0);
>
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index 5c1be854b544..8860f25ffdeb 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -780,9 +780,20 @@ static int ns16x50_init(void *arg)
> struct vuart_ns16x50 *vdev = arg;
> const struct vuart_info *info = vdev->info;
> struct domain *d = vdev->owner;
> + int rc;
>
> ASSERT(vdev);
>
> + /* Disallow sharing physical I/O port */
> + rc = ioports_deny_access(d, info->base_addr,
> + info->base_addr + info->size - 1);
I would be tempted to move ioports_deny_access to hvm_domain_initialise
before vuart_init
> + if ( rc )
> + {
> + ns16x50_err(info, " virtual I/O port range [0x%04lx"PRIx64"..0x%04lx"PRIx64"]: conflict w/ physical range\n",
> + info->base_addr, info->base_addr + info->size - 1);
0x%04lx"PRIx64 seems wrong
> + return rc;
> + }
> +
> /* NB: report 115200 baud rate. */
> vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
> vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 14/15] xen/domain: allocate d->irq_caps before arch-specific initialization
2025-08-28 23:54 ` [PATCH v5 14/15] xen/domain: allocate d->irq_caps before arch-specific initialization dmukhin
@ 2025-08-29 21:46 ` Stefano Stabellini
2025-09-05 22:43 ` dmukhin
0 siblings, 1 reply; 46+ messages in thread
From: Stefano Stabellini @ 2025-08-29 21:46 UTC (permalink / raw)
To: dmukhin
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, sstabellini, dmukhin
On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> From: Denis Mukhin <dmukhin@ford.com>
>
> Make sure that NS16550 emulator does not share virtual device IRQ with the
> physical one. This is needed for enabling NS16550 emulator for PVH hwdom
> (dom0).
>
> To do that, move per-domain interrupt rangeset allocation before arch-specific
> code. Add irqs_setup_access() to setup the initial rangeset.
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v4:
> - new patch
> - Link to original patch in v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-4-dmukhin@ford.com/
> ---
> xen/arch/x86/dom0_build.c | 1 -
> xen/arch/x86/hvm/dom0_build.c | 7 +++++++
> xen/arch/x86/include/asm/irq.h | 2 ++
> xen/arch/x86/irq.c | 8 ++++++++
> xen/arch/x86/pv/dom0_build.c | 3 +++
> xen/common/domain.c | 8 ++++++--
> xen/common/emul/vuart/ns16x50.c | 9 +++++++++
> 7 files changed, 35 insertions(+), 3 deletions(-)
>
> diff --git a/xen/arch/x86/dom0_build.c b/xen/arch/x86/dom0_build.c
> index 26202b33345c..9dc87efbf3e8 100644
> --- a/xen/arch/x86/dom0_build.c
> +++ b/xen/arch/x86/dom0_build.c
> @@ -442,7 +442,6 @@ int __init dom0_setup_permissions(struct domain *d)
>
> rc |= iomem_permit_access(d, 0UL,
> PFN_DOWN(1UL << domain_max_paddr_bits(d)) - 1);
> - rc |= irqs_permit_access(d, 1, nr_irqs_gsi - 1);
>
> /* Local APIC. */
> if ( mp_lapic_addr != 0 )
> diff --git a/xen/arch/x86/hvm/dom0_build.c b/xen/arch/x86/hvm/dom0_build.c
> index 5551f9044836..245a42dec9aa 100644
> --- a/xen/arch/x86/hvm/dom0_build.c
> +++ b/xen/arch/x86/hvm/dom0_build.c
> @@ -1348,6 +1348,13 @@ int __init dom0_construct_pvh(const struct boot_domain *bd)
> */
> pvh_setup_mmcfg(d);
>
> + rc = irqs_setup_access(d);
> + if ( rc )
> + {
> + printk("%pd unable to setup IRQ rangeset: %d\n", d, rc);
> + return rc;
> + }
> +
> /*
> * Setup permissions early so that calls to add MMIO regions to the
> * p2m as part of vPCI setup don't fail due to permission checks.
> diff --git a/xen/arch/x86/include/asm/irq.h b/xen/arch/x86/include/asm/irq.h
> index 8c81f66434a8..8bffec3bbfee 100644
> --- a/xen/arch/x86/include/asm/irq.h
> +++ b/xen/arch/x86/include/asm/irq.h
> @@ -231,4 +231,6 @@ int allocate_and_map_gsi_pirq(struct domain *d, int index, int *pirq_p);
> int allocate_and_map_msi_pirq(struct domain *d, int index, int *pirq_p,
> int type, struct msi_info *msi);
>
> +int irqs_setup_access(struct domain *d);
> +
> #endif /* _ASM_HW_IRQ_H */
> diff --git a/xen/arch/x86/irq.c b/xen/arch/x86/irq.c
> index 556134f85aa0..079277be719d 100644
> --- a/xen/arch/x86/irq.c
> +++ b/xen/arch/x86/irq.c
> @@ -3046,3 +3046,11 @@ int allocate_and_map_msi_pirq(struct domain *d, int index, int *pirq_p,
>
> return ret;
> }
> +
> +int irqs_setup_access(struct domain *d)
> +{
> + if ( is_hardware_domain(d) )
> + return irqs_permit_access(d, 1, nr_irqs_gsi - 1);
> +
> + return 0;
> +}
> diff --git a/xen/arch/x86/pv/dom0_build.c b/xen/arch/x86/pv/dom0_build.c
> index 2b8b4d869ee7..1a092b802833 100644
> --- a/xen/arch/x86/pv/dom0_build.c
> +++ b/xen/arch/x86/pv/dom0_build.c
> @@ -1037,6 +1037,9 @@ static int __init dom0_construct(const struct boot_domain *bd)
> rc = ioports_setup_access(d);
> BUG_ON(rc != 0);
>
> + rc = irqs_setup_access(d);
> + BUG_ON(rc != 0);
> +
> rc = dom0_setup_permissions(d);
> BUG_ON(rc != 0);
>
> diff --git a/xen/common/domain.c b/xen/common/domain.c
> index 104e917f07e3..eb83e3198f37 100644
> --- a/xen/common/domain.c
> +++ b/xen/common/domain.c
> @@ -950,6 +950,11 @@ struct domain *domain_create(domid_t domid,
> radix_tree_init(&d->pirq_tree);
> #endif
>
> + err = -ENOMEM;
> + d->irq_caps = rangeset_new(d, "Interrupts", 0);
> + if ( !d->irq_caps )
> + goto fail;
> +
> if ( (err = arch_domain_create(d, config, flags)) != 0 )
> goto fail;
> init_status |= INIT_arch;
> @@ -959,8 +964,7 @@ struct domain *domain_create(domid_t domid,
>
> err = -ENOMEM;
> d->iomem_caps = rangeset_new(d, "I/O Memory", RANGESETF_prettyprint_hex);
> - d->irq_caps = rangeset_new(d, "Interrupts", 0);
> - if ( !d->iomem_caps || !d->irq_caps )
> + if ( !d->iomem_caps )
> goto fail;
>
> if ( (err = xsm_domain_create(XSM_HOOK, d, config->ssidref)) != 0 )
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index 8860f25ffdeb..aea38304b60c 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -794,6 +794,15 @@ static int ns16x50_init(void *arg)
> return rc;
> }
>
> + /* Disallow sharing physical IRQ */
> + rc = irq_deny_access(d, info->irq);
> + if ( rc )
> + {
> + ns16x50_err(info, "virtual IRQ#%d: conflict w/ physical IRQ: %d\n",
> + info->irq, rc);
> + return rc;
> + }
Also this one I wonder if it could be in hvm_domain_initialise, it would
make more sense to keep the irq_deny_access there, compared to here
which is supposed to be a generic emulator
> /* NB: report 115200 baud rate. */
> vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
> vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 15/15] emul/ns16x50: implement IRQ emulation via vIOAPIC
2025-08-28 23:54 ` [PATCH v5 15/15] emul/ns16x50: implement IRQ emulation via vIOAPIC dmukhin
@ 2025-08-29 22:21 ` Stefano Stabellini
2025-09-05 22:54 ` dmukhin
0 siblings, 1 reply; 46+ messages in thread
From: Stefano Stabellini @ 2025-08-29 22:21 UTC (permalink / raw)
To: dmukhin
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, sstabellini, dmukhin
On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> From: Denis Mukhin <dmukhin@ford.com>
>
> PVH domains use vIOAPIC, not vPIC and NS16550 emulates ISA IRQs which cannot
> be asserted on vIOAPIC.
One option is to enable the vPIT for PVH domains when the NS16550
emulator is enabled. Would that resolve the problem? That would be a
simpler solution compared to adding IRQ_EMU because the IRQ_* logic is
already quite complex.
Alternatively...
> {map,unmap}_domain_emuirq_pirq() infrastructure is modified by adding new
> type of interrupt resources 'IRQ_EMU' which means 'emulated device IRQ'
> (similarly to IRQ_MSI_EMU).
>
> This is necessary to for IOAPIC emulation code to skip IRQ->PIRQ mapping
> (vioapic_hwdom_map_gsi()) when guest OS unmasks vIOAPIC pin corresponding to
> virtual device's IRQ.
>
> Also, hvm_gsi_eoi() is modified to trigger assertion in hvm_gsi_deassert()
> path for ISA IRQs.
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v4:
> - dropped xl bits
> - cosmetic renames
> - fix hvm_gsi_eoi()
> - Link to v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-8-dmukhin@ford.com/
> ---
> xen/arch/x86/domain.c | 2 +-
> xen/arch/x86/hvm/vioapic.c | 10 ++++++++++
> xen/arch/x86/include/asm/irq.h | 3 ++-
> xen/arch/x86/irq.c | 4 ++--
> xen/arch/x86/physdev.c | 8 ++++----
> xen/common/emul/vuart/ns16x50.c | 32 +++++++++++++++++++++++++++++--
> xen/drivers/passthrough/x86/hvm.c | 13 ++++++++-----
> 7 files changed, 57 insertions(+), 15 deletions(-)
>
> diff --git a/xen/arch/x86/domain.c b/xen/arch/x86/domain.c
> index 19fd86ce88d2..0815d0b31827 100644
> --- a/xen/arch/x86/domain.c
> +++ b/xen/arch/x86/domain.c
> @@ -1048,7 +1048,7 @@ int arch_domain_soft_reset(struct domain *d)
> write_lock(&d->event_lock);
> for ( i = 0; i < d->nr_pirqs ; i++ )
> {
> - if ( domain_pirq_to_emuirq(d, i) != IRQ_UNBOUND )
> + if ( domain_irq_to_emuirq(d, i) != IRQ_UNBOUND )
> {
> ret = unmap_domain_pirq_emuirq(d, i);
> if ( ret )
> diff --git a/xen/arch/x86/hvm/vioapic.c b/xen/arch/x86/hvm/vioapic.c
> index 7c725f9e471f..fd073f6fba4b 100644
> --- a/xen/arch/x86/hvm/vioapic.c
> +++ b/xen/arch/x86/hvm/vioapic.c
> @@ -177,6 +177,16 @@ static int vioapic_hwdom_map_gsi(unsigned int gsi, unsigned int trig,
>
> ASSERT(is_hardware_domain(currd));
>
> + /*
> + * Interrupt is claimed by one of the platform virtual devices (e.g.
> + * NS16550); do nothing.
> + */
> + write_lock(&currd->event_lock);
> + ret = domain_irq_to_emuirq(currd, gsi);
> + write_unlock(&currd->event_lock);
> + if ( ret != IRQ_UNBOUND )
> + return 0;
..alternatively, we could have an add-hoc check here? Not very nice but
at least it would be very simple.
In other words, adding vPIT is preferable in my opinion but as a second
option I would consider an ad-hoc check. I would try to avoid adding
IRQ_EMU -- that should be the last resort in my view.
> /* Interrupt has been unmasked, bind it now. */
> ret = mp_register_gsi(gsi, trig, pol);
> if ( ret == -EEXIST )
> diff --git a/xen/arch/x86/include/asm/irq.h b/xen/arch/x86/include/asm/irq.h
> index 8bffec3bbfee..3b24decb05e4 100644
> --- a/xen/arch/x86/include/asm/irq.h
> +++ b/xen/arch/x86/include/asm/irq.h
> @@ -212,7 +212,7 @@ void cleanup_domain_irq_mapping(struct domain *d);
> __ret ? radix_tree_ptr_to_int(__ret) : 0; \
> })
> #define PIRQ_ALLOCATED (-1)
> -#define domain_pirq_to_emuirq(d, pirq) pirq_field(d, pirq, \
> +#define domain_irq_to_emuirq(d, pirq) pirq_field(d, pirq, \
> arch.hvm.emuirq, IRQ_UNBOUND)
> #define domain_emuirq_to_pirq(d, emuirq) ({ \
> void *__ret = radix_tree_lookup(&(d)->arch.hvm.emuirq_pirq, emuirq);\
> @@ -221,6 +221,7 @@ void cleanup_domain_irq_mapping(struct domain *d);
> #define IRQ_UNBOUND (-1)
> #define IRQ_PT (-2)
> #define IRQ_MSI_EMU (-3)
> +#define IRQ_EMU (-4)
>
> bool cpu_has_pending_apic_eoi(void);
>
> diff --git a/xen/arch/x86/irq.c b/xen/arch/x86/irq.c
> index 079277be719d..7a8093cd3238 100644
> --- a/xen/arch/x86/irq.c
> +++ b/xen/arch/x86/irq.c
> @@ -2790,7 +2790,7 @@ int map_domain_emuirq_pirq(struct domain *d, int pirq, int emuirq)
> return -EINVAL;
> }
>
> - old_emuirq = domain_pirq_to_emuirq(d, pirq);
> + old_emuirq = domain_irq_to_emuirq(d, pirq);
> if ( emuirq != IRQ_PT )
> old_pirq = domain_emuirq_to_pirq(d, emuirq);
>
> @@ -2845,7 +2845,7 @@ int unmap_domain_pirq_emuirq(struct domain *d, int pirq)
>
> ASSERT(rw_is_write_locked(&d->event_lock));
>
> - emuirq = domain_pirq_to_emuirq(d, pirq);
> + emuirq = domain_irq_to_emuirq(d, pirq);
> if ( emuirq == IRQ_UNBOUND )
> {
> dprintk(XENLOG_G_ERR, "dom%d: pirq %d not mapped\n",
> diff --git a/xen/arch/x86/physdev.c b/xen/arch/x86/physdev.c
> index 4dfa1c019105..90a9e7d2f120 100644
> --- a/xen/arch/x86/physdev.c
> +++ b/xen/arch/x86/physdev.c
> @@ -145,7 +145,7 @@ int physdev_unmap_pirq(struct domain *d, int pirq)
> if ( is_hvm_domain(d) && has_pirq(d) )
> {
> write_lock(&d->event_lock);
> - if ( domain_pirq_to_emuirq(d, pirq) != IRQ_UNBOUND )
> + if ( domain_irq_to_emuirq(d, pirq) != IRQ_UNBOUND )
> ret = unmap_domain_pirq_emuirq(d, pirq);
> write_unlock(&d->event_lock);
> if ( d == current->domain || ret )
> @@ -191,10 +191,10 @@ ret_t do_physdev_op(int cmd, XEN_GUEST_HANDLE_PARAM(void) arg)
> if ( is_pv_domain(currd) || domain_pirq_to_irq(currd, eoi.irq) > 0 )
> pirq_guest_eoi(pirq);
> if ( is_hvm_domain(currd) &&
> - domain_pirq_to_emuirq(currd, eoi.irq) > 0 )
> + domain_irq_to_emuirq(currd, eoi.irq) > 0 )
> {
> struct hvm_irq *hvm_irq = hvm_domain_irq(currd);
> - int gsi = domain_pirq_to_emuirq(currd, eoi.irq);
> + int gsi = domain_irq_to_emuirq(currd, eoi.irq);
>
> /* if this is a level irq and count > 0, send another
> * notification */
> @@ -267,7 +267,7 @@ ret_t do_physdev_op(int cmd, XEN_GUEST_HANDLE_PARAM(void) arg)
> irq_status_query.flags = 0;
> if ( is_hvm_domain(currd) &&
> domain_pirq_to_irq(currd, irq) <= 0 &&
> - domain_pirq_to_emuirq(currd, irq) == IRQ_UNBOUND )
> + domain_irq_to_emuirq(currd, irq) == IRQ_UNBOUND )
> {
> ret = -EINVAL;
> break;
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index aea38304b60c..1126b53c30df 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -287,7 +287,9 @@ static void ns16x50_irq_assert(const struct vuart_ns16x50 *vdev)
> const struct vuart_info *info = vdev->info;
> int vector;
>
> - if ( has_vpic(d) ) /* HVM */
> + if ( has_vioapic(d) && !has_vpic(d) ) /* PVH */
> + vector = hvm_ioapic_assert(d, info->irq, false);
> + else if ( has_vpic(d) ) /* HVM */
> vector = hvm_isa_irq_assert(d, info->irq, vioapic_get_vector);
> else
> ASSERT_UNREACHABLE();
> @@ -300,7 +302,9 @@ static void ns16x50_irq_deassert(const struct vuart_ns16x50 *vdev)
> struct domain *d = vdev->owner;
> const struct vuart_info *info = vdev->info;
>
> - if ( has_vpic(d) ) /* HVM */
> + if ( has_vioapic(d) && !has_vpic(d) ) /* PVH */
> + hvm_ioapic_deassert(d, info->irq);
> + else if ( has_vpic(d) ) /* HVM */
> hvm_isa_irq_deassert(d, info->irq);
> else
> ASSERT_UNREACHABLE();
> @@ -803,6 +807,17 @@ static int ns16x50_init(void *arg)
> return rc;
> }
>
> + /* Claim virtual IRQ */
> + write_lock(&d->event_lock);
> + rc = map_domain_emuirq_pirq(d, info->irq, IRQ_EMU);
> + write_unlock(&d->event_lock);
> + if ( rc )
> + {
> + ns16x50_err(info, "virtual IRQ#%d: cannot claim: %d\n",
> + info->irq, rc);
> + return rc;
> + }
> +
> /* NB: report 115200 baud rate. */
> vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
> vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
> @@ -822,9 +837,22 @@ static int ns16x50_init(void *arg)
> static void cf_check ns16x50_deinit(void *arg)
> {
> struct vuart_ns16x50 *vdev = arg;
> + const struct vuart_info *info;
> + struct domain *d;
> + int rc;
>
> ASSERT(vdev);
>
> + d = vdev->owner;
> + info = vdev->info;
> +
> + write_lock(&d->event_lock);
> + rc = unmap_domain_pirq_emuirq(d, info->irq);
> + write_unlock(&d->event_lock);
> + if ( rc )
> + ns16x50_err(vdev, "virtual IRQ#%d: cannot unclaim: %d\n",
> + info->irq, rc);
> +
> spin_lock(&vdev->lock);
> ns16x50_fifo_tx_flush(vdev);
> spin_unlock(&vdev->lock);
> diff --git a/xen/drivers/passthrough/x86/hvm.c b/xen/drivers/passthrough/x86/hvm.c
> index a2ca7e0e570c..11711d20a7ea 100644
> --- a/xen/drivers/passthrough/x86/hvm.c
> +++ b/xen/drivers/passthrough/x86/hvm.c
> @@ -923,13 +923,16 @@ static void __hvm_dpci_eoi(struct domain *d,
> static void hvm_gsi_eoi(struct domain *d, unsigned int gsi)
> {
> struct pirq *pirq = pirq_info(d, gsi);
> + int rc;
> +
> + /* Check if GSI is claimed by one of the virtual devices. */
> + rc = domain_irq_to_emuirq(d, gsi);
> + if ( rc != IRQ_UNBOUND )
> + hvm_gsi_deassert(d, gsi);
>
> /* Check if GSI is actually mapped. */
> - if ( !pirq_dpci(pirq) )
> - return;
> -
> - hvm_gsi_deassert(d, gsi);
> - hvm_pirq_eoi(pirq);
> + if ( pirq_dpci(pirq) )
> + hvm_pirq_eoi(pirq);
> }
>
> static int cf_check _hvm_dpci_isairq_eoi(
> --
> 2.51.0
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 01/15] emul/vuart: introduce framework for UART emulators
2025-08-29 19:27 ` Stefano Stabellini
@ 2025-09-01 8:14 ` Jan Beulich
2025-09-01 22:27 ` dmukhin
2025-09-01 22:11 ` dmukhin
1 sibling, 1 reply; 46+ messages in thread
From: Jan Beulich @ 2025-09-01 8:14 UTC (permalink / raw)
To: Stefano Stabellini
Cc: xen-devel, andrew.cooper3, anthony.perard, julien, michal.orzel,
roger.pau, sstabellini, dmukhin, dmukhin
On 29.08.2025 21:27, Stefano Stabellini wrote:
> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
>> --- /dev/null
>> +++ b/xen/common/emul/vuart/vuart.c
>> @@ -0,0 +1,156 @@
>> +/* SPDX-License-Identifier: GPL-2.0-only */
>> +/*
>> + * UART emulator framework.
>> + *
>> + * Copyright 2025 Ford Motor Company
>> + */
>> +
>> +#include <xen/err.h>
>> +#include <xen/sched.h>
>> +#include <xen/vuart.h>
>> +#include <xen/xvmalloc.h>
>> +
>> +#define for_each_emulator(e) \
>> + for ( e = vuart_array_start; e < vuart_array_end; e++ )
>> +
>> +extern const struct vuart_emulator vuart_array_start[];
>> +extern const struct vuart_emulator vuart_array_end[];
>> +
>> +static const struct vuart_emulator *
>> +vuart_match_by_compatible(struct domain *d, const char *compat)
>> +{
>> + const struct vuart_emulator *emulator;
>> +
>> + if ( d->console.vuart )
>> + return NULL;
>> +
>> + for_each_emulator(emulator)
>> + if ( emulator->compatible &&
>> + !strncmp(emulator->compatible, compat,
>> + strlen(emulator->compatible)) )
>
> strncmp will continue until the given count even if compat is shorter
Not really, one string having a nul char and the other not having one is a
difference, at which point comparison will stop. There would be a problem
if "compat" didn't point to a nul-terminated string, though (and I didn't
check that aspect, not the least because then "shorter" doesn't really
make much sense without a length passed in).
Jan
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 01/15] emul/vuart: introduce framework for UART emulators
2025-08-29 19:27 ` Stefano Stabellini
2025-09-01 8:14 ` Jan Beulich
@ 2025-09-01 22:11 ` dmukhin
1 sibling, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-01 22:11 UTC (permalink / raw)
To: Stefano Stabellini
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, sstabellini, dmukhin
On Fri, Aug 29, 2025 at 12:27:13PM -0700, Stefano Stabellini wrote:
> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > From: Denis Mukhin <dmukhin@ford.com>
> >
> > Introduce a driver framework to abstract UART emulators in the hypervisor.
> >
> > That allows for architecture-independent handling of virtual UARTs in the
> > console driver and simplifies enabling new UART emulators.
> >
> > The framework is built under CONFIG_VUART_FRAMEWORK, which will be
> > automatically enabled once the user enables any UART emulator.
> >
> > Current implementation supports maximum of one vUART of each kind per domain.
> >
> > Use new domain_has_vuart() in the console driver code to check whether to
> > forward console input to the domain using vUART.
> >
> > Enable console forwarding over vUART for hardware domains with a vUART. That
> > enables console forwarding to dom0 on x86, since console can be forwarded only
> > to Xen, dom0 and pvshim on x86 as of now.
> >
> > Note: existing vUARTs are deliberately *not* hooked to the new framework to
> > minimize the scope of the patch: vpl011 (i.e. SBSA) emulator and "vuart" (i.e.
> > minimalistic MMIO-mapped dtuart for hwdoms on Arm) are kept unmodified.
> >
> > No functional changes for non-x86 architectures.
> >
> > Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> > ---
> > Changes since v4:
> > - addressed feedback
> > - Link to v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-3-dmukhin@ford.com/
> > ---
> > xen/arch/arm/xen.lds.S | 1 +
> > xen/arch/ppc/xen.lds.S | 1 +
> > xen/arch/riscv/xen.lds.S | 1 +
> > xen/arch/x86/xen.lds.S | 1 +
> > xen/common/Kconfig | 2 +
> > xen/common/Makefile | 1 +
> > xen/common/emul/Kconfig | 6 ++
> > xen/common/emul/Makefile | 1 +
> > xen/common/emul/vuart/Kconfig | 6 ++
> > xen/common/emul/vuart/Makefile | 1 +
> > xen/common/emul/vuart/vuart.c | 156 +++++++++++++++++++++++++++++++++
> > xen/common/keyhandler.c | 3 +
> > xen/drivers/char/console.c | 6 +-
> > xen/include/xen/serial.h | 3 +
> > xen/include/xen/vuart.h | 116 ++++++++++++++++++++++++
> > xen/include/xen/xen.lds.h | 10 +++
> > 16 files changed, 314 insertions(+), 1 deletion(-)
> > create mode 100644 xen/common/emul/Kconfig
> > create mode 100644 xen/common/emul/Makefile
> > create mode 100644 xen/common/emul/vuart/Kconfig
> > create mode 100644 xen/common/emul/vuart/Makefile
> > create mode 100644 xen/common/emul/vuart/vuart.c
> > create mode 100644 xen/include/xen/vuart.h
> >
> > diff --git a/xen/arch/arm/xen.lds.S b/xen/arch/arm/xen.lds.S
> > index db17ff1efa98..cd05b18770f4 100644
> > --- a/xen/arch/arm/xen.lds.S
> > +++ b/xen/arch/arm/xen.lds.S
> > @@ -58,6 +58,7 @@ SECTIONS
> > *(.rodata)
> > *(.rodata.*)
> > VPCI_ARRAY
> > + VUART_ARRAY
> > *(.data.rel.ro)
> > *(.data.rel.ro.*)
> >
> > diff --git a/xen/arch/ppc/xen.lds.S b/xen/arch/ppc/xen.lds.S
> > index 1de0b77fc6b9..f9d4e5b0dcd8 100644
> > --- a/xen/arch/ppc/xen.lds.S
> > +++ b/xen/arch/ppc/xen.lds.S
> > @@ -52,6 +52,7 @@ SECTIONS
> > *(.rodata)
> > *(.rodata.*)
> > VPCI_ARRAY
> > + VUART_ARRAY
> > *(.data.rel.ro)
> > *(.data.rel.ro.*)
> >
> > diff --git a/xen/arch/riscv/xen.lds.S b/xen/arch/riscv/xen.lds.S
> > index edcadff90bfe..59dcaa5fef9a 100644
> > --- a/xen/arch/riscv/xen.lds.S
> > +++ b/xen/arch/riscv/xen.lds.S
> > @@ -47,6 +47,7 @@ SECTIONS
> > *(.rodata)
> > *(.rodata.*)
> > VPCI_ARRAY
> > + VUART_ARRAY
> > *(.data.rel.ro)
> > *(.data.rel.ro.*)
> >
> > diff --git a/xen/arch/x86/xen.lds.S b/xen/arch/x86/xen.lds.S
> > index 966e514f2034..d877b93a6964 100644
> > --- a/xen/arch/x86/xen.lds.S
> > +++ b/xen/arch/x86/xen.lds.S
> > @@ -132,6 +132,7 @@ SECTIONS
> > *(.rodata)
> > *(.rodata.*)
> > VPCI_ARRAY
> > + VUART_ARRAY
> > *(.data.rel.ro)
> > *(.data.rel.ro.*)
> >
> > diff --git a/xen/common/Kconfig b/xen/common/Kconfig
> > index 76f9ce705f7a..78a32b69e2b2 100644
> > --- a/xen/common/Kconfig
> > +++ b/xen/common/Kconfig
> > @@ -676,4 +676,6 @@ config PM_STATS
> > Enable collection of performance management statistics to aid in
> > analyzing and tuning power/performance characteristics of the system
> >
> > +source "common/emul/Kconfig"
> > +
> > endmenu
> > diff --git a/xen/common/Makefile b/xen/common/Makefile
> > index c316957fcb36..c0734480ee4b 100644
> > --- a/xen/common/Makefile
> > +++ b/xen/common/Makefile
> > @@ -11,6 +11,7 @@ obj-$(filter-out $(CONFIG_X86),$(CONFIG_ACPI)) += device.o
> > obj-$(CONFIG_DEVICE_TREE_PARSE) += device-tree/
> > obj-$(CONFIG_IOREQ_SERVER) += dm.o
> > obj-y += domain.o
> > +obj-y += emul/
> > obj-y += event_2l.o
> > obj-y += event_channel.o
> > obj-$(CONFIG_EVTCHN_FIFO) += event_fifo.o
> > diff --git a/xen/common/emul/Kconfig b/xen/common/emul/Kconfig
> > new file mode 100644
> > index 000000000000..7c6764d1756b
> > --- /dev/null
> > +++ b/xen/common/emul/Kconfig
> > @@ -0,0 +1,6 @@
> > +menu "Domain Emulation Features"
> > + visible if EXPERT
> > +
> > +source "common/emul/vuart/Kconfig"
> > +
> > +endmenu
> > diff --git a/xen/common/emul/Makefile b/xen/common/emul/Makefile
> > new file mode 100644
> > index 000000000000..ae0b575c3901
> > --- /dev/null
> > +++ b/xen/common/emul/Makefile
> > @@ -0,0 +1 @@
> > +obj-$(CONFIG_VUART_FRAMEWORK) += vuart/
> > diff --git a/xen/common/emul/vuart/Kconfig b/xen/common/emul/vuart/Kconfig
> > new file mode 100644
> > index 000000000000..ce1b976b7da7
> > --- /dev/null
> > +++ b/xen/common/emul/vuart/Kconfig
> > @@ -0,0 +1,6 @@
> > +config VUART_FRAMEWORK
> > + bool
> > +
> > +menu "UART Emulation"
> > +
> > +endmenu
> > diff --git a/xen/common/emul/vuart/Makefile b/xen/common/emul/vuart/Makefile
> > new file mode 100644
> > index 000000000000..97f792dc6641
> > --- /dev/null
> > +++ b/xen/common/emul/vuart/Makefile
> > @@ -0,0 +1 @@
> > +obj-y += vuart.o
> > diff --git a/xen/common/emul/vuart/vuart.c b/xen/common/emul/vuart/vuart.c
> > new file mode 100644
> > index 000000000000..7b277d00d5c7
> > --- /dev/null
> > +++ b/xen/common/emul/vuart/vuart.c
> > @@ -0,0 +1,156 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * UART emulator framework.
> > + *
> > + * Copyright 2025 Ford Motor Company
> > + */
> > +
> > +#include <xen/err.h>
> > +#include <xen/sched.h>
> > +#include <xen/vuart.h>
> > +#include <xen/xvmalloc.h>
> > +
> > +#define for_each_emulator(e) \
> > + for ( e = vuart_array_start; e < vuart_array_end; e++ )
> > +
> > +extern const struct vuart_emulator vuart_array_start[];
> > +extern const struct vuart_emulator vuart_array_end[];
> > +
> > +static const struct vuart_emulator *
> > +vuart_match_by_compatible(struct domain *d, const char *compat)
> > +{
> > + const struct vuart_emulator *emulator;
> > +
> > + if ( d->console.vuart )
> > + return NULL;
> > +
> > + for_each_emulator(emulator)
> > + if ( emulator->compatible &&
> > + !strncmp(emulator->compatible, compat,
> > + strlen(emulator->compatible)) )
>
> strncmp will continue until the given count even if compat is shorter
I checked lib/strncmp.c. I'll flip the arguments, so that strncmp() will
return early if compat is shorter than emulator->compatible
>
>
> > + return emulator;
> > +
> > + return NULL;
> > +}
> > +
> > +static struct vuart *vuart_find_by_console_permission(const struct domain *d)
> > +{
> > + struct vuart *vuart = d->console.vuart;
> > +
> > + ASSERT(d->console.input_allowed);
>
> This ASSERT looks suspicious as we haven't even checked that vuart!=NULL
> yet. I would remove it
Ack
>
>
> > + if ( !vuart || !vuart->emulator || !vuart->emulator->put_rx ||
> > + !(vuart->flags & VUART_CONSOLE_INPUT))
> > + return NULL;
> > +
> > + return vuart;
> > +}
> > +
> > +struct vuart *vuart_find_by_io_range(struct domain *d, unsigned long addr,
> > + unsigned long size)
> > +{
> > + struct vuart *vuart = d->console.vuart;
> > +
> > + if ( !vuart || !vuart->info )
> > + return NULL;
>
> You might as well call vuart_find_by_console_permission that has all the
> checks already
vuart_find_by_io_range() is used in the I/O hook to find the vUART by its
I/O port range.
Using vuart_find_by_console_permission() here may be problematic if the
vUART does not support/enable console forwarding.
I'll keep this check as is for now.
>
> > +
> > + if ( addr >= vuart->info->base_addr &&
> > + addr + size - 1 <= vuart->info->base_addr + vuart->info->size - 1 )
> > + return vuart;
> > +
> > + return NULL;
> > +}
> > +
> > +int vuart_init(struct domain *d, struct vuart_info *info)
> > +{
> > + const struct vuart_emulator *emulator;
> > + struct vuart *vuart;
> > + int rc;
> > +
> > + emulator = vuart_match_by_compatible(d, info->compatible);
> > + if ( !emulator )
> > + return -ENODEV;
> > +
> > + vuart = xzalloc(typeof(*vuart));
> > + if ( !vuart )
> > + return -ENOMEM;
> > +
> > + vuart->info = xvzalloc(typeof(*info));
> > + if ( !vuart->info )
> > + {
> > + rc = -ENOMEM;
> > + goto err_out;
> > + }
> > + memcpy(vuart->info, info, sizeof(*info));
>
> one thing to note is that the fields (strings) compatible and name are
> copied by address, I am not if it is OK but FYI
Thanks, will update vuart_info struct.
>
>
> > + vuart->vdev = emulator->alloc(d, vuart->info);
> > + if ( IS_ERR(vuart->vdev) )
> > + {
> > + rc = PTR_ERR(vuart->vdev);
> > + goto err_out;
>
> this path leads to vuart->info not being freed
Thanks
>
>
> > + }
> > +
> > + vuart->emulator = emulator;
> > + vuart->owner = d;
> > + vuart->flags |= VUART_CONSOLE_INPUT;
> > +
> > + d->console.input_allowed = true;
> > + d->console.vuart = vuart;
> > +
> > + return 0;
> > +
> > + err_out:
> > + XVFREE(vuart);
> > + return rc;
> > +}
> > +
> > +/*
> > + * Release any resources taken by UART emulators.
> > + *
> > + * NB: no flags are cleared, since currently exit() is called only during
> > + * domain destroy.
> > + */
> > +void vuart_deinit(struct domain *d)
> > +{
> > + struct vuart *vuart = d->console.vuart;
> > +
> > + if ( vuart )
> > + {
> > + vuart->emulator->free(vuart);
>
> should we pass vuart or vuart->vdev here? The emulator state is supposed
> to be vuart->vdev?
That should be vuart->vdev; thanks!
>
> > + XVFREE(vuart->info);
> > + }
> > +
> > + XVFREE(d->console.vuart);
> > +}
> > +
> > +void vuart_dump_state(const struct domain *d)
> > +{
> > + struct vuart *vuart = d->console.vuart;
> > +
> > + if ( vuart )
> > + vuart->emulator->dump_state(vuart);
>
> also here vuart->vdev?
Ack
>
>
> > +}
> > +
> > +/*
> > + * Put character to the *first* suitable emulated UART's FIFO.
> > + */
> > +int vuart_put_rx(struct domain *d, char c)
> > +{
> > + struct vuart *vuart = vuart_find_by_console_permission(d);
> > +
> > + return vuart ? vuart->emulator->put_rx(vuart, c) : -ENODEV;
>
> and here?
Ack
>
>
> > +}
> > +
> > +bool domain_has_vuart(const struct domain *d)
> > +{
> > + return vuart_find_by_console_permission(d);
> > +}
> > +
> > +/*
> > + * Local variables:
> > + * mode: C
> > + * c-file-style: "BSD"
> > + * c-basic-offset: 4
> > + * indent-tabs-mode: nil
> > + * End:
> > + */
> > diff --git a/xen/common/keyhandler.c b/xen/common/keyhandler.c
> > index cb6df2823b00..156e64d9eb58 100644
> > --- a/xen/common/keyhandler.c
> > +++ b/xen/common/keyhandler.c
> > @@ -22,6 +22,7 @@
> > #include <xen/mm.h>
> > #include <xen/watchdog.h>
> > #include <xen/init.h>
> > +#include <xen/vuart.h>
> > #include <asm/div64.h>
> >
> > static unsigned char keypress_key;
> > @@ -352,6 +353,8 @@ static void cf_check dump_domains(unsigned char key)
> > v->periodic_period / 1000000);
> > }
> > }
> > +
> > + vuart_dump_state(d);
> > }
> >
> > for_each_domain ( d )
> > diff --git a/xen/drivers/char/console.c b/xen/drivers/char/console.c
> > index 9bd5b4825da6..d5164897a776 100644
> > --- a/xen/drivers/char/console.c
> > +++ b/xen/drivers/char/console.c
> > @@ -33,6 +33,7 @@
> > #include <asm/setup.h>
> > #include <xen/sections.h>
> > #include <xen/consoled.h>
> > +#include <xen/vuart.h>
> >
> > #ifdef CONFIG_X86
> > #include <asm/guest.h>
> > @@ -596,11 +597,12 @@ static void __serial_rx(char c)
> > if ( !d )
> > return;
> >
> > - if ( is_hardware_domain(d) )
> > + if ( is_hardware_domain(d) && !domain_has_vuart(d) )
> > {
> > /*
> > * Deliver input to the hardware domain buffer, unless it is
> > * already full.
> > + * NB: must be the first check: hardware domain may have emulated UART.
> > */
> > if ( (serial_rx_prod - serial_rx_cons) != SERIAL_RX_SIZE )
> > serial_rx_ring[SERIAL_RX_MASK(serial_rx_prod++)] = c;
> > @@ -611,6 +613,8 @@ static void __serial_rx(char c)
> > */
> > send_global_virq(VIRQ_CONSOLE);
> > }
> > + else if ( domain_has_vuart(d) )
> > + rc = vuart_put_rx(d, c);
> > #ifdef CONFIG_SBSA_VUART_CONSOLE
> > else
> > /* Deliver input to the emulated UART. */
> > diff --git a/xen/include/xen/serial.h b/xen/include/xen/serial.h
> > index 8e1844555208..d7e81f098359 100644
> > --- a/xen/include/xen/serial.h
> > +++ b/xen/include/xen/serial.h
> > @@ -36,6 +36,9 @@ struct vuart_info {
> > unsigned long data_off; /* Data register offset */
> > unsigned long status_off; /* Status register offset */
> > unsigned long status; /* Ready status value */
> > + unsigned int irq; /* Interrupt */
> > + const char *compatible; /* Compatible string */
> > + const char *name; /* User-friendly name */
> > };
> >
> > struct serial_port {
> > diff --git a/xen/include/xen/vuart.h b/xen/include/xen/vuart.h
> > new file mode 100644
> > index 000000000000..ca025b4179be
> > --- /dev/null
> > +++ b/xen/include/xen/vuart.h
> > @@ -0,0 +1,116 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * UART emulator framework.
> > + *
> > + * Copyright 2025 Ford Motor Company
> > + */
> > +
> > +#ifndef XEN_VUART_H
> > +#define XEN_VUART_H
> > +
> > +#include <xen/serial.h>
> > +#include <public/xen.h>
> > +
> > +struct vuart_emulator;
> > +
> > +enum {
> > + VUART_CONSOLE_INPUT = 0U << 1, /** Physical console input forwarding. */
>
> code style: single *
Ack
> This flag is zero, is that intended?
Will fix, thanks
>
>
> > +};
> > +
> > +
> > +/*
> > + * FIXME: #ifdef is temporary to avoid clash with
> > + * arch/arm/include/asm/domain.h
> > + */
> > +#ifdef CONFIG_VUART_FRAMEWORK
> > +struct vuart {
> > + const struct vuart_emulator *emulator;
> > + struct vuart_info *info;
> > + struct domain *owner;
> > + uint32_t flags;
> > + void *vdev;
> > +};
> > +#endif
> > +
> > +struct vuart_emulator {
> > + /* UART compatible string. Cannot be NULL or empty. */
> > + const char *compatible;
> > +
> > + /*
> > + * Allocate emulated UART state (RX/TX FIFOs, locks, initialize registers,
> > + * hook I/O handlers, etc.)
> > + * Cannot be NULL.
> > + */
> > + void *(*alloc)(struct domain *d, const struct vuart_info *info);
> > +
> > + /*
> > + * Release resources used to emulate UART state (flush RX/TX FIFOs, unhook
> > + * I/O handlers, etc.).
> > + * Cannot be NULL.
> > + */
> > + void (*free)(void *arg);
> > +
> > + /*
> > + * Print emulated UART state, including registers, on the console.
> > + * Can be NULL.
> > + */
> > + void (*dump_state)(void *arg);
> > +
> > + /*
> > + * Place character to the emulated RX FIFO.
> > + * Used to forward physical console input to the guest OS.
> > + * Can be NULL.
> > + */
> > + int (*put_rx)(void *arg, char c);
> > +};
> > +
> > +#define VUART_REGISTER(name, x) \
> > + static const struct vuart_emulator name##_entry \
> > + __used_section(".data.rel.ro.vuart") = x
> > +
> > +struct vuart *vuart_find_by_io_range(struct domain *d,
> > + unsigned long base_addr,
> > + unsigned long size);
> > +
> > +int vuart_put_rx(struct domain *d, char c);
> > +
> > +#ifdef CONFIG_VUART_FRAMEWORK
> > +
> > +int vuart_init(struct domain *d, struct vuart_info *info);
> > +void vuart_deinit(struct domain *d);
> > +void vuart_dump_state(const struct domain *d);
> > +bool domain_has_vuart(const struct domain *d);
> > +
> > +#else
> > +
> > +static inline int vuart_init(struct domain *d, struct vuart_info *info)
> > +{
> > + return 0;
> > +}
> > +
> > +static inline void vuart_deinit(struct domain *d)
> > +{
> > +}
> > +
> > +static inline void vuart_dump_state(const struct domain *d)
> > +{
> > +}
> > +
> > +static inline bool domain_has_vuart(const struct domain *d)
> > +{
> > + return false;
> > +}
> > +
> > +#endif /* CONFIG_VUART_FRAMEWORK */
> > +
> > +#endif /* XEN_VUART_H */
> > +
> > +/*
> > + * Local variables:
> > + * mode: C
> > + * c-file-style: "BSD"
> > + * c-basic-offset: 4
> > + * indent-tabs-mode: nil
> > + * End:
> > + */
> > +
> > diff --git a/xen/include/xen/xen.lds.h b/xen/include/xen/xen.lds.h
> > index b126dfe88792..2d65f32ddad3 100644
> > --- a/xen/include/xen/xen.lds.h
> > +++ b/xen/include/xen/xen.lds.h
> > @@ -194,4 +194,14 @@
> > #define VPCI_ARRAY
> > #endif
> >
> > +#ifdef CONFIG_VUART_FRAMEWORK
> > +#define VUART_ARRAY \
> > + . = ALIGN(POINTER_ALIGN); \
> > + vuart_array_start = .; \
> > + *(.data.rel.ro.vuart) \
> > + vuart_array_end = .;
> > +#else
> > +#define VUART_ARRAY
> > +#endif
> > +
> > #endif /* __XEN_LDS_H__ */
> > --
> > 2.51.0
> >
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 01/15] emul/vuart: introduce framework for UART emulators
2025-09-01 8:14 ` Jan Beulich
@ 2025-09-01 22:27 ` dmukhin
2025-09-02 6:14 ` Jan Beulich
0 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-01 22:27 UTC (permalink / raw)
To: Jan Beulich
Cc: Stefano Stabellini, xen-devel, andrew.cooper3, anthony.perard,
julien, michal.orzel, roger.pau, sstabellini, dmukhin
On Mon, Sep 01, 2025 at 10:14:04AM +0200, Jan Beulich wrote:
> On 29.08.2025 21:27, Stefano Stabellini wrote:
> > On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> >> --- /dev/null
> >> +++ b/xen/common/emul/vuart/vuart.c
> >> @@ -0,0 +1,156 @@
> >> +/* SPDX-License-Identifier: GPL-2.0-only */
> >> +/*
> >> + * UART emulator framework.
> >> + *
> >> + * Copyright 2025 Ford Motor Company
> >> + */
> >> +
> >> +#include <xen/err.h>
> >> +#include <xen/sched.h>
> >> +#include <xen/vuart.h>
> >> +#include <xen/xvmalloc.h>
> >> +
> >> +#define for_each_emulator(e) \
> >> + for ( e = vuart_array_start; e < vuart_array_end; e++ )
> >> +
> >> +extern const struct vuart_emulator vuart_array_start[];
> >> +extern const struct vuart_emulator vuart_array_end[];
> >> +
> >> +static const struct vuart_emulator *
> >> +vuart_match_by_compatible(struct domain *d, const char *compat)
> >> +{
> >> + const struct vuart_emulator *emulator;
> >> +
> >> + if ( d->console.vuart )
> >> + return NULL;
> >> +
> >> + for_each_emulator(emulator)
> >> + if ( emulator->compatible &&
> >> + !strncmp(emulator->compatible, compat,
> >> + strlen(emulator->compatible)) )
> >
> > strncmp will continue until the given count even if compat is shorter
>
> Not really, one string having a nul char and the other not having one is a
> difference, at which point comparison will stop. There would be a problem
> if "compat" didn't point to a nul-terminated string, though (and I didn't
> check that aspect, not the least because then "shorter" doesn't really
> make much sense without a length passed in).
re: NUL-termination: current assumption is that both compat and
emulator->compatible are NUL-terminated.
Current `compat` comes from the hardcoded NUL-terminated string (vuart_info).
In case of `compat` is not NUL-terminated (I plan to populate the field from
xl in the future), strncmp() will stop after strlen(emulator->compatible)
bytes.
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 02/15] xen/8250-uart: update definitions
2025-08-29 19:32 ` Stefano Stabellini
@ 2025-09-01 22:28 ` dmukhin
0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-01 22:28 UTC (permalink / raw)
To: Stefano Stabellini
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, dmukhin
On Fri, Aug 29, 2025 at 12:32:46PM -0700, Stefano Stabellini wrote:
> On Thu, 27 Aug 2025, dmukhin@xen.org wrote:
> > From: Denis Mukhin <dmukhin@ford.com>
> >
> > Added missing definitions needed for NS16550 UART emulator.
> >
> > Newly introduced MSR definitions re-used in the existing ns16550 driver.
> >
> > Also, corrected FCR DMA definition bit#3 (0x08) as per:
> > https://www.ti.com/lit/ds/symlink/tl16c550c.pdf
> > See "7.7.2 FIFO Control Register (FCR)".
> >
> > Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> > ---
> > Changes since v4:
> > - reused newly introduced UART_IIR and UART_IER bits in ns16550 driver
> > - Link to v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-5-dmukhin@ford.com/
> > ---
> > xen/drivers/char/ns16550.c | 16 ++++++------
> > xen/include/xen/8250-uart.h | 50 ++++++++++++++++++++++++++++++-------
> > 2 files changed, 49 insertions(+), 17 deletions(-)
> >
> > diff --git a/xen/drivers/char/ns16550.c b/xen/drivers/char/ns16550.c
> > index df7fff7f81df..0e80fadbb894 100644
> > --- a/xen/drivers/char/ns16550.c
> > +++ b/xen/drivers/char/ns16550.c
> > @@ -388,7 +388,7 @@ static void __init cf_check ns16550_init_preirq(struct serial_port *port)
> >
> > /* Check this really is a 16550+. Otherwise we have no FIFOs. */
> > if ( uart->fifo_size <= 1 &&
> > - ((ns_read_reg(uart, UART_IIR) & 0xc0) == 0xc0) &&
> > + ((ns_read_reg(uart, UART_IIR) & UART_IIR_FE) == UART_IIR_FE) &&
> > ((ns_read_reg(uart, UART_FCR) & UART_FCR_TRG14) == UART_FCR_TRG14) )
> > uart->fifo_size = 16;
> > }
> > @@ -728,20 +728,20 @@ static int __init check_existence(struct ns16550 *uart)
> > * Mask out IER[7:4] bits for test as some UARTs (e.g. TL
> > * 16C754B) allow only to modify them if an EFR bit is set.
> > */
> > - scratch2 = ns_read_reg(uart, UART_IER) & 0x0f;
> > - ns_write_reg(uart,UART_IER, 0x0F);
> > - scratch3 = ns_read_reg(uart, UART_IER) & 0x0f;
> > + scratch2 = ns_read_reg(uart, UART_IER) & UART_IER_MASK;
> > + ns_write_reg(uart, UART_IER, UART_IER_MASK);
> > + scratch3 = ns_read_reg(uart, UART_IER) & UART_IER_MASK;
> > ns_write_reg(uart, UART_IER, scratch);
> > - if ( (scratch2 != 0) || (scratch3 != 0x0F) )
> > + if ( (scratch2 != 0) || (scratch3 != UART_IER_MASK) )
> > return 0;
> >
> > /*
> > * Check to see if a UART is really there.
> > * Use loopback test mode.
> > */
> > - ns_write_reg(uart, UART_MCR, UART_MCR_LOOP | 0x0A);
> > - status = ns_read_reg(uart, UART_MSR) & 0xF0;
> > - return (status == 0x90);
> > + ns_write_reg(uart, UART_MCR, UART_MCR_LOOP | UART_MCR_RTS | UART_MCR_OUT2);
> > + status = ns_read_reg(uart, UART_MSR) & UART_MSR_STATUS;
> > + return (status == (UART_MSR_CTS | UART_MSR_DCD));
> > }
> >
> > #ifdef CONFIG_HAS_PCI
> > diff --git a/xen/include/xen/8250-uart.h b/xen/include/xen/8250-uart.h
> > index d13352940c13..bc11cdc376c9 100644
> > --- a/xen/include/xen/8250-uart.h
> > +++ b/xen/include/xen/8250-uart.h
> > @@ -32,6 +32,7 @@
> > #define UART_MCR 0x04 /* Modem control */
> > #define UART_LSR 0x05 /* line status */
> > #define UART_MSR 0x06 /* Modem status */
> > +#define UART_SCR 0x07 /* Scratch pad */
> > #define UART_USR 0x1f /* Status register (DW) */
> > #define UART_DLL 0x00 /* divisor latch (ls) (DLAB=1) */
> > #define UART_DLM 0x01 /* divisor latch (ms) (DLAB=1) */
> > @@ -42,6 +43,8 @@
> > #define UART_IER_ETHREI 0x02 /* tx reg. empty */
> > #define UART_IER_ELSI 0x04 /* rx line status */
> > #define UART_IER_EMSI 0x08 /* MODEM status */
> > +#define UART_IER_MASK \
> > + (UART_IER_ERDAI | UART_IER_ETHREI | UART_IER_ELSI | UART_IER_EMSI)
> >
> > /* Interrupt Identification Register */
> > #define UART_IIR_NOINT 0x01 /* no interrupt pending */
> > @@ -51,12 +54,19 @@
> > #define UART_IIR_THR 0x02 /* - tx reg. empty */
> > #define UART_IIR_MSI 0x00 /* - MODEM status */
> > #define UART_IIR_BSY 0x07 /* - busy detect (DW) */
> > +#define UART_IIR_FE 0xc0 /* FIFO enabled (2 bits) */
> >
> > /* FIFO Control Register */
> > -#define UART_FCR_ENABLE 0x01 /* enable FIFO */
> > -#define UART_FCR_CLRX 0x02 /* clear Rx FIFO */
> > -#define UART_FCR_CLTX 0x04 /* clear Tx FIFO */
> > -#define UART_FCR_DMA 0x10 /* enter DMA mode */
> > +#define UART_FCR_ENABLE BIT(0, U) /* enable FIFO */
> > +#define UART_FCR_CLRX BIT(1, U) /* clear Rx FIFO */
> > +#define UART_FCR_CLTX BIT(2, U) /* clear Tx FIFO */
> > +#define UART_FCR_DMA BIT(3, U) /* enter DMA mode */
> > +#define UART_FCR_RESERVED0 BIT(4, U) /* reserved; always 0 */
> > +#define UART_FCR_RESERVED1 BIT(5, U) /* reserved; always 0 */
> > +#define UART_FCR_RTB0 BIT(6, U) /* receiver trigger bit #0 */
> > +#define UART_FCR_RTB1 BIT(7, U) /* receiver trigger bit #1 */
> > +#define UART_FCR_TRG_MASK (UART_FCR_RTB0 | UART_FCR_RTB1)
> > +
> > #define UART_FCR_TRG1 0x00 /* Rx FIFO trig lev 1 */
> > #define UART_FCR_TRG4 0x40 /* Rx FIFO trig lev 4 */
> > #define UART_FCR_TRG8 0x80 /* Rx FIFO trig lev 8 */
> > @@ -96,11 +106,32 @@
> > #define UART_LCR_CONF_MODE_B 0xBF /* Configuration mode B */
> >
> > /* Modem Control Register */
> > -#define UART_MCR_DTR 0x01 /* Data Terminal Ready */
> > -#define UART_MCR_RTS 0x02 /* Request to Send */
> > -#define UART_MCR_OUT2 0x08 /* OUT2: interrupt mask */
> > -#define UART_MCR_LOOP 0x10 /* Enable loopback test mode */
> > -#define UART_MCR_TCRTLR 0x40 /* Access TCR/TLR (TI16C752, EFR[4]=1) */
> > +#define UART_MCR_DTR BIT(0, U) /* Data Terminal Ready */
> > +#define UART_MCR_RTS BIT(1, U) /* Request to Send */
> > +#define UART_MCR_OUT1 BIT(2, U) /* OUT1: interrupt mask */
>
> is OUT1 an interrupt mask actually?
Yep, thanks, that should be just "Output #1".
>
>
> > +#define UART_MCR_OUT2 BIT(3, U) /* OUT2: interrupt mask */
> > +#define UART_MCR_LOOP BIT(4, U) /* Enable loopback test mode */
> > +#define UART_MCR_RESERVED0 BIT(5, U) /* Reserved #0 */
> > +#define UART_MCR_TCRTLR BIT(6, U) /* Access TCR/TLR (TI16C752, EFR[4]=1) */
> > +#define UART_MCR_RESERVED1 BIT(7, U) /* Reserved #1 */
> > +#define UART_MCR_MASK \
> > + (UART_MCR_DTR | UART_MCR_RTS | \
> > + UART_MCR_OUT1 | UART_MCR_OUT2 | \
> > + UART_MCR_LOOP | UART_MCR_TCRTLR)
> > +
> > +/* Modem Status Register */
> > +#define UART_MSR_DCTS BIT(0, U) /* Change in CTS */
> > +#define UART_MSR_DDSR BIT(1, U) /* Change in DSR */
> > +#define UART_MSR_TERI BIT(2, U) /* Change in RI */
> > +#define UART_MSR_DDCD BIT(3, U) /* Change in CTS */
>
> Changes in DCD, can be done on commit
Will fix.
>
>
> > +#define UART_MSR_CTS BIT(4, U)
> > +#define UART_MSR_DSR BIT(5, U)
> > +#define UART_MSR_RI BIT(6, U)
> > +#define UART_MSR_DCD BIT(7, U)
> > +#define UART_MSR_CHANGE \
> > + (UART_MSR_DCTS | UART_MSR_DDSR | UART_MSR_TERI | UART_MSR_DDCD)
> > +#define UART_MSR_STATUS \
> > + (UART_MSR_CTS | UART_MSR_DSR | UART_MSR_RI | UART_MSR_DCD)
> >
> > /* Line Status Register */
> > #define UART_LSR_DR 0x01 /* Data ready */
> > @@ -111,6 +142,7 @@
> > #define UART_LSR_THRE 0x20 /* Xmit hold reg empty */
> > #define UART_LSR_TEMT 0x40 /* Xmitter empty */
> > #define UART_LSR_ERR 0x80 /* Error */
> > +#define UART_LSR_MASK (UART_LSR_OE | UART_LSR_BI)
> >
> > /* These parity settings can be ORed directly into the LCR. */
> > #define UART_PARITY_NONE (0<<3)
> > --
> > 2.51.0
> >
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 03/15] emul/ns16x50: implement emulator stub
2025-08-29 19:57 ` Stefano Stabellini
@ 2025-09-01 23:11 ` dmukhin
2025-09-02 9:32 ` Jan Beulich
2025-09-02 9:36 ` Jan Beulich
1 sibling, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-01 23:11 UTC (permalink / raw)
To: Stefano Stabellini
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, dmukhin
On Fri, Aug 29, 2025 at 12:57:43PM -0700, Stefano Stabellini wrote:
> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > From: Denis Mukhin <dmukhin@ford.com>
> >
> > The change is the first on the way on introducing minimally functional
> > NS16550-compatible UART emulator.
> >
> > Define UART state and a set of emulated registers.
> >
> > Implement alloc/free vUART hooks.
> >
> > Stub out I/O port handler.
> >
> > Add initialization of the NS16x50-compatible UART emulator state machine.
> >
> > Plumb debug logging.
> >
> > Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> > ---
> > Changes since v4:
> > - new patch
> > ---
> > xen/arch/x86/hvm/hvm.c | 20 ++
> > xen/common/emul/vuart/Kconfig | 18 ++
> > xen/common/emul/vuart/Makefile | 1 +
> > xen/common/emul/vuart/ns16x50.c | 362 ++++++++++++++++++++++++++++++++
> > xen/include/xen/sched.h | 4 +
> > 5 files changed, 405 insertions(+)
> > create mode 100644 xen/common/emul/vuart/ns16x50.c
> >
> > diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c
> > index 23bd7f078a1d..26760cf995df 100644
> > --- a/xen/arch/x86/hvm/hvm.c
> > +++ b/xen/arch/x86/hvm/hvm.c
> > @@ -28,6 +28,7 @@
> > #include <xen/softirq.h>
> > #include <xen/trace.h>
> > #include <xen/vm_event.h>
> > +#include <xen/vuart.h>
> > #include <xen/vpci.h>
> > #include <xen/wait.h>
> > #include <xen/warning.h>
> > @@ -689,6 +690,21 @@ int hvm_domain_initialise(struct domain *d,
> > if ( rc != 0 )
> > goto fail1;
> >
> > + if ( IS_ENABLED(CONFIG_VUART_NS16X50) )
>
> maybe only for the hardware domain?
> or only input_allowed = true only for the hardware domain?
Agreed; I can enable it for dom0 for now.
The plan is have vuart_info populated by xl similarly how it is
done for vpl011.
>
> > + {
> > + struct vuart_info info = {
> > + .name = "COM2",
> > + .compatible = "ns16550",
> > + .base_addr = 0x2f8,
> > + .size = 8,
> > + .irq = 3,
> > + };
> > +
> > + rc = vuart_init(d, &info);
> > + if ( rc )
> > + goto out_vioapic_deinit;
> > + }
> > +
> > stdvga_init(d);
> >
> > rtc_init(d);
> > @@ -712,6 +728,8 @@ int hvm_domain_initialise(struct domain *d,
> > return 0;
> >
> > fail2:
> > + vuart_deinit(d);
> > + out_vioapic_deinit:
> > vioapic_deinit(d);
> > fail1:
> > if ( is_hardware_domain(d) )
> > @@ -774,6 +792,8 @@ void hvm_domain_destroy(struct domain *d)
> > if ( hvm_funcs.domain_destroy )
> > alternative_vcall(hvm_funcs.domain_destroy, d);
> >
> > + vuart_deinit(d);
> > +
> > vioapic_deinit(d);
> >
> > XFREE(d->arch.hvm.pl_time);
> > diff --git a/xen/common/emul/vuart/Kconfig b/xen/common/emul/vuart/Kconfig
> > index ce1b976b7da7..539e6d5e4fc7 100644
> > --- a/xen/common/emul/vuart/Kconfig
> > +++ b/xen/common/emul/vuart/Kconfig
> > @@ -3,4 +3,22 @@ config VUART_FRAMEWORK
> >
> > menu "UART Emulation"
> >
> > +config VUART_NS16X50
> > + bool "NS16550-compatible UART Emulator" if EXPERT
> > + depends on X86 && HVM
> > + select VUART_FRAMEWORK
>
> default n
Ack
>
> > + help
> > + In-hypervisor NS16x50 UART emulation.
> > +
> > + Only legacy PC COM2 port is emulated.
> > +
> > + This is strictly for testing purposes (such as early HVM guest console),
> > + and not appropriate for use in production.
> > +
> > +config VUART_NS16X50_DEBUG
> > + bool "NS16550-compatible UART Emulator Debugging"
> > + depends on VUART_NS16X50 && DEBUG
> > + help
> > + Enable development debugging.
> > +
> > endmenu
> > diff --git a/xen/common/emul/vuart/Makefile b/xen/common/emul/vuart/Makefile
> > index 97f792dc6641..fe904f6cb65d 100644
> > --- a/xen/common/emul/vuart/Makefile
> > +++ b/xen/common/emul/vuart/Makefile
> > @@ -1 +1,2 @@
> > obj-y += vuart.o
> > +obj-$(CONFIG_VUART_NS16X50) += ns16x50.o
> > diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> > new file mode 100644
> > index 000000000000..f0479e1022fb
> > --- /dev/null
> > +++ b/xen/common/emul/vuart/ns16x50.c
> > @@ -0,0 +1,362 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * NS16550-compatible UART Emulator.
> > + *
> > + * See:
> > + * - Serial and UART Tutorial:
> > + * https://download.freebsd.org/doc/en/articles/serial-uart/serial-uart_en.pdf
> > + * - UART w/ 16 byte FIFO:
> > + * https://www.ti.com/lit/ds/symlink/tl16c550c.pdf
> > + * - UART w/ 64 byte FIFO:
> > + * https://www.ti.com/lit/ds/symlink/tl16c750.pdf
> > + *
> > + * Limitations:
> > + * - Only x86;
> > + * - Only Xen console as a backend, no inter-domain communication (similar to
> > + * vpl011 on Arm);
> > + * - Only 8n1 emulation (8-bit data, no parity, 1 stop bit);
> > + * - No baud rate emulation (reports 115200 baud to the guest OS);
> > + * - No FIFO-less mode emulation;
> > + * - No RX FIFO interrupt moderation (FCR) emulation;
> > + * - No integration w/ VM snapshotting (HVM_REGISTER_SAVE_RESTORE() and
> > + * friends);
> > + * - No ISA IRQ sharing allowed;
> > + * - No MMIO-based UART emulation.
> > + */
> > +
> > +#define pr_prefix "ns16x50"
> > +#define pr_fmt(fmt) pr_prefix ": " fmt
> > +
> > +#ifdef CONFIG_VUART_NS16X50_DEBUG
> > +#define guest_prefix "FROM GUEST "
> > +#define ns16x50_log_level 2
> > +#else
> > +#define guest_prefix ""
> > +#define ns16x50_log_level 0
> > +#endif
> > +
> > +#include <xen/8250-uart.h>
> > +#include <xen/console.h>
> > +#include <xen/err.h>
> > +#include <xen/iocap.h>
> > +#include <xen/vuart.h>
> > +#include <xen/xvmalloc.h>
> > +
> > +#include <public/io/console.h>
> > +
> > +#define ns16x50_log(n, lvl, vdev, fmt, args...) do { \
> > + if ( ns16x50_log_level >= n) \
> > + gprintk(lvl, pr_fmt("%s: " fmt), (vdev)->name, ## args); \
> > +} while (0)
> > +
> > +#define ns16x50_err(vdev, fmt, args...) \
> > + ns16x50_log(0, KERN_ERR, vdev, fmt, ## args)
> > +#define ns16x50_warn(vdev, fmt, args...) \
> > + ns16x50_log(1, KERN_WARNING, vdev, fmt, ## args)
> > +#define ns16x50_info(vdev, fmt, args...) \
> > + ns16x50_log(2, KERN_INFO, vdev, fmt, ## args)
> > +#define ns16x50_debug(vdev, fmt, args...) \
> > + ns16x50_log(3, KERN_DEBUG, vdev, fmt, ## args)
> > +
> > +/*
> > + * Number of supported registers in the UART.
> > + */
> > +#define NS16X50_REGS_NUM (UART_SCR + 1)
> > +
> > +/*
> > + * Number of emulated registers.
> > + *
> > + * - Emulated registers [0..NS16X50_REGS_NUM] are R/W registers for DLAB=0.
> > + * - DLAB=1, R/W, DLL = NS16X50_REGS_NUM + 0
> > + * - DLAB=1, R/W, DLM = NS16X50_REGS_NUM + 1
> > + * - R/O, IIR (IIR_THR) = NS16X50_REGS_NUM + 2
> > + */
> > +#define NS16X50_EMU_REGS_NUM (NS16X50_REGS_NUM + 3)
> > +
> > +/*
> > + * Virtual ns16x50 device state.
> > + */
> > +struct vuart_ns16x50 {
> > + struct xencons_interface cons; /* Emulated RX/TX FIFOs */
> > + uint8_t regs[NS16X50_EMU_REGS_NUM]; /* Emulated registers */
> > + const char *name; /* Device name */
> > + struct domain *owner; /* Owner domain */
> > + const struct vuart_info *info; /* UART description */
> > + spinlock_t lock; /* Protection */
> > +};
> > +
> > +/*
> > + * Emulate 8-bit write access to ns16x50 register.
> > + */
> > +static int ns16x50_io_write8(
> > + struct vuart_ns16x50 *vdev, uint32_t reg, uint8_t *data)
> > +{
> > + int rc = 0;
> > +
> > + return rc;
> > +}
> > +
> > +/*
> > + * Emulate 16-bit write access to ns16x50 register.
> > + * NB: some guest OSes use outw() to access UART_DLL.
> > + */
> > +static int ns16x50_io_write16(
> > + struct vuart_ns16x50 *vdev, uint32_t reg, uint16_t *data)
> > +{
> > + int rc = -EINVAL;
> > +
> > + return rc;
> > +}
> > +
> > +/*
> > + * Emulate write access to ns16x50 register.
> > + */
> > +static int ns16x50_io_write(
> > + struct vuart_ns16x50 *vdev, uint8_t reg, uint32_t size, uint32_t *data)
> > +{
> > + int rc;
> > +
> > + switch ( size )
> > + {
> > + case 1:
> > + rc = ns16x50_io_write8(vdev, reg, (uint8_t *)data);
> > + break;
> > +
> > + case 2:
> > + rc = ns16x50_io_write16(vdev, reg, (uint16_t *)data);
> > + break;
> > +
> > + default:
> > + rc = -EINVAL;
> > + break;
> > + }
> > +
> > + return rc;
> > +}
> > +
> > +/*
> > + * Emulate 8-bit read access to ns16x50 register.
> > + */
> > +static int ns16x50_io_read8(
> > + struct vuart_ns16x50 *vdev, uint32_t reg, uint8_t *data)
> > +{
> > + uint8_t val = 0xff;
> > + int rc = 0;
> > +
> > + *data = val;
> > +
> > + return rc;
> > +}
> > +
> > +/*
> > + * Emulate 16-bit read access to ns16x50 register.
> > + */
> > +static int ns16x50_io_read16(
> > + struct vuart_ns16x50 *vdev, uint32_t reg, uint16_t *data)
> > +{
> > + uint16_t val = 0xffff;
> > + int rc = -EINVAL;
> > +
> > + *data = val;
> > +
> > + return rc;
> > +}
> > +
> > +/*
> > + * Emulate read access to ns16x50 register.
> > + */
> > +static int ns16x50_io_read(
> > + struct vuart_ns16x50 *vdev, uint8_t reg, uint32_t size, uint32_t *data)
> > +{
> > + int rc;
> > +
> > + switch ( size )
> > + {
> > + case 1:
> > + rc = ns16x50_io_read8(vdev, reg, (uint8_t *)data);
> > + break;
> > +
> > + case 2:
> > + rc = ns16x50_io_read16(vdev, reg, (uint16_t *)data);
> > + break;
> > +
> > + default:
> > + *data = 0xffffffff;
> > + rc = -EINVAL;
> > + break;
> > + }
> > +
> > + return rc;
> > +}
> > +
> > +/*
> > + * Emulate I/O access to ns16x50 register.
> > + * Note, emulation always returns X86EMUL_OKAY, once I/O port trap is enabled.
> > + */
> > +static int cf_check ns16x50_io_handle(
> > + int dir, unsigned int addr, unsigned int size, uint32_t *data)
> > +{
> > +#define op(dir) (((dir) == IOREQ_WRITE) ? 'W' : 'R')
> > + struct domain *d = rcu_lock_current_domain();
> > + struct vuart *vuart = vuart_find_by_io_range(d, addr, size);
> > + struct vuart_ns16x50 *vdev;
> > + const struct domain *owner;
> > + const struct vuart_info *info;
> > + uint32_t reg;
> > + unsigned dlab;
> > + int rc;
> > +
> > + if ( !vuart || !vuart->vdev )
> > + {
> > + printk(KERN_ERR "%c io 0x%04x %d: not initialized\n",
>
> XENLOG_ERR
Will update KERN to XENLOG everywhere in the new code.
>
>
> > + op(dir), addr, size);
> > +
> > + ASSERT_UNREACHABLE();
> > + goto out;
> > + }
> > + vdev = vuart->vdev;
> > +
> > + owner = vuart->owner;
> > + ASSERT(owner);
> > + if ( d != owner )
> > + {
> > + ns16x50_err(vdev, "%c io 0x%04x %d: does not match current domain %pv\n",
> > + op(dir), addr, size, d);
> > +
> > + ASSERT_UNREACHABLE();
> > + goto out;
> > + }
> > +
> > + info = vuart->info;
> > + ASSERT(info);
> > + reg = addr - info->base_addr;
> > + if ( !IS_ALIGNED(reg, size) )
> > + {
> > + ns16x50_err(vdev, "%c 0x%04x %d: unaligned access\n",
> > + op(dir), addr, size);
>
>
> For this one we could consider returning X86EMUL_UNHANDLEABLE but I am not sure
> as it would crash the guest.
re: crashing the guest: that is the main reason why I kept X86EMUL_OKAY.
I think the emulator should not crash the guest OS since this is facility
for OS bringup debugging.
>
> > + goto out;
> > + }
> > +
> > + dlab = 0;
> > + if ( reg >= NS16X50_REGS_NUM )
> > + {
> > + ns16x50_err(vdev, "%c io 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32": not implemented\n",
> > + op(dir), addr, size, dlab, reg, *data);
> > + goto out;
> > + }
> > +
> > + spin_lock(&vdev->lock);
> > +
> > + if ( dir == IOREQ_WRITE )
> > + {
> > + ns16x50_debug(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32"\n",
> > + op(dir), addr, size, dlab, reg, *data);
> > + rc = ns16x50_io_write(vdev, reg, size, data);
> > + }
> > + else
> > + {
> > + rc = ns16x50_io_read(vdev, reg, size, data);
> > + ns16x50_debug(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32"\n",
> > + op(dir), addr, size, dlab, reg, *data);
> > + }
> > + if ( rc < 0 )
> > + ns16x50_err(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32": unsupported access\n",
> > + op(dir), addr, size, dlab, reg, *data);
> > +
> > + spin_unlock(&vdev->lock);
> > +
> > +out:
> > + rcu_unlock_domain(d);
> > +
> > + return X86EMUL_OKAY;
> > +#undef op
> > +}
> > +
> > +static int ns16x50_init(void *arg)
> > +{
> > + struct vuart_ns16x50 *vdev = arg;
> > + const struct vuart_info *info = vdev->info;
> > + struct domain *d = vdev->owner;
> > +
> > + ASSERT(vdev);
> > +
> > + register_portio_handler(d, info->base_addr, info->size, ns16x50_io_handle);
> > +
> > + return 0;
> > +}
> > +
> > +static void cf_check ns16x50_deinit(void *arg)
> > +{
> > + struct vuart_ns16x50 *vdev = arg;
> > +
> > + ASSERT(vdev);
>
> it should unregister the handlers
Unfortunately, there's no unregister_portio_handler() and AFAIU the reason for
that is there's no need in it since I/O handlers will be destroyed during
domain destruction when ns16x50_deinit() is called.
>
>
> > +}
> > +
> > +static void * cf_check ns16x50_alloc(struct domain *d, const struct vuart_info *info)
> > +{
> > + struct vuart_ns16x50 *vdev;
> > + int rc;
> > +
> > + if ( !info )
> > + return ERR_PTR(-EINVAL);
> > +
> > + if ( vuart_find_by_io_range(d, info->base_addr, info->size) )
> > + {
> > + ns16x50_err(info, "already registered\n");
> > + return ERR_PTR(-EBUSY);
> > + }
> > +
> > + if ( !is_hvm_domain(d) )
> > + {
> > + ns16x50_err(info, "not an HVM domain\n");
> > + return ERR_PTR(-ENOSYS);
> > + }
> > +
> > + vdev = xvzalloc(typeof(*vdev));
> > + if ( !vdev )
> > + {
> > + ns16x50_err(info, "failed to allocate memory\n");
> > + return ERR_PTR(-ENOMEM);
> > + }
> > +
> > + /* Save convenience pointer. */
> > + vdev->name = info->name;
> > + vdev->owner = d;
> > + vdev->info = info;
>
> the spinlock should be initialized
Ack
>
>
> > + rc = ns16x50_init(vdev);
> > + if ( rc )
> > + return ERR_PTR(rc);
> > +
> > + return vdev;
> > +}
> > +
> > +static void cf_check ns16x50_free(void *arg)
> > +{
> > + struct vuart_ns16x50 *vdev = arg;
> > +
> > + if ( vdev )
> > + ns16x50_deinit(vdev);
> > +
> > + XVFREE(vdev);
>
> XVFREE should only be called if ( vdev )
Ack
>
>
> > +}
> > +
> > +#define ns16x50_emulator \
> > +{ \
> > + .compatible = "ns16550", \
> > + .alloc = ns16x50_alloc, \
> > + .free = ns16x50_free, \
> > + .dump_state = NULL, \
> > + .put_rx = NULL, \
> > +}
> > +
> > +VUART_REGISTER(ns16x50, ns16x50_emulator);
> > +
> > +/*
> > + * Local variables:
> > + * mode: C
> > + * c-file-style: "BSD"
> > + * c-basic-offset: 4
> > + * indent-tabs-mode: nil
> > + * End:
> > + */
> > diff --git a/xen/include/xen/sched.h b/xen/include/xen/sched.h
> > index 02bdc256ce37..613f4596e33d 100644
> > --- a/xen/include/xen/sched.h
> > +++ b/xen/include/xen/sched.h
> > @@ -23,6 +23,7 @@
> > #include <asm/atomic.h>
> > #include <asm/current.h>
> > #include <xen/vpci.h>
> > +#include <xen/vuart.h>
> > #include <xen/wait.h>
> > #include <public/xen.h>
> > #include <public/domctl.h>
> > @@ -660,6 +661,9 @@ struct domain
> > struct {
> > /* Permission to take ownership of the physical console input. */
> > bool input_allowed;
> > +#ifdef CONFIG_VUART_FRAMEWORK
> > + struct vuart *vuart;
> > +#endif
>
> this should be in patch #1, as patch #1 does:
>
> d->console.vuart = vuart;
Yes, indeed, thanks.
Will move to patch #1.
>
>
> > } console;
> > } __aligned(PAGE_SIZE);
> >
> > --
> > 2.51.0
> >
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 01/15] emul/vuart: introduce framework for UART emulators
2025-09-01 22:27 ` dmukhin
@ 2025-09-02 6:14 ` Jan Beulich
0 siblings, 0 replies; 46+ messages in thread
From: Jan Beulich @ 2025-09-02 6:14 UTC (permalink / raw)
To: dmukhin
Cc: Stefano Stabellini, xen-devel, andrew.cooper3, anthony.perard,
julien, michal.orzel, roger.pau, sstabellini, dmukhin
On 02.09.2025 00:27, dmukhin@xen.org wrote:
> On Mon, Sep 01, 2025 at 10:14:04AM +0200, Jan Beulich wrote:
>> On 29.08.2025 21:27, Stefano Stabellini wrote:
>>> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
>>>> --- /dev/null
>>>> +++ b/xen/common/emul/vuart/vuart.c
>>>> @@ -0,0 +1,156 @@
>>>> +/* SPDX-License-Identifier: GPL-2.0-only */
>>>> +/*
>>>> + * UART emulator framework.
>>>> + *
>>>> + * Copyright 2025 Ford Motor Company
>>>> + */
>>>> +
>>>> +#include <xen/err.h>
>>>> +#include <xen/sched.h>
>>>> +#include <xen/vuart.h>
>>>> +#include <xen/xvmalloc.h>
>>>> +
>>>> +#define for_each_emulator(e) \
>>>> + for ( e = vuart_array_start; e < vuart_array_end; e++ )
>>>> +
>>>> +extern const struct vuart_emulator vuart_array_start[];
>>>> +extern const struct vuart_emulator vuart_array_end[];
>>>> +
>>>> +static const struct vuart_emulator *
>>>> +vuart_match_by_compatible(struct domain *d, const char *compat)
>>>> +{
>>>> + const struct vuart_emulator *emulator;
>>>> +
>>>> + if ( d->console.vuart )
>>>> + return NULL;
>>>> +
>>>> + for_each_emulator(emulator)
>>>> + if ( emulator->compatible &&
>>>> + !strncmp(emulator->compatible, compat,
>>>> + strlen(emulator->compatible)) )
>>>
>>> strncmp will continue until the given count even if compat is shorter
>>
>> Not really, one string having a nul char and the other not having one is a
>> difference, at which point comparison will stop. There would be a problem
>> if "compat" didn't point to a nul-terminated string, though (and I didn't
>> check that aspect, not the least because then "shorter" doesn't really
>> make much sense without a length passed in).
>
> re: NUL-termination: current assumption is that both compat and
> emulator->compatible are NUL-terminated.
>
> Current `compat` comes from the hardcoded NUL-terminated string (vuart_info).
>
> In case of `compat` is not NUL-terminated (I plan to populate the field from
> xl in the future), strncmp() will stop after strlen(emulator->compatible)
> bytes.
Which might be too late; if not nul-terminated, "compat" may extend past a
page boundary, with the latter page not mapped.
Jan
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 03/15] emul/ns16x50: implement emulator stub
2025-09-01 23:11 ` dmukhin
@ 2025-09-02 9:32 ` Jan Beulich
2025-09-05 23:34 ` dmukhin
0 siblings, 1 reply; 46+ messages in thread
From: Jan Beulich @ 2025-09-02 9:32 UTC (permalink / raw)
To: dmukhin, Stefano Stabellini
Cc: xen-devel, andrew.cooper3, anthony.perard, julien, michal.orzel,
roger.pau, dmukhin
On 02.09.2025 01:11, dmukhin@xen.org wrote:
> On Fri, Aug 29, 2025 at 12:57:43PM -0700, Stefano Stabellini wrote:
>> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
>>> --- a/xen/common/emul/vuart/Kconfig
>>> +++ b/xen/common/emul/vuart/Kconfig
>>> @@ -3,4 +3,22 @@ config VUART_FRAMEWORK
>>>
>>> menu "UART Emulation"
>>>
>>> +config VUART_NS16X50
>>> + bool "NS16550-compatible UART Emulator" if EXPERT
>>> + depends on X86 && HVM
>>> + select VUART_FRAMEWORK
>>
>> default n
>
> Ack
No "default n" should ever be put anywhere; it's simply redundant.
Jan
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 03/15] emul/ns16x50: implement emulator stub
2025-08-29 19:57 ` Stefano Stabellini
2025-09-01 23:11 ` dmukhin
@ 2025-09-02 9:36 ` Jan Beulich
2025-09-05 23:34 ` dmukhin
1 sibling, 1 reply; 46+ messages in thread
From: Jan Beulich @ 2025-09-02 9:36 UTC (permalink / raw)
To: Stefano Stabellini, dmukhin
Cc: xen-devel, andrew.cooper3, anthony.perard, julien, michal.orzel,
roger.pau, dmukhin
On 29.08.2025 21:57, Stefano Stabellini wrote:
> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
>> +static void cf_check ns16x50_free(void *arg)
>> +{
>> + struct vuart_ns16x50 *vdev = arg;
>> +
>> + if ( vdev )
>> + ns16x50_deinit(vdev);
>> +
>> + XVFREE(vdev);
>
> XVFREE should only be called if ( vdev )
Why would this be? Like free(), both xfree() and xvfree() are fine to be
called with a NULL pointer. What's odd here is that the uppercase form (the
wrapper macro) is used - clearing the local variable is pointless when it
is about to go out of scope anyway.
Jan
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 05/15] emul/ns16x50: implement EIR/IIR registers
2025-08-29 20:14 ` Stefano Stabellini
@ 2025-09-05 22:05 ` dmukhin
0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-05 22:05 UTC (permalink / raw)
To: Stefano Stabellini
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, dmukhin
On Fri, Aug 29, 2025 at 01:14:03PM -0700, Stefano Stabellini wrote:
> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > From: Denis Mukhin <dmukhin@ford.com>
> >
> > Add interrupt enable register emulation (EIR) and interrupt identity reason
>
> EIR->IER
Whooops, thanks.
>
> > (IIR) register emulation to the I/O port handler.
> >
> > Also add routines for asserting/deasserting the virtual ns16x50 interrupt
> > line as a dependent on IIR code.
> >
> > Poke ns16x50_irq_check() on every I/O register access because the emulator
> > does not have clock emulation anyway (e.g. for baud rate emulation).
> >
> > Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> > ---
> > Changes since v4:
> > - new patch
> > ---
> > xen/common/emul/vuart/ns16x50.c | 177 +++++++++++++++++++++++++++++++-
> > 1 file changed, 176 insertions(+), 1 deletion(-)
> >
> > diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> > index f9f307a4ad24..20597cc36b35 100644
> > --- a/xen/common/emul/vuart/ns16x50.c
> > +++ b/xen/common/emul/vuart/ns16x50.c
> > @@ -85,9 +85,131 @@ struct vuart_ns16x50 {
> > spinlock_t lock; /* Protection */
> > };
> >
> > +static bool ns16x50_fifo_rx_empty(const struct vuart_ns16x50 *vdev)
> > +{
> > + const struct xencons_interface *cons = &vdev->cons;
> > +
> > + return cons->in_prod == cons->in_cons;
> > +}
>
> there is no ring so far so I would not add ns16x50_fifo_rx_empty for now
Ack.
>
>
> > static inline uint8_t cf_check ns16x50_dlab_get(const struct vuart_ns16x50 *vdev)
> > {
> > - return 0;
> > + return vdev->regs[UART_LCR] & UART_LCR_DLAB ? 1 : 0;
> > +}
> > +
> > +static bool cf_check ns16x50_iir_check_lsi(const struct vuart_ns16x50 *vdev)
> > +{
> > + return vdev->regs[UART_LSR] & UART_LSR_MASK;
> > +}
> > +
> > +static bool cf_check ns16x50_iir_check_rda(const struct vuart_ns16x50 *vdev)
> > +{
> > + return !ns16x50_fifo_rx_empty(vdev);
> > +}
> > +
> > +static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
> > +{
> > + return vdev->regs[NS16X50_REGS_NUM + UART_IIR] & UART_IIR_THR;
> > +}
> > +
> > +static bool cf_check ns16x50_iir_check_msi(const struct vuart_ns16x50 *vdev)
> > +{
> > + return vdev->regs[UART_MSR] & UART_MSR_CHANGE;
> > +}
> > +
> > +/*
> > + * Get the interrupt identity reason.
> > + *
> > + * IIR is re-calculated once called, because ns16x50 always reports high
> > + * priority events first.
> > + * regs[NS16X50_REGS_NUM + UART_IIR] is used to store THR reason only.
> > + */
> > +static uint8_t ns16x50_iir_get(const struct vuart_ns16x50 *vdev)
> > +{
> > + /*
> > + * Interrupt identity reasons by priority.
> > + * NB: high priority are at lower indexes below.
> > + */
> > + static const struct {
> > + bool (*check)(const struct vuart_ns16x50 *vdev);
> > + uint8_t ier;
> > + uint8_t iir;
> > + } iir_by_prio[] = {
> > + [0] = { ns16x50_iir_check_lsi, UART_IER_ELSI, UART_IIR_LSI },
> > + [1] = { ns16x50_iir_check_rda, UART_IER_ERDAI, UART_IIR_RDA },
> > + [2] = { ns16x50_iir_check_thr, UART_IER_ETHREI, UART_IIR_THR },
> > + [3] = { ns16x50_iir_check_msi, UART_IER_EMSI, UART_IIR_MSI },
> > + };
> > + const uint8_t *regs = vdev->regs;
> > + uint8_t iir = 0;
> > + unsigned int i;
> > +
> > + /*
> > + * NB: every interaction w/ ns16x50 registers (except DLAB=1) goes
> > + * through that call.
> > + */
> > + ASSERT(spin_is_locked(&vdev->lock));
> > +
> > + for ( i = 0; i < ARRAY_SIZE(iir_by_prio); i++ )
> > + {
> > + if ( (regs[UART_IER] & iir_by_prio[i].ier) &&
> > + iir_by_prio[i].check(vdev) )
> > + break;
> > +
> > + }
> > + if ( i == ARRAY_SIZE(iir_by_prio) )
> > + iir |= UART_IIR_NOINT;
> > + else
> > + iir |= iir_by_prio[i].iir;
> > +
> > + if ( regs[UART_FCR] & UART_FCR_ENABLE )
> > + iir |= UART_IIR_FE;
> > +
> > + return iir;
> > +}
> > +
> > +static void ns16x50_irq_assert(const struct vuart_ns16x50 *vdev)
> > +{
> > + struct domain *d = vdev->owner;
> > + const struct vuart_info *info = vdev->info;
> > + int vector;
> > +
> > + if ( has_vpic(d) ) /* HVM */
> > + vector = hvm_isa_irq_assert(d, info->irq, vioapic_get_vector);
> > + else
> > + ASSERT_UNREACHABLE();
> > +
> > + ns16x50_debug(vdev, "IRQ#%d vector %d assert\n", info->irq, vector);
> > +}
> > +
> > +static void ns16x50_irq_deassert(const struct vuart_ns16x50 *vdev)
> > +{
> > + struct domain *d = vdev->owner;
> > + const struct vuart_info *info = vdev->info;
> > +
> > + if ( has_vpic(d) ) /* HVM */
> > + hvm_isa_irq_deassert(d, info->irq);
> > + else
> > + ASSERT_UNREACHABLE();
> > +
> > + ns16x50_debug(vdev, "IRQ#%d deassert\n", info->irq);
> > +}
> > +
> > +/*
> > + * Assert/deassert virtual ns16x50 interrupt line.
> > + */
> > +static void ns16x50_irq_check(const struct vuart_ns16x50 *vdev)
> > +{
> > + uint8_t iir = ns16x50_iir_get(vdev);
> > + const struct vuart_info *info = vdev->info;
> > +
> > + if ( iir & UART_IIR_NOINT )
> > + ns16x50_irq_assert(vdev);
>
> It is a bit strange that if "NOINT" is set, we raise the interrupt
Yes, that is wrong.
Thank you!
>
>
> > + else
> > + ns16x50_irq_deassert(vdev);
> > +
> > + ns16x50_debug(vdev, "IRQ#%d IIR 0x%02x %s\n", info->irq, iir,
> > + (iir & UART_IIR_NOINT) ? "deassert" : "assert");
> > }
> >
> > /*
> > @@ -102,6 +224,29 @@ static int ns16x50_io_write8(
> >
> > if ( ns16x50_dlab_get(vdev) && (reg == UART_DLL || reg == UART_DLM) )
> > regs[NS16X50_REGS_NUM + reg] = val;
> > + else
> > + {
> > + switch ( reg )
> > + {
> > + case UART_IER:
> > + /*
> > + * NB: Make sure THR interrupt is re-triggered once guest OS
> > + * re-enabled ETHREI in EIR.
>
> EIR->IER
Will fix.
>
>
> > + */
> > + if ( val & regs[UART_IER] & UART_IER_ETHREI )
> > + regs[NS16X50_REGS_NUM + UART_IIR] |= UART_IIR_THR;
>
> I am confused by this. Shouldn't it be :
>
> if ( (val & UART_IER_ETHREI) && !(regs[UART_IER] & UART_IER_ETHREI) )
>
> Meaning set UART_IIR_THR if ETHREI goes 0->1 ?
That is by design to re-toggle the UART_IIR_THR since there's no baud
rate emulation.
>
>
> > + regs[UART_IER] = val & UART_IER_MASK;
> > +
> > + break;
> > +
> > + default:
> > + rc = -EINVAL;
> > + break;
> > + }
> > +
> > + ns16x50_irq_check(vdev);
> > + }
> >
> > return rc;
> > }
> > @@ -164,6 +309,29 @@ static int ns16x50_io_read8(
> >
> > if ( ns16x50_dlab_get(vdev) && (reg == UART_DLL || reg == UART_DLM) )
> > val = regs[NS16X50_REGS_NUM + reg];
> > + else {
> > + switch ( reg )
> > + {
> > + case UART_IER:
> > + val = regs[UART_IER];
> > + break;
> > +
> > + case UART_IIR: /* RO */
> > + val = ns16x50_iir_get(vdev);
> > +
> > + /* NB: clear IIR scratch location */
> > + if ( val & UART_IIR_THR )
> > + regs[NS16X50_REGS_NUM + UART_IIR] &= ~UART_IIR_THR;
>
> Maybe add an in-code comment why it is a good idea to clear THR here
Will fix.
>
>
> > +
> > + break;
> > +
> > + default:
> > + rc = -EINVAL;
> > + break;
> > + }
> > +
> > + ns16x50_irq_check(vdev);
> > + }
> >
> > *data = val;
> >
> > @@ -314,8 +482,15 @@ static int ns16x50_init(void *arg)
> > vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
> > vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
> >
> > + /* ns16x50 shall assert UART_IIR_THR whenever transmitter is empty. */
> > + vdev->regs[NS16X50_REGS_NUM + UART_IIR] = UART_IIR_THR;
> > +
> > register_portio_handler(d, info->base_addr, info->size, ns16x50_io_handle);
> >
> > + spin_lock(&vdev->lock);
> > + ns16x50_irq_check(vdev);
> > + spin_unlock(&vdev->lock);
> > +
> > return 0;
> > }
> >
> > --
> > 2.51.0
> >
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 06/15] emul/ns16x50: implement THR/RBR registers
2025-08-29 20:28 ` Stefano Stabellini
2025-08-29 20:34 ` Stefano Stabellini
@ 2025-09-05 22:20 ` dmukhin
1 sibling, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-05 22:20 UTC (permalink / raw)
To: Stefano Stabellini
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, dmukhin
On Fri, Aug 29, 2025 at 01:28:41PM -0700, Stefano Stabellini wrote:
> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > From: Denis Mukhin <dmukhin@ford.com>
> >
> > Add RBR/THR registers emulation to the I/O port handlder.
> >
> > Also, add RX/TX FIFO management code since RBR depends on RX FIFO and
> > THR depends on TX FIFO.
> >
> > FIFOs are not emulated as per UART specs for simplicity (not need to emulate
> > baud rate). Emulator does not emulate NS8250 (no FIFO), NS16550a (16 bytes) or
> > NS16750 (64 bytes).
> >
> > FIFOs are emulated by means of using xencons_interface which conveniently
> > provides primitives for buffer management and later can be used for
> > inter-domain communication similarly to vpl011.
> >
> > Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> > ---
> > Changes since v4:
> > - new patch
> > ---
> > xen/common/emul/vuart/ns16x50.c | 135 ++++++++++++++++++++++++++++++++
> > 1 file changed, 135 insertions(+)
> >
> > diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> > index 20597cc36b35..efb2f4c6441c 100644
> > --- a/xen/common/emul/vuart/ns16x50.c
> > +++ b/xen/common/emul/vuart/ns16x50.c
> > @@ -92,6 +92,119 @@ static bool ns16x50_fifo_rx_empty(const struct vuart_ns16x50 *vdev)
> > return cons->in_prod == cons->in_cons;
> > }
> >
> > +static bool ns16x50_fifo_rx_full(const struct vuart_ns16x50 *vdev)
> > +{
> > + const struct xencons_interface *cons = &vdev->cons;
> > +
> > + return cons->in_prod - cons->in_cons == ARRAY_SIZE(cons->in);
> > +}
> > +
> > +static void ns16x50_fifo_rx_reset(struct vuart_ns16x50 *vdev)
> > +{
> > + struct xencons_interface *cons = &vdev->cons;
> > +
> > + cons->in_cons = cons->in_prod;
> > +}
> > +
> > +static int ns16x50_fifo_rx_getchar(struct vuart_ns16x50 *vdev)
> > +{
> > + struct xencons_interface *cons = &vdev->cons;
> > + int rc;
> > +
> > + if ( ns16x50_fifo_rx_empty(vdev) )
> > + {
> > + ns16x50_debug(vdev, "RX FIFO empty\n");
> > + rc = -ENODATA;
> > + }
> > + else
> > + {
> > + rc = cons->in[MASK_XENCONS_IDX(cons->in_cons, cons->in)];
> > + cons->in_cons++;
> > + }
> > +
> > + return rc;
>
> The signed integer to char conversion here is not great from a MISRA
> perspective. I think it would be better to keep rc as success/failure
> return value and take the read char as a pointer parameter.
Ack.
>
>
> > +}
> > +
> > +static int ns16x50_fifo_rx_putchar(struct vuart_ns16x50 *vdev, char c)
> > +{
> > + struct xencons_interface *cons = &vdev->cons;
> > + int rc;
> > +
> > + /*
> > + * FIFO-less 8250/16450 UARTs: newly arrived word overwrites the contents
> > + * of the THR.
> > + */
> > + if ( ns16x50_fifo_rx_full(vdev) )
> > + {
> > + ns16x50_debug(vdev, "RX FIFO full; resetting\n");
> > + ns16x50_fifo_rx_reset(vdev);
> > + rc = -ENOSPC;
> > + }
> > + else
> > + rc = 0;
> > +
> > + cons->in[MASK_XENCONS_IDX(cons->in_prod, cons->in)] = c;
> > + cons->in_prod++;
> > +
> > + return rc;
> > +}
> > +
> > +static bool ns16x50_fifo_tx_empty(const struct vuart_ns16x50 *vdev)
> > +{
> > + const struct xencons_interface *cons = &vdev->cons;
> > +
> > + return cons->out_prod == cons->out_cons;
> > +}
> > +
> > +static void ns16x50_fifo_tx_reset(struct vuart_ns16x50 *vdev)
> > +{
> > + struct xencons_interface *cons = &vdev->cons;
> > +
> > + cons->out_prod = 0;
> > + ASSERT(cons->out_cons == cons->out_prod);
>
> I am not sure about this.. why resetting the out_prod index? In theory
> it could keep increasing until wrap around and still go forward thanks
> to the mask
I used TX buffer not as a FIFO but as a large buffer for simplicity...
I have reworked that into a normal circular buffer in v6.
>
>
> > +}
> > +
> > +/*
> > + * Flush cached output to Xen console.
> > + */
> > +static void ns16x50_fifo_tx_flush(struct vuart_ns16x50 *vdev)
> > +{
> > + struct xencons_interface *cons = &vdev->cons;
> > + struct domain *d = vdev->owner;
> > +
> > + if ( ns16x50_fifo_tx_empty(vdev) )
> > + return;
> > +
> > + UART_IIR_THR ASSERT(cons->out_prod < ARRAY_SIZE(cons->out));
> > + cons->out[cons->out_prod] = '\0';
>
> should use MASK_XENCONS_IDX to access the array
Ack.
>
>
> > + cons->out_prod++;
> > +
> > + guest_printk(d, guest_prefix "%s", cons->out);
> > +
> > + ns16x50_fifo_tx_reset(vdev);
>
> set UART_IIR_THR and call ns16x50_irq_check ?
I moved that to the I/O handler calling ns16x50_fifo_tx_flush() instead,
because ns16x50_fifo_tx_flush() can be called during vuart destroy,
and there's no need to emulate IRQs at domain destroy.
>
>
> > +}
> > +
> > +/*
> > + * Accumulate guest OS output before sending to Xen console.
> > + */
> > +static void ns16x50_fifo_tx_putchar(struct vuart_ns16x50 *vdev, char ch)
> > +{
> > + struct xencons_interface *cons = &vdev->cons;
> > +
> > + if ( !is_console_printable(ch) )
> > + return;
> > +
> > + if ( ch != '\0' )
> > + {
> > + cons->out[cons->out_prod] = ch;
>
> should use MASK_XENCONS_IDX to access the array
Ack.
>
>
> > + cons->out_prod++;
> > + }
> > +
> > + if ( cons->out_prod == ARRAY_SIZE(cons->out) - 1 ||
> > + ch == '\n' || ch == '\0' )
> > + ns16x50_fifo_tx_flush(vdev);
> > +}
> > +
> > static inline uint8_t cf_check ns16x50_dlab_get(const struct vuart_ns16x50 *vdev)
> > {
> > return vdev->regs[UART_LCR] & UART_LCR_DLAB ? 1 : 0;
> > @@ -228,6 +341,16 @@ static int ns16x50_io_write8(
> > {
> > switch ( reg )
> > {
> > + case UART_THR:
> > + if ( regs[UART_MCR] & UART_MCR_LOOP )
> > + rc = ns16x50_fifo_rx_putchar(vdev, val);
> > + else
> > + ns16x50_fifo_tx_putchar(vdev, val);
>
> should UART_IIR_THR be cleared here?
Yes, thanks for the catch!
>
>
> > + rc = 0;
> > +
> > + break;
> > +
> > case UART_IER:
> > /*
> > * NB: Make sure THR interrupt is re-triggered once guest OS
> > @@ -312,6 +435,14 @@ static int ns16x50_io_read8(
> > else {
> > switch ( reg )
> > {
> > + case UART_RBR:
> > + rc = ns16x50_fifo_rx_getchar(vdev);
> > + if ( rc >= 0 )
> > + val = (uint8_t)rc;
>
> Empty fifo check?
Will do.
>
>
> > + rc = 0;
> > + break;
> > +
> > case UART_IER:
> > val = regs[UART_IER];
> > break;
> > @@ -499,6 +630,10 @@ static void cf_check ns16x50_deinit(void *arg)
> > struct vuart_ns16x50 *vdev = arg;
> >
> > ASSERT(vdev);
> > +
> > + spin_lock(&vdev->lock);
> > + ns16x50_fifo_tx_flush(vdev);
> > + spin_unlock(&vdev->lock);
> > }
> >
> > static void * cf_check ns16x50_alloc(struct domain *d, const struct vuart_info *info)
> > --
> > 2.51.0
> >
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 06/15] emul/ns16x50: implement THR/RBR registers
2025-08-29 20:34 ` Stefano Stabellini
@ 2025-09-05 22:29 ` dmukhin
0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-05 22:29 UTC (permalink / raw)
To: Stefano Stabellini
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, dmukhin
On Fri, Aug 29, 2025 at 01:34:39PM -0700, Stefano Stabellini wrote:
> On Fri, 29 Aug 2025, Stefano Stabellini wrote:
> > On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > > From: Denis Mukhin <dmukhin@ford.com>
> > >
> > > Add RBR/THR registers emulation to the I/O port handlder.
> > >
> > > Also, add RX/TX FIFO management code since RBR depends on RX FIFO and
> > > THR depends on TX FIFO.
> > >
> > > FIFOs are not emulated as per UART specs for simplicity (not need to emulate
> > > baud rate). Emulator does not emulate NS8250 (no FIFO), NS16550a (16 bytes) or
> > > NS16750 (64 bytes).
> > >
> > > FIFOs are emulated by means of using xencons_interface which conveniently
> > > provides primitives for buffer management and later can be used for
> > > inter-domain communication similarly to vpl011.
> > >
> > > Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> > > ---
> > > Changes since v4:
> > > - new patch
> > > ---
> > > xen/common/emul/vuart/ns16x50.c | 135 ++++++++++++++++++++++++++++++++
> > > 1 file changed, 135 insertions(+)
> > >
> > > diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> > > index 20597cc36b35..efb2f4c6441c 100644
> > > --- a/xen/common/emul/vuart/ns16x50.c
> > > +++ b/xen/common/emul/vuart/ns16x50.c
> > > @@ -92,6 +92,119 @@ static bool ns16x50_fifo_rx_empty(const struct vuart_ns16x50 *vdev)
> > > return cons->in_prod == cons->in_cons;
> > > }
> > >
> > > +static bool ns16x50_fifo_rx_full(const struct vuart_ns16x50 *vdev)
> > > +{
> > > + const struct xencons_interface *cons = &vdev->cons;
> > > +
> > > + return cons->in_prod - cons->in_cons == ARRAY_SIZE(cons->in);
> > > +}
> > > +
> > > +static void ns16x50_fifo_rx_reset(struct vuart_ns16x50 *vdev)
> > > +{
> > > + struct xencons_interface *cons = &vdev->cons;
> > > +
> > > + cons->in_cons = cons->in_prod;
> > > +}
> > > +
> > > +static int ns16x50_fifo_rx_getchar(struct vuart_ns16x50 *vdev)
> > > +{
> > > + struct xencons_interface *cons = &vdev->cons;
> > > + int rc;
> > > +
> > > + if ( ns16x50_fifo_rx_empty(vdev) )
> > > + {
> > > + ns16x50_debug(vdev, "RX FIFO empty\n");
> > > + rc = -ENODATA;
> > > + }
> > > + else
> > > + {
> > > + rc = cons->in[MASK_XENCONS_IDX(cons->in_cons, cons->in)];
> > > + cons->in_cons++;
> > > + }
> > > +
> > > + return rc;
> >
> > The signed integer to char conversion here is not great from a MISRA
> > perspective. I think it would be better to keep rc as success/failure
> > return value and take the read char as a pointer parameter.
> >
> >
> > > +}
> > > +
> > > +static int ns16x50_fifo_rx_putchar(struct vuart_ns16x50 *vdev, char c)
> > > +{
> > > + struct xencons_interface *cons = &vdev->cons;
> > > + int rc;
> > > +
> > > + /*
> > > + * FIFO-less 8250/16450 UARTs: newly arrived word overwrites the contents
> > > + * of the THR.
> > > + */
> > > + if ( ns16x50_fifo_rx_full(vdev) )
> > > + {
> > > + ns16x50_debug(vdev, "RX FIFO full; resetting\n");
> > > + ns16x50_fifo_rx_reset(vdev);
> > > + rc = -ENOSPC;
> > > + }
> > > + else
> > > + rc = 0;
> > > +
> > > + cons->in[MASK_XENCONS_IDX(cons->in_prod, cons->in)] = c;
> > > + cons->in_prod++;
> > > +
> > > + return rc;
> > > +}
> > > +
> > > +static bool ns16x50_fifo_tx_empty(const struct vuart_ns16x50 *vdev)
> > > +{
> > > + const struct xencons_interface *cons = &vdev->cons;
> > > +
> > > + return cons->out_prod == cons->out_cons;
> > > +}
> > > +
> > > +static void ns16x50_fifo_tx_reset(struct vuart_ns16x50 *vdev)
> > > +{
> > > + struct xencons_interface *cons = &vdev->cons;
> > > +
> > > + cons->out_prod = 0;
> > > + ASSERT(cons->out_cons == cons->out_prod);
> >
> > I am not sure about this.. why resetting the out_prod index? In theory
> > it could keep increasing until wrap around and still go forward thanks
> > to the mask
>
> I get it now .. it is because it is called from ns16x50_fifo_tx_flush
> right after printing to the real console.
>
> I think it is better to simply do:
>
> cons->out_cons = cons->out_prod;
>
> which effectively clears out the whole buffer. It is dangerous to do:
That was OK to use TX buffer in such awkward manner because of the assertions
and buffer size checks. I reworked that part in v6.
>
> cons->out_prod = 0;
>
> without also doing:
>
> cons->out_cons = 0;
>
> Also, if ns16x50_fifo_tx_flush is the only caller of
> ns16x50_fifo_tx_reset, I would open code the implementation directly
> inside ns16x50_fifo_tx_flush to make it more obvious.
The will be another callsite for ns16x50_fifo_tx_reset() - FCR (in v6).
>
>
> > > +}
> > > +
> > > +/*
> > > + * Flush cached output to Xen console.
> > > + */
> > > +static void ns16x50_fifo_tx_flush(struct vuart_ns16x50 *vdev)
> > > +{
> > > + struct xencons_interface *cons = &vdev->cons;
> > > + struct domain *d = vdev->owner;
> > > +
> > > + if ( ns16x50_fifo_tx_empty(vdev) )
> > > + return;
> > > +
> > > + UART_IIR_THR ASSERT(cons->out_prod < ARRAY_SIZE(cons->out));
> > > + cons->out[cons->out_prod] = '\0';
> >
> > should use MASK_XENCONS_IDX to access the array
> >
> >
> > > + cons->out_prod++;
> > > +
> > > + guest_printk(d, guest_prefix "%s", cons->out);
> > > +
> > > + ns16x50_fifo_tx_reset(vdev);
> >
> > set UART_IIR_THR and call ns16x50_irq_check ?
> >
> >
> > > +}
>
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 07/15] emul/ns16x50: implement FCR register (write-only)
2025-08-29 20:38 ` Stefano Stabellini
@ 2025-09-05 22:33 ` dmukhin
0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-05 22:33 UTC (permalink / raw)
To: Stefano Stabellini
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, dmukhin
On Fri, Aug 29, 2025 at 01:38:02PM -0700, Stefano Stabellini wrote:
> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > From: Denis Mukhin <dmukhin@ford.com>
> >
> > Add emulation logic for FCR register.
> >
> > Note, that does not hooks FIFO interrupt moderation to the FIFO management
> > code.
> >
> > Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> > ---
> > Changes since v4:
> > - new patch
> > ---
> > xen/common/emul/vuart/ns16x50.c | 24 ++++++++++++++++++++++++
> > 1 file changed, 24 insertions(+)
> >
> > diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> > index efb2f4c6441c..65ca96dd8bd3 100644
> > --- a/xen/common/emul/vuart/ns16x50.c
> > +++ b/xen/common/emul/vuart/ns16x50.c
> > @@ -363,6 +363,30 @@ static int ns16x50_io_write8(
> >
> > break;
> >
> > + case UART_FCR: /* WO */
> > + if ( val & UART_FCR_RESERVED0 )
> > + ns16x50_warn(vdev, "FCR: attempt to set reserved bit: %x\n",
> > + UART_FCR_RESERVED0);
> > +
> > + if ( val & UART_FCR_RESERVED1 )
> > + ns16x50_warn(vdev, "FCR: attempt to set reserved bit: %x\n",
> > + UART_FCR_RESERVED1);
> > +
> > + if ( val & UART_FCR_CLRX )
> > + ns16x50_fifo_rx_reset(vdev);
> > +
> > + if ( val & UART_FCR_CLTX )
> > + ns16x50_fifo_tx_flush(vdev);
>
> Should UART_FCR_CLTX actually emit data or only clear the buffer?
Yes, thanks; should be just "tx_reset".
>
> set UART_IIR_THR ?
Will do, thanks.
>
>
> > +
> > + if ( val & UART_FCR_ENABLE )
> > + val &= UART_FCR_ENABLE | UART_FCR_DMA | UART_FCR_TRG_MASK;
> > + else
> > + val = 0;
> > +
> > + regs[UART_FCR] = val;
>
>
> ns16x50_irq_check ?
ns16x50_irq_check() is poked after the switch statement.
>
>
> > + break;
> > +
> > default:
> > rc = -EINVAL;
> > break;
> > --
> > 2.51.0
> >
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 13/15] x86/domain: enable per-domain I/O port bitmaps
2025-08-29 21:43 ` Stefano Stabellini
@ 2025-09-05 22:38 ` dmukhin
0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-05 22:38 UTC (permalink / raw)
To: Stefano Stabellini
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, dmukhin
On Fri, Aug 29, 2025 at 02:43:18PM -0700, Stefano Stabellini wrote:
> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > From: Denis Mukhin <dmukhin@ford.com>
> >
> > Current design enables all HVM domains share the same I/O port bitmap.
> >
> > It is necessary for domains crafting its own I/O port address space depending
> > on the user configuration.
> >
> > Ensure NS16550 emulator does not share I/O ports with the physical I/O ports,
> > which is essential for emulation in PVH hwdom case (dom0).
> >
> > Not a functional change.
> >
> > Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> > ---
> > Changes since v4:
> > - new patch
> > - Link tp v4: https://lore.kernel.org/xen-devel/20250731192130.3948419-4-dmukhin@ford.com/
> > ---
> > xen/arch/x86/Makefile | 1 +
> > xen/arch/x86/dom0_build.c | 111 +--------------
> > xen/arch/x86/hvm/hvm.c | 35 +----
> > xen/arch/x86/hvm/nestedhvm.c | 8 +-
> > xen/arch/x86/hvm/quirks.c | 3 -
> > xen/arch/x86/hvm/svm/nestedsvm.c | 2 +-
> > xen/arch/x86/hvm/vmx/vvmx.c | 4 +-
> > xen/arch/x86/include/asm/hvm/nestedhvm.h | 3 +-
> > xen/arch/x86/include/asm/hvm/support.h | 2 -
> > xen/arch/x86/include/asm/iocap.h | 2 +
> > xen/arch/x86/ioport.c | 163 +++++++++++++++++++++++
> > xen/arch/x86/pv/dom0_build.c | 4 +
> > xen/common/emul/vuart/ns16x50.c | 11 ++
> > 13 files changed, 200 insertions(+), 149 deletions(-)
> > create mode 100644 xen/arch/x86/ioport.c
> >
> > diff --git a/xen/arch/x86/Makefile b/xen/arch/x86/Makefile
> > index 9d67ea7cd4a8..5726ecc180eb 100644
> > --- a/xen/arch/x86/Makefile
> > +++ b/xen/arch/x86/Makefile
> > @@ -44,6 +44,7 @@ obj-y += msi.o
> > obj-y += msr.o
> > obj-$(CONFIG_INDIRECT_THUNK) += indirect-thunk.o
> > obj-$(CONFIG_RETURN_THUNK) += indirect-thunk.o
> > +obj-y += ioport.o
> > obj-$(CONFIG_PV) += ioport_emulate.o
> > obj-y += irq.o
> > obj-$(CONFIG_KEXEC) += machine_kexec.o
> > diff --git a/xen/arch/x86/dom0_build.c b/xen/arch/x86/dom0_build.c
> > index 0b467fd4a4fc..26202b33345c 100644
> > --- a/xen/arch/x86/dom0_build.c
> > +++ b/xen/arch/x86/dom0_build.c
> > @@ -298,9 +298,6 @@ int __init parse_arch_dom0_param(const char *s, const char *e)
> > return 0;
> > }
> >
> > -static char __initdata opt_dom0_ioports_disable[200] = "";
> > -string_param("dom0_ioports_disable", opt_dom0_ioports_disable);
> > -
> > static bool __initdata ro_hpet = true;
> > boolean_param("ro-hpet", ro_hpet);
> >
> > @@ -433,122 +430,20 @@ unsigned long __init dom0_compute_nr_pages(
> > return nr_pages;
> > }
> >
> > -static void __init process_dom0_ioports_disable(struct domain *dom0)
> > -{
> > - unsigned long io_from, io_to;
> > - char *t, *s = opt_dom0_ioports_disable;
> > - const char *u;
> > -
> > - if ( *s == '\0' )
> > - return;
> > -
> > - while ( (t = strsep(&s, ",")) != NULL )
> > - {
> > - io_from = simple_strtoul(t, &u, 16);
> > - if ( u == t )
> > - {
> > - parse_error:
> > - printk("Invalid ioport range <%s> "
> > - "in dom0_ioports_disable, skipping\n", t);
> > - continue;
> > - }
> > -
> > - if ( *u == '\0' )
> > - io_to = io_from;
> > - else if ( *u == '-' )
> > - io_to = simple_strtoul(u + 1, &u, 16);
> > - else
> > - goto parse_error;
> > -
> > - if ( (*u != '\0') || (io_to < io_from) || (io_to >= 65536) )
> > - goto parse_error;
> > -
> > - printk("Disabling dom0 access to ioport range %04lx-%04lx\n",
> > - io_from, io_to);
> > -
> > - if ( ioports_deny_access(dom0, io_from, io_to) != 0 )
> > - BUG();
> > - }
> > -}
> > -
> > +/* Modify I/O memory access permissions. */
> > int __init dom0_setup_permissions(struct domain *d)
> > {
> > unsigned long mfn;
> > - unsigned int i, offs;
> > - int rc;
> > + unsigned int i;
> > + int rc = 0;
> >
> > if ( pv_shim )
> > return 0;
> >
> > - /* The hardware domain is initially permitted full I/O capabilities. */
> > - rc = ioports_permit_access(d, 0, 0xFFFF);
> > rc |= iomem_permit_access(d, 0UL,
> > PFN_DOWN(1UL << domain_max_paddr_bits(d)) - 1);
> > rc |= irqs_permit_access(d, 1, nr_irqs_gsi - 1);
> >
> > - /* Modify I/O port access permissions. */
> > -
> > - for ( offs = 0, i = ISOLATE_LSB(i8259A_alias_mask) ?: 2;
> > - offs <= i8259A_alias_mask; offs += i )
> > - {
> > - if ( offs & ~i8259A_alias_mask )
> > - continue;
> > - /* Master Interrupt Controller (PIC). */
> > - rc |= ioports_deny_access(d, 0x20 + offs, 0x21 + offs);
> > - /* Slave Interrupt Controller (PIC). */
> > - rc |= ioports_deny_access(d, 0xA0 + offs, 0xA1 + offs);
> > - }
> > -
> > - /* ELCR of both PICs. */
> > - rc |= ioports_deny_access(d, 0x4D0, 0x4D1);
> > -
> > - /* Interval Timer (PIT). */
> > - for ( offs = 0, i = ISOLATE_LSB(pit_alias_mask) ?: 4;
> > - offs <= pit_alias_mask; offs += i )
> > - if ( !(offs & ~pit_alias_mask) )
> > - rc |= ioports_deny_access(d, PIT_CH0 + offs, PIT_MODE + offs);
> > -
> > - /* PIT Channel 2 / PC Speaker Control. */
> > - rc |= ioports_deny_access(d, 0x61, 0x61);
> > -
> > - /* INIT# and alternative A20M# control. */
> > - rc |= ioports_deny_access(d, 0x92, 0x92);
> > -
> > - /* IGNNE# control. */
> > - rc |= ioports_deny_access(d, 0xF0, 0xF0);
> > -
> > - /* ACPI PM Timer. */
> > - if ( pmtmr_ioport )
> > - rc |= ioports_deny_access(d, pmtmr_ioport, pmtmr_ioport + 3);
> > -
> > - /* Reset control. */
> > - rc |= ioports_deny_access(d, 0xCF9, 0xCF9);
> > -
> > - /* PCI configuration space (NB. 0xCF8 has special treatment). */
> > - rc |= ioports_deny_access(d, 0xCFC, 0xCFF);
> > -
> > -#ifdef CONFIG_HVM
> > - if ( is_hvm_domain(d) )
> > - {
> > - /* ISA DMA controller, channels 0-3 (incl possible aliases). */
> > - rc |= ioports_deny_access(d, 0x00, 0x1F);
> > - /* ISA DMA controller, page registers (incl various reserved ones). */
> > - rc |= ioports_deny_access(d, 0x80 + !!hvm_port80_allowed, 0x8F);
> > - /* ISA DMA controller, channels 4-7 (incl usual aliases). */
> > - rc |= ioports_deny_access(d, 0xC0, 0xDF);
> > -
> > - /* HVM debug console IO port. */
> > - rc |= ioports_deny_access(d, XEN_HVM_DEBUGCONS_IOPORT,
> > - XEN_HVM_DEBUGCONS_IOPORT);
> > - if ( amd_acpi_c1e_quirk )
> > - rc |= ioports_deny_access(d, acpi_smi_cmd, acpi_smi_cmd);
> > - }
> > -#endif
> > - /* Command-line I/O ranges. */
> > - process_dom0_ioports_disable(d);
> > -
> > - /* Modify I/O memory access permissions. */
> > -
> > /* Local APIC. */
> > if ( mp_lapic_addr != 0 )
> > {
> > diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c
> > index 26760cf995df..12736fc61c11 100644
> > --- a/xen/arch/x86/hvm/hvm.c
> > +++ b/xen/arch/x86/hvm/hvm.c
> > @@ -51,6 +51,7 @@
> > #include <asm/hvm/vm_event.h>
> > #include <asm/hvm/vpt.h>
> > #include <asm/i387.h>
> > +#include <asm/iocap.h>
> > #include <asm/mc146818rtc.h>
> > #include <asm/mce.h>
> > #include <asm/monitor.h>
> > @@ -81,14 +82,6 @@ integer_param("hvm_debug", opt_hvm_debug_level);
> >
> > struct hvm_function_table __ro_after_init hvm_funcs;
> >
> > -/*
> > - * The I/O permission bitmap is globally shared by all HVM guests except
> > - * the hardware domain which needs a more permissive one.
> > - */
> > -#define HVM_IOBITMAP_SIZE (3 * PAGE_SIZE)
> > -unsigned long __section(".bss.page_aligned") __aligned(PAGE_SIZE)
> > - hvm_io_bitmap[HVM_IOBITMAP_SIZE / BYTES_PER_LONG];
> > -
> > /* Xen command-line option to enable HAP */
> > static bool __initdata opt_hap_enabled = true;
> > boolean_param("hap", opt_hap_enabled);
> > @@ -205,15 +198,6 @@ static int __init cf_check hvm_enable(void)
> > if ( opt_hvm_fep )
> > warning_add(warning_hvm_fep);
> >
> > - /*
> > - * Allow direct access to the PC debug ports 0x80 and 0xed (they are
> > - * often used for I/O delays, but the vmexits simply slow things down).
> > - */
> > - memset(hvm_io_bitmap, ~0, sizeof(hvm_io_bitmap));
> > - if ( hvm_port80_allowed )
> > - __clear_bit(0x80, hvm_io_bitmap);
> > - __clear_bit(0xed, hvm_io_bitmap);
> > -
> > register_cpu_notifier(&cpu_nfb);
> >
> > return 0;
> > @@ -645,19 +629,12 @@ int hvm_domain_initialise(struct domain *d,
> >
> > rwlock_init(&d->arch.hvm.pl_time->pt_migrate);
> >
> > - /* Set the default IO Bitmap. */
> > - if ( is_hardware_domain(d) )
> > + rc = ioports_setup_access(d);
> > + if ( rc )
> > {
> > - d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);
> > - if ( d->arch.hvm.io_bitmap == NULL )
> > - {
> > - rc = -ENOMEM;
> > - goto fail1;
> > - }
> > - memset(d->arch.hvm.io_bitmap, ~0, HVM_IOBITMAP_SIZE);
> > + printk("%pd failed to setup I/O bitmap: %d\n", d, rc);
> > + goto fail1;
> > }
> > - else
> > - d->arch.hvm.io_bitmap = hvm_io_bitmap;
> >
> > register_g2m_portio_handler(d);
> > register_vpci_portio_handler(d);
> > @@ -684,6 +661,8 @@ int hvm_domain_initialise(struct domain *d,
> > break;
> > }
> >
> > + BUG_ON(!d->arch.ioport_caps);
> > +
> > vpic_init(d);
> >
> > rc = vioapic_init(d);
> > diff --git a/xen/arch/x86/hvm/nestedhvm.c b/xen/arch/x86/hvm/nestedhvm.c
> > index bddd77d8109b..d4e03123d910 100644
> > --- a/xen/arch/x86/hvm/nestedhvm.c
> > +++ b/xen/arch/x86/hvm/nestedhvm.c
> > @@ -107,7 +107,7 @@ nestedhvm_vmcx_flushtlb(struct p2m_domain *p2m)
> > * The users of the bitmap patterns are in SVM/VMX specific code.
> > *
> > * bitmap port 0x80 port 0xed
> > - * hvm_io_bitmap cleared cleared
> > + * hvm.io_bitmap cleared cleared
> > * iomap[0] cleared set
> > * iomap[1] set cleared
> > * iomap[2] set set
> > @@ -115,7 +115,7 @@ nestedhvm_vmcx_flushtlb(struct p2m_domain *p2m)
> >
> > static int __init cf_check nestedhvm_setup(void)
> > {
> > - /* Same format and size as hvm_io_bitmap (Intel needs only 2 pages). */
> > + /* Same format and size as hvm.io_bitmap (Intel needs only 2 pages). */
> > unsigned nr = cpu_has_vmx ? 2 : 3;
> > unsigned int i, order = get_order_from_pages(nr);
> >
> > @@ -165,7 +165,7 @@ static int __init cf_check nestedhvm_setup(void)
> > __initcall(nestedhvm_setup);
> >
> > unsigned long *
> > -nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed)
> > +nestedhvm_vcpu_iomap_get(struct vcpu *v, bool ioport_80, bool ioport_ed)
> > {
> > int i;
> >
> > @@ -174,7 +174,7 @@ nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed)
> >
> > if (ioport_80 == 0) {
> > if (ioport_ed == 0)
> > - return hvm_io_bitmap;
> > + return v->domain->arch.hvm.io_bitmap;
> > i = 0;
> > } else {
> > if (ioport_ed == 0)
> > diff --git a/xen/arch/x86/hvm/quirks.c b/xen/arch/x86/hvm/quirks.c
> > index 9202f5a47fe9..f4d95441fcff 100644
> > --- a/xen/arch/x86/hvm/quirks.c
> > +++ b/xen/arch/x86/hvm/quirks.c
> > @@ -73,9 +73,6 @@ static int __init cf_check check_port80(void)
> >
> > dmi_check_system(hvm_no_port80_dmi_table);
> >
> > - if ( !hvm_port80_allowed )
> > - __set_bit(0x80, hvm_io_bitmap);
> > -
> > return 0;
> > }
> > __initcall(check_port80);
> > diff --git a/xen/arch/x86/hvm/svm/nestedsvm.c b/xen/arch/x86/hvm/svm/nestedsvm.c
> > index dc2b6a42534a..cc8500b61665 100644
> > --- a/xen/arch/x86/hvm/svm/nestedsvm.c
> > +++ b/xen/arch/x86/hvm/svm/nestedsvm.c
> > @@ -381,7 +381,7 @@ static int nsvm_vmrun_permissionmap(struct vcpu *v, bool viopm)
> > hvm_unmap_guest_frame(ns_viomap, 0);
> > }
> >
> > - svm->ns_iomap = nestedhvm_vcpu_iomap_get(ioport_80, ioport_ed);
> > + svm->ns_iomap = nestedhvm_vcpu_iomap_get(v, ioport_80, ioport_ed);
> >
> > nv->nv_ioport80 = ioport_80;
> > nv->nv_ioportED = ioport_ed;
> > diff --git a/xen/arch/x86/hvm/vmx/vvmx.c b/xen/arch/x86/hvm/vmx/vvmx.c
> > index e4f3a5fe4c71..4da3e6e90e6c 100644
> > --- a/xen/arch/x86/hvm/vmx/vvmx.c
> > +++ b/xen/arch/x86/hvm/vmx/vvmx.c
> > @@ -554,7 +554,7 @@ unsigned long *_shadow_io_bitmap(struct vcpu *v)
> > port80 = bitmap[0x80 >> 3] & (1 << (0x80 & 0x7)) ? 1 : 0;
> > portED = bitmap[0xed >> 3] & (1 << (0xed & 0x7)) ? 1 : 0;
> >
> > - return nestedhvm_vcpu_iomap_get(port80, portED);
> > + return nestedhvm_vcpu_iomap_get(v, port80, portED);
> > }
> >
> > static void update_msrbitmap(struct vcpu *v, uint32_t shadow_ctrl)
> > @@ -622,7 +622,7 @@ void nvmx_update_exec_control(struct vcpu *v, u32 host_cntrl)
> > * L1 VMM doesn't intercept IO instruction.
> > * Use host configuration and reset IO_BITMAP
> > */
> > - bitmap = hvm_io_bitmap;
> > + bitmap = v->domain->arch.hvm.io_bitmap;
> > }
> > else {
> > /* use IO bitmap */
> > diff --git a/xen/arch/x86/include/asm/hvm/nestedhvm.h b/xen/arch/x86/include/asm/hvm/nestedhvm.h
> > index ea2c1bc328c7..d691ccb07dd6 100644
> > --- a/xen/arch/x86/include/asm/hvm/nestedhvm.h
> > +++ b/xen/arch/x86/include/asm/hvm/nestedhvm.h
> > @@ -50,7 +50,8 @@ int nestedhvm_hap_nested_page_fault(struct vcpu *v, paddr_t *L2_gpa,
> > struct npfec npfec);
> >
> > /* IO permission map */
> > -unsigned long *nestedhvm_vcpu_iomap_get(bool ioport_80, bool ioport_ed);
> > +unsigned long *nestedhvm_vcpu_iomap_get(struct vcpu *v,
> > + bool ioport_80, bool ioport_ed);
> >
> > /* Misc */
> > #define nestedhvm_paging_mode_hap(v) (!!nhvm_vmcx_hap_enabled(v))
> > diff --git a/xen/arch/x86/include/asm/hvm/support.h b/xen/arch/x86/include/asm/hvm/support.h
> > index 2a7ba36af06f..7e36d00cc188 100644
> > --- a/xen/arch/x86/include/asm/hvm/support.h
> > +++ b/xen/arch/x86/include/asm/hvm/support.h
> > @@ -41,8 +41,6 @@ extern unsigned int opt_hvm_debug_level;
> > #define HVM_DBG_LOG(level, _f, _a...) do {} while (0)
> > #endif
> >
> > -extern unsigned long hvm_io_bitmap[];
> > -
> > enum hvm_translation_result {
> > HVMTRANS_okay,
> > HVMTRANS_bad_linear_to_gfn,
> > diff --git a/xen/arch/x86/include/asm/iocap.h b/xen/arch/x86/include/asm/iocap.h
> > index f948b7186e95..1083f6171cf7 100644
> > --- a/xen/arch/x86/include/asm/iocap.h
> > +++ b/xen/arch/x86/include/asm/iocap.h
> > @@ -22,6 +22,8 @@
> > #define cache_flush_permitted(d) \
> > (has_arch_io_resources(d) || has_arch_pdevs(d))
> >
> > +int ioports_setup_access(struct domain *d);
> > +
> > static inline int ioports_permit_access(struct domain *d, unsigned long s,
> > unsigned long e)
> > {
> > diff --git a/xen/arch/x86/ioport.c b/xen/arch/x86/ioport.c
> > new file mode 100644
> > index 000000000000..dbcd52d37a4f
> > --- /dev/null
> > +++ b/xen/arch/x86/ioport.c
> > @@ -0,0 +1,163 @@
> > +/* SPDX-License-Identifier: GPL-2.0-only */
> > +/*
> > + * Guest I/O port address space configuration.
> > + *
> > + * Copyright 2025 Ford Motor Company
> > + */
> > +
> > +#include <xen/domain.h>
> > +#include <xen/param.h>
> > +
> > +#include <asm/amd.h>
> > +#include <asm/acpi.h>
> > +#include <asm/io-ports.h>
> > +#include <asm/iocap.h>
> > +#include <asm/pv/shim.h>
> > +#include <asm/setup.h>
> > +
> > +static char __initdata opt_dom0_ioports_disable[200] = "";
> > +string_param("dom0_ioports_disable", opt_dom0_ioports_disable);
> > +
> > +/*
> > + * The I/O permission bitmap size.
> > + * See: comment in nestedhvm_setup()
> > + */
> > +#define HVM_IOBITMAP_SIZE (3 * PAGE_SIZE)
> > +
> > +/* Hide user-defined I/O ports from the guest OS. */
> > +static void process_dom0_ioports_disable(struct domain *dom0)
> > +{
> > + unsigned long io_from, io_to;
> > + char *t, *s = opt_dom0_ioports_disable;
> > + const char *u;
> > +
> > + if ( *s == '\0' )
> > + return;
> > +
> > + while ( (t = strsep(&s, ",")) != NULL )
> > + {
> > + io_from = simple_strtoul(t, &u, 16);
> > + if ( u == t )
> > + {
> > + parse_error:
> > + printk("Invalid ioport range <%s> "
> > + "in dom0_ioports_disable, skipping\n", t);
> > + continue;
> > + }
> > +
> > + if ( *u == '\0' )
> > + io_to = io_from;
> > + else if ( *u == '-' )
> > + io_to = simple_strtoul(u + 1, &u, 16);
> > + else
> > + goto parse_error;
> > +
> > + if ( (*u != '\0') || (io_to < io_from) || (io_to >= 65536) )
> > + goto parse_error;
> > +
> > + printk("Disabling dom0 access to ioport range %04lx-%04lx\n",
> > + io_from, io_to);
> > +
> > + if ( ioports_deny_access(dom0, io_from, io_to) != 0 )
> > + BUG();
> > + }
> > +}
> > +
> > +/* Set the default IO Bitmap. */
> > +int ioports_setup_access(struct domain *d)
> > +{
> > + unsigned int i, offs;
> > + int rc;
> > +
> > + if ( pv_shim )
> > + return 0;
> > +
> > +#ifdef CONFIG_HVM
> > + d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);
> > + if ( d->arch.hvm.io_bitmap == NULL )
> > + return -ENOMEM;
> > +
> > + memset(d->arch.hvm.io_bitmap, ~0, HVM_IOBITMAP_SIZE);
> > +
> > + if ( !is_hardware_domain(d) )
> > + {
> > + /*
> > + * Allow direct access to the PC debug ports 0x80 and 0xed (they are
> > + * often used for I/O delays, but the vmexits simply slow things down).
> > + */
> > + if ( hvm_port80_allowed )
> > + __clear_bit(0x80, d->arch.hvm.io_bitmap);
> > +
> > + __clear_bit(0xed, d->arch.hvm.io_bitmap);
> > +
> > + return 0;
> > + }
> > +#endif
> > +
> > + /* The hardware domain is initially permitted full I/O capabilities. */
> > + rc = ioports_permit_access(d, 0, 0xFFFF);
> > +
> > + /* Modify I/O port access permissions. */
> > +
> > + for ( offs = 0, i = ISOLATE_LSB(i8259A_alias_mask) ?: 2;
> > + offs <= i8259A_alias_mask; offs += i )
> > + {
> > + if ( offs & ~i8259A_alias_mask )
> > + continue;
> > + /* Master Interrupt Controller (PIC). */
> > + rc |= ioports_deny_access(d, 0x20 + offs, 0x21 + offs);
> > + /* Slave Interrupt Controller (PIC). */
> > + rc |= ioports_deny_access(d, 0xA0 + offs, 0xA1 + offs);
> > + }
> > +
> > + /* ELCR of both PICs. */
> > + rc |= ioports_deny_access(d, 0x4D0, 0x4D1);
> > +
> > + /* Interval Timer (PIT). */
> > + for ( offs = 0, i = ISOLATE_LSB(pit_alias_mask) ?: 4;
> > + offs <= pit_alias_mask; offs += i )
> > + if ( !(offs & ~pit_alias_mask) )
> > + rc |= ioports_deny_access(d, PIT_CH0 + offs, PIT_MODE + offs);
> > +
> > + /* PIT Channel 2 / PC Speaker Control. */
> > + rc |= ioports_deny_access(d, 0x61, 0x61);
> > +
> > + /* INIT# and alternative A20M# control. */
> > + rc |= ioports_deny_access(d, 0x92, 0x92);
> > +
> > + /* IGNNE# control. */
> > + rc |= ioports_deny_access(d, 0xF0, 0xF0);
> > +
> > + /* ACPI PM Timer. */
> > + if ( pmtmr_ioport )
> > + rc |= ioports_deny_access(d, pmtmr_ioport, pmtmr_ioport + 3);
> > +
> > + /* Reset control. */
> > + rc |= ioports_deny_access(d, 0xCF9, 0xCF9);
> > +
> > + /* PCI configuration space (NB. 0xCF8 has special treatment). */
> > + rc |= ioports_deny_access(d, 0xCFC, 0xCFF);
> > +
> > +#ifdef CONFIG_HVM
> > + if ( is_hvm_domain(d) )
> > + {
> > + /* ISA DMA controller, channels 0-3 (incl possible aliases). */
> > + rc |= ioports_deny_access(d, 0x00, 0x1F);
> > + /* ISA DMA controller, page registers (incl various reserved ones). */
> > + rc |= ioports_deny_access(d, 0x80 + !!hvm_port80_allowed, 0x8F);
> > + /* ISA DMA controller, channels 4-7 (incl usual aliases). */
> > + rc |= ioports_deny_access(d, 0xC0, 0xDF);
> > +
> > + /* HVM debug console IO port. */
> > + rc |= ioports_deny_access(d, XEN_HVM_DEBUGCONS_IOPORT,
> > + XEN_HVM_DEBUGCONS_IOPORT);
> > + if ( amd_acpi_c1e_quirk )
> > + rc |= ioports_deny_access(d, acpi_smi_cmd, acpi_smi_cmd);
> > + }
> > +#endif
> > +
> > + /* Command-line I/O ranges. */
> > + process_dom0_ioports_disable(d);
> > +
> > + return rc;
> > +}
> > diff --git a/xen/arch/x86/pv/dom0_build.c b/xen/arch/x86/pv/dom0_build.c
> > index 21158ce1812e..2b8b4d869ee7 100644
> > --- a/xen/arch/x86/pv/dom0_build.c
> > +++ b/xen/arch/x86/pv/dom0_build.c
> > @@ -17,6 +17,7 @@
> > #include <asm/bootinfo.h>
> > #include <asm/bzimage.h>
> > #include <asm/dom0_build.h>
> > +#include <asm/iocap.h>
> > #include <asm/guest.h>
> > #include <asm/page.h>
> > #include <asm/pv/mm.h>
> > @@ -1033,6 +1034,9 @@ static int __init dom0_construct(const struct boot_domain *bd)
> > if ( test_bit(XENFEAT_supervisor_mode_kernel, parms.f_required) )
> > panic("Dom0 requires supervisor-mode execution\n");
> >
> > + rc = ioports_setup_access(d);
> > + BUG_ON(rc != 0);
> > +
> > rc = dom0_setup_permissions(d);
> > BUG_ON(rc != 0);
> >
> > diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> > index 5c1be854b544..8860f25ffdeb 100644
> > --- a/xen/common/emul/vuart/ns16x50.c
> > +++ b/xen/common/emul/vuart/ns16x50.c
> > @@ -780,9 +780,20 @@ static int ns16x50_init(void *arg)
> > struct vuart_ns16x50 *vdev = arg;
> > const struct vuart_info *info = vdev->info;
> > struct domain *d = vdev->owner;
> > + int rc;
> >
> > ASSERT(vdev);
> >
> > + /* Disallow sharing physical I/O port */
> > + rc = ioports_deny_access(d, info->base_addr,
> > + info->base_addr + info->size - 1);
>
> I would be tempted to move ioports_deny_access to hvm_domain_initialise
> before vuart_init
I thought about it originally, but I decided to keep vuart setup in one place.
>
>
> > + if ( rc )
> > + {
> > + ns16x50_err(info, " virtual I/O port range [0x%04lx"PRIx64"..0x%04lx"PRIx64"]: conflict w/ physical range\n",
> > + info->base_addr, info->base_addr + info->size - 1);
>
> 0x%04lx"PRIx64 seems wrong
Agreed, will fix, thanks.
>
>
> > + return rc;
> > + }
> > +
> > /* NB: report 115200 baud rate. */
> > vdev->regs[NS16X50_REGS_NUM + UART_DLL] = divisor & 0xff;
> > vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
> > --
> > 2.51.0
> >
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 14/15] xen/domain: allocate d->irq_caps before arch-specific initialization
2025-08-29 21:46 ` Stefano Stabellini
@ 2025-09-05 22:43 ` dmukhin
0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-05 22:43 UTC (permalink / raw)
To: Stefano Stabellini
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, dmukhin
On Fri, Aug 29, 2025 at 02:46:29PM -0700, Stefano Stabellini wrote:
> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > From: Denis Mukhin <dmukhin@ford.com>
> >
[..]
> >
> > + /* Disallow sharing physical IRQ */
> > + rc = irq_deny_access(d, info->irq);
> > + if ( rc )
> > + {
> > + ns16x50_err(info, "virtual IRQ#%d: conflict w/ physical IRQ: %d\n",
> > + info->irq, rc);
> > + return rc;
> > + }
>
> Also this one I wonder if it could be in hvm_domain_initialise, it would
> make more sense to keep the irq_deny_access there, compared to here
> which is supposed to be a generic emulator
I think it is better to have all vuart initialization in one place, at least
until MMIO-based variant is not available. Current code is x86-specific (port
handlers and IRQ emulation), there will be some changes to make code a generic
NS16550 emulator.
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 15/15] emul/ns16x50: implement IRQ emulation via vIOAPIC
2025-08-29 22:21 ` Stefano Stabellini
@ 2025-09-05 22:54 ` dmukhin
2025-09-05 23:01 ` Stefano Stabellini
0 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-05 22:54 UTC (permalink / raw)
To: Stefano Stabellini
Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
michal.orzel, roger.pau, dmukhin
On Fri, Aug 29, 2025 at 03:21:13PM -0700, Stefano Stabellini wrote:
> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > From: Denis Mukhin <dmukhin@ford.com>
> >
> > PVH domains use vIOAPIC, not vPIC and NS16550 emulates ISA IRQs which cannot
> > be asserted on vIOAPIC.
>
> One option is to enable the vPIT for PVH domains when the NS16550
> emulator is enabled. Would that resolve the problem? That would be a
> simpler solution compared to adding IRQ_EMU because the IRQ_* logic is
> already quite complex.
vPIC? (PIT is timer).
I tried to enable vPIC for hwdom, but there's some more work to make it work
:-/
>
> Alternatively...
[..]
>
> > --- a/xen/arch/x86/hvm/vioapic.c
> > +++ b/xen/arch/x86/hvm/vioapic.c
> > @@ -177,6 +177,16 @@ static int vioapic_hwdom_map_gsi(unsigned int gsi, unsigned int trig,
> >
> > ASSERT(is_hardware_domain(currd));
> >
> > + /*
> > + * Interrupt is claimed by one of the platform virtual devices (e.g.
> > + * NS16550); do nothing.
> > + */
> > + write_lock(&currd->event_lock);
> > + ret = domain_irq_to_emuirq(currd, gsi);
> > + write_unlock(&currd->event_lock);
> > + if ( ret != IRQ_UNBOUND )
> > + return 0;
>
> ..alternatively, we could have an add-hoc check here? Not very nice but
> at least it would be very simple.
>
> In other words, adding vPIT is preferable in my opinion but as a second
> option I would consider an ad-hoc check. I would try to avoid adding
> IRQ_EMU -- that should be the last resort in my view.
In my opinion, IRQ_EMU falls into category of an ad-hoc.
I think vioapic_hwdom_map_gsi() should not depend on the presence of certain
virtual devices but rely on some global flag, e.g. via quering
domain_irq_to_emuirq() infra.
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 15/15] emul/ns16x50: implement IRQ emulation via vIOAPIC
2025-09-05 22:54 ` dmukhin
@ 2025-09-05 23:01 ` Stefano Stabellini
2025-09-05 23:44 ` dmukhin
0 siblings, 1 reply; 46+ messages in thread
From: Stefano Stabellini @ 2025-09-05 23:01 UTC (permalink / raw)
To: dmukhin
Cc: Stefano Stabellini, xen-devel, andrew.cooper3, anthony.perard,
jbeulich, julien, michal.orzel, roger.pau, dmukhin
On Fri, 5 Sep 2025, dmukhin@xen.org wrote:
> On Fri, Aug 29, 2025 at 03:21:13PM -0700, Stefano Stabellini wrote:
> > On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > > From: Denis Mukhin <dmukhin@ford.com>
> > >
> > > PVH domains use vIOAPIC, not vPIC and NS16550 emulates ISA IRQs which cannot
> > > be asserted on vIOAPIC.
> >
> > One option is to enable the vPIT for PVH domains when the NS16550
> > emulator is enabled. Would that resolve the problem? That would be a
> > simpler solution compared to adding IRQ_EMU because the IRQ_* logic is
> > already quite complex.
>
> vPIC? (PIT is timer).
>
> I tried to enable vPIC for hwdom, but there's some more work to make it work
> :-/
>
> >
> > Alternatively...
> [..]
> >
> > > --- a/xen/arch/x86/hvm/vioapic.c
> > > +++ b/xen/arch/x86/hvm/vioapic.c
> > > @@ -177,6 +177,16 @@ static int vioapic_hwdom_map_gsi(unsigned int gsi, unsigned int trig,
> > >
> > > ASSERT(is_hardware_domain(currd));
> > >
> > > + /*
> > > + * Interrupt is claimed by one of the platform virtual devices (e.g.
> > > + * NS16550); do nothing.
> > > + */
> > > + write_lock(&currd->event_lock);
> > > + ret = domain_irq_to_emuirq(currd, gsi);
> > > + write_unlock(&currd->event_lock);
> > > + if ( ret != IRQ_UNBOUND )
> > > + return 0;
> >
> > ..alternatively, we could have an add-hoc check here? Not very nice but
> > at least it would be very simple.
> >
> > In other words, adding vPIT is preferable in my opinion but as a second
> > option I would consider an ad-hoc check. I would try to avoid adding
> > IRQ_EMU -- that should be the last resort in my view.
>
> In my opinion, IRQ_EMU falls into category of an ad-hoc.
>
> I think vioapic_hwdom_map_gsi() should not depend on the presence of certain
> virtual devices but rely on some global flag, e.g. via quering
> domain_irq_to_emuirq() infra.
Hi Denis, the reason why IRQ_EMU is dangerous is that there are a bunch
of checks emuirq != IRQ_PT and also emuirq != IRQ_MSI_EMU which I don't
know if they would still behave correctly after the introduction of
IRQ_EMU.
I would prefer to avoid the problem if possible...
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 03/15] emul/ns16x50: implement emulator stub
2025-09-02 9:36 ` Jan Beulich
@ 2025-09-05 23:34 ` dmukhin
0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-05 23:34 UTC (permalink / raw)
To: Jan Beulich
Cc: Stefano Stabellini, xen-devel, andrew.cooper3, anthony.perard,
julien, michal.orzel, roger.pau, dmukhin
On Tue, Sep 02, 2025 at 11:36:19AM +0200, Jan Beulich wrote:
> On 29.08.2025 21:57, Stefano Stabellini wrote:
> > On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> >> +static void cf_check ns16x50_free(void *arg)
> >> +{
> >> + struct vuart_ns16x50 *vdev = arg;
> >> +
> >> + if ( vdev )
> >> + ns16x50_deinit(vdev);
> >> +
> >> + XVFREE(vdev);
> >
> > XVFREE should only be called if ( vdev )
>
> Why would this be? Like free(), both xfree() and xvfree() are fine to be
> called with a NULL pointer. What's odd here is that the uppercase form (the
> wrapper macro) is used - clearing the local variable is pointless when it
> is about to go out of scope anyway.
Thank you for remark!
I switched the code to xvfree() in v6
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 03/15] emul/ns16x50: implement emulator stub
2025-09-02 9:32 ` Jan Beulich
@ 2025-09-05 23:34 ` dmukhin
2025-09-06 0:07 ` dmukhin
0 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-05 23:34 UTC (permalink / raw)
To: Jan Beulich
Cc: Stefano Stabellini, xen-devel, andrew.cooper3, anthony.perard,
julien, michal.orzel, roger.pau, dmukhin
On Tue, Sep 02, 2025 at 11:32:31AM +0200, Jan Beulich wrote:
> On 02.09.2025 01:11, dmukhin@xen.org wrote:
> > On Fri, Aug 29, 2025 at 12:57:43PM -0700, Stefano Stabellini wrote:
> >> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> >>> --- a/xen/common/emul/vuart/Kconfig
> >>> +++ b/xen/common/emul/vuart/Kconfig
> >>> @@ -3,4 +3,22 @@ config VUART_FRAMEWORK
> >>>
> >>> menu "UART Emulation"
> >>>
> >>> +config VUART_NS16X50
> >>> + bool "NS16550-compatible UART Emulator" if EXPERT
> >>> + depends on X86 && HVM
> >>> + select VUART_FRAMEWORK
> >>
> >> default n
> >
> > Ack
>
> No "default n" should ever be put anywhere; it's simply redundant.
Kept code as is in v6.
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 15/15] emul/ns16x50: implement IRQ emulation via vIOAPIC
2025-09-05 23:01 ` Stefano Stabellini
@ 2025-09-05 23:44 ` dmukhin
0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-05 23:44 UTC (permalink / raw)
To: Stefano Stabellini
Cc: Stefano Stabellini, xen-devel, andrew.cooper3, anthony.perard,
jbeulich, julien, michal.orzel, roger.pau, dmukhin
On Fri, Sep 05, 2025 at 04:01:15PM -0700, Stefano Stabellini wrote:
> On Fri, 5 Sep 2025, dmukhin@xen.org wrote:
> > On Fri, Aug 29, 2025 at 03:21:13PM -0700, Stefano Stabellini wrote:
> > > On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > > > From: Denis Mukhin <dmukhin@ford.com>
> > > >
> > > > PVH domains use vIOAPIC, not vPIC and NS16550 emulates ISA IRQs which cannot
> > > > be asserted on vIOAPIC.
> > >
> > > One option is to enable the vPIT for PVH domains when the NS16550
> > > emulator is enabled. Would that resolve the problem? That would be a
> > > simpler solution compared to adding IRQ_EMU because the IRQ_* logic is
> > > already quite complex.
> >
> > vPIC? (PIT is timer).
> >
> > I tried to enable vPIC for hwdom, but there's some more work to make it work
> > :-/
> >
> > >
> > > Alternatively...
> > [..]
> > >
> > > > --- a/xen/arch/x86/hvm/vioapic.c
> > > > +++ b/xen/arch/x86/hvm/vioapic.c
> > > > @@ -177,6 +177,16 @@ static int vioapic_hwdom_map_gsi(unsigned int gsi, unsigned int trig,
> > > >
> > > > ASSERT(is_hardware_domain(currd));
> > > >
> > > > + /*
> > > > + * Interrupt is claimed by one of the platform virtual devices (e.g.
> > > > + * NS16550); do nothing.
> > > > + */
> > > > + write_lock(&currd->event_lock);
> > > > + ret = domain_irq_to_emuirq(currd, gsi);
> > > > + write_unlock(&currd->event_lock);
> > > > + if ( ret != IRQ_UNBOUND )
> > > > + return 0;
> > >
> > > ..alternatively, we could have an add-hoc check here? Not very nice but
> > > at least it would be very simple.
> > >
> > > In other words, adding vPIT is preferable in my opinion but as a second
> > > option I would consider an ad-hoc check. I would try to avoid adding
> > > IRQ_EMU -- that should be the last resort in my view.
> >
> > In my opinion, IRQ_EMU falls into category of an ad-hoc.
> >
> > I think vioapic_hwdom_map_gsi() should not depend on the presence of certain
> > virtual devices but rely on some global flag, e.g. via quering
> > domain_irq_to_emuirq() infra.
>
> Hi Denis, the reason why IRQ_EMU is dangerous is that there are a bunch
> of checks emuirq != IRQ_PT and also emuirq != IRQ_MSI_EMU which I don't
> know if they would still behave correctly after the introduction of
> IRQ_EMU.
!= IRQ_PT checks (arch/x86/irq.c) are done from map_domain_emuirq_pirq()
and unmap_domain_pirq_emuirq() which I use for IRQ_EMU case.
Those checks are needed to correctly allocate an "ad-hoc flag" in a radix
"IRQ_EMU" tree, i.e. IRQ_EMU case is handled correctly.
!= IRQ_MSI_EMU check (there's only one in arch/x86/hvm/irq.c) is done from
hvm_inject_msi(), it guarantees that IRQ_EMU case is handled correctly.
>
> I would prefer to avoid the problem if possible...
^ permalink raw reply [flat|nested] 46+ messages in thread
* Re: [PATCH v5 03/15] emul/ns16x50: implement emulator stub
2025-09-05 23:34 ` dmukhin
@ 2025-09-06 0:07 ` dmukhin
0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-06 0:07 UTC (permalink / raw)
To: Jan Beulich
Cc: Stefano Stabellini, xen-devel, andrew.cooper3, anthony.perard,
julien, michal.orzel, roger.pau, dmukhin
On Fri, Sep 05, 2025 at 04:34:34PM -0700, dmukhin@xen.org wrote:
> On Tue, Sep 02, 2025 at 11:32:31AM +0200, Jan Beulich wrote:
> > On 02.09.2025 01:11, dmukhin@xen.org wrote:
> > > On Fri, Aug 29, 2025 at 12:57:43PM -0700, Stefano Stabellini wrote:
> > >> On Thu, 28 Aug 2025, dmukhin@xen.org wrote:
> > >>> --- a/xen/common/emul/vuart/Kconfig
> > >>> +++ b/xen/common/emul/vuart/Kconfig
> > >>> @@ -3,4 +3,22 @@ config VUART_FRAMEWORK
> > >>>
> > >>> menu "UART Emulation"
> > >>>
> > >>> +config VUART_NS16X50
> > >>> + bool "NS16550-compatible UART Emulator" if EXPERT
> > >>> + depends on X86 && HVM
> > >>> + select VUART_FRAMEWORK
> > >>
> > >> default n
> > >
> > > Ack
> >
> > No "default n" should ever be put anywhere; it's simply redundant.
>
> Kept code as is in v6.
Sorry, v6 has "default n" update which I need to remove.
^ permalink raw reply [flat|nested] 46+ messages in thread
end of thread, other threads:[~2025-09-06 0:07 UTC | newest]
Thread overview: 46+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-28 23:53 [PATCH v5 00/15] x86: introduce NS16550-compatible UART emulator dmukhin
2025-08-28 23:53 ` [PATCH v5 01/15] emul/vuart: introduce framework for UART emulators dmukhin
2025-08-29 19:27 ` Stefano Stabellini
2025-09-01 8:14 ` Jan Beulich
2025-09-01 22:27 ` dmukhin
2025-09-02 6:14 ` Jan Beulich
2025-09-01 22:11 ` dmukhin
2025-08-28 23:53 ` [PATCH v5 02/15] xen/8250-uart: update definitions dmukhin
2025-08-29 19:32 ` Stefano Stabellini
2025-09-01 22:28 ` dmukhin
2025-08-28 23:53 ` [PATCH v5 03/15] emul/ns16x50: implement emulator stub dmukhin
2025-08-29 19:57 ` Stefano Stabellini
2025-09-01 23:11 ` dmukhin
2025-09-02 9:32 ` Jan Beulich
2025-09-05 23:34 ` dmukhin
2025-09-06 0:07 ` dmukhin
2025-09-02 9:36 ` Jan Beulich
2025-09-05 23:34 ` dmukhin
2025-08-28 23:53 ` [PATCH v5 04/15] emul/ns16x50: implement DLL/DLM registers dmukhin
2025-08-28 23:53 ` [PATCH v5 05/15] emul/ns16x50: implement EIR/IIR registers dmukhin
2025-08-29 20:14 ` Stefano Stabellini
2025-09-05 22:05 ` dmukhin
2025-08-28 23:54 ` [PATCH v5 06/15] emul/ns16x50: implement THR/RBR registers dmukhin
2025-08-29 20:28 ` Stefano Stabellini
2025-08-29 20:34 ` Stefano Stabellini
2025-09-05 22:29 ` dmukhin
2025-09-05 22:20 ` dmukhin
2025-08-28 23:54 ` [PATCH v5 07/15] emul/ns16x50: implement FCR register (write-only) dmukhin
2025-08-29 20:38 ` Stefano Stabellini
2025-09-05 22:33 ` dmukhin
2025-08-28 23:54 ` [PATCH v5 08/15] emul/ns16x50: implement LCR/LSR registers dmukhin
2025-08-28 23:54 ` [PATCH v5 09/15] emul/ns16x50: implement MCR/MSR registers dmukhin
2025-08-28 23:54 ` [PATCH v5 10/15] emul/ns16x50: implement SCR register dmukhin
2025-08-28 23:54 ` [PATCH v5 11/15] emul/ns16x50: implement put_rx() hook dmukhin
2025-08-28 23:54 ` [PATCH v5 12/15] emul/ns16550: implement dump_state() hook dmukhin
2025-08-28 23:54 ` [PATCH v5 13/15] x86/domain: enable per-domain I/O port bitmaps dmukhin
2025-08-29 21:43 ` Stefano Stabellini
2025-09-05 22:38 ` dmukhin
2025-08-28 23:54 ` [PATCH v5 14/15] xen/domain: allocate d->irq_caps before arch-specific initialization dmukhin
2025-08-29 21:46 ` Stefano Stabellini
2025-09-05 22:43 ` dmukhin
2025-08-28 23:54 ` [PATCH v5 15/15] emul/ns16x50: implement IRQ emulation via vIOAPIC dmukhin
2025-08-29 22:21 ` Stefano Stabellini
2025-09-05 22:54 ` dmukhin
2025-09-05 23:01 ` Stefano Stabellini
2025-09-05 23:44 ` dmukhin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).