All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator
@ 2025-09-08 21:11 dmukhin
  2025-09-08 21:11 ` [PATCH v7 01/16] emul/vuart: introduce framework for UART emulators dmukhin
                   ` (15 more replies)
  0 siblings, 16 replies; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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
0x3f8, IRQ#4 in guest OS (legacy COM1). Legacy COM resources can be selected
at run-time via temporary and undocumented switch 'vuart='.

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 legacy COM resources, 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 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-11 incrementally populate the minimal NS16550 register emulation.

  Patch 12 adds Kconfig for enabling minimal NS16550 UART (disabled by default).

  Patch 13 hooks vUART state debugging (disabled by default).

  Pathes 14-16 introduce necessary changes to enable NS16550 on dom0 (and PVH).

Link to CI: https://gitlab.com/xen-project/people/dmukhin/xen/-/pipelines/2028388344
Link to branch: https://gitlab.com/xen-project/people/dmukhin/xen/-/tree/vuart-ns8250-v7?ref_type=heads

Testing
=======

  ```shell
  echo CONFIG_EXPERT=y >> .config
  echo CONFIG_VUART_NS16X50=y >> .config
  make olddefconfig
  ```

  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

  Xen command line should have:
    vuart=<DOMID>,com2

  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.
  Console switch is limited on x86 to dom0 and Xen (fixes pending).

Changes since v6:
- Addressed feedback from v6, mainly DLL, THRE interrupt reason handling and
  fixups in vuart.c and ns16x50.c. Also plumbed new temporary swicth 'vuart='
  for ease of testing (CI unit test jobs are pending).
- Link to v6:  https://lore.kernel.org/xen-devel/20250905232715.440758-1-dmukhin@ford.com/

Changes since v5:
- Split THR/RBR into two separate patches.
- Addressed feedback from v5.
- Link to v5: https://lore.kernel.org/xen-devel/20250828235409.2835815-1-dmukhin@ford.com/

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 (16):
  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 SCR register
  emul/ns16x50: implement IER/IIR registers
  emul/ns16x50: implement LCR/LSR registers
  emul/ns16x50: implement MCR/MSR registers
  emul/ns16x50: implement RBR register
  emul/ns16x50: implement THR register
  emul/ns16x50: implement FCR register (write-only)
  emul/ns16550: implement dump_state() hook
  emul/ns16x50: add Kconfig options
  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/hvm/dom0_build.c            |   7 +
 xen/arch/x86/hvm/hvm.c                   | 110 ++-
 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           |   8 +
 xen/arch/x86/ioport.c                    | 163 ++++
 xen/arch/x86/irq.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            |  25 +
 xen/common/emul/vuart/Makefile           |   2 +
 xen/common/emul/vuart/ns16x50.c          | 970 +++++++++++++++++++++++
 xen/common/emul/vuart/vuart.c            | 165 ++++
 xen/common/keyhandler.c                  |   3 +
 xen/drivers/char/console.c               |   6 +-
 xen/drivers/char/ns16550.c               |  16 +-
 xen/drivers/passthrough/x86/hvm.c        |  11 +-
 xen/include/xen/8250-uart.h              |  36 +-
 xen/include/xen/sched.h                  |   4 +
 xen/include/xen/serial.h                 |   3 +
 xen/include/xen/vuart.h                  | 115 +++
 xen/include/xen/xen.lds.h                |  10 +
 38 files changed, 1674 insertions(+), 164 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 v7 01/16] emul/vuart: introduce framework for UART emulators
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-09-10  7:57   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 02/16] xen/8250-uart: update definitions dmukhin
                   ` (14 subsequent siblings)
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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 v6:
- addresses Mykola's feedback
- some renaming (vuart_find_by_flags())
- added extra checks to put_rx and dump_state
- fixed vuart_init() error path
- simplified some checks during vUART state 'search'
---
 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  | 165 +++++++++++++++++++++++++++++++++
 xen/common/keyhandler.c        |   3 +
 xen/drivers/char/console.c     |   6 +-
 xen/include/xen/sched.h        |   4 +
 xen/include/xen/serial.h       |   3 +
 xen/include/xen/vuart.h        | 115 +++++++++++++++++++++++
 xen/include/xen/xen.lds.h      |  10 ++
 17 files changed, 326 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 0c7d0f5d46e1..8c8462565050 100644
--- a/xen/common/Makefile
+++ b/xen/common/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_DEVICE_TREE_PARSE) += device-tree/
 obj-$(CONFIG_IOREQ_SERVER) += dm.o
 obj-y += domain.o
 obj-y += domid.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..ba89d608aeb2
--- /dev/null
+++ b/xen/common/emul/vuart/vuart.c
@@ -0,0 +1,165 @@
+/* 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(const struct domain *d, const char *compat)
+{
+    const struct vuart_emulator *emulator;
+
+    for_each_emulator(emulator)
+        if ( emulator->compatible &&
+             !strncmp(compat, emulator->compatible,
+                      strlen(emulator->compatible)) )
+            return emulator;
+
+    return NULL;
+}
+
+const static struct vuart *
+vuart_find_by_flags(const struct domain *d, unsigned int flags)
+{
+    const struct vuart *vuart = d->console.vuart;
+
+    if ( vuart && (vuart->flags & flags) )
+        return vuart;
+
+    return NULL;
+}
+
+struct vuart *vuart_find_by_io_range(struct domain *d, unsigned long addr,
+                                     unsigned long size)
+{
+    struct vuart *vuart = d->console.vuart;
+
+    if ( vuart &&
+         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, const struct vuart_info *info)
+{
+    const struct vuart_emulator *emulator;
+    struct vuart *vuart;
+    int rc;
+
+    if ( d->console.vuart )
+        return -EBUSY;
+
+    emulator = vuart_match_by_compatible(d, info->compatible);
+    if ( !emulator )
+        return -ENODEV;
+
+    vuart = xzalloc(typeof(*vuart));
+    if ( !vuart )
+        return -ENOMEM;
+
+    vuart->info = xvzalloc(typeof(*vuart->info));
+    if ( !vuart->info )
+    {
+        rc = -ENOMEM;
+        goto err_out1;
+    }
+    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_out2;
+    }
+
+    vuart->emulator = emulator;
+    vuart->owner = d;
+    vuart->flags |= VUART_CONSOLE_INPUT;
+
+    d->console.input_allowed = true;
+    d->console.vuart = vuart;
+
+    return 0;
+
+ err_out2:
+    xvfree(vuart->info);
+ err_out1:
+    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->vdev);
+        xvfree(vuart->info);
+    }
+    XVFREE(d->console.vuart);
+}
+
+/*
+ * Print emulated UART state on the console.
+ *
+ * Must be called under rcu_lock_domain().
+ */
+void vuart_dump_state(const struct domain *d)
+{
+    struct vuart *vuart = d->console.vuart;
+
+    if ( vuart && vuart->emulator->dump_state )
+        vuart->emulator->dump_state(vuart->vdev);
+}
+
+/*
+ * Put character to the first emulated UART's FIFO with the physical console
+ * forwarding enabled.
+ *
+ * Must be called under rcu_lock_domain().
+ */
+int vuart_put_rx(struct domain *d, char c)
+{
+    const struct vuart *vuart = vuart_find_by_flags(d, VUART_CONSOLE_INPUT);
+
+    if ( vuart && vuart->emulator->put_rx )
+        return vuart->emulator->put_rx(vuart->vdev, c);
+
+    return  -ENODEV;
+}
+
+bool domain_has_vuart(const struct domain *d)
+{
+    return vuart_find_by_flags(d, VUART_CONSOLE_INPUT);
+}
+
+/*
+ * 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/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);
 
diff --git a/xen/include/xen/serial.h b/xen/include/xen/serial.h
index 8e1844555208..123eee67df35 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 */
+    char compatible[16];        /* Compatible string */
+    char name[16];              /* 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..55828f8498ce
--- /dev/null
+++ b/xen/include/xen/vuart.h
@@ -0,0 +1,115 @@
+/* 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 = BIT(0, U), /* 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;
+    unsigned int 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, const 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, const 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 v7 02/16] xen/8250-uart: update definitions
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
  2025-09-08 21:11 ` [PATCH v7 01/16] emul/vuart: introduce framework for UART emulators dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-09-09 10:05   ` Jan Beulich
  2025-09-08 21:11 ` [PATCH v7 03/16] emul/ns16x50: implement emulator stub dmukhin
                   ` (13 subsequent siblings)
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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 v6:
- used raw bitmasks instead of BIT() for consistency
---
 xen/drivers/char/ns16550.c  | 16 ++++++++--------
 xen/include/xen/8250-uart.h | 36 ++++++++++++++++++++++++++++++++++--
 2 files changed, 42 insertions(+), 10 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..a8a26b64689e 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_DMA      0x08    /* enter DMA mode       */
+#define UART_FCR_RSRVD0   0x10    /* reserved; always 0   */
+#define UART_FCR_RSRVD1   0x20    /* reserved; always 0   */
+#define UART_FCR_RTB0     0x40    /* receiver trigger bit #0 */
+#define UART_FCR_RTB1     0x80    /* 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   */
@@ -98,9 +108,30 @@
 /* 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_OUT1     0x04    /* Output #1 */
+#define UART_MCR_OUT2     0x08    /* Output #2 */
 #define UART_MCR_LOOP     0x10    /* Enable loopback test mode */
+#define UART_MCR_RSRVD0   0x20    /* Reserved #0 */
 #define UART_MCR_TCRTLR   0x40    /* Access TCR/TLR (TI16C752, EFR[4]=1) */
+#define UART_MCR_RSRVD1   0x80    /* 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     0x01    /* Change in CTS */
+#define UART_MSR_DDSR     0x02    /* Change in DSR */
+#define UART_MSR_TERI     0x04    /* Change in RI */
+#define UART_MSR_DDCD     0x08    /* Change in DCD */
+#define UART_MSR_CTS      0x10
+#define UART_MSR_DSR      0x20
+#define UART_MSR_RI       0x40
+#define UART_MSR_DCD      0x80
+#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 v7 03/16] emul/ns16x50: implement emulator stub
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
  2025-09-08 21:11 ` [PATCH v7 01/16] emul/vuart: introduce framework for UART emulators dmukhin
  2025-09-08 21:11 ` [PATCH v7 02/16] xen/8250-uart: update definitions dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-09-10 10:05   ` Mykola Kvach
  2025-09-15 10:16   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 04/16] emul/ns16x50: implement DLL/DLM registers dmukhin
                   ` (12 subsequent siblings)
  15 siblings, 2 replies; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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.

Only one domain, defined via 'vuart=' parameter, will have UART emulator
initially. The command line option is not documented yet because of the plan
to adjust this code for vUART configuration via xl.

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 v6:
- feedback from Mykola
- added temporary 'vuart=' run-time option to enable emulator for certain
  domain for ease of testing
---
 xen/arch/x86/hvm/hvm.c          |  75 +++++++
 xen/common/emul/vuart/Makefile  |   1 +
 xen/common/emul/vuart/ns16x50.c | 364 ++++++++++++++++++++++++++++++++
 3 files changed, 440 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..363c010f8dcc 100644
--- a/xen/arch/x86/hvm/hvm.c
+++ b/xen/arch/x86/hvm/hvm.c
@@ -29,6 +29,7 @@
 #include <xen/trace.h>
 #include <xen/vm_event.h>
 #include <xen/vpci.h>
+#include <xen/vuart.h>
 #include <xen/wait.h>
 #include <xen/warning.h>
 
@@ -107,6 +108,67 @@ static const char __initconst warning_hvm_fep[] =
 static bool __initdata opt_altp2m_enabled;
 boolean_param("altp2m", opt_altp2m_enabled);
 
+/* Enable NS16550 emulator for certain domain only. */
+static int __read_mostly opt_vuart_domid = -1;
+
+#ifdef CONFIG_VUART_NS16X50
+static int __read_mostly opt_vuart_id;
+static int __init cf_check parse_vuart_param(const char *s)
+{
+    if ( !isdigit(*s) )
+        return -EINVAL;
+
+    opt_vuart_domid = simple_strtoul(s, &s, 0);
+
+    if ( *s != ':' )
+        return 0;
+
+    if ( strncmp(s, "com", 3) )
+        return -EINVAL;
+
+    opt_vuart_id = *(s + 3) - '1';
+    if ( opt_vuart_id < 0 || opt_vuart_id > 3 )
+        return -EINVAL;
+
+    return 0;
+}
+custom_param("vuart", parse_vuart_param);
+
+static const struct vuart_info *get_vuart_info(struct domain *d)
+{
+#define PC_UART(n,p,i) { \
+    .name = n, \
+    .compatible = "ns16550", \
+    .base_addr = p, \
+    .size = 8, \
+    .irq = i, \
+}
+    static const struct vuart_info pc_uarts[4] =
+    {
+        PC_UART("com1", 0x3f8, 4),
+        PC_UART("com2", 0x2f8, 3),
+        PC_UART("com3", 0x3fe, 4),
+        PC_UART("com4", 0x2fe, 3),
+    };
+    unsigned i;
+
+    for ( i = 0; i < ARRAY_SIZE(pc_uarts); i++ )
+        if ( i == opt_vuart_id )
+            break;
+
+    if ( i != ARRAY_SIZE(pc_uarts) )
+        return &pc_uarts[i];
+
+    return NULL;
+#undef PC_UART
+}
+#else
+static const struct vuart_info *get_vuart_info(struct domain *d)
+{
+    return NULL;
+}
+#endif /* CONFIG_VUART_NS16X50 */
+
 static int cf_check cpu_callback(
     struct notifier_block *nfb, unsigned long action, void *hcpu)
 {
@@ -689,6 +751,15 @@ int hvm_domain_initialise(struct domain *d,
     if ( rc != 0 )
         goto fail1;
 
+    if ( IS_ENABLED(CONFIG_VUART_NS16X50) && d->domain_id == opt_vuart_domid )
+    {
+        const struct vuart_info *info = get_vuart_info(d);
+
+        rc = vuart_init(d, info);
+        if ( rc )
+            goto out_vioapic_deinit;
+    }
+
     stdvga_init(d);
 
     rtc_init(d);
@@ -712,6 +783,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 +847,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/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..a3bdf9f415ca
--- /dev/null
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -0,0 +1,364 @@
+/* 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, XENLOG_ERR, vdev, fmt, ## args)
+#define ns16x50_warn(vdev, fmt, args...) \
+    ns16x50_log(1, XENLOG_WARNING, vdev, fmt, ## args)
+#define ns16x50_info(vdev, fmt, args...) \
+    ns16x50_log(2, XENLOG_INFO, vdev, fmt, ## args)
+#define ns16x50_debug(vdev, fmt, args...) \
+    ns16x50_log(3, XENLOG_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)
+ */
+#define NS16X50_EMU_REGS_NUM    (NS16X50_REGS_NUM + 2)
+
+/*
+ * Virtual ns16x50 device state.
+ */
+struct vuart_ns16x50 {
+    uint8_t regs[NS16X50_EMU_REGS_NUM]; /* Emulated registers */
+    const struct vuart_info *info;      /* UART description */
+    struct domain *owner;               /* Owner domain */
+    const char *name;                   /* Device name */
+    spinlock_t lock;                    /* Protection */
+    struct xencons_interface cons;      /* Emulated RX/TX FIFOs */
+};
+
+static uint8_t 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)
+{
+    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 = UINT8_MAX;
+    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 = UINT16_MAX;
+    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 = UINT32_MAX;
+        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)
+{
+    const char op = (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 )
+    {
+        printk(XENLOG_ERR "%c io 0x%04x %d: not initialized\n",
+               op, addr, size);
+
+        ASSERT_UNREACHABLE();
+        goto out;
+    }
+
+    vdev = vuart->vdev;
+    ASSERT(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, 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, addr, size);
+        goto out;
+    }
+
+    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",
+                    op, addr, size, dlab, reg, *data);
+        goto out;
+    }
+
+    spin_lock(&vdev->lock);
+
+    if ( dir == IOREQ_WRITE )
+        rc = ns16x50_io_write(vdev, reg, size, data);
+    else
+        rc = ns16x50_io_read(vdev, reg, size, data);
+
+    spin_unlock(&vdev->lock);
+
+    if ( rc == 0 )
+        ns16x50_debug(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32"\n",
+                      op, addr, size, dlab, reg, *data);
+    else
+        ns16x50_err(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32": unsupported access\n",
+                    op, addr, size, dlab, reg, *data);
+
+out:
+    rcu_unlock_domain(d);
+
+    return X86EMUL_OKAY;
+}
+
+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 ( !is_hvm_domain(d) )
+    {
+        ns16x50_err(info, "not an HVM domain\n");
+        return ERR_PTR(-ENOSYS);
+    }
+
+    if ( vuart_find_by_io_range(d, info->base_addr, info->size) )
+    {
+        ns16x50_err(info, "already registered\n");
+        return ERR_PTR(-EBUSY);
+    }
+
+    vdev = xvzalloc(typeof(*vdev));
+    if ( !vdev )
+    {
+        ns16x50_err(info, "failed to allocate memory\n");
+        return ERR_PTR(-ENOMEM);
+    }
+
+    spin_lock_init(&vdev->lock);
+    vdev->name = info->name;
+    vdev->owner = d;
+    vdev->info = info;
+
+    rc = ns16x50_init(vdev);
+    if ( rc )
+    {
+        xvfree(vdev);
+        return ERR_PTR(rc);
+    }
+
+    return vdev;
+}
+
+static void cf_check ns16x50_free(void *arg)
+{
+    if ( arg )
+        ns16x50_deinit(arg);
+
+    xvfree(arg);
+}
+
+#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:
+ */
-- 
2.51.0



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

* [PATCH v7 04/16] emul/ns16x50: implement DLL/DLM registers
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
                   ` (2 preceding siblings ...)
  2025-09-08 21:11 ` [PATCH v7 03/16] emul/ns16x50: implement emulator stub dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-09-10 10:16   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 05/16] emul/ns16x50: implement SCR register dmukhin
                   ` (11 subsequent siblings)
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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 v6:
- added default registers handling for non-DLL/DLM accesses
- used UINT8_MAX
---
 xen/common/emul/vuart/ns16x50.c | 47 +++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index a3bdf9f415ca..da8583a1dc93 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -96,8 +96,22 @@ static uint8_t ns16x50_dlab_get(const struct vuart_ns16x50 *vdev)
 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;
+    else
+    {
+        switch ( reg )
+        {
+        default:
+            rc = -EINVAL;
+            break;
+        }
+    }
+
     return rc;
 }
 
