[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH v4 5/8] emul/vuart-ns16550: introduce NS16550-compatible UART emulator (x86)



From: Denis Mukhin <dmukhin@xxxxxxxx> 

Add initial in-hypervisor emulator for NS8250/NS16x50-compatible UARTs under
CONFIG_VUART_NS16550 for x86 port of Xen.

x86 port of Xen lacks vUART facility similar to Arm's SBSA emulator to support
x86 guest OS bring up in the embedded setups.

In parallel domain creation scenario (hyperlaunch), NS16550 emulator helps
early guest firmware and/or 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. Such functionality is limited to dom0
use currently.

By default, CONFIG_VUART_NS16550 enables emulation of NS16550 at I/O port
0x3f8, IRQ#4 in guest OS (legacy COM1).

Legacy COM resources can be selected at built-time and cannot be configured
per-domain via .cfg or DT yet.

Introduce new emulation flag for virtual UART on x86 and plumb it through
domain creation code so NS16550 emulator can be instantiated properly.

Please refer to the NS16550 emulator code for full list of limitations.

Signed-off-by: Denis Mukhin <dmukhin@xxxxxxxx>
---
Changes since v3:
- feedback addressed
- adjusted to new vUART framework APIs
- Link to v3: 
https://lore.kernel.org/xen-devel/20250103-vuart-ns8250-v3-v1-21-c5d36b31d66c@xxxxxxxx/
---
 xen/arch/x86/hvm/hvm.c                |    9 +
 xen/arch/x86/include/asm/domain.h     |    4 +-
 xen/arch/x86/include/asm/hvm/domain.h |    4 +
 xen/common/emul/vuart/Kconfig         |   48 ++
 xen/common/emul/vuart/Makefile        |    1 +
 xen/common/emul/vuart/vuart-ns16550.c | 1009 +++++++++++++++++++++++++
 xen/common/emul/vuart/vuart.c         |    4 +
 xen/include/public/arch-x86/xen.h     |    4 +-
 xen/include/xen/resource.h            |    3 +
 9 files changed, 1084 insertions(+), 2 deletions(-)
 create mode 100644 xen/common/emul/vuart/vuart-ns16550.c

diff --git a/xen/arch/x86/hvm/hvm.c b/xen/arch/x86/hvm/hvm.c
index b7edb1d6555d..1156e7ebcc4c 100644
--- a/xen/arch/x86/hvm/hvm.c
+++ b/xen/arch/x86/hvm/hvm.c
@@ -31,6 +31,7 @@
 #include <xen/nospec.h>
 #include <xen/vm_event.h>
 #include <xen/console.h>
+#include <xen/vuart.h>
 #include <asm/shadow.h>
 #include <asm/hap.h>
 #include <asm/current.h>
@@ -702,6 +703,10 @@ int hvm_domain_initialise(struct domain *d,
     if ( rc != 0 )
         goto fail1;
 
+    rc = vuart_init(d, NULL);
+    if ( rc != 0 )
+        goto out_vioapic_deinit;
+
     stdvga_init(d);
 
     rtc_init(d);
@@ -725,6 +730,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) )
@@ -787,6 +794,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/arch/x86/include/asm/domain.h 
b/xen/arch/x86/include/asm/domain.h
index eafd5cfc903d..1ecc7c2cae32 100644
--- a/xen/arch/x86/include/asm/domain.h
+++ b/xen/arch/x86/include/asm/domain.h
@@ -468,6 +468,7 @@ struct arch_domain
 #define X86_EMU_IOMMU    XEN_X86_EMU_IOMMU
 #define X86_EMU_USE_PIRQ XEN_X86_EMU_USE_PIRQ
 #define X86_EMU_VPCI     XEN_X86_EMU_VPCI
+#define X86_EMU_NS16550  XEN_X86_EMU_NS16550
 #else
 #define X86_EMU_LAPIC    0
 #define X86_EMU_HPET     0
@@ -479,6 +480,7 @@ struct arch_domain
 #define X86_EMU_IOMMU    0
 #define X86_EMU_USE_PIRQ 0
 #define X86_EMU_VPCI     0
+#define X86_EMU_NS16550  0
 #endif
 
 #define X86_EMU_PIT     XEN_X86_EMU_PIT
@@ -489,7 +491,7 @@ struct arch_domain
                                  X86_EMU_IOAPIC | X86_EMU_PIC |         \
                                  X86_EMU_VGA | X86_EMU_IOMMU |          \
                                  X86_EMU_PIT | X86_EMU_USE_PIRQ |       \
-                                 X86_EMU_VPCI)
+                                 X86_EMU_VPCI | X86_EMU_NS16550)
 
 #define has_vlapic(d)      (!!((d)->emulation_flags & X86_EMU_LAPIC))
 #define has_vhpet(d)       (!!((d)->emulation_flags & X86_EMU_HPET))
diff --git a/xen/arch/x86/include/asm/hvm/domain.h 
b/xen/arch/x86/include/asm/hvm/domain.h
index 333501d5f2ac..9945b16d1a6e 100644
--- a/xen/arch/x86/include/asm/hvm/domain.h
+++ b/xen/arch/x86/include/asm/hvm/domain.h
@@ -149,6 +149,10 @@ struct hvm_domain {
 #ifdef CONFIG_MEM_SHARING
     struct mem_sharing_domain mem_sharing;
 #endif
+
+#ifdef CONFIG_VUART_NS16550
+    void *vuart; /* Virtual UART handle. */
+#endif
 };
 
 #endif /* __ASM_X86_HVM_DOMAIN_H__ */
