On Thu, 31 Jul 2025, [email protected] wrote:
> From: Denis Mukhin <[email protected]>
>
> 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 <[email protected]>
> ---
> Changes since v3:
> - feedback addressed
> - adjusted to new vUART framework APIs
> - Link to v3:
> https://lore.kernel.org/xen-devel/[email protected]/
> ---
> 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;
Why is UART_LSR_OE set unconditionally here instead of checking if
ns16550_fifo_rx_putchar returned -ENOSPC?
> + }
> + 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;
Should we check UART_MSR_DSR instead of UART_MCR_RTS to set
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
>
>