@@ -108,8 +122,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 & UINT8_MAX;
+        vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (val >> 8) & UINT8_MAX;
+        rc = 0;
+    }
+
     return rc;
 }
 
@@ -145,9 +167,22 @@ 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 = UINT8_MAX;
     int rc = 0;
 
+    if ( ns16x50_dlab_get(vdev) && (reg == UART_DLL || reg == UART_DLM) )
+        val = regs[NS16X50_REGS_NUM + reg];
+    else
+    {
+        switch ( reg )
+        {
+        default:
+            rc = -EINVAL;
+            break;
+        }
+    }
+
     *data = val;
 
     return rc;
@@ -162,6 +197,13 @@ static int ns16x50_io_read16(
     uint16_t val = UINT16_MAX;
     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;
@@ -278,12 +320,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 & UINT8_MAX;
+    vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & UINT8_MAX;
+
     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 v7 05/16] emul/ns16x50: implement SCR register
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
                   ` (3 preceding siblings ...)
  2025-09-08 21:11 ` [PATCH v7 04/16] emul/ns16x50: implement DLL/DLM registers dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-09-12  7:23   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 06/16] emul/ns16x50: implement IER/IIR registers dmukhin
                   ` (10 subsequent siblings)
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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 v6:
- default handling of non-DLL/DLM registers moved to the previous 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 da8583a1dc93..5643ef4cc01e 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -106,6 +106,11 @@ static int ns16x50_io_write8(
     {
         switch ( reg )
         {
+        /* NB: Firmware (e.g. OVMF) may rely on SCR presence. */
+        case UART_SCR:
+            regs[UART_SCR] = val;
+            break;
+
         default:
             rc = -EINVAL;
             break;
@@ -177,6 +182,10 @@ static int ns16x50_io_read8(
     {
         switch ( reg )
         {
+        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 v7 06/16] emul/ns16x50: implement IER/IIR registers
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
                   ` (4 preceding siblings ...)
  2025-09-08 21:11 ` [PATCH v7 05/16] emul/ns16x50: implement SCR register dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-09-15  6:00   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 07/16] emul/ns16x50: implement LCR/LSR registers dmukhin
                   ` (9 subsequent siblings)
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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 (IER) 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. vPIC case is implemented (HVM), vIOAPIC
case is stubbed out (for follow on PVH).

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 v6:
- removed asserts for !has_vpic() paths
---
 xen/common/emul/vuart/ns16x50.c | 138 ++++++++++++++++++++++++++++++++
 1 file changed, 138 insertions(+)

diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 5643ef4cc01e..664d799ddaee 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -90,6 +90,124 @@ static uint8_t ns16x50_dlab_get(const struct vuart_ns16x50 *vdev)
     return 0;
 }
 
+static bool cf_check ns16x50_iir_check_lsi(const struct vuart_ns16x50 *vdev)
+{
+    return false;
+}
+
+static bool cf_check ns16x50_iir_check_rda(const struct vuart_ns16x50 *vdev)
+{
+    return false;
+}
+
+static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
+{
+    return false;
+}
+
+static bool cf_check ns16x50_iir_check_msi(const struct vuart_ns16x50 *vdev)
+{
+    return false;
+}
+
+/*
+ * Get the interrupt identity reason.
+ *
+ * IIR is re-calculated once called, because ns16x50 always reports high
+ * priority events first.
+ */
+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) )
+        vector = hvm_isa_irq_assert(d, info->irq, vioapic_get_vector);
+    else if ( has_vioapic(d) )
+        /* TODO */
+    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_isa_irq_deassert(d, info->irq);
+    else if ( has_vioapic(d) )
+        /* TODO */
+    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_deassert(vdev);
+    else
+        ns16x50_irq_assert(vdev);
+
+    ns16x50_debug(vdev, "IRQ#%d IIR 0x%02x %s\n", info->irq, iir,
+                  (iir & UART_IIR_NOINT) ? "deassert" : "assert");
+}
+
 /*
  * Emulate 8-bit write access to ns16x50 register.
  */