diff --git a/xen/common/emul/vuart/Kconfig b/xen/common/emul/vuart/Kconfig
index 02f7dd6dc1a1..ebefd90d913e 100644
--- a/xen/common/emul/vuart/Kconfig
+++ b/xen/common/emul/vuart/Kconfig
@@ -3,4 +3,52 @@ config HAS_VUART
 
 menu "UART Emulation"
 
+config VUART_NS16550
+       bool "NS16550-compatible UART Emulation" if EXPERT
+       depends on X86 && HVM
+       select HAS_VUART
+       help
+         In-hypervisor NS16550/NS16x50 UART emulation.
+
+         Only legacy PC I/O ports are emulated.
+
+         This is strictly for testing purposes (such as early HVM guest 
console),
+         and not appropriate for use in production.
+
+choice VUART_NS16550_PC
+       prompt "IBM PC COM resources"
+       depends on VUART_NS16550
+       default VUART_NS16550_PC_COM1
+       help
+         Default emulated NS16550 resources.
+
+config VUART_NS16550_PC_COM1
+       bool "COM1 (I/O port 0x3f8, IRQ#4)"
+
+config VUART_NS16550_PC_COM2
+       bool "COM2 (I/O port 0x2f8, IRQ#3)"
+
+config VUART_NS16550_PC_COM3
+       bool "COM3 (I/O port 0x3e8, IRQ#4)"
+
+config VUART_NS16550_PC_COM4
+       bool "COM4 (I/O port 0x2e8, IRQ#3)"
+
+endchoice
+
+config VUART_NS16550_LOG_LEVEL
+       int "UART emulator verbosity level"
+       range 0 3
+       default "1"
+       depends on VUART_NS16550
+       help
+         Set the default log level of UART emulator.
+         See include/xen/config.h for more details.
+
+config VUART_NS16550_DEBUG
+       bool "UART emulator development debugging"
+       depends on VUART_NS16550
+       help
+         Enable development debugging.
+
 endmenu
diff --git a/xen/common/emul/vuart/Makefile b/xen/common/emul/vuart/Makefile
index c6400b001e85..85650ca5d8ce 100644
--- a/xen/common/emul/vuart/Makefile
+++ b/xen/common/emul/vuart/Makefile
@@ -1 +1,2 @@
 obj-$(CONFIG_HAS_VUART) += vuart.o
