@@ -71,4 +71,52 @@ config HVM_FEP
If unsure, say N.
+config VUART_NS16550
+ bool "NS16550-compatible UART Emulation" if EXPERT
+ depends on HAS_IOPORTS
+ 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.
+
endif
@@ -29,3 +29,4 @@ obj-y += vm_event.o
obj-y += vmsi.o
obj-y += vpic.o
obj-y += vpt.o
+obj-$(CONFIG_VUART_NS16550) += vuart-ns16550.o
@@ -680,6 +680,13 @@ int hvm_domain_initialise(struct domain *d,
if ( rc != 0 )
goto fail1;
+ if ( domain_has_vuart(d) )
+ {
+ rc = virtdev_uart_init(d, NULL);
+ if ( rc != 0 )
+ goto out_vioapic_deinit;
+ }
+
stdvga_init(d);
rtc_init(d);
@@ -700,6 +707,9 @@ int hvm_domain_initialise(struct domain *d,
return 0;
fail2:
+ if ( domain_has_vuart(d) )
+ virtdev_uart_exit(d);
+ out_vioapic_deinit:
vioapic_deinit(d);
fail1:
if ( is_hardware_domain(d) )
new file mode 100644
@@ -0,0 +1,928 @@
+/* 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 <asm/setup.h> /* max_init_domid */
+#include <public/io/console.h>
+#include <xen/8250-uart.h>
+#include <xen/ctype.h>
+#include <xen/ioreq.h>
+#include <xen/resource.h>
+#include <xen/virtdev-uart.h>
+#include <xen/xvmalloc.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
+
+/*
+ * 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, "%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;
+}
+
+/*
+ * 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 )
+ hvm_isa_irq_assert(vdev->owner, vdev->irq, NULL);
+ else
+ hvm_isa_irq_deassert(vdev->owner, vdev->irq);
+
+ pr_debug("%s: IRQ#%d %x %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_RESERVED1 )
+ pr_warn("%s: MCR: attempt to set reserved bit: %x\n",
+ vdev->name, UART_MCR_RESERVED1);
+
+ if ( val & UART_MCR_RESERVED2 )
+ pr_warn("%s: MCR: attempt to set reserved bit: %x\n",
+ vdev->name, UART_MCR_RESERVED2);
+
+ /* 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 = 0xFFU;
+ 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;
+
+ 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 = 0xFFFFU;
+ 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 = 0xFFFFFFFFU;
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static void cf_check ns16550_dump(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" x%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(d);
+#endif
+
+out:
+ rcu_unlock_domain(d);
+
+ return X86EMUL_OKAY;
+#undef op
+}
+
+static int cf_check ns16550_init(struct domain *d,
+ struct virtdev_uart_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;
+
+ BUG_ON(d->arch.hvm.vuart);
+
+ vdev = xvzalloc(typeof(*vdev));
+ if ( !vdev )
+ return -ENOMEM;
+
+ for_each_resource(r)
+ {
+ if ( r->type & IORESOURCE_IO )
+ {
+ register_portio_handler(d, r->addr, r->size, ns16550_io_handle);
+
+ /* Used to assert I/O port handler */
+ vdev->io_addr = r->addr;
+ vdev->io_size = r->size;
+ }
+ else if ( r->type & IORESOURCE_IRQ )
+ /* "Claim" virtual IRQ; assumes no ISA-device IRQ sharing */
+ vdev->irq = r->addr;
+ else
+ ASSERT_UNREACHABLE();
+ }
+
+ vdev->owner = d;
+ vdev->name = desc->name;
+
+ /* NB: report 115200 baud rate */
+ vdev->regs[NS16550_REGS_NUM + UART_DLL] = divisor & 0xFFU;
+ vdev->regs[NS16550_REGS_NUM + UART_DLM] = (divisor >> 8) & 0xFFU;
+
+ spin_lock_init(&vdev->lock);
+ hvm_isa_irq_deassert(vdev->owner, vdev->irq);
+
+ d->arch.hvm.vuart = vdev;
+
+ return 0;
+}
+
+static void cf_check ns16550_exit(struct domain *d)
+{
+ struct vuart_ns16550 *vdev = d->arch.hvm.vuart;
+
+ if ( vdev )
+ {
+ spin_lock(&vdev->lock);
+ ns16550_fifo_tx_flush(vdev);
+ spin_unlock(&vdev->lock);
+ }
+
+ XFREE(d->arch.hvm.vuart);
+}
+
+static int cf_check ns16550_putchar(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.
+ * NB: use 'console_timestamps=none' to disable Xen timestamps.
+ */
+ printk("%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(d);
+#endif
+
+ return rc;
+}
+
+VIRTDEV_UART_REGISTER(ns16550);
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
@@ -149,6 +149,10 @@ struct hvm_domain {
#ifdef CONFIG_MEM_SHARING
struct mem_sharing_domain mem_sharing;
#endif
+
+#ifdef CONFIG_HAS_VUART
+ void *vuart; /* Virtual UART handle. */
+#endif
};
#endif /* __ASM_X86_HVM_DOMAIN_H__ */
@@ -12,6 +12,9 @@ int virtdev_uart_init(struct domain *d, struct virtdev_uart_params *params)
int rc;
ASSERT(__start_virtdev_uart);
+#if defined(__i386__) || defined(__x86_64__)
+ ASSERT(d->arch.emulation_flags & VIRTDEV_UART);
+#endif
rc = __start_virtdev_uart->init(d, params);
if ( rc )
@@ -28,6 +31,9 @@ int virtdev_uart_init(struct domain *d, struct virtdev_uart_params *params)
void virtdev_uart_exit(struct domain *d)
{
ASSERT(__start_virtdev_uart);
+#if defined(__i386__) || defined(__x86_64__)
+ ASSERT(d->arch.emulation_flags & VIRTDEV_UART);
+#endif
__start_virtdev_uart->exit(d);
@@ -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 */