@@ -106,6 +224,10 @@ static int ns16x50_io_write8(
     {
         switch ( reg )
         {
+        case UART_IER:
+            regs[UART_IER] = val & UART_IER_MASK;
+            break;
+
         /* NB: Firmware (e.g. OVMF) may rely on SCR presence. */
         case UART_SCR:
             regs[UART_SCR] = val;
@@ -115,6 +237,8 @@ static int ns16x50_io_write8(
             rc = -EINVAL;
             break;
         }
+
+        ns16x50_irq_check(vdev);
     }
 
     return rc;
@@ -182,6 +306,14 @@ static int ns16x50_io_read8(
     {
         switch ( reg )
         {
+        case UART_IER:
+            val = regs[UART_IER];
+            break;
+
+        case UART_IIR: /* RO */
+            val = ns16x50_iir_get(vdev);
+            break;
+
         case UART_SCR:
             val = regs[UART_SCR];
             break;
@@ -190,6 +322,8 @@ static int ns16x50_io_read8(
             rc = -EINVAL;
             break;
         }
+
+        ns16x50_irq_check(vdev);
     }
 
     *data = val;
@@ -342,6 +476,10 @@ static int ns16x50_init(void *arg)
 
     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 v7 07/16] emul/ns16x50: implement LCR/LSR registers
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
                   ` (5 preceding siblings ...)
  2025-09-08 21:11 ` [PATCH v7 06/16] emul/ns16x50: implement IER/IIR registers dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-09-15  6:00   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 08/16] emul/ns16x50: implement MCR/MSR registers dmukhin
                   ` (8 subsequent siblings)
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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.

Add implementation of ns16x50_dlab_get() and ns16x50_iir_check_lsi().

Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v6:
- n/a
---
 xen/common/emul/vuart/ns16x50.c | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 664d799ddaee..0831a576cd9e 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -87,12 +87,12 @@ struct vuart_ns16x50 {
 
 static uint8_t 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 false;
+    return vdev->regs[UART_LSR] & UART_LSR_MASK;
 }
 
 static bool cf_check ns16x50_iir_check_rda(const struct vuart_ns16x50 *vdev)
@@ -228,11 +228,16 @@ static int ns16x50_io_write8(
             regs[UART_IER] = val & UART_IER_MASK;
             break;
 
+        case UART_LCR:
+            regs[UART_LCR] = val;
+            break;
+
         /* NB: Firmware (e.g. OVMF) may rely on SCR presence. */
         case UART_SCR:
             regs[UART_SCR] = val;
             break;
 
+        case UART_LSR: /* RO */
         default:
             rc = -EINVAL;
             break;
@@ -314,6 +319,15 @@ static int ns16x50_io_read8(
             val = ns16x50_iir_get(vdev);
             break;
 
+        case UART_LCR:
+            val = regs[UART_LCR];
+            break;
+
+        case UART_LSR:
+            val = regs[UART_LSR] | UART_LSR_THRE | UART_LSR_TEMT;
+            regs[UART_LSR] = val & ~UART_LSR_MASK;
+            break;
+
         case UART_SCR:
             val = regs[UART_SCR];
             break;
-- 
2.51.0



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

* [PATCH v7 08/16] emul/ns16x50: implement MCR/MSR registers
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
                   ` (6 preceding siblings ...)
  2025-09-08 21:11 ` [PATCH v7 07/16] emul/ns16x50: implement LCR/LSR registers dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-09-15  6:00   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 09/16] emul/ns16x50: implement RBR register dmukhin
                   ` (7 subsequent siblings)
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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/MSR registers emulation to the I/O port handler.

Add implementation of ns16x50_iir_check_msi().

Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v6:
- fixed UART_MSR_TERI handling
---
 xen/common/emul/vuart/ns16x50.c | 62 ++++++++++++++++++++++++++++++++-
 1 file changed, 61 insertions(+), 1 deletion(-)

diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 0831a576cd9e..fdc20124d4c9 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -107,7 +107,7 @@ static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
 
 static bool cf_check ns16x50_iir_check_msi(const struct vuart_ns16x50 *vdev)
 {
-    return false;
+    return vdev->regs[UART_MSR] & UART_MSR_CHANGE;
 }
 
 /*
@@ -232,12 +232,63 @@ 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_RSRVD0 )
+                ns16x50_warn(vdev, "MCR: attempt to set reserved bit: %x\n",
+                             UART_MCR_RSRVD0);
+
+            if ( val & UART_MCR_TCRTLR )
+                ns16x50_warn(vdev, "MCR: not supported: %x\n",
+                             UART_MCR_TCRTLR);
+
+            if ( val & UART_MCR_RSRVD1 )
+                ns16x50_warn(vdev, "MCR: attempt to set reserved bit: %x\n",
+                             UART_MCR_RSRVD1);
+
+            /* 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;
+        }
+
         /* 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:
             rc = -EINVAL;
             break;
@@ -323,11 +374,20 @@ 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;
             regs[UART_LSR] = val & ~UART_LSR_MASK;
             break;
 
+        case UART_MSR:
+            val = regs[UART_MSR];
+            regs[UART_MSR] &= ~UART_MSR_CHANGE;
+            break;
+
         case UART_SCR:
             val = regs[UART_SCR];
             break;
-- 
2.51.0



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

* [PATCH v7 09/16] emul/ns16x50: implement RBR register
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
                   ` (7 preceding siblings ...)
  2025-09-08 21:11 ` [PATCH v7 08/16] emul/ns16x50: implement MCR/MSR registers dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-11-18  6:00   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 10/16] emul/ns16x50: implement THR register dmukhin
                   ` (6 subsequent siblings)
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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 register emulation to the I/O port handlder.

Add RX FIFO management code since RBR depends on RX FIFO.

RX FIFO is 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).

RX FIFO is 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.

Account for DLL == 0: in this case, disable receiver.

Add UART_LSR_DR handling since it depends on RBR register access.

Finally, 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 v6:
- added DLL == 0 case handling as per Mykola's suggestion
---
 xen/common/emul/vuart/ns16x50.c | 134 +++++++++++++++++++++++++++++++-
 1 file changed, 132 insertions(+), 2 deletions(-)

diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index fdc20124d4c9..250411e0a7d8 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -9,6 +9,8 @@
  *     https://www.ti.com/lit/ds/symlink/tl16c550c.pdf
  * - UART w/ 64 byte FIFO:
  *     https://www.ti.com/lit/ds/symlink/tl16c750.pdf
+ * - DesignWare DW_apb_uart Databook, v4.02a:
+ *     https://iccircle.com/static/upload/img20240313113905.pdf
  *
  * Limitations:
  * - Only x86;
@@ -85,6 +87,74 @@ struct vuart_ns16x50 {
     struct xencons_interface cons;      /* Emulated RX/TX FIFOs */
 };
 
+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 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;
+}
+
+/*
+ * Transfer character from RX FIFO and return the RX FIFO status after the
+ * transfer.
+ */
+static int ns16x50_fifo_rx_getchar(struct vuart_ns16x50 *vdev, uint8_t *ptr)
+{
+    struct xencons_interface *cons = &vdev->cons;
+
+    if ( ns16x50_fifo_rx_empty(vdev) )
+        return -ENODATA;
+
+    *ptr = cons->in[MASK_XENCONS_IDX(cons->in_cons, cons->in)];
+    cons->in_cons++;
+
+    return ns16x50_fifo_rx_empty(vdev) ? -ENODATA : 0;
+}
+
+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_is_running(const struct vuart_ns16x50 *vdev)
+{
+    /* DLL set to 0 disables serial communication. */
+    return vdev->regs[NS16X50_REGS_NUM + UART_DLL];
+}
+
 static uint8_t ns16x50_dlab_get(const struct vuart_ns16x50 *vdev)
 {
     return vdev->regs[UART_LCR] & UART_LCR_DLAB ? 1 : 0;
@@ -97,7 +167,7 @@ static bool cf_check ns16x50_iir_check_lsi(const struct vuart_ns16x50 *vdev)
 
 static bool cf_check ns16x50_iir_check_rda(const struct vuart_ns16x50 *vdev)
 {
-    return false;
+    return !ns16x50_fifo_rx_empty(vdev);
 }
 
 static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
@@ -362,6 +432,20 @@ static int ns16x50_io_read8(
     {
         switch ( reg )
         {
+        case UART_RBR:
+            if ( !ns16x50_is_running(vdev) )
+                break;
+
+            /* NB: do not forget to clear overrun condition */
+            regs[UART_LSR] &= ~UART_LSR_OE;
+
+            if ( ns16x50_fifo_rx_getchar(vdev, &val) )
+                regs[UART_LSR] &= ~UART_LSR_DR;
+            else
+                regs[UART_LSR] |= UART_LSR_DR;
+
+            break;
+
         case UART_IER:
             val = regs[UART_IER];
             break;
@@ -611,13 +695,59 @@ static void cf_check ns16x50_free(void *arg)
     xvfree(arg);
 }
 
+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 ( !ns16x50_is_running(vdev) )
+        ns16x50_debug(vdev, "THR/RBR access disabled: DLL == 0\n");
+    else 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
+    {
+        const struct domain *d = vdev->owner;
+
+        /*
+         * 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);
+
+        if ( ns16x50_fifo_rx_putchar(vdev, ch) )
+            regs[UART_LSR] |= UART_LSR_OE;
+
+        regs[UART_LSR] |= UART_LSR_DR;
+
+        /* TODO: check FCR when to fire an interrupt */
+        ns16x50_irq_check(vdev);
+
+        rc = 0;
+    }
+
+    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 v7 10/16] emul/ns16x50: implement THR register
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
                   ` (8 preceding siblings ...)
  2025-09-08 21:11 ` [PATCH v7 09/16] emul/ns16x50: implement RBR register dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-11-18  6:00   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 11/16] emul/ns16x50: implement FCR register (write-only) dmukhin
                   ` (5 subsequent siblings)
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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 THR register emulation to the I/O port handlder.

Add TX FIFO management code since THR depends on TX FIFO.

TX FIFOs is 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).

TX FIFOs is emulated by using xencons_interface which conveniently provides
primitives for buffer management and later can be used for inter-domain
communication similarly to vpl011.

Account for DLL == 0: in this case, disable transmitter.

Add UART_IIR_THR interrupt reason handling since it depends on THR register
access.

Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v6:
- added DLL == 0 case handling as per Mykola's suggestion
- dropped UART_IIR_THR clearing in UART_IIR register emulation in ns16x50_io_write8()
- simplified UART_IIR_THR handling
- updated ns16x50_iir_check_thr()
---
 xen/common/emul/vuart/ns16x50.c | 82 ++++++++++++++++++++++++++++++++-
 1 file changed, 81 insertions(+), 1 deletion(-)

diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 250411e0a7d8..137ce08f4e1d 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -149,6 +149,66 @@ static int ns16x50_fifo_rx_putchar(struct vuart_ns16x50 *vdev, char c)
     return rc;
 }
 
+static bool ns16x50_fifo_tx_full(const struct vuart_ns16x50 *vdev)
+{
+    const struct xencons_interface *cons = &vdev->cons;
+
+    return cons->out_prod - cons->out_cons == ARRAY_SIZE(cons->out);
+}
+
+static void ns16x50_fifo_tx_reset(struct vuart_ns16x50 *vdev)
+{
+    struct xencons_interface *cons = &vdev->cons;
+
+    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;
+    XENCONS_RING_IDX i, n, len = cons->out_prod - cons->out_cons;
+
+    ASSERT(len <= ARRAY_SIZE(cons->out));
+    if ( !len )
+        return;
+
+    i = MASK_XENCONS_IDX(cons->out_cons, cons->out);
+    n = min_t(XENCONS_RING_IDX, len, ARRAY_SIZE(cons->out) - i);
+    if ( n )
+        guest_printk(d, guest_prefix "%.*s", n, &cons->out[i]);
+
+    i = 0;
+    n = len - n;
+    if ( n )
+        guest_printk(d, guest_prefix "%.*s", n, &cons->out[i]);
+
+    cons->out_cons += len;
+}
+
+/*
+ * 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 ( !ns16x50_fifo_tx_full(vdev) )
+    {
+        cons->out[MASK_XENCONS_IDX(cons->out_prod, cons->out)] = ch;
+        cons->out_prod++;
+    }
+
+    if ( ch == '\n' || ch == '\0' || ns16x50_fifo_tx_full(vdev) )
+        ns16x50_fifo_tx_flush(vdev);
+}
+
 static bool ns16x50_is_running(const struct vuart_ns16x50 *vdev)
 {
     /* DLL set to 0 disables serial communication. */
@@ -172,7 +232,7 @@ static bool cf_check ns16x50_iir_check_rda(const struct vuart_ns16x50 *vdev)
 
 static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
 {
-    return false;
+    return !ns16x50_fifo_tx_full(vdev);
 }
 
 static bool cf_check ns16x50_iir_check_msi(const struct vuart_ns16x50 *vdev)
@@ -294,6 +354,22 @@ static int ns16x50_io_write8(
     {
         switch ( reg )
         {
+        case UART_THR:
+            if ( !ns16x50_is_running(vdev) )
+                break;
+
+            if ( regs[UART_MCR] & UART_MCR_LOOP )
+            {
+                if ( ns16x50_fifo_rx_putchar(vdev, val) )
+                    regs[UART_LSR] |= UART_LSR_OE;
+
+                regs[UART_LSR] |= UART_LSR_DR;
+            }
+            else
+                ns16x50_fifo_tx_putchar(vdev, val);
+
+            break;
+
         case UART_IER:
             regs[UART_IER] = val & UART_IER_MASK;
             break;
@@ -646,6 +722,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 v7 11/16] emul/ns16x50: implement FCR register (write-only)
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
                   ` (9 preceding siblings ...)
  2025-09-08 21:11 ` [PATCH v7 10/16] emul/ns16x50: implement THR register dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-11-18  6:00   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 12/16] emul/ns16550: implement dump_state() hook dmukhin
                   ` (4 subsequent siblings)
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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 hook FIFO interrupt moderation to the FIFO management
code for simplicity.

Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v6:
- dropped UART_IIR_THR handling from UART_FCR_CLTX case
---
 xen/common/emul/vuart/ns16x50.c | 27 +++++++++++++++++++++++++++
 1 file changed, 27 insertions(+)

diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 137ce08f4e1d..a92df6923aa5 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -374,6 +374,33 @@ static int ns16x50_io_write8(
             regs[UART_IER] = val & UART_IER_MASK;
             break;
 
+        case UART_FCR: /* WO */
+            if ( val & UART_FCR_RSRVD0 )
+                ns16x50_warn(vdev, "FCR: attempt to set reserved bit: %x\n",
+                             UART_FCR_RSRVD0);
+
+            if ( val & UART_FCR_RSRVD1 )
+                ns16x50_warn(vdev, "FCR: attempt to set reserved bit: %x\n",
+                             UART_FCR_RSRVD1);
+
+            if ( val & UART_FCR_CLRX )
+            {
+                ns16x50_fifo_rx_reset(vdev);
+                regs[UART_LSR] &= ~UART_LSR_DR;
+            }
+
+            if ( val & UART_FCR_CLTX )
+                ns16x50_fifo_tx_reset(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;
+
         case UART_LCR:
             regs[UART_LCR] = val;
             break;
-- 
2.51.0



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

* [PATCH v7 12/16] emul/ns16550: implement dump_state() hook
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
                   ` (10 preceding siblings ...)
  2025-09-08 21:11 ` [PATCH v7 11/16] emul/ns16x50: implement FCR register (write-only) dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-11-18  6:00   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 13/16] emul/ns16x50: add Kconfig options dmukhin
                   ` (3 subsequent siblings)
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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 v6:
- n/a
---
 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 a92df6923aa5..c341f012d005 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -640,6 +640,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%04x 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.
@@ -709,6 +761,9 @@ static int cf_check ns16x50_io_handle(
 
     spin_unlock(&vdev->lock);
 
+    if ( ns16x50_log_level >= 3 )
+        ns16x50_dump_state(vdev);
+
     if ( rc == 0 )
         ns16x50_debug(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32"\n",
                       op, addr, size, dlab, reg, *data);
@@ -844,6 +899,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;
 }
@@ -853,7 +910,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 v7 13/16] emul/ns16x50: add Kconfig options
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
                   ` (11 preceding siblings ...)
  2025-09-08 21:11 ` [PATCH v7 12/16] emul/ns16550: implement dump_state() hook dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-11-18  6:00   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 14/16] x86/domain: enable per-domain I/O port bitmaps dmukhin
                   ` (2 subsequent siblings)
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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 initial Kconfig options configure NS16550-capable emulator.

Signed-off-by: Denis Mukhin <dmukhin@ford.com>
---
Changes since v6:
- new patch
---
 xen/common/emul/vuart/Kconfig | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/xen/common/emul/vuart/Kconfig b/xen/common/emul/vuart/Kconfig
index ce1b976b7da7..9a49a6528b5a 100644
--- a/xen/common/emul/vuart/Kconfig
+++ b/xen/common/emul/vuart/Kconfig
@@ -3,4 +3,23 @@ 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 NS16550-compatible UART emulation.
+
+	  Only one legacy PC COM port is emulated for domain with a certain ID
+	  (set via 'vuart-domid=' command line setting).
+
+	  This is strictly for testing purposes (such as early HVM guest console),
+	  and not appropriate for use in production.
+
+config VUART_NS16X50_DEBUG
+	bool "Development: NS16550-compatible UART Emulator Debugging"
+	depends on VUART_NS16X50 && DEBUG
+	help
+	  Enable development debugging.
+
 endmenu
-- 
2.51.0



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

* [PATCH v7 14/16] x86/domain: enable per-domain I/O port bitmaps
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
                   ` (12 preceding siblings ...)
  2025-09-08 21:11 ` [PATCH v7 13/16] emul/ns16x50: add Kconfig options dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-11-18  6:00   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 15/16] xen/domain: allocate d->irq_caps before arch-specific initialization dmukhin
  2025-09-08 21:11 ` [PATCH v7 16/16] emul/ns16x50: implement IRQ emulation via vIOAPIC dmukhin
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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 v6:
- n/a
---
 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 d7aed7d92c15..85a8475e126c 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 363c010f8dcc..1fc16a22e157 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);
@@ -266,15 +259,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;
@@ -706,19 +690,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);
@@ -745,6 +722,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 c341f012d005..ea34c3ae598a 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -783,9 +783,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..0x%04lx]: 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 & UINT8_MAX;
     vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & UINT8_MAX;
-- 
2.51.0



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

* [PATCH v7 15/16] xen/domain: allocate d->irq_caps before arch-specific initialization
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
                   ` (13 preceding siblings ...)
  2025-09-08 21:11 ` [PATCH v7 14/16] x86/domain: enable per-domain I/O port bitmaps dmukhin
@ 2025-09-08 21:11 ` dmukhin
  2025-11-18  6:00   ` Mykola Kvach
  2025-09-08 21:11 ` [PATCH v7 16/16] emul/ns16x50: implement IRQ emulation via vIOAPIC dmukhin
  15 siblings, 1 reply; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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 v6:
- n/a
---
 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 775c33928585..edf76b02e1a1 100644
--- a/xen/common/domain.c
+++ b/xen/common/domain.c
@@ -952,6 +952,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;
@@ -961,8 +966,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 ea34c3ae598a..6bd58ba5540b 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -797,6 +797,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 & UINT8_MAX;
     vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & UINT8_MAX;
-- 
2.51.0



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

* [PATCH v7 16/16] emul/ns16x50: implement IRQ emulation via vIOAPIC
  2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
                   ` (14 preceding siblings ...)
  2025-09-08 21:11 ` [PATCH v7 15/16] xen/domain: allocate d->irq_caps before arch-specific initialization dmukhin
@ 2025-09-08 21:11 ` dmukhin
  15 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-08 21:11 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_pirq_emuirq() 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 v6:
- n/a
---
 xen/arch/x86/hvm/vioapic.c        | 10 ++++++++++
 xen/arch/x86/include/asm/irq.h    |  6 ++++++
 xen/common/emul/vuart/ns16x50.c   | 28 ++++++++++++++++++++++++++--
 xen/drivers/passthrough/x86/hvm.c | 11 ++++++++++-
 4 files changed, 52 insertions(+), 3 deletions(-)

diff --git a/xen/arch/x86/hvm/vioapic.c b/xen/arch/x86/hvm/vioapic.c
index 7c725f9e471f..6314874b64f7 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 = is_domain_emuirq_claimed(currd, gsi);
+    write_unlock(&currd->event_lock);
+    if ( ret )
+        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..bdbe700274e9 100644
--- a/xen/arch/x86/include/asm/irq.h
+++ b/xen/arch/x86/include/asm/irq.h
@@ -168,6 +168,11 @@ void free_domain_pirqs(struct domain *d);
 int map_domain_emuirq_pirq(struct domain *d, int pirq, int emuirq);
 int unmap_domain_pirq_emuirq(struct domain *d, int pirq);
 
+#define domain_emuirq_claim(d, irq)     map_domain_emuirq_pirq(d, irq, IRQ_EMU)
+#define domain_emuirq_unclaim(d, irq)   unmap_domain_pirq_emuirq(d, irq)
+#define is_domain_emuirq_claimed(d, irq) \
+    (domain_pirq_to_emuirq(d, irq) != IRQ_UNBOUND)
+
 /* Evacuate interrupts assigned to CPUs not present in the CPU online map. */
 void fixup_irqs(void);
 void fixup_eoi(void);
@@ -221,6 +226,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/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
index 6bd58ba5540b..081d2639aa7a 100644
--- a/xen/common/emul/vuart/ns16x50.c
+++ b/xen/common/emul/vuart/ns16x50.c
@@ -299,7 +299,7 @@ static void ns16x50_irq_assert(const struct vuart_ns16x50 *vdev)
     if ( has_vpic(d) )
         vector = hvm_isa_irq_assert(d, info->irq, vioapic_get_vector);
     else if ( has_vioapic(d) )
-        /* TODO */
+        vector = hvm_ioapic_assert(d, info->irq, false);
     else
         ASSERT_UNREACHABLE();
 
@@ -314,7 +314,7 @@ static void ns16x50_irq_deassert(const struct vuart_ns16x50 *vdev)
     if ( has_vpic(d) )
         hvm_isa_irq_deassert(d, info->irq);
     else if ( has_vioapic(d) )
-        /* TODO */
+        hvm_ioapic_deassert(d, info->irq);
     else
         ASSERT_UNREACHABLE();
 
@@ -806,6 +806,17 @@ static int ns16x50_init(void *arg)
         return rc;
     }
 
+    /* Claim virtual IRQ */
+    write_lock(&d->event_lock);
+    rc = domain_emuirq_claim(d, info->irq);
+    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 & UINT8_MAX;
     vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & UINT8_MAX;
@@ -822,9 +833,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 = domain_emuirq_unclaim(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..20641194561f 100644
--- a/xen/drivers/passthrough/x86/hvm.c
+++ b/xen/drivers/passthrough/x86/hvm.c
@@ -922,7 +922,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);
+    struct pirq *pirq;
+
+    /* Check if GSI is claimed by one of the virtual devices. */
+    if ( is_domain_emuirq_claimed(d, gsi) )
+    {
+        hvm_gsi_deassert(d, gsi);
+        return;
+    }
+
+    pirq = pirq_info(d, gsi);
 
     /* Check if GSI is actually mapped. */
     if ( !pirq_dpci(pirq) )
-- 
2.51.0



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

* Re: [PATCH v7 02/16] xen/8250-uart: update definitions
  2025-09-08 21:11 ` [PATCH v7 02/16] xen/8250-uart: update definitions dmukhin
@ 2025-09-09 10:05   ` Jan Beulich
  2025-09-09 19:42     ` dmukhin
  2025-09-10  8:39     ` Mykola Kvach
  0 siblings, 2 replies; 46+ messages in thread
From: Jan Beulich @ 2025-09-09 10:05 UTC (permalink / raw)
  To: dmukhin
  Cc: andrew.cooper3, anthony.perard, julien, michal.orzel, roger.pau,
	sstabellini, dmukhin, xen-devel

On 08.09.2025 23:11, dmukhin@xen.org wrote:
> --- 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)

Here, aiui, ..._MASK covers all known bits. No #define-s for reserved
ones.

> @@ -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_DMA      0x08    /* enter DMA mode       */

Question is whether we can actually use the source you indicate as
reference. TL16C550C may already be too different from what a "standard"
16550 is (where admittedly it also looks unclear what "standard" would be,
as I'm unaware of a "canonical" spec).

The source I'm looking at says something entirely different. Maybe we're
better off simply omitting this #define?

> +#define UART_FCR_RSRVD0   0x10    /* reserved; always 0   */
> +#define UART_FCR_RSRVD1   0x20    /* reserved; always 0   */
> +#define UART_FCR_RTB0     0x40    /* receiver trigger bit #0 */
> +#define UART_FCR_RTB1     0x80    /* receiver trigger bit #1 */
> +#define UART_FCR_TRG_MASK (UART_FCR_RTB0 | UART_FCR_RTB1)

Continuing from the top comment - here, with the TRG infix, the scope is
clear, too.

> @@ -98,9 +108,30 @@
>  /* 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_OUT1     0x04    /* Output #1 */
> +#define UART_MCR_OUT2     0x08    /* Output #2 */
>  #define UART_MCR_LOOP     0x10    /* Enable loopback test mode */
> +#define UART_MCR_RSRVD0   0x20    /* Reserved #0 */
>  #define UART_MCR_TCRTLR   0x40    /* Access TCR/TLR (TI16C752, EFR[4]=1) */
> +#define UART_MCR_RSRVD1   0x80    /* Reserved #1 */
> +#define UART_MCR_MASK \
> +    (UART_MCR_DTR | UART_MCR_RTS | \
> +     UART_MCR_OUT1 | UART_MCR_OUT2 | \
> +     UART_MCR_LOOP | UART_MCR_TCRTLR)

Here it's again all non-reserved bits. Yet why do we need #define-s for
the two reserved ones here? (Same question for FCR, even if there's no
UART_FCR_MASK.)

> +/* Modem Status Register */
> +#define UART_MSR_DCTS     0x01    /* Change in CTS */
> +#define UART_MSR_DDSR     0x02    /* Change in DSR */
> +#define UART_MSR_TERI     0x04    /* Change in RI */
> +#define UART_MSR_DDCD     0x08    /* Change in DCD */
> +#define UART_MSR_CTS      0x10
> +#define UART_MSR_DSR      0x20
> +#define UART_MSR_RI       0x40
> +#define UART_MSR_DCD      0x80
> +#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)

Here it's properly two subsets.

> @@ -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)

But what's the deal here? Why would only two of the bits be covered?

Jan


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

* Re: [PATCH v7 02/16] xen/8250-uart: update definitions
  2025-09-09 10:05   ` Jan Beulich
@ 2025-09-09 19:42     ` dmukhin
  2025-09-10  8:39     ` Mykola Kvach
  1 sibling, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-09 19:42 UTC (permalink / raw)
  To: Jan Beulich
  Cc: andrew.cooper3, anthony.perard, julien, michal.orzel, roger.pau,
	sstabellini, dmukhin, xen-devel

On Tue, Sep 09, 2025 at 12:05:41PM +0200, Jan Beulich wrote:
> On 08.09.2025 23:11, dmukhin@xen.org wrote:
> > --- 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)
> 
> Here, aiui, ..._MASK covers all known bits. No #define-s for reserved
> ones.
> 
> > @@ -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_DMA      0x08    /* enter DMA mode       */
> 
> Question is whether we can actually use the source you indicate as
> reference. TL16C550C may already be too different from what a "standard"
> 16550 is (where admittedly it also looks unclear what "standard" would be,
> as I'm unaware of a "canonical" spec).

Yeah, I am not sure there's a "standard" spec for NS16550.

> 
> The source I'm looking at says something entirely different. Maybe we're
> better off simply omitting this #define?

All TL16Cx50 I have mentioned, including Synopsys uart databook, say
FCR's "DMA mode select" is Bit 3.

And Linux'es driver defines it as 0x08 (include/uapi/linux/serial_reg.h)

> 
> > +#define UART_FCR_RSRVD0   0x10    /* reserved; always 0   */
> > +#define UART_FCR_RSRVD1   0x20    /* reserved; always 0   */
> > +#define UART_FCR_RTB0     0x40    /* receiver trigger bit #0 */
> > +#define UART_FCR_RTB1     0x80    /* receiver trigger bit #1 */
> > +#define UART_FCR_TRG_MASK (UART_FCR_RTB0 | UART_FCR_RTB1)
> 
> Continuing from the top comment - here, with the TRG infix, the scope is
> clear, too.
> 
> > @@ -98,9 +108,30 @@
> >  /* 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_OUT1     0x04    /* Output #1 */
> > +#define UART_MCR_OUT2     0x08    /* Output #2 */
> >  #define UART_MCR_LOOP     0x10    /* Enable loopback test mode */
> > +#define UART_MCR_RSRVD0   0x20    /* Reserved #0 */
> >  #define UART_MCR_TCRTLR   0x40    /* Access TCR/TLR (TI16C752, EFR[4]=1) */
> > +#define UART_MCR_RSRVD1   0x80    /* Reserved #1 */
> > +#define UART_MCR_MASK \
> > +    (UART_MCR_DTR | UART_MCR_RTS | \
> > +     UART_MCR_OUT1 | UART_MCR_OUT2 | \
> > +     UART_MCR_LOOP | UART_MCR_TCRTLR)
> 
> Here it's again all non-reserved bits. Yet why do we need #define-s for
> the two reserved ones here? (Same question for FCR, even if there's no
> UART_FCR_MASK.)
> 
> > +/* Modem Status Register */
> > +#define UART_MSR_DCTS     0x01    /* Change in CTS */
> > +#define UART_MSR_DDSR     0x02    /* Change in DSR */
> > +#define UART_MSR_TERI     0x04    /* Change in RI */
> > +#define UART_MSR_DDCD     0x08    /* Change in DCD */
> > +#define UART_MSR_CTS      0x10
> > +#define UART_MSR_DSR      0x20
> > +#define UART_MSR_RI       0x40
> > +#define UART_MSR_DCD      0x80
> > +#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)
> 
> Here it's properly two subsets.
> 
> > @@ -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)
> 
> But what's the deal here? Why would only two of the bits be covered?
> 
> Jan


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

