[PATCH v7 03/16] emul/ns16x50: implement emulator stub

dmukhin@xen.org posted 16 patches 1 day, 4 hours ago
[PATCH v7 03/16] emul/ns16x50: implement emulator stub
Posted by dmukhin@xen.org 1 day, 4 hours ago
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