+obj-$(CONFIG_VUART_NS16550) += vuart-ns16550.o
diff --git a/xen/common/emul/vuart/vuart-ns16550.c 
b/xen/common/emul/vuart/vuart-ns16550.c
new file mode 100644
index 000000000000..48bbf58264fe
--- /dev/null
+++ b/xen/common/emul/vuart/vuart-ns16550.c
@@ -0,0 +1,1009 @@
+/* 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 HVM domains support (build-time), PVH domains are not supported yet;
+ * - Only legacy COM{1,2,3,4} resources via Kconfig, custom I/O ports/IRQs
+ *   are not supported;
+ * - Only Xen console as a backend, no inter-domain communication (similar to
+ *   vpl011 on Arm);
+ * - Only 8n1 emulation (8-bit data, no parity, 1 stop bit);
+ * - No toolstack integration;
+ * - No baud rate emulation (reports 115200 baud to the guest OS);
+ * - No FIFO-less mode emulation;
+ * - No RX FIFO interrupt moderation (FCR) emulation;
+ * - No integration w/ VM snapshotting (HVM_REGISTER_SAVE_RESTORE() and
+ *   friends);
+ * - No ISA IRQ sharing allowed;
+ * - No MMIO-based UART emulation.
+ */
+
+#define pr_prefix               "ns16550"
+#define pr_fmt(fmt)             pr_prefix ": " fmt
+#define pr_log_level            CONFIG_VUART_NS16550_LOG_LEVEL
+
+#include <xen/8250-uart.h>
+#include <xen/console.h>
+#include <xen/iocap.h>
+#include <xen/ioreq.h>
+#include <xen/resource.h>
+#include <xen/vuart.h>
+#include <xen/xvmalloc.h>
+
+#include <public/io/console.h>
+
+#define pr_err(fmt, args...) do { \
+    gprintk(KERN_ERR, pr_fmt(fmt), ## args); \
+} while (0)
+
+#define pr_warn(fmt, args...) do { \
+    if ( pr_log_level >= 1) \
+        gprintk(KERN_WARNING, pr_fmt(fmt), ## args); \
+} while (0)
+
+#define pr_info(fmt, args...) do { \
+    if ( pr_log_level >= 2 ) \
+        gprintk(KERN_INFO, pr_fmt(fmt), ## args); \
+} while (0)
+
+#define pr_debug(fmt, args...) do { \
+    if ( pr_log_level >= 3 ) \
+        gprintk(KERN_DEBUG, pr_fmt(fmt), ## args); \
+} while (0)
+
+#if defined(CONFIG_VUART_NS16550_PC_COM1)
+#define X86_PC_UART_IDX         0
+#elif defined(CONFIG_VUART_NS16550_PC_COM2)
+#define X86_PC_UART_IDX         1
+#elif defined(CONFIG_VUART_NS16550_PC_COM3)
+#define X86_PC_UART_IDX         2
+#elif defined(CONFIG_VUART_NS16550_PC_COM4)
+#define X86_PC_UART_IDX         3
+#else
+#error "Unsupported I/O port"
+#endif
+
+#ifdef CONFIG_VUART_NS16550_DEBUG
+#define guest_prefix            "FROM GUEST "
+#else
+#define guest_prefix            ""
+#endif
+
+/*
+ * Number of supported registers in the UART.
+ */
+#define NS16550_REGS_NUM        ( UART_SCR + 1 )
+
+/*
+ * Number of emulated registers.
+ *
+ * - Emulated registers [0..NS16550_REGS_NUM] are R/W registers for DLAB=0.
+ * - DLAB=1, R/W, DLL            = NS16550_REGS_NUM + 0
+ * - DLAB=1, R/W, DLM            = NS16550_REGS_NUM + 1
+ * -         R/O, IIR (IIR_THR)  = NS16550_REGS_NUM + 2
+ */
+#define NS16550_EMU_REGS_NUM    ( NS16550_REGS_NUM + 3 )
+
+/*
+ * Virtual NS16550 device state.
+ */
+struct vuart_ns16550 {
+    struct xencons_interface cons;      /* Emulated RX/TX FIFOs */
+    uint8_t regs[NS16550_EMU_REGS_NUM]; /* Emulated registers */
+    unsigned int irq;                   /* Emulated IRQ# */
+    uint64_t io_addr;                   /* Emulated I/O region base address */
+    uint64_t io_size;                   /* Emulated I/O region size */
+    const char *name;                   /* Device name */
+    struct domain *owner;               /* Owner domain */
+    spinlock_t lock;                    /* Protection */
+};
+
+/*
+ * Virtual device description.
+ */
+struct virtdev_desc {
+    const char *name;
+    const struct resource *res;
+};
+
+/*
+ * Legacy IBM PC NS16550 resources.
+ * There are only 4 I/O port ranges, hardcoding all of them here.
+ */
+static const struct virtdev_desc x86_pc_uarts[4] = {
+    [0] = {
+        .name = "COM1",
+        .res = (const struct resource[]){
+            { .type = IORESOURCE_IO,  .addr = 0x3f8, .size = NS16550_REGS_NUM 
},
+            { .type = IORESOURCE_IRQ, .addr = 4,     .size = 1 },
+            { .type = IORESOURCE_UNKNOWN },
+        },
+    },
+    [1] = {
+        .name = "COM2",
+        .res = (const struct resource[]){
+            { .type = IORESOURCE_IO,  .addr = 0x2f8, .size = NS16550_REGS_NUM 
},
+            { .type = IORESOURCE_IRQ, .addr = 3,     .size = 1 },
+            { .type = IORESOURCE_UNKNOWN },
+        },
+    },
+    [2] = {
+        .name = "COM3",
+        .res = (const struct resource[]){
+            { .type = IORESOURCE_IO,  .addr = 0x3e8, .size = NS16550_REGS_NUM 
},
+            { .type = IORESOURCE_IRQ, .addr = 4,     .size = 1 },
+            { .type = IORESOURCE_UNKNOWN },
+        },
+    },
+    [3] = {
+        .name = "COM4",
+        .res = (const struct resource[]){
+            { .type = IORESOURCE_IO,  .addr = 0x2e8, .size = NS16550_REGS_NUM 
},
+            { .type = IORESOURCE_IRQ, .addr = 3,     .size = 1 },
+            { .type = IORESOURCE_UNKNOWN },
+        },
+    },
+};
+
+static bool ns16550_fifo_rx_empty(const struct vuart_ns16550 *vdev)
+{
+    const struct xencons_interface *cons = &vdev->cons;
+
+    return cons->in_prod == cons->in_cons;
+}
+
+static bool ns16550_fifo_rx_full(const struct vuart_ns16550 *vdev)
+{
+    const struct xencons_interface *cons = &vdev->cons;
+
+    return cons->in_prod - cons->in_cons == ARRAY_SIZE(cons->in);
+}
+
+static void ns16550_fifo_rx_reset(struct vuart_ns16550 *vdev)
+{
+    struct xencons_interface *cons = &vdev->cons;
+
+    cons->in_cons = cons->in_prod;
+}
+
+static int ns16550_fifo_rx_getchar(struct vuart_ns16550 *vdev)
+{
+    struct xencons_interface *cons = &vdev->cons;
+    int rc;
+
+    if ( ns16550_fifo_rx_empty(vdev) )
+    {
+        pr_debug("%s: RX FIFO empty\n", vdev->name);
+        rc = -ENODATA;
+    }
+    else
+    {
+        rc = cons->in[MASK_XENCONS_IDX(cons->in_cons, cons->in)];
+        cons->in_cons++;
+    }
+
+    return rc;
+}
+
+static int ns16550_fifo_rx_putchar(struct vuart_ns16550 *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 ( ns16550_fifo_rx_full(vdev) )
+    {
+        pr_debug("%s: RX FIFO full; resetting\n", vdev->name);
+        ns16550_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 ns16550_fifo_tx_empty(const struct vuart_ns16550 *vdev)
+{
+    const struct xencons_interface *cons = &vdev->cons;
+
+    return cons->out_prod == cons->out_cons;
+}
+
+static void ns16550_fifo_tx_reset(struct vuart_ns16550 *vdev)
+{
+    struct xencons_interface *cons = &vdev->cons;
+
+    cons->out_prod = 0;
+    ASSERT(cons->out_cons == cons->out_prod);
+}
+
+/*
+ * Flush cached output to Xen console.
+ */
+static void ns16550_fifo_tx_flush(struct vuart_ns16550 *vdev)
+{
+    struct xencons_interface *cons = &vdev->cons;
+
+    if ( ns16550_fifo_tx_empty(vdev) )
+        return;
+
+    ASSERT(cons->out_prod < ARRAY_SIZE(cons->out));
+    cons->out[cons->out_prod] = '\0';
+    cons->out_prod++;
+
+    guest_printk(vdev->owner, guest_prefix "%s", cons->out);
+
+    ns16550_fifo_tx_reset(vdev);
+}
+
+/*
+ * Accumulate guest OS output before sending to Xen console.
+ */
+static void ns16550_fifo_tx_putchar(struct vuart_ns16550 *vdev, char ch)
+{
+    struct xencons_interface *cons = &vdev->cons;
+
+    if ( !is_console_printable(ch) )
+        return;
+
+    if ( ch != '\0' )
+    {
+        cons->out[cons->out_prod] = ch;
+        cons->out_prod++;
+    }
+
+    if ( cons->out_prod == ARRAY_SIZE(cons->out) - 1 ||
+         ch == '\n' || ch == '\0' )
+        ns16550_fifo_tx_flush(vdev);
+}
+
+static inline uint8_t cf_check ns16550_dlab_get(const struct vuart_ns16550 
*vdev)
+{
+    return vdev->regs[UART_LCR] & UART_LCR_DLAB ? 1 : 0;
+}
+
+static bool cf_check ns16550_iir_check_lsi(const struct vuart_ns16550 *vdev)
+{
+    return !!(vdev->regs[UART_LSR] & UART_LSR_MASK);
+}
+
+static bool cf_check ns16550_iir_check_rda(const struct vuart_ns16550 *vdev)
+{
+    return !ns16550_fifo_rx_empty(vdev);
+}
+
+static bool cf_check ns16550_iir_check_thr(const struct vuart_ns16550 *vdev)
+{
+    return !!(vdev->regs[NS16550_REGS_NUM + UART_IIR] & UART_IIR_THR);
+}
+
+static bool cf_check ns16550_iir_check_msi(const struct vuart_ns16550 *vdev)
+{
+    return !!(vdev->regs[UART_MSR] & UART_MSR_CHANGE);
+}
+
+/*
+ * Get the interrupt identity reason.
+ *
+ * IIR is re-calculated once called, because NS16550 always reports high
+ * priority events first.
+ * regs[NS16550_REGS_NUM + UART_IIR] is used to store THR reason only.
+ */
+static uint8_t ns16550_iir_get(const struct vuart_ns16550 *vdev)
+{
+    /*
+     * Interrupt identity reasons by priority.
+     * NB: high priority are at lower indexes below.
+     */
+    static const struct {
+        bool (*check)(const struct vuart_ns16550 *vdev);
+        uint8_t ier;
+        uint8_t iir;
+    } iir_by_prio[] = {
+        [0] = { ns16550_iir_check_lsi, UART_IER_ELSI,   UART_IIR_LSI },
+        [1] = { ns16550_iir_check_rda, UART_IER_ERDAI,  UART_IIR_RDA },
+        [2] = { ns16550_iir_check_thr, UART_IER_ETHREI, UART_IIR_THR },
+        [3] = { ns16550_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/ NS16550 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 ns16550_irq_assert(const struct vuart_ns16550 *vdev)
+{
+    struct domain *d = vdev->owner;
+    int vector;
+
+    if ( has_vpic(d) ) /* HVM */
+        vector = hvm_isa_irq_assert(d, vdev->irq, vioapic_get_vector);
+    else
+        ASSERT_UNREACHABLE();
+
+    pr_debug("%s: IRQ#%d vector %d assert\n", vdev->name, vdev->irq, vector);
+}
+
+static void ns16550_irq_deassert(const struct vuart_ns16550 *vdev)
+{
+    struct domain *d = vdev->owner;
+
+    if ( has_vpic(d) ) /* HVM */
+        hvm_isa_irq_deassert(d, vdev->irq);
+    else
+        ASSERT_UNREACHABLE();
+
+    pr_debug("%s: IRQ#%d deassert\n", vdev->name, vdev->irq);
+}
+
+/*
+ * Assert/deassert virtual NS16550 interrupt line.
+ */
+static void ns16550_irq_check(const struct vuart_ns16550 *vdev)
+{
+    uint8_t iir = ns16550_iir_get(vdev);
+
+    if ( iir & UART_IIR_NOINT )
+        ns16550_irq_assert(vdev);
+    else
+        ns16550_irq_deassert(vdev);
+
+    pr_debug("%s: IRQ#%d IIR 0x%02x %s\n", vdev->name, vdev->irq, iir,
+             (iir & UART_IIR_NOINT) ? "deassert" : "assert");
+}
+
+/*
+ * Emulate 8-bit write access to NS16550 register.
+ */
+static int ns16550_io_write8(
+    struct vuart_ns16550 *vdev, uint32_t reg, uint8_t *data)
+{
+    uint8_t *regs = vdev->regs;
+    uint8_t val = *data;
+    int rc = 0;
+
+    if ( ns16550_dlab_get(vdev) && (reg == UART_DLL || reg == UART_DLM) )
+        regs[NS16550_REGS_NUM + reg] = val;
+    else
+    {
+        switch ( reg )
+        {
+        case UART_THR:
+            if ( regs[UART_MCR] & UART_MCR_LOOP )
+            {
+                (void)ns16550_fifo_rx_putchar(vdev, val);
+                regs[UART_LSR] |= UART_LSR_OE;
+            }
+            else
+                ns16550_fifo_tx_putchar(vdev, val);
+
+            regs[NS16550_REGS_NUM + UART_IIR] |= UART_IIR_THR;
+
+            break;
+
+        case UART_IER:
+            /*
+             * NB: Make sure THR interrupt is re-triggered once guest OS
+             * re-enabled ETHREI in EIR.
+             */
+            if ( val & regs[UART_IER] & UART_IER_ETHREI )
+                regs[NS16550_REGS_NUM + UART_IIR] |= UART_IIR_THR;
+
+            regs[UART_IER] = val & UART_IER_MASK;
+
+            break;
+
+        case UART_FCR: /* WO */
+            if ( val & UART_FCR_RESERVED0 )
+                pr_warn("%s: FCR: attempt to set reserved bit: %x\n",
+                        vdev->name, UART_FCR_RESERVED0);
+
+            if ( val & UART_FCR_RESERVED1 )
+                pr_warn("%s: FCR: attempt to set reserved bit: %x\n",
+                        vdev->name, UART_FCR_RESERVED1);
+
+            if ( val & UART_FCR_CLRX )
+                ns16550_fifo_rx_reset(vdev);
+
+            if ( val & UART_FCR_CLTX )
+                ns16550_fifo_tx_flush(vdev);
+
+            if ( val & UART_FCR_ENABLE )
+                val &= UART_FCR_ENABLE | UART_FCR_DMA | UART_FCR_TRG_MASK;
+            else
+                val = 0;
+
+            regs[UART_FCR] = val;
+
+            break;
+
+        case UART_LCR:
+            regs[UART_LCR] = val;
+            break;
+
+        case UART_MCR: {
+            uint8_t msr_curr, msr_next, msr_delta;
+
+            msr_curr = regs[UART_MSR];
+            msr_next = 0;
+            msr_delta = 0;
+
+            if ( val & UART_MCR_RESERVED0 )
+                pr_warn("%s: MCR: attempt to set reserved bit: %x\n",
+                        vdev->name, UART_MCR_RESERVED0);
+
+            if ( val & UART_MCR_TCRTLR )
+                pr_warn("%s: MCR: not supported: %x\n",
+                        vdev->name, UART_MCR_TCRTLR);
+
+            if ( val & UART_MCR_RESERVED1 )
+                pr_warn("%s: MCR: attempt to set reserved bit: %x\n",
+                        vdev->name, UART_MCR_RESERVED1);
+
+            /* Set modem status */
+            if ( val & UART_MCR_LOOP )
+            {
+                if ( val & UART_MCR_DTR )
+                    msr_next |= UART_MSR_DSR;
+                if ( val & UART_MCR_RTS )
+                    msr_next |= UART_MSR_CTS;
+                if ( val & UART_MCR_OUT1 )
+                    msr_next |= UART_MSR_RI;
+                if ( val & UART_MCR_OUT2 )
+                    msr_next |= UART_MSR_DCD;
+            }
+            else
+                msr_next |= UART_MSR_DCD | UART_MSR_DSR | UART_MSR_CTS;
+
+            /* Calculate changes in modem status */
+            if ( (msr_curr & UART_MSR_CTS) ^ (msr_next & UART_MSR_CTS) )
+                msr_delta |= UART_MSR_DCTS;
+            if ( (msr_curr & UART_MCR_RTS) ^ (msr_next & UART_MCR_RTS) )
+                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;
+        }
+
+        ns16550_irq_check(vdev);
+    }
+
+    return rc;
+}
+
+/*
+ * Emulate 16-bit write access to NS16550 register.
+ * NB: some guest OSes use outw() to access UART_DLL.
+ */
+static int ns16550_io_write16(
+    struct vuart_ns16550 *vdev, uint32_t reg, uint16_t *data)
+{
+    uint16_t val = *data;
+    int rc;
+
+    if ( ns16550_dlab_get(vdev) && reg == UART_DLL )
+    {
+        vdev->regs[NS16550_REGS_NUM + UART_DLL] = val & 0xff;
+        vdev->regs[NS16550_REGS_NUM + UART_DLM] = (val >> 8) & 0xff;
+        rc = 0;
+    }
+    else
+        rc = -EINVAL;
+
+    return rc;
+}
+
+/*
+ * Emulate write access to NS16550 register.
+ */
+static int ns16550_io_write(
+    struct vuart_ns16550 *vdev, uint8_t reg, uint32_t size, uint32_t *data)
+{
+    int rc;
+
+    switch ( size )
+    {
+    case 1:
+        rc = ns16550_io_write8(vdev, reg, (uint8_t *)data);
+        break;
+
+    case 2:
+        rc = ns16550_io_write16(vdev, reg, (uint16_t *)data);
+        break;
+
+    default:
+        rc = -EINVAL;
+        break;
+    }
+
+    return rc;
+}
+
+/*
+ * Emulate 8-bit read access to NS16550 register.
+ */
+static int ns16550_io_read8(
+    struct vuart_ns16550 *vdev, uint32_t reg, uint8_t *data)
+{
+    uint8_t *regs = vdev->regs;
+    uint8_t val = 0xff;
+    int rc = 0;
+
+    if ( ns16550_dlab_get(vdev) && (reg == UART_DLL || reg == UART_DLM) )
+        val = regs[NS16550_REGS_NUM + reg];
+    else {
+        switch ( reg )
+        {
+        case UART_RBR:
+            /* NB: do not forget to clear overrun condition */
+            regs[UART_LSR] &= ~UART_LSR_OE;
+
+            rc = ns16550_fifo_rx_getchar(vdev);
+            if ( rc >= 0 )
+                val = (uint8_t)rc;
+
+            rc = 0;
+            break;
+
+        case UART_IER:
+            val = regs[UART_IER];
+            break;
+
+        case UART_IIR: /* RO */
+            val = ns16550_iir_get(vdev);
+
+            /* NB: clear IIR scratch location */
+            if ( val & UART_IIR_THR )
+                regs[NS16550_REGS_NUM + UART_IIR] &= ~UART_IIR_THR;
+
+            break;
+
+        case UART_LCR:
+            val = regs[UART_LCR];
+            break;
+
+        case UART_MCR:
+            val = regs[UART_MCR];
+            break;
+
+        case UART_LSR:
+            val = regs[UART_LSR] | UART_LSR_THRE | UART_LSR_TEMT;
+            if ( ns16550_fifo_rx_empty(vdev) )
+                val &= ~UART_LSR_DR;
+            else
+                val |= UART_LSR_DR;
+
+            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;
+
+        default:
+            rc = -EINVAL;
+            break;
+        }
+
+        ns16550_irq_check(vdev);
+    }
+
+    *data = val;
+
+    return rc;
+}
+
+/*
+ * Emulate 16-bit read access to NS16550 register.
+ */
+static int ns16550_io_read16(
+    struct vuart_ns16550 *vdev, uint32_t reg, uint16_t *data)
+{
+    uint16_t val = 0xffff;
+    int rc = -EINVAL;
+
+    if ( ns16550_dlab_get(vdev) && reg == UART_DLL )
+    {
+        val = vdev->regs[NS16550_REGS_NUM + UART_DLM] << 8 |
+              vdev->regs[NS16550_REGS_NUM + UART_DLL];
+        rc = 0;
+    }
+
+    *data = val;
+
+    return rc;
+}
+
+/*
+ * Emulate read access to NS16550 register.
+ */
+static int ns16550_io_read(
+    struct vuart_ns16550 *vdev, uint8_t reg, uint32_t size, uint32_t *data)
+{
+    int rc;
+
+    switch ( size )
+    {
+    case 1:
+        rc = ns16550_io_read8(vdev, reg, (uint8_t *)data);
+        break;
+
+    case 2:
+        rc = ns16550_io_read16(vdev, reg, (uint16_t *)data);
+        break;
+
+    default:
+        *data = 0xffffffff;
+        rc = -EINVAL;
+        break;
+    }
+
+    return rc;
+}
+
+static void cf_check ns16550_dump_state(const struct domain *d)
+{
+    struct vuart_ns16550 *vdev = d->arch.hvm.vuart;
+    const struct xencons_interface *cons;
+    const uint8_t *regs;
+
+    if ( !vdev )
+        return;
+
+    /* Allow printing state in case of a deadlock. */
+    if ( !spin_trylock(&vdev->lock) )
+        return;
+
+    cons = &vdev->cons;
+    regs = &vdev->regs[0];
+
+    printk("Virtual " pr_prefix " (%s) I/O port 0x%04"PRIx64" IRQ#%d owner 
%pd\n",
+            vdev->name, vdev->io_addr, vdev->irq, vdev->owner);
+
+    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[NS16550_REGS_NUM + UART_DLL],
+            regs[NS16550_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], ns16550_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);
+}
+
+/*
+ * Emulate I/O access to NS16550 register.
+ * Note, emulation always returns X86EMUL_OKAY, once I/O port trap is enabled.
+ */
+static int cf_check ns16550_io_handle(
+    int dir, unsigned int addr, unsigned int size, uint32_t *data)
+{
+#define op(dir)     (((dir) == IOREQ_WRITE) ? 'W' : 'R')
+    struct domain *d = rcu_lock_current_domain();
+    struct vuart_ns16550 *vdev = d->arch.hvm.vuart;
+    uint32_t reg;
+    unsigned dlab;
+    int rc;
+
+    if ( !vdev )
+    {
+        pr_err("%s: %c io 0x%04x %d: not initialized\n",
+                vdev->name, op(dir), addr, size);
+
+        ASSERT_UNREACHABLE();
+        goto out;
+    }
+
+    if ( d != vdev->owner )
+    {
+        pr_err("%s: %c io 0x%04x %d: does not match current domain %pv\n",
+                vdev->name, op(dir), addr, size, d);
+
+        ASSERT_UNREACHABLE();
+        goto out;
+    }
+
+    reg = addr - vdev->io_addr;
+    if ( !IS_ALIGNED(reg, size) )
+    {
+        pr_err("%s: %c 0x%04x %d: unaligned access\n",
+                vdev->name, op(dir), addr, size);
+        goto out;
+    }
+
+    dlab = ns16550_dlab_get(vdev);
+    if ( reg >= NS16550_REGS_NUM )
+    {
+        pr_err("%s: %c io 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32": not 
implemented\n",
+                vdev->name, op(dir), addr, size,
+                dlab, reg, *data);
+        goto out;
+    }
+
+    spin_lock(&vdev->lock);
+
+    if ( dir == IOREQ_WRITE )
+    {
+        pr_debug("%s: %c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32"\n",
+                 vdev->name, op(dir), addr, size,
+                 dlab, reg, *data);
+        rc = ns16550_io_write(vdev, reg, size, data);
+    }
+    else
+    {
+        rc = ns16550_io_read(vdev, reg, size, data);
+        pr_debug("%s: %c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32"\n",
+                 vdev->name, op(dir), addr, size,
+                 dlab, reg, *data);
+    }
+    if ( rc < 0 )
+        pr_err("%s: %c 0x%04x %d: DLAB=%d %02"PRIx32" 0x%08"PRIx32": 
unsupported access\n",
+               vdev->name, op(dir), addr, size,
+               dlab, reg, *data);
+
+    spin_unlock(&vdev->lock);
+#ifdef CONFIG_VUART_NS16550_DEBUG
+    ns16550_dump_state(d);
+#endif
+
+out:
+    rcu_unlock_domain(d);
+
+    return X86EMUL_OKAY;
+#undef op
+}
+
+static int cf_check ns16550_init(struct domain *d,
+                                 struct vuart_params *params)
+{
+    const struct virtdev_desc *desc = &x86_pc_uarts[X86_PC_UART_IDX];
+    const struct resource *r = desc->res;
+    const uint16_t divisor = (UART_CLOCK_HZ / 115200) >> 4;
+    struct vuart_ns16550 *vdev;
+    int rc;
+
+    BUG_ON(d->arch.hvm.vuart);
+
+    if ( !is_hvm_domain(d) )
+    {
+        pr_err("%s: not an HVM domain\n", desc->name);
+        return -ENOSYS;
+    }
+
+    vdev = xvzalloc(typeof(*vdev));
+    if ( !vdev )
+    {
+        pr_err("%s: failed to allocate memory\n", desc->name);
+        return -ENOMEM;
+    }
+
+    for_each_resource(r)
+    {
+        if ( r->type & IORESOURCE_IO )
+        {
+            /* Disallow sharing physical I/O port */
+            rc = ioports_deny_access(d, r->addr, r->addr + r->size - 1);
+            if ( rc )
+            {
+                pr_err("%s: virtual I/O port range 
[0x%04x"PRIx64"..0x%04x"PRIx64"]: conflict w/ physical range\n",
+                        desc->name,
+                        (unsigned int)r->addr,
+                        (unsigned int)(r->addr + r->size - 1));
+                return rc;
+            }
+
+            register_portio_handler(d, r->addr, r->size, ns16550_io_handle);
+
+            vdev->io_addr = r->addr;
+            vdev->io_size = r->size;
+        }
+        else if ( r->type & IORESOURCE_IRQ )
+        {
+            /* Disallow sharing physical IRQ */
+            rc = irq_deny_access(d, r->addr);
+            if ( rc )
+            {
+                pr_err("%s: virtual IRQ#%"PRIu64": conflict w/ physical IRQ: 
%d\n",
+                        desc->name, r->addr, rc);
+                return rc;
+            }
+
+            vdev->irq = r->addr;
+        }
+        else
+            ASSERT_UNREACHABLE();
+    }
+
+    spin_lock_init(&vdev->lock);
+
+    vdev->owner = d;
+    vdev->name = desc->name;
+
+    /* NB: report 115200 baud rate */
+    vdev->regs[NS16550_REGS_NUM + UART_DLL] = divisor & 0xff;
+    vdev->regs[NS16550_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xff;
+
+    /* NS16550 shall assert UART_IIR_THR whenever transmitter is empty. */
+    vdev->regs[NS16550_REGS_NUM + UART_IIR] = UART_IIR_THR;
+
+    d->arch.hvm.vuart = vdev;
+
+    spin_lock(&vdev->lock);
+    ns16550_irq_check(vdev);
+    spin_unlock(&vdev->lock);
+
+    return 0;
+}
+
+static void cf_check ns16550_deinit(struct domain *d)
+{
+    struct vuart_ns16550 *vdev = d->arch.hvm.vuart;
+
+    if ( !vdev )
+        return;
+
+    spin_lock(&vdev->lock);
+
+    ns16550_fifo_tx_flush(vdev);
+
+    spin_unlock(&vdev->lock);
+
+    XVFREE(d->arch.hvm.vuart);
+}
+
+static int cf_check ns16550_put_rx(struct domain *d, char ch)
+{
+    struct vuart_ns16550 *vdev = d->arch.hvm.vuart;
+    uint8_t *regs;
+    uint8_t dlab;
+    int rc;
+
+    ASSERT(d == vdev->owner);
+    if ( !vdev )
+        return -ENODEV;
+
+    spin_lock(&vdev->lock);
+
+    dlab = ns16550_dlab_get(vdev);
+    regs = vdev->regs;
+
+    if ( dlab )
+    {
+        pr_debug("%s: THR/RBR access disabled: DLAB=1\n", vdev->name);
+        rc = -EBUSY;
+    }
+    else if ( regs[UART_MCR] & UART_MCR_LOOP )
+    {
+        pr_debug("%s: THR/RBR access disabled: loopback mode\n", vdev->name);
+        rc = -EBUSY;
+    }
+    else
+    {
+        uint8_t val = 0;
+
+        rc = ns16550_fifo_rx_putchar(vdev, ch);
+        if ( rc == -ENOSPC )
+            val |= UART_LSR_OE;
+
+        /* NB: UART_LSR_DR is also set when UART_LSR is accessed. */
+        regs[UART_LSR] |= UART_LSR_DR | val;
+
+        /*
+         * Echo the user input on Xen console iff Xen console input is owned
+         * by NS16550 domain.
+         * NB: use 'console_timestamps=none' to disable Xen timestamps.
+         */
+        if ( is_console_printable(ch) )
+            guest_printk(d, "%c", ch);
+
+        /* FIXME: check FCR when to fire an interrupt */
+        ns16550_irq_check(vdev);
+    }
+
+    spin_unlock(&vdev->lock);
+#ifdef CONFIG_VUART_NS16550_DEBUG
+    ns16550_dump_state(d);
+#endif
+
+    return rc;
+}
+
+static const struct vuart_ops ns16550_ops = {
+    .add_node   = NULL,
+    .init       = ns16550_init,
+    .deinit     = ns16550_deinit,
+    .dump_state = ns16550_dump_state,
+    .put_rx     = ns16550_put_rx,
+};
+
+VUART_REGISTER(ns16550, &ns16550_ops);
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/common/emul/vuart/vuart.c b/xen/common/emul/vuart/vuart.c
index 14a7f8bd8b79..7971813a723d 100644
--- a/xen/common/emul/vuart/vuart.c
+++ b/xen/common/emul/vuart/vuart.c
@@ -99,6 +99,10 @@ bool domain_has_vuart(const struct domain *d)
 {
     uint32_t mask = 0;
 
+#ifdef CONFIG_VUART_NS16550
+    mask |= XEN_X86_EMU_NS16550;
+#endif
+
     return !!(d->emulation_flags & mask);
 }
 
diff --git a/xen/include/public/arch-x86/xen.h 
b/xen/include/public/arch-x86/xen.h
index fc2487986642..f905e1252c70 100644
--- a/xen/include/public/arch-x86/xen.h
+++ b/xen/include/public/arch-x86/xen.h
@@ -283,13 +283,15 @@ struct xen_arch_domainconfig {
 #define XEN_X86_EMU_USE_PIRQ        (1U<<_XEN_X86_EMU_USE_PIRQ)
 #define _XEN_X86_EMU_VPCI           10
 #define XEN_X86_EMU_VPCI            (1U<<_XEN_X86_EMU_VPCI)
+#define _XEN_X86_EMU_NS16550        11
+#define XEN_X86_EMU_NS16550         (1U<<_XEN_X86_EMU_NS16550)
 
 #define XEN_X86_EMU_ALL             (XEN_X86_EMU_LAPIC | XEN_X86_EMU_HPET |  \
                                      XEN_X86_EMU_PM | XEN_X86_EMU_RTC |      \
                                      XEN_X86_EMU_IOAPIC | XEN_X86_EMU_PIC |  \
                                      XEN_X86_EMU_VGA | XEN_X86_EMU_IOMMU |   \
                                      XEN_X86_EMU_PIT | XEN_X86_EMU_USE_PIRQ |\
-                                     XEN_X86_EMU_VPCI)
+                                     XEN_X86_EMU_VPCI | XEN_X86_EMU_NS16550)
     uint32_t emulation_flags;
 
 /*
diff --git a/xen/include/xen/resource.h b/xen/include/xen/resource.h
index 5d103631288d..56fb8101edd6 100644
--- a/xen/include/xen/resource.h
+++ b/xen/include/xen/resource.h
@@ -31,4 +31,7 @@ struct resource {
 
 #define resource_size(res)      ((res)->size)
 
+#define for_each_resource(res) \
+    for ( ; (res) && (res)->type != IORESOURCE_UNKNOWN; (res)++ )
+
 #endif /* XEN__RESOURCE_H */
-- 
2.34.1





 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.