* Re: [PATCH v7 01/16] emul/vuart: introduce framework for UART emulators
  2025-09-08 21:11 ` [PATCH v7 01/16] emul/vuart: introduce framework for UART emulators dmukhin
@ 2025-09-10  7:57   ` Mykola Kvach
  2025-09-13 18:09     ` dmukhin
  0 siblings, 1 reply; 46+ messages in thread
From: Mykola Kvach @ 2025-09-10  7:57 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Denis,

Thank you for addressing the issues from the previous version of the
patch series,

Everything looks good to me, with just a few questions:

On Tue, Sep 9, 2025 at 12:56 AM <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 v6:
> - addresses Mykola's feedback
> - some renaming (vuart_find_by_flags())
> - added extra checks to put_rx and dump_state
> - fixed vuart_init() error path
> - simplified some checks during vUART state 'search'
> ---
>  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  | 165 +++++++++++++++++++++++++++++++++
>  xen/common/keyhandler.c        |   3 +
>  xen/drivers/char/console.c     |   6 +-
>  xen/include/xen/sched.h        |   4 +
>  xen/include/xen/serial.h       |   3 +
>  xen/include/xen/vuart.h        | 115 +++++++++++++++++++++++
>  xen/include/xen/xen.lds.h      |  10 ++
>  17 files changed, 326 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 0c7d0f5d46e1..8c8462565050 100644
> --- a/xen/common/Makefile
> +++ b/xen/common/Makefile
> @@ -12,6 +12,7 @@ obj-$(CONFIG_DEVICE_TREE_PARSE) += device-tree/
>  obj-$(CONFIG_IOREQ_SERVER) += dm.o
>  obj-y += domain.o
>  obj-y += domid.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..ba89d608aeb2
> --- /dev/null
> +++ b/xen/common/emul/vuart/vuart.c
> @@ -0,0 +1,165 @@
> +/* 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(const struct domain *d, const char *compat)
> +{
> +    const struct vuart_emulator *emulator;
> +
> +    for_each_emulator(emulator)
> +        if ( emulator->compatible &&
> +             !strncmp(compat, emulator->compatible,
> +                      strlen(emulator->compatible)) )
> +            return emulator;
> +
> +    return NULL;
> +}
> +
> +const static struct vuart *
> +vuart_find_by_flags(const struct domain *d, unsigned int flags)
> +{
> +    const struct vuart *vuart = d->console.vuart;
> +
> +    if ( vuart && (vuart->flags & flags) )
> +        return vuart;
> +
> +    return NULL;
> +}
> +
> +struct vuart *vuart_find_by_io_range(struct domain *d, unsigned long addr,
> +                                     unsigned long size)
> +{
> +    struct vuart *vuart = d->console.vuart;
> +
> +    if ( vuart &&
> +         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, const struct vuart_info *info)
> +{
> +    const struct vuart_emulator *emulator;
> +    struct vuart *vuart;
> +    int rc;
> +
> +    if ( d->console.vuart )
> +        return -EBUSY;
> +
> +    emulator = vuart_match_by_compatible(d, info->compatible);
> +    if ( !emulator )
> +        return -ENODEV;
> +
> +    vuart = xzalloc(typeof(*vuart));
> +    if ( !vuart )
> +        return -ENOMEM;
> +
> +    vuart->info = xvzalloc(typeof(*vuart->info));
> +    if ( !vuart->info )
> +    {
> +        rc = -ENOMEM;
> +        goto err_out1;
> +    }
> +    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_out2;
> +    }
> +
> +    vuart->emulator = emulator;
> +    vuart->owner = d;
> +    vuart->flags |= VUART_CONSOLE_INPUT;
> +
> +    d->console.input_allowed = true;

I'm not a specialist in the area of consoles, but I'm wondering:
Does the input_allowed flag serve the same purpose as
VUART_CONSOLE_INPUT? If so, do we need both, or
could one be removed to simplify the code?

At least here they are set in sync.

> +    d->console.vuart = vuart;
> +
> +    return 0;
> +
> + err_out2:
> +    xvfree(vuart->info);
> + err_out1:
> +    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->vdev);
> +        xvfree(vuart->info);
> +    }
> +    XVFREE(d->console.vuart);
> +}
> +
> +/*
> + * Print emulated UART state on the console.
> + *
> + * Must be called under rcu_lock_domain().
> + */
> +void vuart_dump_state(const struct domain *d)
> +{
> +    struct vuart *vuart = d->console.vuart;
> +
> +    if ( vuart && vuart->emulator->dump_state )
> +        vuart->emulator->dump_state(vuart->vdev);
> +}
> +
> +/*
> + * Put character to the first emulated UART's FIFO with the physical console
> + * forwarding enabled.
> + *
> + * Must be called under rcu_lock_domain().
> + */
> +int vuart_put_rx(struct domain *d, char c)
> +{
> +    const struct vuart *vuart = vuart_find_by_flags(d, VUART_CONSOLE_INPUT);

The call to vuart_find_by_flags() with VUART_CONSOLE_INPUT in
vuart_put_rx() appears unnecessary. Every vUART console is always
initialized with VUART_CONSOLE_INPUT, so even if multiple consoles
exist, the search will always return the first console. It would be
simpler and clearer to use d->console.vuart directly.

Consider updating the function to remove the flag-based search and add a
short comment explaining why checking the flag isn’t needed. This will
help avoid confusion for future maintainers. Alternatively, we could
pass flags to the init functions instead of hardcoding
VUART_CONSOLE_INPUT for every console.

> +
> +    if ( vuart && vuart->emulator->put_rx )
> +        return vuart->emulator->put_rx(vuart->vdev, c);
> +
> +    return  -ENODEV;
> +}
> +
> +bool domain_has_vuart(const struct domain *d)
> +{
> +    return vuart_find_by_flags(d, VUART_CONSOLE_INPUT);

The same issue applies here: domain_has_vuart() calls
vuart_find_by_flags() with VUART_CONSOLE_INPUT, but every
vUART console is always initialized with this flag

> +}
> +
> +/*
> + * 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/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);
>
> diff --git a/xen/include/xen/serial.h b/xen/include/xen/serial.h
> index 8e1844555208..123eee67df35 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 */
> +    char compatible[16];        /* Compatible string */
> +    char name[16];              /* 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..55828f8498ce
> --- /dev/null
> +++ b/xen/include/xen/vuart.h
> @@ -0,0 +1,115 @@
> +/* 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 = BIT(0, U), /* 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;
> +    unsigned int 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, const 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, const 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
>
>

Best regards,
Mykola


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

* Re: [PATCH v7 02/16] xen/8250-uart: update definitions
  2025-09-09 10:05   ` Jan Beulich
  2025-09-09 19:42     ` dmukhin
@ 2025-09-10  8:39     ` Mykola Kvach
  2025-09-13 17:50       ` dmukhin
  1 sibling, 1 reply; 46+ messages in thread
From: Mykola Kvach @ 2025-09-10  8:39 UTC (permalink / raw)
  To: Jan Beulich
  Cc: dmukhin, andrew.cooper3, anthony.perard, julien, michal.orzel,
	roger.pau, sstabellini, dmukhin, xen-devel

Hi Denis,

Thank you for the updates from the previous version of patch series,

On Tue, Sep 9, 2025 at 1:47 PM Jan Beulich <jbeulich@suse.com> wrote:
>
> On 08.09.2025 23:11, dmukhin@xen.org wrote:
> > --- 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)
>
> Here, aiui, ..._MASK covers all known bits. No #define-s for reserved
> ones.

I think we can follow the Linux kernel style here [1]. For example:

#define UART_IER_ALL_INTR (UART_IER_MSI | \
        UART_IER_RLSI | \
        UART_IER_THRI | \
        UART_IER_RDI)

This way we avoid using the *_MASK suffix, which could be confusing,
and clearly indicate that these are all valid interrupt bits.

>
> > @@ -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_DMA      0x08    /* enter DMA mode       */
>
> Question is whether we can actually use the source you indicate as
> reference. TL16C550C may already be too different from what a "standard"
> 16550 is (where admittedly it also looks unclear what "standard" would be,
> as I'm unaware of a "canonical" spec).
>
> The source I'm looking at says something entirely different. Maybe we're
> better off simply omitting this #define?
>
> > +#define UART_FCR_RSRVD0   0x10    /* reserved; always 0   */
> > +#define UART_FCR_RSRVD1   0x20    /* reserved; always 0   */
> > +#define UART_FCR_RTB0     0x40    /* receiver trigger bit #0 */
> > +#define UART_FCR_RTB1     0x80    /* receiver trigger bit #1 */
> > +#define UART_FCR_TRG_MASK (UART_FCR_RTB0 | UART_FCR_RTB1)
>
> Continuing from the top comment - here, with the TRG infix, the scope is
> clear, too.
>
> > @@ -98,9 +108,30 @@
> >  /* 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_OUT1     0x04    /* Output #1 */
> > +#define UART_MCR_OUT2     0x08    /* Output #2 */
> >  #define UART_MCR_LOOP     0x10    /* Enable loopback test mode */
> > +#define UART_MCR_RSRVD0   0x20    /* Reserved #0 */
> >  #define UART_MCR_TCRTLR   0x40    /* Access TCR/TLR (TI16C752, EFR[4]=1) */
> > +#define UART_MCR_RSRVD1   0x80    /* Reserved #1 */
> > +#define UART_MCR_MASK \
> > +    (UART_MCR_DTR | UART_MCR_RTS | \
> > +     UART_MCR_OUT1 | UART_MCR_OUT2 | \
> > +     UART_MCR_LOOP | UART_MCR_TCRTLR)
>
> Here it's again all non-reserved bits. Yet why do we need #define-s for
> the two reserved ones here? (Same question for FCR, even if there's no
> UART_FCR_MASK.)
>
> > +/* Modem Status Register */
> > +#define UART_MSR_DCTS     0x01    /* Change in CTS */
> > +#define UART_MSR_DDSR     0x02    /* Change in DSR */
> > +#define UART_MSR_TERI     0x04    /* Change in RI */
> > +#define UART_MSR_DDCD     0x08    /* Change in DCD */
> > +#define UART_MSR_CTS      0x10
> > +#define UART_MSR_DSR      0x20
> > +#define UART_MSR_RI       0x40
> > +#define UART_MSR_DCD      0x80
> > +#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)
>
> Here it's properly two subsets.
>
> > @@ -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)
>
> But what's the deal here? Why would only two of the bits be covered?
>
> Jan
>

[1] https://elixir.bootlin.com/linux/v6.16.5/source/include/linux/serial.h#L15

Best regards,
Mykola


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

* Re: [PATCH v7 03/16] emul/ns16x50: implement emulator stub
  2025-09-08 21:11 ` [PATCH v7 03/16] emul/ns16x50: implement emulator stub dmukhin
@ 2025-09-10 10:05   ` Mykola Kvach
  2025-09-13 17:29     ` dmukhin
  2025-11-14  5:19     ` dmukhin
  2025-09-15 10:16   ` Mykola Kvach
  1 sibling, 2 replies; 46+ messages in thread
From: Mykola Kvach @ 2025-09-10 10:05 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Denis,

I appreciate you addressing the comments from the earlier version
of the patch series.

On Tue, Sep 9, 2025 at 12:12 AM <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.
>
> Only one domain, defined via 'vuart=' parameter, will have UART emulator
> initially. The command line option is not documented yet because of the plan
> to adjust this code for vUART configuration via xl.

Since the command line option is not yet documented, it would be
helpful to include the expected format of the 'vuart=' parameter in
the commit message. This will make it easier for reviewers and future
readers to understand how to use the option.

>
> 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 v6:
> - feedback from Mykola
> - added temporary 'vuart=' run-time option to enable emulator for certain
>   domain for ease of testing
> ---
>  xen/arch/x86/hvm/hvm.c          |  75 +++++++
>  xen/common/emul/vuart/Makefile  |   1 +
>  xen/common/emul/vuart/ns16x50.c | 364 ++++++++++++++++++++++++++++++++
>  3 files changed, 440 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..363c010f8dcc 100644
> --- a/xen/arch/x86/hvm/hvm.c
> +++ b/xen/arch/x86/hvm/hvm.c
> @@ -29,6 +29,7 @@
>  #include <xen/trace.h>
>  #include <xen/vm_event.h>
>  #include <xen/vpci.h>
> +#include <xen/vuart.h>
>  #include <xen/wait.h>
>  #include <xen/warning.h>
>
> @@ -107,6 +108,67 @@ static const char __initconst warning_hvm_fep[] =
>  static bool __initdata opt_altp2m_enabled;
>  boolean_param("altp2m", opt_altp2m_enabled);
>
> +/* Enable NS16550 emulator for certain domain only. */
> +static int __read_mostly opt_vuart_domid = -1;

Should opt_vuart_domid be initialized to DOMID_INVALID instead of -1?
Using the standard DOMID_INVALID constant would make the intent clearer
and avoid potential confusion with valid domain IDs.
---
Should the variable type be domid_t or at least unsigned?

> +
> +#ifdef CONFIG_VUART_NS16X50
> +static int __read_mostly opt_vuart_id;
> +static int __init cf_check parse_vuart_param(const char *s)
> +{
> +    if ( !isdigit(*s) )
> +        return -EINVAL;
> +
> +    opt_vuart_domid = simple_strtoul(s, &s, 0);

Should we check the resulting value against DOMID_MASK to ensure it
is a valid domain ID?

> +
> +    if ( *s != ':' )
> +        return 0;

It seems that if the COM ID is not provided on the command line, the
default value will come from the static variable, which is 0 (treated
as COM1). Is this intended behavior?

If this is by design, it would be helpful to add a comment explaining
why we allow a valid domain ID with a default COM ID. Otherwise, maybe
opt_vuart_id should be set to an invalid value or opt_vuart_domid
reset here to avoid ambiguity.

> +
> +    if ( strncmp(s, "com", 3) )
> +        return -EINVAL;
> +
> +    opt_vuart_id = *(s + 3) - '1';
> +    if ( opt_vuart_id < 0 || opt_vuart_id > 3 )

Would it be better to define the pc_uarts array outside the function
and then use ARRAY_SIZE(pc_uarts) here for the bounds check? This
would make the code more maintainable in case the number of UARTs
changes in the future.
---
Do we really need the search function below at all? Instead of
storing an opt_vuart_id, we could store a pointer to the chosen
vUART directly here and eliminate the search, simplifying the code.

> +        return -EINVAL;
> +
> +    return 0;
> +}
> +custom_param("vuart", parse_vuart_param);
> +
> +static const struct vuart_info *get_vuart_info(struct domain *d)
> +{
> +#define PC_UART(n,p,i) { \
> +    .name = n, \
> +    .compatible = "ns16550", \
> +    .base_addr = p, \
> +    .size = 8, \
> +    .irq = i, \
> +}
> +    static const struct vuart_info pc_uarts[4] =
> +    {
> +        PC_UART("com1", 0x3f8, 4),
> +        PC_UART("com2", 0x2f8, 3),
> +        PC_UART("com3", 0x3fe, 4),
> +        PC_UART("com4", 0x2fe, 3),
> +    };
> +    unsigned i;
> +
> +    for ( i = 0; i < ARRAY_SIZE(pc_uarts); i++ )
> +        if ( i == opt_vuart_id )
> +            break;

Instead of breaking from the loop, why not return the pointer directly
when a match is found? For example:

for ( i = 0; i < ARRAY_SIZE(pc_uarts); i++ )
    if ( i == opt_vuart_id )
        return &pc_uarts[i];

return NULL;

This eliminates the need for a separate break and makes the code
clearer.
---

Actually, we can simplify this further: since the array is indexed by
opt_vuart_id, we can directly check the bounds and return the entry:

if ( opt_vuart_id > -1 && opt_vuart_id < ARRAY_SIZE(pc_uarts) )
    return &pc_uarts[opt_vuart_id];

return NULL;

If opt_vuart_id were defined as unsigned, the lower-bound check could be
dropped entirely, leaving only the upper-bound check, which would make
the code even cleaner.

> +
> +    if ( i != ARRAY_SIZE(pc_uarts) )
> +        return &pc_uarts[i];
> +
> +    return NULL;
> +#undef PC_UART
> +}
> +#else
> +static const struct vuart_info *get_vuart_info(struct domain *d)

inline ?

> +{
> +    return NULL;
> +}
> +#endif /* CONFIG_VUART_NS16X50 */

Should all of the code above be made common? If in the future other
architectures also use this vUART mechanism, it would be better to
make it generic from the start. In that case, vuart_info would
probably need a "compatible" property to support different hardware
types.

Then the search procedure through the vuart array would make
much more sense.

> +
>  static int cf_check cpu_callback(
>      struct notifier_block *nfb, unsigned long action, void *hcpu)
>  {
> @@ -689,6 +751,15 @@ int hvm_domain_initialise(struct domain *d,
>      if ( rc != 0 )
>          goto fail1;
>
> +    if ( IS_ENABLED(CONFIG_VUART_NS16X50) && d->domain_id == opt_vuart_domid )
> +    {
> +        const struct vuart_info *info = get_vuart_info(d);
> +
> +        rc = vuart_init(d, info);
> +        if ( rc )
> +            goto out_vioapic_deinit;
> +    }
> +
>      stdvga_init(d);
>
>      rtc_init(d);
> @@ -712,6 +783,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 +847,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/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..a3bdf9f415ca
> --- /dev/null
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -0,0 +1,364 @@
> +/* 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, XENLOG_ERR, vdev, fmt, ## args)
> +#define ns16x50_warn(vdev, fmt, args...) \
> +    ns16x50_log(1, XENLOG_WARNING, vdev, fmt, ## args)
> +#define ns16x50_info(vdev, fmt, args...) \
> +    ns16x50_log(2, XENLOG_INFO, vdev, fmt, ## args)
> +#define ns16x50_debug(vdev, fmt, args...) \
> +    ns16x50_log(3, XENLOG_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)
> + */
> +#define NS16X50_EMU_REGS_NUM    (NS16X50_REGS_NUM + 2)
> +
> +/*
> + * Virtual ns16x50 device state.
> + */
> +struct vuart_ns16x50 {
> +    uint8_t regs[NS16X50_EMU_REGS_NUM]; /* Emulated registers */
> +    const struct vuart_info *info;      /* UART description */
> +    struct domain *owner;               /* Owner domain */
> +    const char *name;                   /* Device name */
> +    spinlock_t lock;                    /* Protection */
> +    struct xencons_interface cons;      /* Emulated RX/TX FIFOs */
> +};
> +
> +static uint8_t 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)
> +{
> +    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 = UINT8_MAX;
> +    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 = UINT16_MAX;
> +    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 = UINT32_MAX;
> +        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)
> +{
> +    const char op = (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 )
> +    {
> +        printk(XENLOG_ERR "%c io 0x%04x %d: not initialized\n",
> +               op, addr, size);
> +
> +        ASSERT_UNREACHABLE();
> +        goto out;
> +    }
> +
> +    vdev = vuart->vdev;
> +    ASSERT(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, 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, addr, size);
> +        goto out;
> +    }
> +
> +    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",
> +                    op, addr, size, dlab, reg, *data);
> +        goto out;
> +    }
> +
> +    spin_lock(&vdev->lock);
> +
> +    if ( dir == IOREQ_WRITE )
> +        rc = ns16x50_io_write(vdev, reg, size, data);
> +    else
> +        rc = ns16x50_io_read(vdev, reg, size, data);
> +
> +    spin_unlock(&vdev->lock);
> +
> +    if ( rc == 0 )
> +        ns16x50_debug(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32"\n",
> +                      op, addr, size, dlab, reg, *data);
> +    else
> +        ns16x50_err(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32": unsupported access\n",
> +                    op, addr, size, dlab, reg, *data);
> +
> +out:
> +    rcu_unlock_domain(d);
> +
> +    return X86EMUL_OKAY;
> +}
> +
> +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 ( !is_hvm_domain(d) )
> +    {
> +        ns16x50_err(info, "not an HVM domain\n");
> +        return ERR_PTR(-ENOSYS);
> +    }
> +
> +    if ( vuart_find_by_io_range(d, info->base_addr, info->size) )
> +    {
> +        ns16x50_err(info, "already registered\n");
> +        return ERR_PTR(-EBUSY);
> +    }
> +
> +    vdev = xvzalloc(typeof(*vdev));
> +    if ( !vdev )
> +    {
> +        ns16x50_err(info, "failed to allocate memory\n");
> +        return ERR_PTR(-ENOMEM);
> +    }
> +
> +    spin_lock_init(&vdev->lock);
> +    vdev->name = info->name;
> +    vdev->owner = d;
> +    vdev->info = info;
> +
> +    rc = ns16x50_init(vdev);
> +    if ( rc )
> +    {
> +        xvfree(vdev);
> +        return ERR_PTR(rc);
> +    }
> +
> +    return vdev;
> +}
> +
> +static void cf_check ns16x50_free(void *arg)
> +{
> +    if ( arg )
> +        ns16x50_deinit(arg);
> +
> +    xvfree(arg);
> +}
> +
> +#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:
> + */
> --
> 2.51.0
>
>

Best regards,
Mykola


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

* Re: [PATCH v7 04/16] emul/ns16x50: implement DLL/DLM registers
  2025-09-08 21:11 ` [PATCH v7 04/16] emul/ns16x50: implement DLL/DLM registers dmukhin
@ 2025-09-10 10:16   ` Mykola Kvach
  0 siblings, 0 replies; 46+ messages in thread
From: Mykola Kvach @ 2025-09-10 10:16 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Denis,

I appreciate you addressing the comments from the earlier version
of the patch series.

On Tue, Sep 9, 2025 at 12:12 AM <dmukhin@xen.org> wrote:
>
> 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 v6:
> - added default registers handling for non-DLL/DLM accesses
> - used UINT8_MAX
> ---
>  xen/common/emul/vuart/ns16x50.c | 47 +++++++++++++++++++++++++++++++++
>  1 file changed, 47 insertions(+)
>
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index a3bdf9f415ca..da8583a1dc93 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -96,8 +96,22 @@ static uint8_t ns16x50_dlab_get(const struct vuart_ns16x50 *vdev)
>  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;
> +    else
> +    {
> +        switch ( reg )
> +        {
> +        default:
> +            rc = -EINVAL;
> +            break;
> +        }
> +    }
> +
>      return rc;
>  }
>
> @@ -108,8 +122,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 & UINT8_MAX;
> +        vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (val >> 8) & UINT8_MAX;
> +        rc = 0;
> +    }
> +
>      return rc;
>  }
>
> @@ -145,9 +167,22 @@ 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 = UINT8_MAX;
>      int rc = 0;
>
> +    if ( ns16x50_dlab_get(vdev) && (reg == UART_DLL || reg == UART_DLM) )
> +        val = regs[NS16X50_REGS_NUM + reg];
> +    else
> +    {
> +        switch ( reg )
> +        {
> +        default:
> +            rc = -EINVAL;
> +            break;
> +        }
> +    }
> +
>      *data = val;
>
>      return rc;
> @@ -162,6 +197,13 @@ static int ns16x50_io_read16(
>      uint16_t val = UINT16_MAX;
>      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;
> @@ -278,12 +320,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 & UINT8_MAX;
> +    vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & UINT8_MAX;
> +
>      register_portio_handler(d, info->base_addr, info->size, ns16x50_io_handle);
>
>      return 0;
> --
> 2.51.0
>
>

Reviewed-by: Mykola Kvach <mykola_kvach@epam.com>

Best regards,
Mykola


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

* Re: [PATCH v7 05/16] emul/ns16x50: implement SCR register
  2025-09-08 21:11 ` [PATCH v7 05/16] emul/ns16x50: implement SCR register dmukhin
@ 2025-09-12  7:23   ` Mykola Kvach
  0 siblings, 0 replies; 46+ messages in thread
From: Mykola Kvach @ 2025-09-12  7:23 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Denis,

Thank you for the patch.

On Tue, Sep 9, 2025 at 12:12 AM <dmukhin@xen.org> wrote:
>
> 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 v6:
> - default handling of non-DLL/DLM registers moved to the previous 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 da8583a1dc93..5643ef4cc01e 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -106,6 +106,11 @@ static int ns16x50_io_write8(
>      {
>          switch ( reg )
>          {
> +        /* NB: Firmware (e.g. OVMF) may rely on SCR presence. */
> +        case UART_SCR:
> +            regs[UART_SCR] = val;
> +            break;
> +
>          default:
>              rc = -EINVAL;
>              break;
> @@ -177,6 +182,10 @@ static int ns16x50_io_read8(
>      {
>          switch ( reg )
>          {
> +        case UART_SCR:
> +            val = regs[UART_SCR];
> +            break;
> +
>          default:
>              rc = -EINVAL;
>              break;
> --
> 2.51.0
>
>

Reviewed-by: Mykola Kvach <mykola_kvach@epam.com>

Best regards,
Mykola


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

* Re: [PATCH v7 03/16] emul/ns16x50: implement emulator stub
  2025-09-10 10:05   ` Mykola Kvach
@ 2025-09-13 17:29     ` dmukhin
  2025-11-14  5:19     ` dmukhin
  1 sibling, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-13 17:29 UTC (permalink / raw)
  To: Mykola Kvach
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

On Wed, Sep 10, 2025 at 01:05:13PM +0300, Mykola Kvach wrote:
[..]
> >
> > +/* Enable NS16550 emulator for certain domain only. */
> > +static int __read_mostly opt_vuart_domid = -1;
> 
> Should opt_vuart_domid be initialized to DOMID_INVALID instead of -1?
> Using the standard DOMID_INVALID constant would make the intent clearer
> and avoid potential confusion with valid domain IDs.
> ---
> Should the variable type be domid_t or at least unsigned?

Yes, absolutely; thanks.

> 
> > +
> > +#ifdef CONFIG_VUART_NS16X50
> > +static int __read_mostly opt_vuart_id;
> > +static int __init cf_check parse_vuart_param(const char *s)
> > +{
> > +    if ( !isdigit(*s) )
> > +        return -EINVAL;
> > +
> > +    opt_vuart_domid = simple_strtoul(s, &s, 0);
> 
> Should we check the resulting value against DOMID_MASK to ensure it
> is a valid domain ID?

Good point, will hook the check.

> 
> > +
> > +    if ( *s != ':' )
> > +        return 0;
> 
> It seems that if the COM ID is not provided on the command line, the
> default value will come from the static variable, which is 0 (treated
> as COM1). Is this intended behavior?

Correct, the idea was using COM1 by default.

> 
> If this is by design, it would be helpful to add a comment explaining
> why we allow a valid domain ID with a default COM ID. Otherwise, maybe
> opt_vuart_id should be set to an invalid value or opt_vuart_domid
> reset here to avoid ambiguity.

I will add a comment.

> 
> > +
> > +    if ( strncmp(s, "com", 3) )
> > +        return -EINVAL;
> > +
> > +    opt_vuart_id = *(s + 3) - '1';
> > +    if ( opt_vuart_id < 0 || opt_vuart_id > 3 )
> 
> Would it be better to define the pc_uarts array outside the function
> and then use ARRAY_SIZE(pc_uarts) here for the bounds check? This
> would make the code more maintainable in case the number of UARTs
> changes in the future.

Makes sense.
Let me see how that can be improved.

> ---
> Do we really need the search function below at all? Instead of
> storing an opt_vuart_id, we could store a pointer to the chosen
> vUART directly here and eliminate the search, simplifying the code.
> 
> > +        return -EINVAL;
> > +
> > +    return 0;
> > +}
> > +custom_param("vuart", parse_vuart_param);
> > +
> > +static const struct vuart_info *get_vuart_info(struct domain *d)
> > +{
> > +#define PC_UART(n,p,i) { \
> > +    .name = n, \
> > +    .compatible = "ns16550", \
> > +    .base_addr = p, \
> > +    .size = 8, \
> > +    .irq = i, \
> > +}
> > +    static const struct vuart_info pc_uarts[4] =
> > +    {
> > +        PC_UART("com1", 0x3f8, 4),
> > +        PC_UART("com2", 0x2f8, 3),
> > +        PC_UART("com3", 0x3fe, 4),
> > +        PC_UART("com4", 0x2fe, 3),
> > +    };
> > +    unsigned i;
> > +
> > +    for ( i = 0; i < ARRAY_SIZE(pc_uarts); i++ )
> > +        if ( i == opt_vuart_id )
> > +            break;
> 
> Instead of breaking from the loop, why not return the pointer directly
> when a match is found? For example:
> 
> for ( i = 0; i < ARRAY_SIZE(pc_uarts); i++ )
>     if ( i == opt_vuart_id )
>         return &pc_uarts[i];
> 
> return NULL;
> 
> This eliminates the need for a separate break and makes the code
> clearer.

Yep, will do.

> ---
> 
> Actually, we can simplify this further: since the array is indexed by
> opt_vuart_id, we can directly check the bounds and return the entry:
> 
> if ( opt_vuart_id > -1 && opt_vuart_id < ARRAY_SIZE(pc_uarts) )
>     return &pc_uarts[opt_vuart_id];
> 
> return NULL;
> 
> If opt_vuart_id were defined as unsigned, the lower-bound check could be
> dropped entirely, leaving only the upper-bound check, which would make
> the code even cleaner.

Ack.

> 
> > +
> > +    if ( i != ARRAY_SIZE(pc_uarts) )
> > +        return &pc_uarts[i];
> > +
> > +    return NULL;
> > +#undef PC_UART
> > +}
> > +#else
> > +static const struct vuart_info *get_vuart_info(struct domain *d)
> 
> inline ?

Yes, thanks.

> 
> > +{
> > +    return NULL;
> > +}
> > +#endif /* CONFIG_VUART_NS16X50 */
> 
> Should all of the code above be made common? If in the future other
> architectures also use this vUART mechanism, it would be better to
> make it generic from the start. In that case, vuart_info would
> probably need a "compatible" property to support different hardware
> types.
> 
> Then the search procedure through the vuart array would make
> much more sense.

My plan is have DT-binding for dom0less and xl config for legacy
configuration. Let me see, looks like dom0 vUART configuration
should be supplied via command line anyway in non-dom0less case.


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

* Re: [PATCH v7 02/16] xen/8250-uart: update definitions
  2025-09-10  8:39     ` Mykola Kvach
@ 2025-09-13 17:50       ` dmukhin
  0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-13 17:50 UTC (permalink / raw)
  To: Mykola Kvach
  Cc: Jan Beulich, andrew.cooper3, anthony.perard, julien, michal.orzel,
	roger.pau, sstabellini, dmukhin, xen-devel

On Wed, Sep 10, 2025 at 11:39:07AM +0300, Mykola Kvach wrote:
> > > +#define UART_IER_MASK \
> > > +    (UART_IER_ERDAI | UART_IER_ETHREI | UART_IER_ELSI | UART_IER_EMSI)
> >
> > Here, aiui, ..._MASK covers all known bits. No #define-s for reserved
> > ones.
> 
> I think we can follow the Linux kernel style here [1]. For example:
> 
> #define UART_IER_ALL_INTR (UART_IER_MSI | \
>         UART_IER_RLSI | \
>         UART_IER_THRI | \
>         UART_IER_RDI)
> 
> This way we avoid using the *_MASK suffix, which could be confusing,
> and clearly indicate that these are all valid interrupt bits.

Agreed, will update.


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

* Re: [PATCH v7 01/16] emul/vuart: introduce framework for UART emulators
  2025-09-10  7:57   ` Mykola Kvach
@ 2025-09-13 18:09     ` dmukhin
  0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-09-13 18:09 UTC (permalink / raw)
  To: Mykola Kvach
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

On Wed, Sep 10, 2025 at 10:57:14AM +0300, Mykola Kvach wrote:
> > +int vuart_init(struct domain *d, const struct vuart_info *info)
> > +{
> > +    const struct vuart_emulator *emulator;
> > +    struct vuart *vuart;
> > +    int rc;
> > +
> > +    if ( d->console.vuart )
> > +        return -EBUSY;
> > +
> > +    emulator = vuart_match_by_compatible(d, info->compatible);
> > +    if ( !emulator )
> > +        return -ENODEV;
> > +
> > +    vuart = xzalloc(typeof(*vuart));
> > +    if ( !vuart )
> > +        return -ENOMEM;
> > +
> > +    vuart->info = xvzalloc(typeof(*vuart->info));
> > +    if ( !vuart->info )
> > +    {
> > +        rc = -ENOMEM;
> > +        goto err_out1;
> > +    }
> > +    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_out2;
> > +    }
> > +
> > +    vuart->emulator = emulator;
> > +    vuart->owner = d;
> > +    vuart->flags |= VUART_CONSOLE_INPUT;
> > +
> > +    d->console.input_allowed = true;
> 
> I'm not a specialist in the area of consoles, but I'm wondering:
> Does the input_allowed flag serve the same purpose as
> VUART_CONSOLE_INPUT? If so, do we need both, or
> could one be removed to simplify the code?

At this point these two flags must be in sync.

`console.input_allowed` is a permission for a domain to take the physical
console input.

`VUART_CONSOLE_INPUT` in `vuart->flags` is a permission for vUART to take
the physical console input.

pvshim does not have vUART, but can have console focus.

And not every vUART can be configured to have a console input (e.g. Arm's
"hwdom vuart").

> > +/*
> > + * Put character to the first emulated UART's FIFO with the physical console
> > + * forwarding enabled.
> > + *
> > + * Must be called under rcu_lock_domain().
> > + */
> > +int vuart_put_rx(struct domain *d, char c)
> > +{
> > +    const struct vuart *vuart = vuart_find_by_flags(d, VUART_CONSOLE_INPUT);
> 
> The call to vuart_find_by_flags() with VUART_CONSOLE_INPUT in
> vuart_put_rx() appears unnecessary. Every vUART console is always
> initialized with VUART_CONSOLE_INPUT, so even if multiple consoles
> exist, the search will always return the first console. It would be
> simpler and clearer to use d->console.vuart directly.

There's no certain order in which multiple vUARTs are initialized, so there
should be something which scans the vUART list and selects the vUART with
console input permission. Follow on Arm's change will add multiple vUARTs
and I decided to generalize logic in this patch, so that there will be 
minimal update in vuart_find_by_flags() only.

> 
> Consider updating the function to remove the flag-based search and add a
> short comment explaining why checking the flag isn’t needed. This will
> help avoid confusion for future maintainers. Alternatively, we could
> pass flags to the init functions instead of hardcoding
> VUART_CONSOLE_INPUT for every console.

That will work, thanks for suggestion.


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

* Re: [PATCH v7 06/16] emul/ns16x50: implement IER/IIR registers
  2025-09-08 21:11 ` [PATCH v7 06/16] emul/ns16x50: implement IER/IIR registers dmukhin
@ 2025-09-15  6:00   ` Mykola Kvach
  0 siblings, 0 replies; 46+ messages in thread
From: Mykola Kvach @ 2025-09-15  6:00 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Denis,

Thank you for the patch.

On Tue, Sep 9, 2025 at 12:12 AM <dmukhin@xen.org> wrote:
>
> From: Denis Mukhin <dmukhin@ford.com>
>
> Add interrupt enable register emulation (IER) 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. vPIC case is implemented (HVM), vIOAPIC
> case is stubbed out (for follow on PVH).
>
> 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 v6:
> - removed asserts for !has_vpic() paths
> ---
>  xen/common/emul/vuart/ns16x50.c | 138 ++++++++++++++++++++++++++++++++
>  1 file changed, 138 insertions(+)
>
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index 5643ef4cc01e..664d799ddaee 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -90,6 +90,124 @@ static uint8_t ns16x50_dlab_get(const struct vuart_ns16x50 *vdev)
>      return 0;
>  }
>
> +static bool cf_check ns16x50_iir_check_lsi(const struct vuart_ns16x50 *vdev)
> +{
> +    return false;
> +}
> +
> +static bool cf_check ns16x50_iir_check_rda(const struct vuart_ns16x50 *vdev)
> +{
> +    return false;
> +}
> +
> +static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
> +{
> +    return false;
> +}
> +
> +static bool cf_check ns16x50_iir_check_msi(const struct vuart_ns16x50 *vdev)
> +{
> +    return false;
> +}
> +
> +/*
> + * Get the interrupt identity reason.
> + *
> + * IIR is re-calculated once called, because ns16x50 always reports high
> + * priority events first.
> + */
> +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 },

According to the spec (PC16550D and others also state this), if the
source of the IRQ is Transmitter Holding Register Empty, then reading
from IIR clears this interrupt.

So, I think it is not safe to call ns16x50_irq_check for every I/O.
According to the documentation, reset conditions for interrupts are:

    Reads:
        UART_RBR
        UART_IIR (only for the above case)
        UART_LSR
        UART_MSR

    Writes:
        UART_THR
        UART_IER

Of course, it is also necessary to think about how to handle the setting
of bits in IIR properly.

> +        [3] = { ns16x50_iir_check_msi, UART_IER_EMSI,   UART_IIR_MSI },

Are you going to implement support for the Timeout Interrupt bit in
FIFO mode (PC16550D)?

    Bit 3: In the 16450 Mode this bit is 0. In the FIFO mode this
    bit is set along with bit 2 when a timeout interrupt is pending.
?

> +    };
> +    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;
> +

Do we need this extra line?

> +    }
> +    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) )
> +        vector = hvm_isa_irq_assert(d, info->irq, vioapic_get_vector);
> +    else if ( has_vioapic(d) )
> +        /* TODO */
> +    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_isa_irq_deassert(d, info->irq);
> +    else if ( has_vioapic(d) )
> +        /* TODO */
> +    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_deassert(vdev);
> +    else
> +        ns16x50_irq_assert(vdev);
> +
> +    ns16x50_debug(vdev, "IRQ#%d IIR 0x%02x %s\n", info->irq, iir,
> +                  (iir & UART_IIR_NOINT) ? "deassert" : "assert");
> +}
> +
>  /*
>   * Emulate 8-bit write access to ns16x50 register.
>   */
> @@ -106,6 +224,10 @@ static int ns16x50_io_write8(
>      {
>          switch ( reg )
>          {
> +        case UART_IER:
> +            regs[UART_IER] = val & UART_IER_MASK;
> +            break;
> +
>          /* NB: Firmware (e.g. OVMF) may rely on SCR presence. */
>          case UART_SCR:
>              regs[UART_SCR] = val;
> @@ -115,6 +237,8 @@ static int ns16x50_io_write8(
>              rc = -EINVAL;
>              break;
>          }
> +
> +        ns16x50_irq_check(vdev);
>      }
>
>      return rc;
> @@ -182,6 +306,14 @@ static int ns16x50_io_read8(
>      {
>          switch ( reg )
>          {
> +        case UART_IER:
> +            val = regs[UART_IER];
> +            break;
> +
> +        case UART_IIR: /* RO */
> +            val = ns16x50_iir_get(vdev);
> +            break;
> +
>          case UART_SCR:
>              val = regs[UART_SCR];
>              break;
> @@ -190,6 +322,8 @@ static int ns16x50_io_read8(
>              rc = -EINVAL;
>              break;
>          }
> +
> +        ns16x50_irq_check(vdev);
>      }
>
>      *data = val;
> @@ -342,6 +476,10 @@ static int ns16x50_init(void *arg)
>
>      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
>
>

Best regards,
Mykola


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

* Re: [PATCH v7 07/16] emul/ns16x50: implement LCR/LSR registers
  2025-09-08 21:11 ` [PATCH v7 07/16] emul/ns16x50: implement LCR/LSR registers dmukhin
@ 2025-09-15  6:00   ` Mykola Kvach
  0 siblings, 0 replies; 46+ messages in thread
From: Mykola Kvach @ 2025-09-15  6:00 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Denis,

Thank you for the patch.

On Tue, Sep 9, 2025 at 1:16 AM <dmukhin@xen.org> wrote:
>
> From: Denis Mukhin <dmukhin@ford.com>
>
> Add LCR/LSR registers implementation to the I/O port handler.
>
> Add implementation of ns16x50_dlab_get() and ns16x50_iir_check_lsi().
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v6:
> - n/a
> ---
>  xen/common/emul/vuart/ns16x50.c | 18 ++++++++++++++++--
>  1 file changed, 16 insertions(+), 2 deletions(-)
>
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index 664d799ddaee..0831a576cd9e 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -87,12 +87,12 @@ struct vuart_ns16x50 {
>
>  static uint8_t 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 false;
> +    return vdev->regs[UART_LSR] & UART_LSR_MASK;
>  }
>
>  static bool cf_check ns16x50_iir_check_rda(const struct vuart_ns16x50 *vdev)
> @@ -228,11 +228,16 @@ static int ns16x50_io_write8(
>              regs[UART_IER] = val & UART_IER_MASK;
>              break;
>
> +        case UART_LCR:
> +            regs[UART_LCR] = val;

I understand that this register is mostly used to control the hardware
behavior, but I think we should pay attention to Bit 7 in this register:

    Bit 7: Divisor Latch Access Bit (DLAB). It must be set high (logic 1)
    to access the Divisor Latches of the Baud Generator during a read or
    write operation. It must be set low (logic 0) to access the Receiver
    Buffer, the Transmitter Holding Register, or the Interrupt Enable
    Register.

I know that you are checking it before accessing DLAB registers, but what
about accesses to RBR, THR, and IER?

> +            break;
> +
>          /* NB: Firmware (e.g. OVMF) may rely on SCR presence. */
>          case UART_SCR:
>              regs[UART_SCR] = val;
>              break;
>
> +        case UART_LSR: /* RO */

Is there a reason we cannot simply leave this unchanged?

>          default:
>              rc = -EINVAL;
>              break;
> @@ -314,6 +319,15 @@ static int ns16x50_io_read8(
>              val = ns16x50_iir_get(vdev);
>              break;
>
> +        case UART_LCR:
> +            val = regs[UART_LCR];
> +            break;
> +
> +        case UART_LSR:
> +            val = regs[UART_LSR] | UART_LSR_THRE | UART_LSR_TEMT;

Why are UART_LSR_THRE and UART_LSR_TEMT set unconditionally?
What about UART_LSR_DR?

> +            regs[UART_LSR] = val & ~UART_LSR_MASK;

Maybe it would be good to mention why we are not resetting
UART_LSR_PE and UART_LSR_FE on read.

> +            break;
> +
>          case UART_SCR:
>              val = regs[UART_SCR];
>              break;
> --
> 2.51.0
>
>

Best regards,
Mykola


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

* Re: [PATCH v7 08/16] emul/ns16x50: implement MCR/MSR registers
  2025-09-08 21:11 ` [PATCH v7 08/16] emul/ns16x50: implement MCR/MSR registers dmukhin
@ 2025-09-15  6:00   ` Mykola Kvach
  2025-09-15 14:49     ` Jan Beulich
  0 siblings, 1 reply; 46+ messages in thread
From: Mykola Kvach @ 2025-09-15  6:00 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Denis,

Thank you for the patch.

On Tue, Sep 9, 2025 at 12:12 AM <dmukhin@xen.org> wrote:
>
> From: Denis Mukhin <dmukhin@ford.com>
>
> Add MCR/MSR registers emulation to the I/O port handler.
>
> Add implementation of ns16x50_iir_check_msi().
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v6:
> - fixed UART_MSR_TERI handling
> ---
>  xen/common/emul/vuart/ns16x50.c | 62 ++++++++++++++++++++++++++++++++-
>  1 file changed, 61 insertions(+), 1 deletion(-)
>
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index 0831a576cd9e..fdc20124d4c9 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -107,7 +107,7 @@ static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
>
>  static bool cf_check ns16x50_iir_check_msi(const struct vuart_ns16x50 *vdev)
>  {
> -    return false;
> +    return vdev->regs[UART_MSR] & UART_MSR_CHANGE;
>  }
>
>  /*
> @@ -232,12 +232,63 @@ static int ns16x50_io_write8(
>              regs[UART_LCR] = val;
>              break;
>
> +        case UART_MCR: {

Probably the opening brace should be moved to the next line.
See CODING_STYLE:

Braces ('{' and '}') are usually placed on a line of their own, except
for:

- the do/while loop
- the opening brace in definitions of enum, struct, and union
- the opening brace in initializers
- compound literals

> +            uint8_t msr_curr, msr_next, msr_delta;
> +
> +            msr_curr = regs[UART_MSR];
> +            msr_next = 0;
> +            msr_delta = 0;
> +
> +            if ( val & UART_MCR_RSRVD0 )
> +                ns16x50_warn(vdev, "MCR: attempt to set reserved bit: %x\n",
> +                             UART_MCR_RSRVD0);
> +
> +            if ( val & UART_MCR_TCRTLR )
> +                ns16x50_warn(vdev, "MCR: not supported: %x\n",
> +                             UART_MCR_TCRTLR);
> +
> +            if ( val & UART_MCR_RSRVD1 )
> +                ns16x50_warn(vdev, "MCR: attempt to set reserved bit: %x\n",
> +                             UART_MCR_RSRVD1);
> +
> +            /* 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;

It looks like this could be simplified to something like:
    msr_delta = ((regs[UART_MSR] ^ msr_next) >> 4);

> +
> +            regs[UART_MCR] = val & UART_MCR_MASK;
> +            regs[UART_MSR] = msr_next | msr_delta;

Does this description for the first four bits of MSR:
    "... input to the chip has changed state since the last time it was
    read by the CPU"

mean that we shouldn't modify bits that are already set but have not yet
been read by the CPU?

> +
> +            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:
>              rc = -EINVAL;
>              break;
> @@ -323,11 +374,20 @@ 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;
>              regs[UART_LSR] = val & ~UART_LSR_MASK;
>              break;
>
> +        case UART_MSR:
> +            val = regs[UART_MSR];
> +            regs[UART_MSR] &= ~UART_MSR_CHANGE;
> +            break;
> +
>          case UART_SCR:
>              val = regs[UART_SCR];
>              break;
> --
> 2.51.0
>
>

Best regards,
Mykola


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

* Re: [PATCH v7 03/16] emul/ns16x50: implement emulator stub
  2025-09-08 21:11 ` [PATCH v7 03/16] emul/ns16x50: implement emulator stub dmukhin
  2025-09-10 10:05   ` Mykola Kvach
@ 2025-09-15 10:16   ` Mykola Kvach
  2025-11-14  5:28     ` dmukhin
  1 sibling, 1 reply; 46+ messages in thread
From: Mykola Kvach @ 2025-09-15 10:16 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

On Tue, Sep 9, 2025 at 12:12 AM <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.
>
> Only one domain, defined via 'vuart=' parameter, will have UART emulator
> initially. The command line option is not documented yet because of the plan
> to adjust this code for vUART configuration via xl.
>
> 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 v6:
> - feedback from Mykola
> - added temporary 'vuart=' run-time option to enable emulator for certain
>   domain for ease of testing
> ---
>  xen/arch/x86/hvm/hvm.c          |  75 +++++++
>  xen/common/emul/vuart/Makefile  |   1 +
>  xen/common/emul/vuart/ns16x50.c | 364 ++++++++++++++++++++++++++++++++
>  3 files changed, 440 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..363c010f8dcc 100644
> --- a/xen/arch/x86/hvm/hvm.c
> +++ b/xen/arch/x86/hvm/hvm.c
> @@ -29,6 +29,7 @@
>  #include <xen/trace.h>
>  #include <xen/vm_event.h>
>  #include <xen/vpci.h>
> +#include <xen/vuart.h>
>  #include <xen/wait.h>
>  #include <xen/warning.h>
>
> @@ -107,6 +108,67 @@ static const char __initconst warning_hvm_fep[] =
>  static bool __initdata opt_altp2m_enabled;
>  boolean_param("altp2m", opt_altp2m_enabled);
>
> +/* Enable NS16550 emulator for certain domain only. */
> +static int __read_mostly opt_vuart_domid = -1;
> +
> +#ifdef CONFIG_VUART_NS16X50
> +static int __read_mostly opt_vuart_id;
> +static int __init cf_check parse_vuart_param(const char *s)
> +{
> +    if ( !isdigit(*s) )
> +        return -EINVAL;
> +
> +    opt_vuart_domid = simple_strtoul(s, &s, 0);
> +
> +    if ( *s != ':' )
> +        return 0;
> +
> +    if ( strncmp(s, "com", 3) )
> +        return -EINVAL;
> +
> +    opt_vuart_id = *(s + 3) - '1';
> +    if ( opt_vuart_id < 0 || opt_vuart_id > 3 )
> +        return -EINVAL;
> +
> +    return 0;
> +}
> +custom_param("vuart", parse_vuart_param);
> +
> +static const struct vuart_info *get_vuart_info(struct domain *d)
> +{
> +#define PC_UART(n,p,i) { \
> +    .name = n, \
> +    .compatible = "ns16550", \
> +    .base_addr = p, \
> +    .size = 8, \
> +    .irq = i, \
> +}
> +    static const struct vuart_info pc_uarts[4] =
> +    {
> +        PC_UART("com1", 0x3f8, 4),
> +        PC_UART("com2", 0x2f8, 3),
> +        PC_UART("com3", 0x3fe, 4),
> +        PC_UART("com4", 0x2fe, 3),
> +    };
> +    unsigned i;
> +
> +    for ( i = 0; i < ARRAY_SIZE(pc_uarts); i++ )
> +        if ( i == opt_vuart_id )
> +            break;
> +
> +    if ( i != ARRAY_SIZE(pc_uarts) )
> +        return &pc_uarts[i];
> +
> +    return NULL;
> +#undef PC_UART
> +}
> +#else
> +static const struct vuart_info *get_vuart_info(struct domain *d)
> +{
> +    return NULL;
> +}
> +#endif /* CONFIG_VUART_NS16X50 */
> +
>  static int cf_check cpu_callback(
>      struct notifier_block *nfb, unsigned long action, void *hcpu)
>  {
> @@ -689,6 +751,15 @@ int hvm_domain_initialise(struct domain *d,
>      if ( rc != 0 )
>          goto fail1;
>
> +    if ( IS_ENABLED(CONFIG_VUART_NS16X50) && d->domain_id == opt_vuart_domid )
> +    {
> +        const struct vuart_info *info = get_vuart_info(d);
> +
> +        rc = vuart_init(d, info);
> +        if ( rc )
> +            goto out_vioapic_deinit;
> +    }
> +
>      stdvga_init(d);
>
>      rtc_init(d);
> @@ -712,6 +783,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 +847,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/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..a3bdf9f415ca
> --- /dev/null
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -0,0 +1,364 @@
> +/* 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, XENLOG_ERR, vdev, fmt, ## args)
> +#define ns16x50_warn(vdev, fmt, args...) \
> +    ns16x50_log(1, XENLOG_WARNING, vdev, fmt, ## args)
> +#define ns16x50_info(vdev, fmt, args...) \
> +    ns16x50_log(2, XENLOG_INFO, vdev, fmt, ## args)
> +#define ns16x50_debug(vdev, fmt, args...) \
> +    ns16x50_log(3, XENLOG_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)
> + */
> +#define NS16X50_EMU_REGS_NUM    (NS16X50_REGS_NUM + 2)
> +
> +/*
> + * Virtual ns16x50 device state.
> + */
> +struct vuart_ns16x50 {
> +    uint8_t regs[NS16X50_EMU_REGS_NUM]; /* Emulated registers */

I think it would be better to add an init procedure for the registers.
At least not all bits in all registers should be initialized to zero.
Or will this be handled in some way during I/O reads?

> +    const struct vuart_info *info;      /* UART description */
> +    struct domain *owner;               /* Owner domain */
> +    const char *name;                   /* Device name */
> +    spinlock_t lock;                    /* Protection */
> +    struct xencons_interface cons;      /* Emulated RX/TX FIFOs */
> +};
> +
> +static uint8_t 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)
> +{
> +    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 = UINT8_MAX;
> +    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 = UINT16_MAX;
> +    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 = UINT32_MAX;
> +        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)
> +{
> +    const char op = (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 )
> +    {
> +        printk(XENLOG_ERR "%c io 0x%04x %d: not initialized\n",
> +               op, addr, size);
> +
> +        ASSERT_UNREACHABLE();
> +        goto out;
> +    }
> +
> +    vdev = vuart->vdev;
> +    ASSERT(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, 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, addr, size);
> +        goto out;
> +    }
> +
> +    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",
> +                    op, addr, size, dlab, reg, *data);
> +        goto out;
> +    }
> +
> +    spin_lock(&vdev->lock);
> +
> +    if ( dir == IOREQ_WRITE )
> +        rc = ns16x50_io_write(vdev, reg, size, data);
> +    else
> +        rc = ns16x50_io_read(vdev, reg, size, data);
> +
> +    spin_unlock(&vdev->lock);
> +
> +    if ( rc == 0 )
> +        ns16x50_debug(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32"\n",
> +                      op, addr, size, dlab, reg, *data);
> +    else
> +        ns16x50_err(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32": unsupported access\n",
> +                    op, addr, size, dlab, reg, *data);
> +
> +out:
> +    rcu_unlock_domain(d);
> +
> +    return X86EMUL_OKAY;
> +}
> +
> +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 ( !is_hvm_domain(d) )
> +    {
> +        ns16x50_err(info, "not an HVM domain\n");
> +        return ERR_PTR(-ENOSYS);
> +    }
> +
> +    if ( vuart_find_by_io_range(d, info->base_addr, info->size) )
> +    {
> +        ns16x50_err(info, "already registered\n");
> +        return ERR_PTR(-EBUSY);
> +    }
> +
> +    vdev = xvzalloc(typeof(*vdev));
> +    if ( !vdev )
> +    {
> +        ns16x50_err(info, "failed to allocate memory\n");
> +        return ERR_PTR(-ENOMEM);
> +    }
> +
> +    spin_lock_init(&vdev->lock);
> +    vdev->name = info->name;
> +    vdev->owner = d;
> +    vdev->info = info;
> +
> +    rc = ns16x50_init(vdev);
> +    if ( rc )
> +    {
> +        xvfree(vdev);
> +        return ERR_PTR(rc);
> +    }
> +
> +    return vdev;
> +}
> +
> +static void cf_check ns16x50_free(void *arg)
> +{
> +    if ( arg )
> +        ns16x50_deinit(arg);
> +
> +    xvfree(arg);
> +}
> +
> +#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:
> + */
> --
> 2.51.0
>
>

Best regards,
Mykola


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

* Re: [PATCH v7 08/16] emul/ns16x50: implement MCR/MSR registers
  2025-09-15  6:00   ` Mykola Kvach
@ 2025-09-15 14:49     ` Jan Beulich
  2025-09-16  8:00       ` Mykola Kvach
  0 siblings, 1 reply; 46+ messages in thread
From: Jan Beulich @ 2025-09-15 14:49 UTC (permalink / raw)
  To: Mykola Kvach
  Cc: xen-devel, andrew.cooper3, anthony.perard, julien, michal.orzel,
	roger.pau, sstabellini, dmukhin, dmukhin

On 15.09.2025 08:00, Mykola Kvach wrote:
> On Tue, Sep 9, 2025 at 12:12 AM <dmukhin@xen.org> wrote:
>> --- a/xen/common/emul/vuart/ns16x50.c
>> +++ b/xen/common/emul/vuart/ns16x50.c
>> @@ -107,7 +107,7 @@ static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
>>
>>  static bool cf_check ns16x50_iir_check_msi(const struct vuart_ns16x50 *vdev)
>>  {
>> -    return false;
>> +    return vdev->regs[UART_MSR] & UART_MSR_CHANGE;
>>  }
>>
>>  /*
>> @@ -232,12 +232,63 @@ static int ns16x50_io_write8(
>>              regs[UART_LCR] = val;
>>              break;
>>
>> +        case UART_MCR: {
> 
> Probably the opening brace should be moved to the next line.
> See CODING_STYLE:
> 
> Braces ('{' and '}') are usually placed on a line of their own, except
> for:
> 
> - the do/while loop
> - the opening brace in definitions of enum, struct, and union
> - the opening brace in initializers
> - compound literals

strictly by the wording of the doc you're right, yet if you go look then
you'll see that we really permit both forms (and apparently prefer the
one used here).

Jan


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

* Re: [PATCH v7 08/16] emul/ns16x50: implement MCR/MSR registers
  2025-09-15 14:49     ` Jan Beulich
@ 2025-09-16  8:00       ` Mykola Kvach
  2025-09-16 14:13         ` Jan Beulich
  0 siblings, 1 reply; 46+ messages in thread
From: Mykola Kvach @ 2025-09-16  8:00 UTC (permalink / raw)
  To: Jan Beulich
  Cc: xen-devel, andrew.cooper3, anthony.perard, julien, michal.orzel,
	roger.pau, sstabellini, dmukhin, dmukhin

Hi Jan,

On Mon, Sep 15, 2025 at 5:49 PM Jan Beulich <jbeulich@suse.com> wrote:
>
> On 15.09.2025 08:00, Mykola Kvach wrote:
> > On Tue, Sep 9, 2025 at 12:12 AM <dmukhin@xen.org> wrote:
> >> --- a/xen/common/emul/vuart/ns16x50.c
> >> +++ b/xen/common/emul/vuart/ns16x50.c
> >> @@ -107,7 +107,7 @@ static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
> >>
> >>  static bool cf_check ns16x50_iir_check_msi(const struct vuart_ns16x50 *vdev)
> >>  {
> >> -    return false;
> >> +    return vdev->regs[UART_MSR] & UART_MSR_CHANGE;
> >>  }
> >>
> >>  /*
> >> @@ -232,12 +232,63 @@ static int ns16x50_io_write8(
> >>              regs[UART_LCR] = val;
> >>              break;
> >>
> >> +        case UART_MCR: {
> >
> > Probably the opening brace should be moved to the next line.
> > See CODING_STYLE:
> >
> > Braces ('{' and '}') are usually placed on a line of their own, except
> > for:
> >
> > - the do/while loop
> > - the opening brace in definitions of enum, struct, and union
> > - the opening brace in initializers
> > - compound literals
>

Thanks for clarifying.

> strictly by the wording of the doc you're right, yet if you go look then
> you'll see that we really permit both forms (and apparently prefer the
> one used here).

I just want to make sure I understand the expectation correctly.
The CODING_STYLE document has wording about brace placement, but as
you noted, the actual code in this subsystem uses both styles, and the
one used here seems to be preferred in practice.

To get a better sense, I did a quick search in the repository. The
pattern with the brace on the next line after case appears roughly
340 times, while the variant with the brace on the same line as case
appears about 75 times. So overall the first form seems to be much
more common.

That makes me think the choice here is more a matter of maintainer
preference than a global convention. My main concern is consistency:
if in one place both forms are accepted, but in another case reviewers
point back to the document and ask for strict compliance, it could
create confusion for contributors.

I'm fine if Denis leaves it as is. I just wanted to note the
misalignment with the CODING_STYLE doc.

>
> Jan

Best regard,
Mykola


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

* Re: [PATCH v7 08/16] emul/ns16x50: implement MCR/MSR registers
  2025-09-16  8:00       ` Mykola Kvach
@ 2025-09-16 14:13         ` Jan Beulich
  0 siblings, 0 replies; 46+ messages in thread
From: Jan Beulich @ 2025-09-16 14:13 UTC (permalink / raw)
  To: Mykola Kvach
  Cc: xen-devel, andrew.cooper3, anthony.perard, julien, michal.orzel,
	roger.pau, sstabellini, dmukhin, dmukhin

On 16.09.2025 10:00, Mykola Kvach wrote:
> Hi Jan,
> 
> On Mon, Sep 15, 2025 at 5:49 PM Jan Beulich <jbeulich@suse.com> wrote:
>>
>> On 15.09.2025 08:00, Mykola Kvach wrote:
>>> On Tue, Sep 9, 2025 at 12:12 AM <dmukhin@xen.org> wrote:
>>>> --- a/xen/common/emul/vuart/ns16x50.c
>>>> +++ b/xen/common/emul/vuart/ns16x50.c
>>>> @@ -107,7 +107,7 @@ static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
>>>>
>>>>  static bool cf_check ns16x50_iir_check_msi(const struct vuart_ns16x50 *vdev)
>>>>  {
>>>> -    return false;
>>>> +    return vdev->regs[UART_MSR] & UART_MSR_CHANGE;
>>>>  }
>>>>
>>>>  /*
>>>> @@ -232,12 +232,63 @@ static int ns16x50_io_write8(
>>>>              regs[UART_LCR] = val;
>>>>              break;
>>>>
>>>> +        case UART_MCR: {
>>>
>>> Probably the opening brace should be moved to the next line.
>>> See CODING_STYLE:
>>>
>>> Braces ('{' and '}') are usually placed on a line of their own, except
>>> for:
>>>
>>> - the do/while loop
>>> - the opening brace in definitions of enum, struct, and union
>>> - the opening brace in initializers
>>> - compound literals
>>
> 
> Thanks for clarifying.
> 
>> strictly by the wording of the doc you're right, yet if you go look then
>> you'll see that we really permit both forms (and apparently prefer the
>> one used here).
> 
> I just want to make sure I understand the expectation correctly.
> The CODING_STYLE document has wording about brace placement, but as
> you noted, the actual code in this subsystem uses both styles, and the
> one used here seems to be preferred in practice.
> 
> To get a better sense, I did a quick search in the repository. The
> pattern with the brace on the next line after case appears roughly
> 340 times, while the variant with the brace on the same line as case
> appears about 75 times. So overall the first form seems to be much
> more common.
> 
> That makes me think the choice here is more a matter of maintainer
> preference than a global convention. My main concern is consistency:
> if in one place both forms are accepted, but in another case reviewers
> point back to the document and ask for strict compliance, it could
> create confusion for contributors.
> 
> I'm fine if Denis leaves it as is. I just wanted to note the
> misalignment with the CODING_STYLE doc.

Yes, the situation with ./CODING_STYLE is suboptimal. Yet trying to get
in changes to that file also has proven difficult.

As to the brace placement in case block: Please realize that this is
also special because of the case labels indented as much as switch()
statement's opening figure brace. While nothing can be done for the
closing braces (i.e. there being successive ones with the same
indentation), the opening ones have this alternative placement as an
option.

What we could consider is to allow omitting the braces altogether in
case blocks. That comes with its own downsides, but we may want to
weigh things as to what's deemed worse.

Jan


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

* Re: [PATCH v7 03/16] emul/ns16x50: implement emulator stub
  2025-09-10 10:05   ` Mykola Kvach
  2025-09-13 17:29     ` dmukhin
@ 2025-11-14  5:19     ` dmukhin
  1 sibling, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-11-14  5:19 UTC (permalink / raw)
  To: Mykola Kvach
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Mykola,

Thanks for the earlier reviews!

I am getting back to that series.

On Wed, Sep 10, 2025 at 01:05:13PM +0300, Mykola Kvach wrote:
> Hi Denis,
> 
> I appreciate you addressing the comments from the earlier version
> of the patch series.
> 
> On Tue, Sep 9, 2025 at 12:12 AM <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.
> >
> > Only one domain, defined via 'vuart=' parameter, will have UART emulator
> > initially. The command line option is not documented yet because of the plan
> > to adjust this code for vUART configuration via xl.
> 
> Since the command line option is not yet documented, it would be
> helpful to include the expected format of the 'vuart=' parameter in
> the commit message. This will make it easier for reviewers and future
> readers to understand how to use the option.

So I ended up moving that code into vuart.c where it belongs, along with
addressing all your feedback.

--
Denis


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

* Re: [PATCH v7 03/16] emul/ns16x50: implement emulator stub
  2025-09-15 10:16   ` Mykola Kvach
@ 2025-11-14  5:28     ` dmukhin
  0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2025-11-14  5:28 UTC (permalink / raw)
  To: Mykola Kvach
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

On Mon, Sep 15, 2025 at 01:16:26PM +0300, Mykola Kvach wrote:
> > +/*
> > + * Virtual ns16x50 device state.
> > + */
> > +struct vuart_ns16x50 {
> > +    uint8_t regs[NS16X50_EMU_REGS_NUM]; /* Emulated registers */
> 
> I think it would be better to add an init procedure for the registers.
> At least not all bits in all registers should be initialized to zero.
> Or will this be handled in some way during I/O reads?

I can do it, something like ns16x50_reset() should work I think.
Thanks for suggestion!


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

* Re: [PATCH v7 13/16] emul/ns16x50: add Kconfig options
  2025-09-08 21:11 ` [PATCH v7 13/16] emul/ns16x50: add Kconfig options dmukhin
@ 2025-11-18  6:00   ` Mykola Kvach
  0 siblings, 0 replies; 46+ messages in thread
From: Mykola Kvach @ 2025-11-18  6:00 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Denis,

Thank you for the patch.

Reviewed-by: Mykola Kvach <mykola_kvach@epam.com>


Best regards,
Mykola


On Tue, Sep 9, 2025 at 12:12 AM <dmukhin@xen.org> wrote:
>
> From: Denis Mukhin <dmukhin@ford.com>
>
> Add initial Kconfig options configure NS16550-capable emulator.
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v6:
> - new patch
> ---
>  xen/common/emul/vuart/Kconfig | 19 +++++++++++++++++++
>  1 file changed, 19 insertions(+)
>
> diff --git a/xen/common/emul/vuart/Kconfig b/xen/common/emul/vuart/Kconfig
> index ce1b976b7da7..9a49a6528b5a 100644
> --- a/xen/common/emul/vuart/Kconfig
> +++ b/xen/common/emul/vuart/Kconfig
> @@ -3,4 +3,23 @@ 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 NS16550-compatible UART emulation.
> +
> +         Only one legacy PC COM port is emulated for domain with a certain ID
> +         (set via 'vuart-domid=' command line setting).
> +
> +         This is strictly for testing purposes (such as early HVM guest console),
> +         and not appropriate for use in production.
> +
> +config VUART_NS16X50_DEBUG
> +       bool "Development: NS16550-compatible UART Emulator Debugging"
> +       depends on VUART_NS16X50 && DEBUG
> +       help
> +         Enable development debugging.
> +
>  endmenu
> --
> 2.51.0
>
>


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

* Re: [PATCH v7 14/16] x86/domain: enable per-domain I/O port bitmaps
  2025-09-08 21:11 ` [PATCH v7 14/16] x86/domain: enable per-domain I/O port bitmaps dmukhin
@ 2025-11-18  6:00   ` Mykola Kvach
  2026-05-14 23:52     ` dmukhin
  0 siblings, 1 reply; 46+ messages in thread
From: Mykola Kvach @ 2025-11-18  6:00 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Denis,

Thank you for the patch.

On Tue, Sep 9, 2025 at 12:12 AM <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 v6:
> - n/a
> ---
>  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 d7aed7d92c15..85a8475e126c 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 363c010f8dcc..1fc16a22e157 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);
> @@ -266,15 +259,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;
> @@ -706,19 +690,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);
> @@ -745,6 +722,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

ioports_setup_access() now allocates a per-domain io_bitmap for all HVM domains:

> +    d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);

but I only see this being freed for the hardware domain in the
existing failure paths.
Where is io_bitmap freed for non-hardware HVM domains?

> +    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 c341f012d005..ea34c3ae598a 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -783,9 +783,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 */

Should this be undone on teardown and error paths?

> +    rc = ioports_deny_access(d, info->base_addr,
> +                             info->base_addr + info->size - 1);


Best regards,
Mykola


> +    if ( rc )
> +    {
> +        ns16x50_err(info, " virtual I/O port range [0x%04lx..0x%04lx]: 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 & UINT8_MAX;
>      vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & UINT8_MAX;
> --
> 2.51.0
>
>


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

* Re: [PATCH v7 12/16] emul/ns16550: implement dump_state() hook
  2025-09-08 21:11 ` [PATCH v7 12/16] emul/ns16550: implement dump_state() hook dmukhin
@ 2025-11-18  6:00   ` Mykola Kvach
  2026-05-14 23:35     ` dmukhin
  0 siblings, 1 reply; 46+ messages in thread
From: Mykola Kvach @ 2025-11-18  6:00 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Denis,

Thank you for the patch.

On Tue, Sep 9, 2025 at 12:12 AM <dmukhin@xen.org> wrote:
>
> 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 v6:
> - n/a
> ---
>  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 a92df6923aa5..c341f012d005 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -640,6 +640,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 )

Is this NULL check actually useful here? At this point we’ve already
dereferenced vdev (vdev->owner / vdev->info), so if arg could be NULL
we’d already be in UB. Either the hook never receives NULL (and we can
drop the check or turn it into ASSERT(vdev)), or the check should be
moved before the first dereference.

> +        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%04x 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,

Should this be using cons->in / cons->out instead of cons?

> +            cons->in[MASK_XENCONS_IDX(cons->in_prod, cons)],
> +            cons->out[MASK_XENCONS_IDX(cons->out_prod, cons)],

As written, MASK_XENCONS_IDX() gets &vdev->cons (struct pointer), not the
RX/TX arrays themselves, so its size/index calculation will use the size
of the pointer/struct rather than the in[]/out[] ring size. I think this
should be:

    cons->in[MASK_XENCONS_IDX(cons->in_prod, cons->in)],
    cons->out[MASK_XENCONS_IDX(cons->out_prod, cons->out)],


Best regards,
Mykola


> +            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.
> @@ -709,6 +761,9 @@ static int cf_check ns16x50_io_handle(
>
>      spin_unlock(&vdev->lock);
>
> +    if ( ns16x50_log_level >= 3 )
> +        ns16x50_dump_state(vdev);
> +
>      if ( rc == 0 )
>          ns16x50_debug(vdev, "%c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32"\n",
>                        op, addr, size, dlab, reg, *data);
> @@ -844,6 +899,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;
>  }
> @@ -853,7 +910,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	[flat|nested] 46+ messages in thread

* Re: [PATCH v7 11/16] emul/ns16x50: implement FCR register (write-only)
  2025-09-08 21:11 ` [PATCH v7 11/16] emul/ns16x50: implement FCR register (write-only) dmukhin
@ 2025-11-18  6:00   ` Mykola Kvach
  0 siblings, 0 replies; 46+ messages in thread
From: Mykola Kvach @ 2025-11-18  6:00 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Denis,

Thank you for the patch.

On Tue, Sep 9, 2025 at 1:06 AM <dmukhin@xen.org> wrote:
>
> From: Denis Mukhin <dmukhin@ford.com>
>
> Add emulation logic for FCR register.
>
> Note, that does not hook FIFO interrupt moderation to the FIFO management
> code for simplicity.
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v6:
> - dropped UART_IIR_THR handling from UART_FCR_CLTX case
> ---
>  xen/common/emul/vuart/ns16x50.c | 27 +++++++++++++++++++++++++++
>  1 file changed, 27 insertions(+)
>
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index 137ce08f4e1d..a92df6923aa5 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -374,6 +374,33 @@ static int ns16x50_io_write8(
>              regs[UART_IER] = val & UART_IER_MASK;
>              break;
>
> +        case UART_FCR: /* WO */
> +            if ( val & UART_FCR_RSRVD0 )
> +                ns16x50_warn(vdev, "FCR: attempt to set reserved bit: %x\n",
> +                             UART_FCR_RSRVD0);
> +
> +            if ( val & UART_FCR_RSRVD1 )
> +                ns16x50_warn(vdev, "FCR: attempt to set reserved bit: %x\n",
> +                             UART_FCR_RSRVD1);

Do we really need these checks and prints?

> +
> +            if ( val & UART_FCR_CLRX )
> +            {
> +                ns16x50_fifo_rx_reset(vdev);

Note from the NS16550A datasheet:
Bit 0: This bit must be a 1 when other FCR bits are written to, or they
will not be programmed.

> +                regs[UART_LSR] &= ~UART_LSR_DR;
> +            }
> +
> +            if ( val & UART_FCR_CLTX )
> +                ns16x50_fifo_tx_reset(vdev);
> +
> +            if ( val & UART_FCR_ENABLE )
> +                val &= UART_FCR_ENABLE | UART_FCR_DMA | UART_FCR_TRG_MASK;

Why can’t we just write back val as is, but with CLTX/CLRX cleared when
UART_FCR_ENABLE is set? For example:

    regs[UART_FCR] = val & ~(UART_FCR_CLTX | UART_FCR_CLRX);

> +            else
> +                val = 0;

If we take the above into account, I think we shouldn’t change the
content of FCR in the case where bit 0 is 0.

Also, from the spec:
“Resetting FCR0 will clear all bytes in both FIFOs.”


Best regards,
Mykola



> +
> +            regs[UART_FCR] = val;
> +
> +            break;
> +
>          case UART_LCR:
>              regs[UART_LCR] = val;
>              break;
> --
> 2.51.0
>
>


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

* Re: [PATCH v7 10/16] emul/ns16x50: implement THR register
  2025-09-08 21:11 ` [PATCH v7 10/16] emul/ns16x50: implement THR register dmukhin
@ 2025-11-18  6:00   ` Mykola Kvach
  2026-05-14 23:23     ` dmukhin
  0 siblings, 1 reply; 46+ messages in thread
From: Mykola Kvach @ 2025-11-18  6:00 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Denis,

Thank you for the patch.

On Tue, Sep 9, 2025 at 1:26 AM <dmukhin@xen.org> wrote:
>
> From: Denis Mukhin <dmukhin@ford.com>
>
> Add THR register emulation to the I/O port handlder.
>
> Add TX FIFO management code since THR depends on TX FIFO.
>
> TX FIFOs is 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).
>
> TX FIFOs is emulated by using xencons_interface which conveniently provides
> primitives for buffer management and later can be used for inter-domain
> communication similarly to vpl011.
>
> Account for DLL == 0: in this case, disable transmitter.
>
> Add UART_IIR_THR interrupt reason handling since it depends on THR register
> access.
>
> Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> ---
> Changes since v6:
> - added DLL == 0 case handling as per Mykola's suggestion
> - dropped UART_IIR_THR clearing in UART_IIR register emulation in ns16x50_io_write8()
> - simplified UART_IIR_THR handling
> - updated ns16x50_iir_check_thr()
> ---
>  xen/common/emul/vuart/ns16x50.c | 82 ++++++++++++++++++++++++++++++++-
>  1 file changed, 81 insertions(+), 1 deletion(-)
>
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index 250411e0a7d8..137ce08f4e1d 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -149,6 +149,66 @@ static int ns16x50_fifo_rx_putchar(struct vuart_ns16x50 *vdev, char c)
>      return rc;
>  }
>
> +static bool ns16x50_fifo_tx_full(const struct vuart_ns16x50 *vdev)
> +{
> +    const struct xencons_interface *cons = &vdev->cons;
> +
> +    return cons->out_prod - cons->out_cons == ARRAY_SIZE(cons->out);
> +}
> +
> +static void ns16x50_fifo_tx_reset(struct vuart_ns16x50 *vdev)
> +{
> +    struct xencons_interface *cons = &vdev->cons;
> +
> +    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;
> +    XENCONS_RING_IDX i, n, len = cons->out_prod - cons->out_cons;
> +
> +    ASSERT(len <= ARRAY_SIZE(cons->out));
> +    if ( !len )
> +        return;
> +
> +    i = MASK_XENCONS_IDX(cons->out_cons, cons->out);
> +    n = min_t(XENCONS_RING_IDX, len, ARRAY_SIZE(cons->out) - i);
> +    if ( n )
> +        guest_printk(d, guest_prefix "%.*s", n, &cons->out[i]);
> +
> +    i = 0;
> +    n = len - n;
> +    if ( n )
> +        guest_printk(d, guest_prefix "%.*s", n, &cons->out[i]);

ns16x50_fifo_tx_flush() splits wrapped output into two guest_printk()
calls, so the log gets two prefixes for a single line:
    (d1) PART1(d1) PART2

Could we linearize the wrapped buffer and emit a single guest_printk()
(e.g. by printing both spans in one format string) to keep just one prefix?


Best regards,
Mykola


> +
> +    cons->out_cons += len;
> +}
> +
> +/*
> + * 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 ( !ns16x50_fifo_tx_full(vdev) )
> +    {
> +        cons->out[MASK_XENCONS_IDX(cons->out_prod, cons->out)] = ch;
> +        cons->out_prod++;
> +    }
> +
> +    if ( ch == '\n' || ch == '\0' || ns16x50_fifo_tx_full(vdev) )
> +        ns16x50_fifo_tx_flush(vdev);
> +}
> +
>  static bool ns16x50_is_running(const struct vuart_ns16x50 *vdev)
>  {
>      /* DLL set to 0 disables serial communication. */
> @@ -172,7 +232,7 @@ static bool cf_check ns16x50_iir_check_rda(const struct vuart_ns16x50 *vdev)
>
>  static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
>  {
> -    return false;
> +    return !ns16x50_fifo_tx_full(vdev);
>  }
>
>  static bool cf_check ns16x50_iir_check_msi(const struct vuart_ns16x50 *vdev)
> @@ -294,6 +354,22 @@ static int ns16x50_io_write8(
>      {
>          switch ( reg )
>          {
> +        case UART_THR:
> +            if ( !ns16x50_is_running(vdev) )
> +                break;
> +
> +            if ( regs[UART_MCR] & UART_MCR_LOOP )
> +            {
> +                if ( ns16x50_fifo_rx_putchar(vdev, val) )
> +                    regs[UART_LSR] |= UART_LSR_OE;
> +
> +                regs[UART_LSR] |= UART_LSR_DR;
> +            }
> +            else
> +                ns16x50_fifo_tx_putchar(vdev, val);
> +
> +            break;
> +
>          case UART_IER:
>              regs[UART_IER] = val & UART_IER_MASK;
>              break;
> @@ -646,6 +722,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 v7 15/16] xen/domain: allocate d->irq_caps before arch-specific initialization
  2025-09-08 21:11 ` [PATCH v7 15/16] xen/domain: allocate d->irq_caps before arch-specific initialization dmukhin
@ 2025-11-18  6:00   ` Mykola Kvach
  0 siblings, 0 replies; 46+ messages in thread
From: Mykola Kvach @ 2025-11-18  6:00 UTC (permalink / raw)
  To: dmukhin
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Denis,

Thank you for the patch.

On Tue, Sep 9, 2025 at 12:12 AM <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 v6:
> - n/a
> ---
>  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 775c33928585..edf76b02e1a1 100644
> --- a/xen/common/domain.c
> +++ b/xen/common/domain.c
> @@ -952,6 +952,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;
> @@ -961,8 +966,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 ea34c3ae598a..6bd58ba5540b 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -797,6 +797,15 @@ static int ns16x50_init(void *arg)
>          return rc;
>      }
>
> +    /* Disallow sharing physical IRQ */

Should this be undone on teardown and error paths?


Best regards,
Mykola


> +    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 & UINT8_MAX;
>      vdev->regs[NS16X50_REGS_NUM + UART_DLM] = (divisor >> 8) & UINT8_MAX;
> --
> 2.51.0
>
>


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

* Re: [PATCH v7 09/16] emul/ns16x50: implement RBR register
  2025-09-08 21:11 ` [PATCH v7 09/16] emul/ns16x50: implement RBR register dmukhin
@ 2025-11-18  6:00   ` Mykola Kvach
  0 siblings, 0 replies; 46+ messages in thread
From: Mykola Kvach @ 2025-11-18  6:00 UTC (permalink / raw)
  To: dmukhin, xen-devel
  Cc: andrew.cooper3, anthony.perard, jbeulich, julien, michal.orzel,
	roger.pau, sstabellini, dmukhin

Hi Denis,

Thank you for the patch.

On 09/09/2025 00:11, dmukhin@xen.org wrote:
> From: Denis Mukhin <dmukhin@ford.com>
>
> Add RBR register emulation to the I/O port handlder.
>
> Add RX FIFO management code since RBR depends on RX FIFO.
>
> RX FIFO is 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).
>
> RX FIFO is 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.
>
> Account for DLL == 0: in this case, disable receiver.
>
> Add UART_LSR_DR handling since it depends on RBR register access.
>
> Finally, 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 v6:
> - added DLL == 0 case handling as per Mykola's suggestion
> ---
>   xen/common/emul/vuart/ns16x50.c | 134 +++++++++++++++++++++++++++++++-
>   1 file changed, 132 insertions(+), 2 deletions(-)
>
> diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> index fdc20124d4c9..250411e0a7d8 100644
> --- a/xen/common/emul/vuart/ns16x50.c
> +++ b/xen/common/emul/vuart/ns16x50.c
> @@ -9,6 +9,8 @@
>    *     https://www.ti.com/lit/ds/symlink/tl16c550c.pdf
>    * - UART w/ 64 byte FIFO:
>    *     https://www.ti.com/lit/ds/symlink/tl16c750.pdf
> + * - DesignWare DW_apb_uart Databook, v4.02a:
> + *     https://iccircle.com/static/upload/img20240313113905.pdf
>    *
>    * Limitations:
>    * - Only x86;
> @@ -85,6 +87,74 @@ struct vuart_ns16x50 {
>       struct xencons_interface cons;      /* Emulated RX/TX FIFOs */
>   };
>
> +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 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;
> +}
> +
> +/*
> + * Transfer character from RX FIFO and return the RX FIFO status after the
> + * transfer.
> + */
> +static int ns16x50_fifo_rx_getchar(struct vuart_ns16x50 *vdev, uint8_t *ptr)
> +{
> +    struct xencons_interface *cons = &vdev->cons;
> +
> +    if ( ns16x50_fifo_rx_empty(vdev) )
> +        return -ENODATA;
> +
> +    *ptr = cons->in[MASK_XENCONS_IDX(cons->in_cons, cons->in)];
> +    cons->in_cons++;
> +
> +    return ns16x50_fifo_rx_empty(vdev) ? -ENODATA : 0;
> +}
> +
> +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);

Do we really need to drop everything from the buffer here?
Should we document somewhere that in case of OE the whole RX buffer is dropped?

> +        rc = -ENOSPC;
> +    }
> +    else
> +        rc = 0;
> +
> +    cons->in[MASK_XENCONS_IDX(cons->in_prod, cons->in)] = c;
> +    cons->in_prod++;
> +
> +    return rc;
> +}
> +
> +static bool ns16x50_is_running(const struct vuart_ns16x50 *vdev)
> +{
> +    /* DLL set to 0 disables serial communication. */
> +    return vdev->regs[NS16X50_REGS_NUM + UART_DLL];
> +}
> +
>   static uint8_t ns16x50_dlab_get(const struct vuart_ns16x50 *vdev)
>   {
>       return vdev->regs[UART_LCR] & UART_LCR_DLAB ? 1 : 0;
> @@ -97,7 +167,7 @@ static bool cf_check ns16x50_iir_check_lsi(const struct vuart_ns16x50 *vdev)
>
>   static bool cf_check ns16x50_iir_check_rda(const struct vuart_ns16x50 *vdev)
>   {
> -    return false;
> +    return !ns16x50_fifo_rx_empty(vdev);
>   }
>
>   static bool cf_check ns16x50_iir_check_thr(const struct vuart_ns16x50 *vdev)
> @@ -362,6 +432,20 @@ static int ns16x50_io_read8(
>       {
>           switch ( reg )
>           {
> +        case UART_RBR:
> +            if ( !ns16x50_is_running(vdev) )
> +                break;
> +
> +            /* NB: do not forget to clear overrun condition */
> +            regs[UART_LSR] &= ~UART_LSR_OE;

The "NS16550A Universal Asynchronous Receiver/Transmitter
with FIFOs" states:

The OE indicator is set to a logic 1 upon detection of an overrun
condition and reset whenever the CPU reads the contents of the Line
Status Register.

So it looks like we don't need to clear this bit here.
According to the spec it should be cleared only on LSR read.

Best regards,
Mykola


> +
> +            if ( ns16x50_fifo_rx_getchar(vdev, &val) )
> +                regs[UART_LSR] &= ~UART_LSR_DR;
> +            else
> +                regs[UART_LSR] |= UART_LSR_DR;
> +
> +            break;
> +
>           case UART_IER:
>               val = regs[UART_IER];
>               break;
> @@ -611,13 +695,59 @@ static void cf_check ns16x50_free(void *arg)
>       xvfree(arg);
>   }
>
> +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 ( !ns16x50_is_running(vdev) )
> +        ns16x50_debug(vdev, "THR/RBR access disabled: DLL == 0\n");
> +    else 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
> +    {
> +        const struct domain *d = vdev->owner;
> +
> +        /*
> +         * 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);
> +
> +        if ( ns16x50_fifo_rx_putchar(vdev, ch) )
> +            regs[UART_LSR] |= UART_LSR_OE;
> +
> +        regs[UART_LSR] |= UART_LSR_DR;
> +
> +        /* TODO: check FCR when to fire an interrupt */
> +        ns16x50_irq_check(vdev);
> +
> +        rc = 0;
> +    }
> +
> +    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);


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

* Re: [PATCH v7 10/16] emul/ns16x50: implement THR register
  2025-11-18  6:00   ` Mykola Kvach
@ 2026-05-14 23:23     ` dmukhin
  0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2026-05-14 23:23 UTC (permalink / raw)
  To: Mykola Kvach
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

Hi Mykola,

Thanks for detailed review for v7 series, much appreciated!

Sorry, it's been a while for this and other series...
I am picking that up.

> 
> On Tue, Sep 9, 2025 at 1:26 AM <dmukhin@xen.org> wrote:
> >
> > From: Denis Mukhin <dmukhin@ford.com>
> >
> > Add THR register emulation to the I/O port handlder.
> >
> > Add TX FIFO management code since THR depends on TX FIFO.
> >
> > TX FIFOs is 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).
> >
> > TX FIFOs is emulated by using xencons_interface which conveniently provides
> > primitives for buffer management and later can be used for inter-domain
> > communication similarly to vpl011.
> >
> > Account for DLL == 0: in this case, disable transmitter.
> >
> > Add UART_IIR_THR interrupt reason handling since it depends on THR register
> > access.
> >
> > Signed-off-by: Denis Mukhin <dmukhin@ford.com>
> > ---
> > Changes since v6:
> > - added DLL == 0 case handling as per Mykola's suggestion
> > - dropped UART_IIR_THR clearing in UART_IIR register emulation in ns16x50_io_write8()
> > - simplified UART_IIR_THR handling
> > - updated ns16x50_iir_check_thr()
> > ---
> >  xen/common/emul/vuart/ns16x50.c | 82 ++++++++++++++++++++++++++++++++-
> >  1 file changed, 81 insertions(+), 1 deletion(-)
> >
> > diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> > index 250411e0a7d8..137ce08f4e1d 100644
> > --- a/xen/common/emul/vuart/ns16x50.c
> > +++ b/xen/common/emul/vuart/ns16x50.c
> > @@ -149,6 +149,66 @@ static int ns16x50_fifo_rx_putchar(struct vuart_ns16x50 *vdev, char c)
> >      return rc;
> >  }
> >
> > +static bool ns16x50_fifo_tx_full(const struct vuart_ns16x50 *vdev)
> > +{
> > +    const struct xencons_interface *cons = &vdev->cons;
> > +
> > +    return cons->out_prod - cons->out_cons == ARRAY_SIZE(cons->out);
> > +}
> > +
> > +static void ns16x50_fifo_tx_reset(struct vuart_ns16x50 *vdev)
> > +{
> > +    struct xencons_interface *cons = &vdev->cons;
> > +
> > +    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;
> > +    XENCONS_RING_IDX i, n, len = cons->out_prod - cons->out_cons;
> > +
> > +    ASSERT(len <= ARRAY_SIZE(cons->out));
> > +    if ( !len )
> > +        return;
> > +
> > +    i = MASK_XENCONS_IDX(cons->out_cons, cons->out);
> > +    n = min_t(XENCONS_RING_IDX, len, ARRAY_SIZE(cons->out) - i);
> > +    if ( n )
> > +        guest_printk(d, guest_prefix "%.*s", n, &cons->out[i]);
> > +
> > +    i = 0;
> > +    n = len - n;
> > +    if ( n )
> > +        guest_printk(d, guest_prefix "%.*s", n, &cons->out[i]);
> 
> ns16x50_fifo_tx_flush() splits wrapped output into two guest_printk()
> calls, so the log gets two prefixes for a single line:
>     (d1) PART1(d1) PART2
> 
> Could we linearize the wrapped buffer and emit a single guest_printk()
> (e.g. by printing both spans in one format string) to keep just one prefix?

Good idea, will do.

--
Denis


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

* Re: [PATCH v7 12/16] emul/ns16550: implement dump_state() hook
  2025-11-18  6:00   ` Mykola Kvach
@ 2026-05-14 23:35     ` dmukhin
  0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2026-05-14 23:35 UTC (permalink / raw)
  To: Mykola Kvach
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

On Tue, Nov 18, 2025 at 08:00:00AM +0200, Mykola Kvach wrote:
> Hi Denis,
> 
> Thank you for the patch.
..
> 
> > +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 )
> 
> Is this NULL check actually useful here? At this point we’ve already
> dereferenced vdev (vdev->owner / vdev->info), so if arg could be NULL
> we’d already be in UB. Either the hook never receives NULL (and we can
> drop the check or turn it into ASSERT(vdev)), or the check should be
> moved before the first dereference.

Will promote to ASSERT().

> 
> > +        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%04x 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,
> 
> Should this be using cons->in / cons->out instead of cons?

Yes, it should!
Thanks for the catch!

> 
> > +            cons->in[MASK_XENCONS_IDX(cons->in_prod, cons)],
> > +            cons->out[MASK_XENCONS_IDX(cons->out_prod, cons)],
> 
> As written, MASK_XENCONS_IDX() gets &vdev->cons (struct pointer), not the
> RX/TX arrays themselves, so its size/index calculation will use the size
> of the pointer/struct rather than the in[]/out[] ring size. I think this
> should be:
> 
>     cons->in[MASK_XENCONS_IDX(cons->in_prod, cons->in)],
>     cons->out[MASK_XENCONS_IDX(cons->out_prod, cons->out)],
> 
> 
> Best regards,
> Mykola


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

* Re: [PATCH v7 14/16] x86/domain: enable per-domain I/O port bitmaps
  2025-11-18  6:00   ` Mykola Kvach
@ 2026-05-14 23:52     ` dmukhin
  0 siblings, 0 replies; 46+ messages in thread
From: dmukhin @ 2026-05-14 23:52 UTC (permalink / raw)
  To: Mykola Kvach
  Cc: xen-devel, andrew.cooper3, anthony.perard, jbeulich, julien,
	michal.orzel, roger.pau, sstabellini, dmukhin

On Tue, Nov 18, 2025 at 08:00:00AM +0200, Mykola Kvach wrote:
[..]

> > +/* 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
> 
> ioports_setup_access() now allocates a per-domain io_bitmap for all HVM domains:
> 
> > +    d->arch.hvm.io_bitmap = _xmalloc(HVM_IOBITMAP_SIZE, PAGE_SIZE);
> 
> but I only see this being freed for the hardware domain in the
> existing failure paths.
> Where is io_bitmap freed for non-hardware HVM domains?

Missed that.
I will add something like ioports_release_access() to release resources.

[..]

> >
> > diff --git a/xen/common/emul/vuart/ns16x50.c b/xen/common/emul/vuart/ns16x50.c
> > index c341f012d005..ea34c3ae598a 100644
> > --- a/xen/common/emul/vuart/ns16x50.c
> > +++ b/xen/common/emul/vuart/ns16x50.c
> > @@ -783,9 +783,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 */
> 
> Should this be undone on teardown and error paths?
> 
> > +    rc = ioports_deny_access(d, info->base_addr,
> > +                             info->base_addr + info->size - 1);
> 

AFAIR, there was a feedback on that during the earlier iterations of the
series and the agreement was - no need to release resources on teardown
since the domain is being destroyed at this point. 

Thanks,
Denis


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

end of thread, other threads:[~2026-05-14 23:53 UTC | newest]

Thread overview: 46+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-09-08 21:11 [PATCH v7 00/16] x86: introduce NS16550-compatible UART emulator dmukhin
2025-09-08 21:11 ` [PATCH v7 01/16] emul/vuart: introduce framework for UART emulators dmukhin
2025-09-10  7:57   ` Mykola Kvach
2025-09-13 18:09     ` dmukhin
2025-09-08 21:11 ` [PATCH v7 02/16] xen/8250-uart: update definitions dmukhin
2025-09-09 10:05   ` Jan Beulich
2025-09-09 19:42     ` dmukhin
2025-09-10  8:39     ` Mykola Kvach
2025-09-13 17:50       ` dmukhin
2025-09-08 21:11 ` [PATCH v7 03/16] emul/ns16x50: implement emulator stub dmukhin
2025-09-10 10:05   ` Mykola Kvach
2025-09-13 17:29     ` dmukhin
2025-11-14  5:19     ` dmukhin
2025-09-15 10:16   ` Mykola Kvach
2025-11-14  5:28     ` dmukhin
2025-09-08 21:11 ` [PATCH v7 04/16] emul/ns16x50: implement DLL/DLM registers dmukhin
2025-09-10 10:16   ` Mykola Kvach
2025-09-08 21:11 ` [PATCH v7 05/16] emul/ns16x50: implement SCR register dmukhin
2025-09-12  7:23   ` Mykola Kvach
2025-09-08 21:11 ` [PATCH v7 06/16] emul/ns16x50: implement IER/IIR registers dmukhin
2025-09-15  6:00   ` Mykola Kvach
2025-09-08 21:11 ` [PATCH v7 07/16] emul/ns16x50: implement LCR/LSR registers dmukhin
2025-09-15  6:00   ` Mykola Kvach
2025-09-08 21:11 ` [PATCH v7 08/16] emul/ns16x50: implement MCR/MSR registers dmukhin
2025-09-15  6:00   ` Mykola Kvach
2025-09-15 14:49     ` Jan Beulich
2025-09-16  8:00       ` Mykola Kvach
2025-09-16 14:13         ` Jan Beulich
2025-09-08 21:11 ` [PATCH v7 09/16] emul/ns16x50: implement RBR register dmukhin
2025-11-18  6:00   ` Mykola Kvach
2025-09-08 21:11 ` [PATCH v7 10/16] emul/ns16x50: implement THR register dmukhin
2025-11-18  6:00   ` Mykola Kvach
2026-05-14 23:23     ` dmukhin
2025-09-08 21:11 ` [PATCH v7 11/16] emul/ns16x50: implement FCR register (write-only) dmukhin
2025-11-18  6:00   ` Mykola Kvach
2025-09-08 21:11 ` [PATCH v7 12/16] emul/ns16550: implement dump_state() hook dmukhin
2025-11-18  6:00   ` Mykola Kvach
2026-05-14 23:35     ` dmukhin
2025-09-08 21:11 ` [PATCH v7 13/16] emul/ns16x50: add Kconfig options dmukhin
2025-11-18  6:00   ` Mykola Kvach
2025-09-08 21:11 ` [PATCH v7 14/16] x86/domain: enable per-domain I/O port bitmaps dmukhin
2025-11-18  6:00   ` Mykola Kvach
2026-05-14 23:52     ` dmukhin
2025-09-08 21:11 ` [PATCH v7 15/16] xen/domain: allocate d->irq_caps before arch-specific initialization dmukhin
2025-11-18  6:00   ` Mykola Kvach
2025-09-08 21:11 ` [PATCH v7 16/16] emul/ns16x50: implement IRQ emulation via vIOAPIC dmukhin

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.