summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Serial
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/Serial')
-rw-r--r--src/VBox/Devices/Serial/DevOxPcie958.cpp688
-rw-r--r--src/VBox/Devices/Serial/DevSerial.cpp537
-rw-r--r--src/VBox/Devices/Serial/DrvChar.cpp479
-rw-r--r--src/VBox/Devices/Serial/DrvHostSerial.cpp736
-rw-r--r--src/VBox/Devices/Serial/DrvNamedPipe.cpp1110
-rw-r--r--src/VBox/Devices/Serial/DrvRawFile.cpp287
-rw-r--r--src/VBox/Devices/Serial/DrvTCP.cpp651
-rw-r--r--src/VBox/Devices/Serial/Makefile.kup0
-rw-r--r--src/VBox/Devices/Serial/UartCore.cpp1878
-rw-r--r--src/VBox/Devices/Serial/UartCore.h348
10 files changed, 6714 insertions, 0 deletions
diff --git a/src/VBox/Devices/Serial/DevOxPcie958.cpp b/src/VBox/Devices/Serial/DevOxPcie958.cpp
new file mode 100644
index 00000000..c034a7c8
--- /dev/null
+++ b/src/VBox/Devices/Serial/DevOxPcie958.cpp
@@ -0,0 +1,688 @@
+/* $Id: DevOxPcie958.cpp $ */
+/** @file
+ * DevOxPcie958 - Oxford Semiconductor OXPCIe958 PCI Express bridge to octal serial port emulation
+ */
+
+/*
+ * Copyright (C) 2018-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/** @page pg_dev_oxpcie958 OXPCIe958 - Oxford Semiconductor OXPCIe958 PCI Express bridge to octal serial port emulation.
+ * @todo Write something
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_SERIAL
+#include <VBox/pci.h>
+#include <VBox/msi.h>
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/pdmpci.h>
+#include <VBox/vmm/vm.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/list.h>
+#include <iprt/asm.h>
+
+#include "VBoxDD.h"
+#include "UartCore.h"
+
+
+/** @name PCI device related constants.
+ * @} */
+/** The PCI device ID. */
+#define OX958_PCI_DEVICE_ID 0xc308
+/** The PCI vendor ID. */
+#define OX958_PCI_VENDOR_ID 0x1415
+/** Where the MSI capability starts. */
+#define OX958_PCI_MSI_CAP_OFS 0x80
+/** Where the MSI-X capability starts. */
+#define OX958_PCI_MSIX_CAP_OFS (OX958_PCI_MSI_CAP_OFS + VBOX_MSI_CAP_SIZE_64)
+/** The BAR for the MSI-X related functionality. */
+#define OX958_PCI_MSIX_BAR 1
+/** @} */
+
+/** Maximum number of UARTs supported by the device. */
+#define OX958_UARTS_MAX 16
+
+/** Offset op the class code and revision ID register. */
+#define OX958_REG_CC_REV_ID 0x00
+/** Offset fof the UART count register. */
+#define OX958_REG_UART_CNT 0x04
+/** Offset of the global UART IRQ status register. */
+#define OX958_REG_UART_IRQ_STS 0x08
+/** Offset of the global UART IRQ enable register. */
+#define OX958_REG_UART_IRQ_ENABLE 0x0c
+/** Offset of the global UART IRQ disable register. */
+#define OX958_REG_UART_IRQ_DISABLE 0x10
+/** Offset of the global UART wake IRQ enable register. */
+#define OX958_REG_UART_WAKE_IRQ_ENABLE 0x14
+/** Offset of the global UART wake IRQ disable register. */
+#define OX958_REG_UART_WAKE_IRQ_DISABLE 0x18
+/** Offset of the region in MMIO space where the UARTs actually start. */
+#define OX958_REG_UART_REGION_OFFSET 0x1000
+/** Register region size for each UART. */
+#define OX958_REG_UART_REGION_SIZE 0x200
+/** Offset where the DMA channels registers start for each UART. */
+#define OX958_REG_UART_DMA_REGION_OFFSET 0x100
+
+
+/**
+ * OXPCIe958 UART core.
+ */
+typedef struct OX958UART
+{
+ /** The UART core. */
+ UARTCORE UartCore;
+ /** DMA address configured. */
+ RTGCPHYS GCPhysDmaAddr;
+ /** The DMA transfer length configured. */
+ uint32_t cbDmaXfer;
+ /** The DMA status registers. */
+ uint32_t u32RegDmaSts;
+} OX958UART;
+/** Pointer to a OXPCIe958 UART core. */
+typedef OX958UART *POX958UART;
+
+
+/**
+ * OXPCIe958 device instance data.
+ */
+typedef struct DEVOX958
+{
+ /** The corresponding PCI device. */
+ PDMPCIDEV PciDev;
+
+ /** Pointer to the device instance - R3 ptr. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to the device instance - R0 ptr */
+ PPDMDEVINSR0 pDevInsR0;
+ /** Pointer to the device instance - RC ptr. */
+ PPDMDEVINSRC pDevInsRC;
+ /** Flag whether R0 is enabled. */
+ bool fR0Enabled;
+ /** Flag whether RC is enabled. */
+ bool fRCEnabled;
+ /** Alignment. */
+ bool afAlignment[2];
+ /** UART global IRQ status. */
+ volatile uint32_t u32RegIrqStsGlob;
+ /** UART global IRQ enable mask. */
+ volatile uint32_t u32RegIrqEnGlob;
+ /** UART wake IRQ enable mask. */
+ volatile uint32_t u32RegIrqEnWake;
+ /** Number of UARTs configured. */
+ uint32_t cUarts;
+ /** MMIO Base address. */
+ RTGCPHYS GCPhysMMIO;
+ /** The UARTs. */
+ OX958UART aUarts[OX958_UARTS_MAX];
+
+} DEVOX958;
+/** Pointer to an OXPCIe958 device instance. */
+typedef DEVOX958 *PDEVOX958;
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Update IRQ status of the device.
+ *
+ * @returns nothing.
+ * @param pThis The OXPCIe958 device instance.
+ */
+static void ox958IrqUpdate(PDEVOX958 pThis)
+{
+ uint32_t u32IrqSts = ASMAtomicReadU32(&pThis->u32RegIrqStsGlob);
+ uint32_t u32IrqEn = ASMAtomicReadU32(&pThis->u32RegIrqEnGlob);
+
+ if (u32IrqSts & u32IrqEn)
+ PDMDevHlpPCISetIrq(pThis->CTX_SUFF(pDevIns), 0, PDM_IRQ_LEVEL_HIGH);
+ else
+ PDMDevHlpPCISetIrq(pThis->CTX_SUFF(pDevIns), 0, PDM_IRQ_LEVEL_LOW);
+}
+
+
+/**
+ * Performs a register read from the given UART.
+ *
+ * @returns nothing.
+ * @param pThis The OXPCIe958 device instance.
+ * @param pUart The UART accessed.
+ * @param offUartReg Offset of the register being read.
+ * @param pv Where to store the read data.
+ * @param cb Number of bytes to read.
+ */
+static int ox958UartRegRead(PDEVOX958 pThis, POX958UART pUart, uint32_t offUartReg, void *pv, unsigned cb)
+{
+ int rc = VINF_SUCCESS;
+ RT_NOREF(pThis);
+
+ if (offUartReg >= OX958_REG_UART_DMA_REGION_OFFSET)
+ {
+ /* Access to the DMA registers. */
+ }
+ else /* Access UART registers. */
+ rc = uartRegRead(&pUart->UartCore, offUartReg, (uint32_t *)pv, cb);
+
+ return rc;
+}
+
+
+/**
+ * Performs a register write to the given UART.
+ *
+ * @returns nothing.
+ * @param pThis The OXPCIe958 device instance.
+ * @param pUart The UART accessed.
+ * @param offUartReg Offset of the register being written.
+ * @param pv The data to write.
+ * @param cb Number of bytes to write.
+ */
+static int ox958UartRegWrite(PDEVOX958 pThis, POX958UART pUart, uint32_t offUartReg, const void *pv, unsigned cb)
+{
+ int rc = VINF_SUCCESS;
+ RT_NOREF(pThis);
+
+ if (offUartReg >= OX958_REG_UART_DMA_REGION_OFFSET)
+ {
+ /* Access to the DMA registers. */
+ }
+ else /* Access UART registers. */
+ rc = uartRegWrite(&pUart->UartCore, offUartReg, *(const uint32_t *)pv, cb);
+
+ return rc;
+}
+
+
+/**
+ * UART core IRQ request callback.
+ *
+ * @returns nothing.
+ * @param pDevIns The device instance.
+ * @param pUart The UART requesting an IRQ update.
+ * @param iLUN The UART index.
+ * @param iLvl IRQ level requested.
+ */
+PDMBOTHCBDECL(void) ox958IrqReq(PPDMDEVINS pDevIns, PUARTCORE pUart, unsigned iLUN, int iLvl)
+{
+ RT_NOREF(pUart);
+ PDEVOX958 pThis = PDMINS_2_DATA(pDevIns, PDEVOX958);
+
+ if (iLvl)
+ ASMAtomicOrU32(&pThis->u32RegIrqStsGlob, RT_BIT_32(iLUN));
+ else
+ ASMAtomicAndU32(&pThis->u32RegIrqStsGlob, ~RT_BIT_32(iLUN));
+ ox958IrqUpdate(pThis);
+}
+
+
+/**
+ * Read a MMIO register.
+ *
+ * @returns VBox status code suitable for scheduling.
+ * @param pDevIns The device instance.
+ * @param pvUser A user argument (ignored).
+ * @param GCPhysAddr The physical address being written to. (This is within our MMIO memory range.)
+ * @param pv Where to put the data we read.
+ * @param cb The size of the read.
+ */
+PDMBOTHCBDECL(int) ox958MmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+ PDEVOX958 pThis = PDMINS_2_DATA(pDevIns, PDEVOX958);
+ uint32_t offReg = (GCPhysAddr - pThis->GCPhysMMIO);
+ int rc = VINF_SUCCESS;
+ RT_NOREF(pThis, pvUser);
+
+ if (offReg < OX958_REG_UART_REGION_OFFSET)
+ {
+ uint32_t *pu32 = (uint32_t *)pv;
+ Assert(cb == 4);
+
+ switch (offReg)
+ {
+ case OX958_REG_CC_REV_ID:
+ *pu32 = 0x00070002;
+ break;
+ case OX958_REG_UART_CNT:
+ *pu32 = pThis->cUarts;
+ break;
+ case OX958_REG_UART_IRQ_STS:
+ *pu32 = ASMAtomicReadU32(&pThis->u32RegIrqStsGlob);
+ break;
+ case OX958_REG_UART_IRQ_ENABLE:
+ *pu32 = ASMAtomicReadU32(&pThis->u32RegIrqEnGlob);
+ break;
+ case OX958_REG_UART_IRQ_DISABLE:
+ *pu32 = ~ASMAtomicReadU32(&pThis->u32RegIrqEnGlob);
+ break;
+ case OX958_REG_UART_WAKE_IRQ_ENABLE:
+ *pu32 = ASMAtomicReadU32(&pThis->u32RegIrqEnWake);
+ break;
+ case OX958_REG_UART_WAKE_IRQ_DISABLE:
+ *pu32 = ~ASMAtomicReadU32(&pThis->u32RegIrqEnWake);
+ break;
+ default:
+ rc = VINF_IOM_MMIO_UNUSED_00;
+ }
+ }
+ else
+ {
+ /* Figure out the UART accessed from the offset. */
+ offReg -= OX958_REG_UART_REGION_OFFSET;
+ uint32_t iUart = offReg / OX958_REG_UART_REGION_SIZE;
+ uint32_t offUartReg = offReg % OX958_REG_UART_REGION_SIZE;
+ if (iUart < pThis->cUarts)
+ {
+ POX958UART pUart = &pThis->aUarts[iUart];
+ rc = ox958UartRegRead(pThis, pUart, offUartReg, pv, cb);
+ if (rc == VINF_IOM_R3_IOPORT_READ)
+ rc = VINF_IOM_R3_MMIO_READ;
+ }
+ else
+ rc = VINF_IOM_MMIO_UNUSED_00;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Write to a MMIO register.
+ *
+ * @returns VBox status code suitable for scheduling.
+ * @param pDevIns The device instance.
+ * @param pvUser A user argument (ignored).
+ * @param GCPhysAddr The physical address being written to. (This is within our MMIO memory range.)
+ * @param pv Pointer to the data being written.
+ * @param cb The size of the data being written.
+ */
+PDMBOTHCBDECL(int) ox958MmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void const *pv, unsigned cb)
+{
+ PDEVOX958 pThis = PDMINS_2_DATA(pDevIns, PDEVOX958);
+ uint32_t offReg = (GCPhysAddr - pThis->GCPhysMMIO);
+ int rc = VINF_SUCCESS;
+ RT_NOREF1(pvUser);
+
+ if (offReg < OX958_REG_UART_REGION_OFFSET)
+ {
+ const uint32_t u32 = *(const uint32_t *)pv;
+ Assert(cb == 4);
+
+ switch (offReg)
+ {
+ case OX958_REG_UART_IRQ_ENABLE:
+ ASMAtomicOrU32(&pThis->u32RegIrqEnGlob, u32);
+ ox958IrqUpdate(pThis);
+ break;
+ case OX958_REG_UART_IRQ_DISABLE:
+ ASMAtomicAndU32(&pThis->u32RegIrqEnGlob, ~u32);
+ ox958IrqUpdate(pThis);
+ break;
+ case OX958_REG_UART_WAKE_IRQ_ENABLE:
+ ASMAtomicOrU32(&pThis->u32RegIrqEnWake, u32);
+ break;
+ case OX958_REG_UART_WAKE_IRQ_DISABLE:
+ ASMAtomicAndU32(&pThis->u32RegIrqEnWake, ~u32);
+ break;
+ case OX958_REG_UART_IRQ_STS: /* Readonly */
+ case OX958_REG_CC_REV_ID: /* Readonly */
+ case OX958_REG_UART_CNT: /* Readonly */
+ default:
+ rc = VINF_SUCCESS;
+ }
+ }
+ else
+ {
+ /* Figure out the UART accessed from the offset. */
+ offReg -= OX958_REG_UART_REGION_OFFSET;
+ uint32_t iUart = offReg / OX958_REG_UART_REGION_SIZE;
+ uint32_t offUartReg = offReg % OX958_REG_UART_REGION_SIZE;
+ if (iUart < pThis->cUarts)
+ {
+ POX958UART pUart = &pThis->aUarts[iUart];
+ rc = ox958UartRegWrite(pThis, pUart, offUartReg, pv, cb);
+ if (rc == VINF_IOM_R3_IOPORT_WRITE)
+ rc = VINF_IOM_R3_MMIO_WRITE;
+ }
+ }
+
+ return rc;
+}
+
+
+#ifdef IN_RING3
+/**
+ * @callback_method_impl{FNPCIIOREGIONMAP}
+ */
+static DECLCALLBACK(int) ox958R3Map(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion,
+ RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType)
+{
+ RT_NOREF(enmType);
+ PDEVOX958 pThis = (PDEVOX958)pPciDev;
+ int rc = VINF_SUCCESS;
+
+ if (iRegion == 0)
+ {
+ Assert(enmType == PCI_ADDRESS_SPACE_MEM);
+
+ rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_PASSTHRU | IOMMMIO_FLAGS_WRITE_PASSTHRU,
+ ox958MmioWrite, ox958MmioRead, "OxPCIe958");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Enable (or not) RC/R0 support. */
+ if (pThis->fRCEnabled)
+ {
+ rc = PDMDevHlpMMIORegisterRC(pDevIns, GCPhysAddress, cb, NIL_RTRCPTR /*pvUser*/,
+ "ox958MmioWrite", "ox958MmioRead");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (pThis->fR0Enabled)
+ {
+ rc = PDMDevHlpMMIORegisterR0(pDevIns, GCPhysAddress, cb, NIL_RTR0PTR /*pvUser*/,
+ "ox958MmioWrite", "ox958MmioRead");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ pThis->GCPhysMMIO = GCPhysAddress;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{PDMDEVREG,pfnDetach} */
+static DECLCALLBACK(void) ox958R3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PDEVOX958 pThis = PDMINS_2_DATA(pDevIns, PDEVOX958);
+ AssertReturnVoid(iLUN >= pThis->cUarts);
+
+ RT_NOREF(fFlags);
+
+ return uartR3Detach(&pThis->aUarts[iLUN].UartCore);
+}
+
+
+/** @interface_method_impl{PDMDEVREG,pfnAttach} */
+static DECLCALLBACK(int) ox958R3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PDEVOX958 pThis = PDMINS_2_DATA(pDevIns, PDEVOX958);
+
+ RT_NOREF(fFlags);
+
+ if (iLUN >= pThis->cUarts)
+ return VERR_PDM_LUN_NOT_FOUND;
+
+ return uartR3Attach(&pThis->aUarts[iLUN].UartCore, iLUN);
+}
+
+
+/** @interface_method_impl{PDMDEVREG,pfnReset} */
+static DECLCALLBACK(void) ox958R3Reset(PPDMDEVINS pDevIns)
+{
+ PDEVOX958 pThis = PDMINS_2_DATA(pDevIns, PDEVOX958);
+
+ pThis->u32RegIrqStsGlob = 0x00;
+ pThis->u32RegIrqEnGlob = 0x00;
+ pThis->u32RegIrqEnWake = 0x00;
+
+ for (uint32_t i = 0; i < pThis->cUarts; i++)
+ uartR3Reset(&pThis->aUarts[i].UartCore);
+}
+
+
+/** @interface_method_impl{PDMDEVREG,pfnRelocate} */
+static DECLCALLBACK(void) ox958R3Relocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+ PDEVOX958 pThis = PDMINS_2_DATA(pDevIns, PDEVOX958);
+ RT_NOREF(offDelta);
+
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ for (uint32_t i = 0; i < pThis->cUarts; i++)
+ uartR3Relocate(&pThis->aUarts[i].UartCore, offDelta);
+}
+
+
+/** @interface_method_impl{PDMDEVREG,pfnDestruct} */
+static DECLCALLBACK(int) ox958R3Destruct(PPDMDEVINS pDevIns)
+{
+ PDEVOX958 pThis = PDMINS_2_DATA(pDevIns, PDEVOX958);
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+
+ for (uint32_t i = 0; i < pThis->cUarts; i++)
+ uartR3Destruct(&pThis->aUarts[i].UartCore);
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{PDMDEVREG,pfnConstruct} */
+static DECLCALLBACK(int) ox958R3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ RT_NOREF(iInstance);
+ PDEVOX958 pThis = PDMINS_2_DATA(pDevIns, PDEVOX958);
+ bool fRCEnabled = true;
+ bool fR0Enabled = true;
+ bool fMsiXSupported = false;
+ int rc;
+
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+
+ /*
+ * Validate and read configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, "RCEnabled\0"
+ "R0Enabled\0"
+ "MsiXSupported\0"
+ "UartCount\0"))
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("OXPCIe958 configuration error: Unknown option specified"));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "RCEnabled", &fRCEnabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("OXPCIe958 configuration error: Failed to read \"RCEnabled\" as boolean"));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "R0Enabled", &fR0Enabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("OXPCIe958 configuration error: failed to read \"R0Enabled\" as boolean"));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "MsiXSupported", &fMsiXSupported, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("OXPCIe958 configuration error: failed to read \"MsiXSupported\" as boolean"));
+
+ rc = CFGMR3QueryU32Def(pCfg, "UartCount", &pThis->cUarts, OX958_UARTS_MAX);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("OXPCIe958 configuration error: failed to read \"UartCount\" as unsigned 32bit integer"));
+
+ if (!pThis->cUarts || pThis->cUarts > OX958_UARTS_MAX)
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("OXPCIe958 configuration error: \"UartCount\" has invalid value %u (must be in range [1 .. %u]"),
+ pThis->cUarts, OX958_UARTS_MAX);
+
+ /*
+ * Init instance data.
+ */
+ pThis->fR0Enabled = fR0Enabled;
+ pThis->fRCEnabled = fRCEnabled;
+ pThis->pDevInsR3 = pDevIns;
+ pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+
+ /* Fill PCI config space. */
+ PDMPciDevSetVendorId (&pThis->PciDev, OX958_PCI_VENDOR_ID);
+ PDMPciDevSetDeviceId (&pThis->PciDev, OX958_PCI_DEVICE_ID);
+ PDMPciDevSetCommand (&pThis->PciDev, 0x0000);
+#ifdef VBOX_WITH_MSI_DEVICES
+ PDMPciDevSetStatus (&pThis->PciDev, VBOX_PCI_STATUS_CAP_LIST);
+ PDMPciDevSetCapabilityList (&pThis->PciDev, OX958_PCI_MSI_CAP_OFS);
+#else
+ PDMPciDevSetCapabilityList (&pThis->PciDev, 0x70);
+#endif
+ PDMPciDevSetRevisionId (&pThis->PciDev, 0x00);
+ PDMPciDevSetClassBase (&pThis->PciDev, 0x07); /* Communication controller. */
+ PDMPciDevSetClassSub (&pThis->PciDev, 0x00); /* Serial controller. */
+ PDMPciDevSetClassProg (&pThis->PciDev, 0x02); /* 16550. */
+
+ PDMPciDevSetRevisionId (&pThis->PciDev, 0x00);
+ PDMPciDevSetSubSystemVendorId(&pThis->PciDev, OX958_PCI_VENDOR_ID);
+ PDMPciDevSetSubSystemId (&pThis->PciDev, OX958_PCI_DEVICE_ID);
+
+ PDMPciDevSetInterruptLine (&pThis->PciDev, 0x00);
+ PDMPciDevSetInterruptPin (&pThis->PciDev, 0x01);
+ /** @todo More Capabilities. */
+
+ rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Register PCI device and I/O region.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, &pThis->PciDev);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#ifdef VBOX_WITH_MSI_DEVICES
+ PDMMSIREG MsiReg;
+ RT_ZERO(MsiReg);
+ MsiReg.cMsiVectors = 1;
+ MsiReg.iMsiCapOffset = OX958_PCI_MSI_CAP_OFS;
+ MsiReg.iMsiNextOffset = OX958_PCI_MSIX_CAP_OFS;
+ MsiReg.fMsi64bit = true;
+ if (fMsiXSupported)
+ {
+ MsiReg.cMsixVectors = VBOX_MSIX_MAX_ENTRIES;
+ MsiReg.iMsixCapOffset = OX958_PCI_MSIX_CAP_OFS;
+ MsiReg.iMsixNextOffset = 0x00;
+ MsiReg.iMsixBar = OX958_PCI_MSIX_BAR;
+ }
+ rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg);
+ if (RT_FAILURE(rc))
+ {
+ PCIDevSetCapabilityList(&pThis->PciDev, 0x0);
+ /* That's OK, we can work without MSI */
+ }
+#endif
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, _16K, PCI_ADDRESS_SPACE_MEM, ox958R3Map);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ PVM pVM = PDMDevHlpGetVM(pDevIns);
+ RTR0PTR pfnSerialIrqReqR0 = NIL_RTR0PTR;
+ RTRCPTR pfnSerialIrqReqRC = NIL_RTRCPTR;
+
+ if ( fRCEnabled
+ && VM_IS_RAW_MODE_ENABLED(pVM))
+ {
+ rc = PDMR3LdrGetSymbolRC(pVM, pDevIns->pReg->szRCMod, "ox958IrqReq", &pfnSerialIrqReqRC);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (fR0Enabled)
+ {
+ rc = PDMR3LdrGetSymbolR0(pVM, pDevIns->pReg->szR0Mod, "ox958IrqReq", &pfnSerialIrqReqR0);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ for (uint32_t i = 0; i < pThis->cUarts; i++)
+ {
+ POX958UART pUart = &pThis->aUarts[i];
+ rc = uartR3Init(&pUart->UartCore, pDevIns, UARTTYPE_16550A, i, 0, ox958IrqReq, pfnSerialIrqReqR0, pfnSerialIrqReqRC);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("OXPCIe958 configuration error: failed to initialize UART %u"), i);
+ }
+
+ ox958R3Reset(pDevIns);
+ return VINF_SUCCESS;
+}
+
+
+const PDMDEVREG g_DeviceOxPcie958 =
+{
+ /* u32version */
+ PDM_DEVREG_VERSION,
+ /* szName */
+ "oxpcie958uart",
+ /* szRCMod */
+ "VBoxDDRC.rc",
+ /* szR0Mod */
+ "VBoxDDR0.r0",
+ /* pszDescription */
+ "OXPCIe958 based UART controller.\n",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0,
+ /* fClass */
+ PDM_DEVREG_CLASS_SERIAL,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DEVOX958),
+ /* pfnConstruct */
+ ox958R3Construct,
+ /* pfnDestruct */
+ ox958R3Destruct,
+ /* pfnRelocate */
+ ox958R3Relocate,
+ /* pfnMemSetup */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ ox958R3Reset,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ ox958R3Attach,
+ /* pfnDetach */
+ ox958R3Detach,
+ /* pfnQueryInterface */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+
+#endif /* IN_RING3 */
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
+
diff --git a/src/VBox/Devices/Serial/DevSerial.cpp b/src/VBox/Devices/Serial/DevSerial.cpp
new file mode 100644
index 00000000..74decf64
--- /dev/null
+++ b/src/VBox/Devices/Serial/DevSerial.cpp
@@ -0,0 +1,537 @@
+/* $Id: DevSerial.cpp $ */
+/** @file
+ * DevSerial - 16550A UART emulation.
+ *
+ * The documentation for this device was taken from the PC16550D spec from TI.
+ */
+
+/*
+ * Copyright (C) 2018-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_SERIAL
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmserialifs.h>
+#include <VBox/vmm/vm.h>
+#include <iprt/assert.h>
+#include <iprt/uuid.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/critsect.h>
+
+#include "VBoxDD.h"
+#include "UartCore.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Serial device.
+ */
+typedef struct DEVSERIAL
+{
+ /** Pointer to the device instance - R3 Ptr. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to the device instance - R0 Ptr. */
+ PPDMDEVINSR0 pDevInsR0;
+ /** Pointer to the device instance - RC Ptr. */
+ PPDMDEVINSRC pDevInsRC;
+ /** Alignment. */
+ RTRCPTR Alignment0;
+ /** Flag whether the R0 portion of this device is enabled. */
+ bool fR0Enabled;
+ /** Flag whether the RC portion of this device is enabled. */
+ bool fRCEnabled;
+ /** Alignment. */
+ bool afAlignment1[2];
+ /** The IRQ value. */
+ uint8_t uIrq;
+ /** The base I/O port the device is registered at. */
+ RTIOPORT PortBase;
+
+ /** The UART core. */
+ UARTCORE UartCore;
+} DEVSERIAL;
+/** Pointer to the serial device state. */
+typedef DEVSERIAL *PDEVSERIAL;
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+PDMBOTHCBDECL(void) serialIrqReq(PPDMDEVINS pDevIns, PUARTCORE pUart, unsigned iLUN, int iLvl)
+{
+ RT_NOREF(pUart, iLUN);
+ PDEVSERIAL pThis = PDMINS_2_DATA(pDevIns, PDEVSERIAL);
+ PDMDevHlpISASetIrqNoWait(pDevIns, pThis->uIrq, iLvl);
+}
+
+
+/* -=-=-=-=-=-=-=-=- I/O Port Access Handlers -=-=-=-=-=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNIOMIOPORTOUT}
+ */
+PDMBOTHCBDECL(int) serialIoPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t u32, unsigned cb)
+{
+ PDEVSERIAL pThis = PDMINS_2_DATA(pDevIns, PDEVSERIAL);
+ RT_NOREF_PV(pvUser);
+
+ return uartRegWrite(&pThis->UartCore, uPort - pThis->PortBase, u32, cb);
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTIN}
+ */
+PDMBOTHCBDECL(int) serialIoPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT uPort, uint32_t *pu32, unsigned cb)
+{
+ PDEVSERIAL pThis = PDMINS_2_DATA(pDevIns, PDEVSERIAL);
+ RT_NOREF_PV(pvUser);
+
+ return uartRegRead(&pThis->UartCore, uPort - pThis->PortBase, pu32, cb);
+}
+
+
+#ifdef IN_RING3
+
+
+/**
+ * Returns the matching UART type from the given string.
+ *
+ * @returns UART type based on the given string or UARTTYPE_INVALID if an invalid type was passed.
+ * @param pszUartType The UART type.
+ */
+static UARTTYPE serialR3GetUartTypeFromString(const char *pszUartType)
+{
+ if (!RTStrCmp(pszUartType, "16450"))
+ return UARTTYPE_16450;
+ else if (!RTStrCmp(pszUartType, "16550A"))
+ return UARTTYPE_16550A;
+ else if (!RTStrCmp(pszUartType, "16750"))
+ return UARTTYPE_16750;
+
+ AssertLogRelMsgFailedReturn(("Unknown UART type \"%s\" specified", pszUartType),
+ UARTTYPE_INVALID);
+}
+
+
+/* -=-=-=-=-=-=-=-=- Saved State -=-=-=-=-=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNSSMDEVLIVEEXEC}
+ */
+static DECLCALLBACK(int) serialR3LiveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uPass)
+{
+ RT_NOREF(uPass);
+ PDEVSERIAL pThis = PDMINS_2_DATA(pDevIns, PDEVSERIAL);
+ SSMR3PutU8(pSSM, pThis->uIrq);
+ SSMR3PutIOPort(pSSM, pThis->PortBase);
+ SSMR3PutU32(pSSM, pThis->UartCore.enmType);
+
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) serialR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PDEVSERIAL pThis = PDMINS_2_DATA(pDevIns, PDEVSERIAL);
+
+ SSMR3PutU8( pSSM, pThis->uIrq);
+ SSMR3PutIOPort(pSSM, pThis->PortBase);
+ SSMR3PutU32( pSSM, pThis->UartCore.enmType);
+
+ uartR3SaveExec(&pThis->UartCore, pSSM);
+ return SSMR3PutU32(pSSM, UINT32_MAX); /* sanity/terminator */
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) serialR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PDEVSERIAL pThis = PDMINS_2_DATA(pDevIns, PDEVSERIAL);
+ uint8_t bIrq;
+ RTIOPORT PortBase;
+ UARTTYPE enmType;
+ int rc;
+
+ AssertMsgReturn(uVersion >= UART_SAVED_STATE_VERSION_16450, ("%d\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
+ if (uVersion > UART_SAVED_STATE_VERSION_LEGACY_CODE)
+ {
+ SSMR3GetU8( pSSM, &bIrq);
+ SSMR3GetIOPort(pSSM, &PortBase);
+ rc = SSMR3GetU32( pSSM, (uint32_t *)&enmType);
+ AssertRCReturn(rc, rc);
+ if (uPass == SSM_PASS_FINAL)
+ {
+ rc = uartR3LoadExec(&pThis->UartCore, pSSM, uVersion, uPass, NULL, NULL);
+ AssertRCReturn(rc, rc);
+ }
+ }
+ else
+ {
+ enmType = uVersion > UART_SAVED_STATE_VERSION_16450 ? UARTTYPE_16550A : UARTTYPE_16450;
+ if (uPass != SSM_PASS_FINAL)
+ {
+ int32_t iIrqTmp;
+ SSMR3GetS32(pSSM, &iIrqTmp);
+ uint32_t uPortBaseTmp;
+ rc = SSMR3GetU32(pSSM, &uPortBaseTmp);
+ AssertRCReturn(rc, rc);
+
+ bIrq = (uint8_t)iIrqTmp;
+ PortBase = (uint32_t)uPortBaseTmp;
+ }
+ else
+ {
+ rc = uartR3LoadExec(&pThis->UartCore, pSSM, uVersion, uPass, &bIrq, &PortBase);
+ AssertRCReturn(rc, rc);
+ }
+ }
+
+ if (uPass == SSM_PASS_FINAL)
+ {
+ /* The marker. */
+ uint32_t u32;
+ rc = SSMR3GetU32(pSSM, &u32);
+ AssertRCReturn(rc, rc);
+ AssertMsgReturn(u32 == UINT32_MAX, ("%#x\n", u32), VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ }
+
+ /*
+ * Check the config.
+ */
+ if ( pThis->uIrq != bIrq
+ || pThis->PortBase != PortBase
+ || pThis->UartCore.enmType != enmType)
+ return SSMR3SetCfgError(pSSM, RT_SRC_POS,
+ N_("Config mismatch - saved IRQ=%#x PortBase=%#x Type=%d; configured IRQ=%#x PortBase=%#x Type=%d"),
+ bIrq, PortBase, enmType, pThis->uIrq, pThis->PortBase, pThis->UartCore.enmType);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADDONE}
+ */
+static DECLCALLBACK(int) serialR3LoadDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PDEVSERIAL pThis = PDMINS_2_DATA(pDevIns, PDEVSERIAL);
+ return uartR3LoadDone(&pThis->UartCore, pSSM);
+}
+
+
+/* -=-=-=-=-=-=-=-=- PDMDEVREG -=-=-=-=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnRelocate}
+ */
+static DECLCALLBACK(void) serialR3Relocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+ PDEVSERIAL pThis = PDMINS_2_DATA(pDevIns, PDEVSERIAL);
+ uartR3Relocate(&pThis->UartCore, offDelta);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ */
+static DECLCALLBACK(void) serialR3Reset(PPDMDEVINS pDevIns)
+{
+ PDEVSERIAL pThis = PDMINS_2_DATA(pDevIns, PDEVSERIAL);
+ uartR3Reset(&pThis->UartCore);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnAttach}
+ */
+static DECLCALLBACK(int) serialR3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDEVSERIAL pThis = PDMINS_2_DATA(pDevIns, PDEVSERIAL);
+ return uartR3Attach(&pThis->UartCore, iLUN);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDetach}
+ */
+static DECLCALLBACK(void) serialR3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ RT_NOREF(iLUN, fFlags);
+ PDEVSERIAL pThis = PDMINS_2_DATA(pDevIns, PDEVSERIAL);
+ uartR3Detach(&pThis->UartCore);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) serialR3Destruct(PPDMDEVINS pDevIns)
+{
+ PDEVSERIAL pThis = PDMINS_2_DATA(pDevIns, PDEVSERIAL);
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+
+ uartR3Destruct(&pThis->UartCore);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) serialR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDEVSERIAL pThis = PDMINS_2_DATA(pDevIns, PDEVSERIAL);
+ int rc = VINF_SUCCESS;
+
+ Assert(iInstance < 4);
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+
+ /*
+ * Initialize the instance data.
+ * (Do this early or the destructor might choke on something!)
+ */
+ pThis->pDevInsR3 = pDevIns;
+ pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+
+ /*
+ * Validate and read the configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, "IRQ\0"
+ "IOBase\0"
+ "GCEnabled\0"
+ "R0Enabled\0"
+ "YieldOnLSRRead\0"
+ "UartType\0"
+ ))
+ {
+ AssertMsgFailed(("serialConstruct Invalid configuration values\n"));
+ return VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES;
+ }
+
+ rc = CFGMR3QueryBoolDef(pCfg, "GCEnabled", &pThis->fRCEnabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("Configuration error: Failed to get the \"GCEnabled\" value"));
+
+ rc = CFGMR3QueryBoolDef(pCfg, "R0Enabled", &pThis->fR0Enabled, true);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("Configuration error: Failed to get the \"R0Enabled\" value"));
+
+ bool fYieldOnLSRRead = false;
+ rc = CFGMR3QueryBoolDef(pCfg, "YieldOnLSRRead", &fYieldOnLSRRead, false);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("Configuration error: Failed to get the \"YieldOnLSRRead\" value"));
+
+ uint8_t uIrq = 0;
+ rc = CFGMR3QueryU8(pCfg, "IRQ", &uIrq);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ {
+ /* Provide sensible defaults. */
+ if (iInstance == 0)
+ uIrq = 4;
+ else if (iInstance == 1)
+ uIrq = 3;
+ else
+ AssertReleaseFailed(); /* irq_lvl is undefined. */
+ }
+ else if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("Configuration error: Failed to get the \"IRQ\" value"));
+
+ uint16_t uIoBase = 0;
+ rc = CFGMR3QueryU16(pCfg, "IOBase", &uIoBase);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ {
+ if (iInstance == 0)
+ uIoBase = 0x3f8;
+ else if (iInstance == 1)
+ uIoBase = 0x2f8;
+ else
+ AssertReleaseFailed(); /* uIoBase is undefined */
+ }
+ else if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("Configuration error: Failed to get the \"IOBase\" value"));
+
+ char *pszUartType;
+ rc = CFGMR3QueryStringAllocDef(pCfg, "UartType", &pszUartType, "16550A");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("Configuration error: failed to read \"UartType\" as string"));
+
+ UARTTYPE enmUartType = serialR3GetUartTypeFromString(pszUartType);
+
+ if (enmUartType != UARTTYPE_INVALID)
+ LogRel(("Serial#%d: emulating %s (IOBase: %04x IRQ: %u)\n",
+ pDevIns->iInstance, pszUartType, uIoBase, uIrq));
+
+ MMR3HeapFree(pszUartType);
+
+ if (enmUartType == UARTTYPE_INVALID)
+ return PDMDEV_SET_ERROR(pDevIns, VERR_INVALID_PARAMETER,
+ N_("Configuration error: \"UartType\" contains invalid type"));
+
+ pThis->uIrq = uIrq;
+ pThis->PortBase = uIoBase;
+
+ /*
+ * Init locks, using explicit locking where necessary.
+ */
+ rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Register the I/O ports.
+ */
+ rc = PDMDevHlpIOPortRegister(pDevIns, uIoBase, 8, 0,
+ serialIoPortWrite, serialIoPortRead,
+ NULL, NULL, "SERIAL");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ PVM pVM = PDMDevHlpGetVM(pDevIns);
+ RTR0PTR pfnSerialIrqReqR0 = NIL_RTR0PTR;
+ RTRCPTR pfnSerialIrqReqRC = NIL_RTRCPTR;
+
+ if (pThis->fRCEnabled)
+ {
+ rc = PDMDevHlpIOPortRegisterRC(pDevIns, uIoBase, 8, 0, "serialIoPortWrite",
+ "serialIoPortRead", NULL, NULL, "SERIAL");
+ if ( RT_SUCCESS(rc)
+ && VM_IS_RAW_MODE_ENABLED(pVM))
+ rc = PDMR3LdrGetSymbolRC(pVM, pDevIns->pReg->szRCMod, "serialIrqReq", &pfnSerialIrqReqRC);
+
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ if (pThis->fR0Enabled)
+ {
+ rc = PDMDevHlpIOPortRegisterR0(pDevIns, uIoBase, 8, 0, "serialIoPortWrite",
+ "serialIoPortRead", NULL, NULL, "SERIAL");
+ if (RT_SUCCESS(rc))
+ rc = PDMR3LdrGetSymbolR0(pVM, pDevIns->pReg->szR0Mod, "serialIrqReq", &pfnSerialIrqReqR0);
+
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ /*
+ * Saved state.
+ */
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, UART_SAVED_STATE_VERSION, sizeof(*pThis), NULL,
+ NULL, serialR3LiveExec, NULL,
+ NULL, serialR3SaveExec, NULL,
+ NULL, serialR3LoadExec, serialR3LoadDone);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Init the UART core structure. */
+ rc = uartR3Init(&pThis->UartCore, pDevIns, enmUartType, 0,
+ fYieldOnLSRRead ? UART_CORE_YIELD_ON_LSR_READ : 0, serialIrqReq, pfnSerialIrqReqR0, pfnSerialIrqReqRC);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ serialR3Reset(pDevIns);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceSerialPort =
+{
+ /* u32Version */
+ PDM_DEVREG_VERSION,
+ /* szName */
+ "serial",
+ /* szRCMod */
+ "VBoxDDRC.rc",
+ /* szR0Mod */
+ "VBoxDDR0.r0",
+ /* pszDescription */
+ "Serial Communication Port",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0,
+ /* fClass */
+ PDM_DEVREG_CLASS_SERIAL,
+ /* cMaxInstances */
+ UINT32_MAX,
+ /* cbInstance */
+ sizeof(DEVSERIAL),
+ /* pfnConstruct */
+ serialR3Construct,
+ /* pfnDestruct */
+ serialR3Destruct,
+ /* pfnRelocate */
+ serialR3Relocate,
+ /* pfnMemSetup */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ serialR3Reset,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ serialR3Attach,
+ /* pfnDetach */
+ serialR3Detach,
+ /* pfnQueryInterface. */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+#endif /* IN_RING3 */
+
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
diff --git a/src/VBox/Devices/Serial/DrvChar.cpp b/src/VBox/Devices/Serial/DrvChar.cpp
new file mode 100644
index 00000000..d059dd64
--- /dev/null
+++ b/src/VBox/Devices/Serial/DrvChar.cpp
@@ -0,0 +1,479 @@
+/* $Id: DrvChar.cpp $ */
+/** @file
+ * Driver that adapts PDMISTREAM into PDMISERIALCONNECTOR / PDMISERIALPORT.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_CHAR
+#include <VBox/vmm/pdmdrv.h>
+#include <VBox/vmm/pdmserialifs.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/poll.h>
+#include <iprt/stream.h>
+#include <iprt/critsect.h>
+#include <iprt/semaphore.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Char driver instance data.
+ *
+ * @implements PDMISERIALCONNECTOR
+ */
+typedef struct DRVCHAR
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the char port interface of the driver/device above us. */
+ PPDMISERIALPORT pDrvSerialPort;
+ /** Pointer to the stream interface of the driver below us. */
+ PPDMISTREAM pDrvStream;
+ /** Our serial interface. */
+ PDMISERIALCONNECTOR ISerialConnector;
+ /** Flag to notify the receive thread it should terminate. */
+ volatile bool fShutdown;
+ /** Flag whether data is available from the device/driver above as notified by the driver. */
+ volatile bool fAvailWrExt;
+ /** Internal copy of the flag which gets reset when there is no data anymore. */
+ bool fAvailWrInt;
+ /** I/O thread. */
+ PPDMTHREAD pThrdIo;
+
+ /** Small send buffer. */
+ uint8_t abTxBuf[16];
+ /** Amount of data in the buffer. */
+ size_t cbTxUsed;
+
+ /** Receive buffer. */
+ uint8_t abBuffer[256];
+ /** Number of bytes remaining in the receive buffer. */
+ volatile size_t cbRemaining;
+ /** Current position into the read buffer. */
+ uint8_t *pbBuf;
+
+#if HC_ARCH_BITS == 32
+ uint32_t uAlignment0;
+#endif
+
+ /** Read/write statistics */
+ STAMCOUNTER StatBytesRead;
+ STAMCOUNTER StatBytesWritten;
+} DRVCHAR, *PDRVCHAR;
+AssertCompileMemberAlignment(DRVCHAR, StatBytesRead, 8);
+
+
+
+
+/* -=-=-=-=- IBase -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvCharQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVCHAR pThis = PDMINS_2_DATA(pDrvIns, PDRVCHAR);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMISERIALCONNECTOR, &pThis->ISerialConnector);
+ return NULL;
+}
+
+
+/* -=-=-=-=- ISerialConnector -=-=-=-=- */
+
+
+/**
+ * @interface_method_impl{PDMISERIALCONNECTOR,pfnDataAvailWrNotify}
+ */
+static DECLCALLBACK(int) drvCharDataAvailWrNotify(PPDMISERIALCONNECTOR pInterface)
+{
+ LogFlowFunc(("pInterface=%#p\n", pInterface));
+ PDRVCHAR pThis = RT_FROM_MEMBER(pInterface, DRVCHAR, ISerialConnector);
+
+ int rc = VINF_SUCCESS;
+ bool fAvailOld = ASMAtomicXchgBool(&pThis->fAvailWrExt, true);
+ if (!fAvailOld)
+ rc = pThis->pDrvStream->pfnPollInterrupt(pThis->pDrvStream);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMISERIALCONNECTOR,pfnReadRdr}
+ */
+static DECLCALLBACK(int) drvCharReadRdr(PPDMISERIALCONNECTOR pInterface, void *pvBuf, size_t cbRead, size_t *pcbRead)
+{
+ LogFlowFunc(("pInterface=%#p pvBuf=%#p cbRead=%zu pcbRead=%#p\n", pInterface, pvBuf, cbRead, pcbRead));
+ PDRVCHAR pThis = RT_FROM_MEMBER(pInterface, DRVCHAR, ISerialConnector);
+ int rc = VINF_SUCCESS;
+
+ AssertReturn(pThis->cbRemaining, VERR_INVALID_STATE);
+ size_t cbToRead = RT_MIN(cbRead, pThis->cbRemaining);
+ memcpy(pvBuf, pThis->pbBuf, cbToRead);
+
+ pThis->pbBuf += cbToRead;
+ *pcbRead = cbToRead;
+ size_t cbOld = ASMAtomicSubZ(&pThis->cbRemaining, cbToRead);
+ if (!(cbOld - cbToRead)) /* Kick the I/O thread to fetch new data. */
+ rc = pThis->pDrvStream->pfnPollInterrupt(pThis->pDrvStream);
+ STAM_COUNTER_ADD(&pThis->StatBytesRead, cbToRead);
+
+ LogFlowFunc(("-> %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMISERIALCONNECTOR,pfnChgParams}
+ */
+static DECLCALLBACK(int) drvCharChgParams(PPDMISERIALCONNECTOR pInterface, uint32_t uBps,
+ PDMSERIALPARITY enmParity, unsigned cDataBits,
+ PDMSERIALSTOPBITS enmStopBits)
+{
+ /* Nothing to do here. */
+ RT_NOREF(pInterface, uBps, enmParity, cDataBits, enmStopBits);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{PDMISERIALCONNECTOR,pfnChgModemLines}
+ */
+static DECLCALLBACK(int) drvCharChgModemLines(PPDMISERIALCONNECTOR pInterface, bool fRts, bool fDtr)
+{
+ /* Nothing to do here. */
+ RT_NOREF(pInterface, fRts, fDtr);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{PDMISERIALCONNECTOR,pfnChgBrk}
+ */
+static DECLCALLBACK(int) drvCharChgBrk(PPDMISERIALCONNECTOR pInterface, bool fBrk)
+{
+ /* Nothing to do here. */
+ RT_NOREF(pInterface, fBrk);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{PDMISERIALCONNECTOR,pfnQueryStsLines}
+ */
+static DECLCALLBACK(int) drvCharQueryStsLines(PPDMISERIALCONNECTOR pInterface, uint32_t *pfStsLines)
+{
+ /* Nothing to do here. */
+ *pfStsLines = 0;
+ RT_NOREF(pInterface);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{PDMISERIALCONNECTOR,pfnQueuesFlush}
+ */
+static DECLCALLBACK(int) drvCharQueuesFlush(PPDMISERIALCONNECTOR pInterface, bool fQueueRecv, bool fQueueXmit)
+{
+ RT_NOREF(fQueueXmit);
+ LogFlowFunc(("pInterface=%#p fQueueRecv=%RTbool fQueueXmit=%RTbool\n", pInterface, fQueueRecv, fQueueXmit));
+ int rc = VINF_SUCCESS;
+ PDRVCHAR pThis = RT_FROM_MEMBER(pInterface, DRVCHAR, ISerialConnector);
+
+ if (fQueueRecv)
+ {
+ size_t cbOld = 0;
+ cbOld = ASMAtomicXchgZ(&pThis->cbRemaining, 0);
+ if (cbOld) /* Kick the I/O thread to fetch new data. */
+ rc = pThis->pDrvStream->pfnPollInterrupt(pThis->pDrvStream);
+ }
+
+ LogFlowFunc(("-> %Rrc\n", rc));
+ return VINF_SUCCESS; /** @todo r=bird: return rc? */
+}
+
+
+/* -=-=-=-=- I/O thread -=-=-=-=- */
+
+/**
+ * Send thread loop - pushes data down thru the driver chain.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The char driver instance.
+ * @param pThread The worker thread.
+ */
+static DECLCALLBACK(int) drvCharIoLoop(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pDrvIns);
+ PDRVCHAR pThis = (PDRVCHAR)pThread->pvUser;
+
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ return VINF_SUCCESS;
+
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ uint32_t fEvts = 0;
+
+ if (!pThis->fAvailWrInt)
+ pThis->fAvailWrInt = ASMAtomicXchgBool(&pThis->fAvailWrExt, false);
+
+ if ( !pThis->cbRemaining
+ && pThis->pDrvStream->pfnRead)
+ fEvts |= RTPOLL_EVT_READ;
+ if ( pThis->fAvailWrInt
+ || pThis->cbTxUsed)
+ fEvts |= RTPOLL_EVT_WRITE;
+
+ uint32_t fEvtsRecv = 0;
+ int rc = pThis->pDrvStream->pfnPoll(pThis->pDrvStream, fEvts, &fEvtsRecv, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ if (fEvtsRecv & RTPOLL_EVT_WRITE)
+ {
+ if (pThis->fAvailWrInt)
+ {
+ /* Stuff as much data into the TX buffer as we can. */
+ size_t cbToFetch = RT_ELEMENTS(pThis->abTxBuf) - pThis->cbTxUsed;
+ size_t cbFetched = 0;
+ rc = pThis->pDrvSerialPort->pfnReadWr(pThis->pDrvSerialPort, &pThis->abTxBuf[pThis->cbTxUsed], cbToFetch,
+ &cbFetched);
+ AssertRC(rc);
+
+ if (cbFetched > 0)
+ pThis->cbTxUsed += cbFetched;
+ else
+ {
+ /* There is no data available anymore. */
+ pThis->fAvailWrInt = false;
+ }
+ }
+
+ if (pThis->cbTxUsed)
+ {
+ size_t cbProcessed = pThis->cbTxUsed;
+ rc = pThis->pDrvStream->pfnWrite(pThis->pDrvStream, &pThis->abTxBuf[0], &cbProcessed);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbTxUsed -= cbProcessed;
+ if (pThis->cbTxUsed)
+ {
+ /* Move the data in the TX buffer to the front to fill the end again. */
+ memmove(&pThis->abTxBuf[0], &pThis->abTxBuf[cbProcessed], pThis->cbTxUsed);
+ }
+ else
+ pThis->pDrvSerialPort->pfnDataSentNotify(pThis->pDrvSerialPort);
+ STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbProcessed);
+ }
+ else if (rc != VERR_TIMEOUT)
+ {
+ LogRel(("Char#%d: Write failed with %Rrc; skipping\n", pDrvIns->iInstance, rc));
+ break;
+ }
+ }
+ }
+
+ if (fEvtsRecv & RTPOLL_EVT_READ)
+ {
+ AssertPtr(pThis->pDrvStream->pfnRead);
+ Assert(!pThis->cbRemaining);
+
+ size_t cbRead = sizeof(pThis->abBuffer);
+ rc = pThis->pDrvStream->pfnRead(pThis->pDrvStream, &pThis->abBuffer[0], &cbRead);
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("Read failed with %Rrc\n", rc));
+ break;
+ }
+
+ if (cbRead)
+ {
+ pThis->pbBuf = &pThis->abBuffer[0];
+ ASMAtomicWriteZ(&pThis->cbRemaining, cbRead);
+ /* Notify the upper device/driver. */
+ rc = pThis->pDrvSerialPort->pfnDataAvailRdrNotify(pThis->pDrvSerialPort, cbRead);
+ }
+ }
+ }
+ else if (rc != VERR_INTERRUPTED)
+ LogRelMax(10, ("Char#%d: Polling failed with %Rrc\n", pDrvIns->iInstance, rc));
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Unblock the send worker thread so it can respond to a state change.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The char driver instance.
+ * @param pThread The worker thread.
+ */
+static DECLCALLBACK(int) drvCharIoLoopWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ PDRVCHAR pThis = (PDRVCHAR)pThread->pvUser;
+
+ RT_NOREF(pDrvIns);
+ return pThis->pDrvStream->pfnPollInterrupt(pThis->pDrvStream);
+}
+
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ */
+static DECLCALLBACK(void) drvCharReset(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVCHAR pThis = PDMINS_2_DATA(pDrvIns, PDRVCHAR);
+
+ /* Reset TX and RX buffers. */
+ pThis->fAvailWrExt = false;
+ pThis->fAvailWrInt = false;
+ pThis->cbTxUsed = 0;
+ pThis->cbRemaining = 0;
+}
+
+
+/**
+ * Construct a char driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvCharConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(pCfg);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVCHAR pThis = PDMINS_2_DATA(pDrvIns, PDRVCHAR);
+ LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
+
+ /*
+ * Init basic data members and interfaces.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->pThrdIo = NIL_RTTHREAD;
+ /* IBase. */
+ pDrvIns->IBase.pfnQueryInterface = drvCharQueryInterface;
+ /* ISerialConnector. */
+ pThis->ISerialConnector.pfnDataAvailWrNotify = drvCharDataAvailWrNotify;
+ pThis->ISerialConnector.pfnReadRdr = drvCharReadRdr;
+ pThis->ISerialConnector.pfnChgParams = drvCharChgParams;
+ pThis->ISerialConnector.pfnChgModemLines = drvCharChgModemLines;
+ pThis->ISerialConnector.pfnChgBrk = drvCharChgBrk;
+ pThis->ISerialConnector.pfnQueryStsLines = drvCharQueryStsLines;
+ pThis->ISerialConnector.pfnQueuesFlush = drvCharQueuesFlush;
+
+ /*
+ * Get the ISerialPort interface of the above driver/device.
+ */
+ pThis->pDrvSerialPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMISERIALPORT);
+ if (!pThis->pDrvSerialPort)
+ return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE, RT_SRC_POS,
+ N_("Char#%d has no serial port interface above"), pDrvIns->iInstance);
+
+ /*
+ * Attach driver below and query its stream interface.
+ */
+ PPDMIBASE pBase;
+ int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pBase);
+ if (RT_FAILURE(rc))
+ return rc; /* Don't call PDMDrvHlpVMSetError here as we assume that the driver already set an appropriate error */
+ pThis->pDrvStream = PDMIBASE_QUERY_INTERFACE(pBase, PDMISTREAM);
+ if (!pThis->pDrvStream)
+ return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW, RT_SRC_POS,
+ N_("Char#%d has no stream interface below"), pDrvIns->iInstance);
+
+ rc = PDMDrvHlpThreadCreate(pThis->pDrvIns, &pThis->pThrdIo, pThis, drvCharIoLoop,
+ drvCharIoLoopWakeup, 0, RTTHREADTYPE_IO, "CharIo");
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("Char#%d cannot create I/O thread"), pDrvIns->iInstance);
+
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Nr of bytes written", "/Devices/Char%d/Written", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Nr of bytes read", "/Devices/Char%d/Read", pDrvIns->iInstance);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Char driver registration record.
+ */
+const PDMDRVREG g_DrvChar =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "Char",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Generic char driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_CHAR,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVCHAR),
+ /* pfnConstruct */
+ drvCharConstruct,
+ /* pfnDestruct */
+ NULL,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ drvCharReset,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
diff --git a/src/VBox/Devices/Serial/DrvHostSerial.cpp b/src/VBox/Devices/Serial/DrvHostSerial.cpp
new file mode 100644
index 00000000..fa88e637
--- /dev/null
+++ b/src/VBox/Devices/Serial/DrvHostSerial.cpp
@@ -0,0 +1,736 @@
+/* $Id: DrvHostSerial.cpp $ */
+/** @file
+ * VBox serial devices: Host serial driver
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_HOST_SERIAL
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/pdmserialifs.h>
+#include <VBox/err.h>
+
+#include <VBox/log.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/pipe.h>
+#include <iprt/semaphore.h>
+#include <iprt/uuid.h>
+#include <iprt/serialport.h>
+
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Char driver instance data.
+ *
+ * @implements PDMISERIALCONNECTOR
+ */
+typedef struct DRVHOSTSERIAL
+{
+ /** Pointer to the driver instance structure. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the serial port interface of the driver/device above us. */
+ PPDMISERIALPORT pDrvSerialPort;
+ /** Our serial interface. */
+ PDMISERIALCONNECTOR ISerialConnector;
+ /** I/O thread. */
+ PPDMTHREAD pIoThrd;
+ /** The serial port handle. */
+ RTSERIALPORT hSerialPort;
+ /** the device path */
+ char *pszDevicePath;
+
+ /** Flag whether data is available from the device/driver above as notified by the driver. */
+ volatile bool fAvailWrExt;
+ /** Internal copy of the flag which gets reset when there is no data anymore. */
+ bool fAvailWrInt;
+ /** Small send buffer. */
+ uint8_t abTxBuf[16];
+ /** Amount of data in the buffer. */
+ size_t cbTxUsed;
+
+ /** The read queue. */
+ uint8_t abReadBuf[256];
+ /** Current offset to write to next. */
+ volatile uint32_t offWrite;
+ /** Current offset into the read buffer. */
+ volatile uint32_t offRead;
+ /** Current amount of data in the buffer. */
+ volatile size_t cbReadBuf;
+
+ /** Read/write statistics */
+ STAMCOUNTER StatBytesRead;
+ STAMCOUNTER StatBytesWritten;
+} DRVHOSTSERIAL, *PDRVHOSTSERIAL;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Returns number of bytes free in the read buffer and pointer to the start of the free space
+ * in the read buffer.
+ *
+ * @returns Number of bytes free in the buffer.
+ * @param pThis The host serial driver instance.
+ * @param ppv Where to return the pointer if there is still free space.
+ */
+DECLINLINE(size_t) drvHostSerialReadBufGetWrite(PDRVHOSTSERIAL pThis, void **ppv)
+{
+ if (ppv)
+ *ppv = &pThis->abReadBuf[pThis->offWrite];
+
+ size_t cbFree = sizeof(pThis->abReadBuf) - ASMAtomicReadZ(&pThis->cbReadBuf);
+ if (cbFree)
+ cbFree = RT_MIN(cbFree, sizeof(pThis->abReadBuf) - pThis->offWrite);
+
+ return cbFree;
+}
+
+
+/**
+ * Returns number of bytes used in the read buffer and pointer to the next byte to read.
+ *
+ * @returns Number of bytes free in the buffer.
+ * @param pThis The host serial driver instance.
+ * @param ppv Where to return the pointer to the next data to read.
+ */
+DECLINLINE(size_t) drvHostSerialReadBufGetRead(PDRVHOSTSERIAL pThis, void **ppv)
+{
+ if (ppv)
+ *ppv = &pThis->abReadBuf[pThis->offRead];
+
+ size_t cbUsed = ASMAtomicReadZ(&pThis->cbReadBuf);
+ if (cbUsed)
+ cbUsed = RT_MIN(cbUsed, sizeof(pThis->abReadBuf) - pThis->offRead);
+
+ return cbUsed;
+}
+
+
+/**
+ * Advances the write position of the read buffer by the given amount of bytes.
+ *
+ * @returns nothing.
+ * @param pThis The host serial driver instance.
+ * @param cbAdv Number of bytes to advance.
+ */
+DECLINLINE(void) drvHostSerialReadBufWriteAdv(PDRVHOSTSERIAL pThis, size_t cbAdv)
+{
+ uint32_t offWrite = ASMAtomicReadU32(&pThis->offWrite);
+ offWrite = (offWrite + cbAdv) % sizeof(pThis->abReadBuf);
+ ASMAtomicWriteU32(&pThis->offWrite, offWrite);
+ ASMAtomicAddZ(&pThis->cbReadBuf, cbAdv);
+}
+
+
+/**
+ * Advances the read position of the read buffer by the given amount of bytes.
+ *
+ * @returns nothing.
+ * @param pThis The host serial driver instance.
+ * @param cbAdv Number of bytes to advance.
+ */
+DECLINLINE(void) drvHostSerialReadBufReadAdv(PDRVHOSTSERIAL pThis, size_t cbAdv)
+{
+ uint32_t offRead = ASMAtomicReadU32(&pThis->offRead);
+ offRead = (offRead + cbAdv) % sizeof(pThis->abReadBuf);
+ ASMAtomicWriteU32(&pThis->offRead, offRead);
+ ASMAtomicSubZ(&pThis->cbReadBuf, cbAdv);
+}
+
+
+/* -=-=-=-=- IBase -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvHostSerialQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMISERIALCONNECTOR, &pThis->ISerialConnector);
+ return NULL;
+}
+
+
+/* -=-=-=-=- ISerialConnector -=-=-=-=- */
+
+/** @interface_method_impl{PDMISERIALCONNECTOR,pfnDataAvailWrNotify} */
+static DECLCALLBACK(int) drvHostSerialDataAvailWrNotify(PPDMISERIALCONNECTOR pInterface)
+{
+ PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
+
+ int rc = VINF_SUCCESS;
+ bool fAvailOld = ASMAtomicXchgBool(&pThis->fAvailWrExt, true);
+ if (!fAvailOld)
+ rc = RTSerialPortEvtPollInterrupt(pThis->hSerialPort);
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMISERIALCONNECTOR,pfnReadRdr}
+ */
+static DECLCALLBACK(int) drvHostSerialReadRdr(PPDMISERIALCONNECTOR pInterface, void *pvBuf,
+ size_t cbRead, size_t *pcbRead)
+{
+ PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
+ int rc = VINF_SUCCESS;
+ uint8_t *pbDst = (uint8_t *)pvBuf;
+ size_t cbReadAll = 0;
+
+ do
+ {
+ void *pvSrc = NULL;
+ size_t cbThisRead = RT_MIN(drvHostSerialReadBufGetRead(pThis, &pvSrc), cbRead);
+ if (cbThisRead)
+ {
+ memcpy(pbDst, pvSrc, cbThisRead);
+ cbRead -= cbThisRead;
+ pbDst += cbThisRead;
+ cbReadAll += cbThisRead;
+ drvHostSerialReadBufReadAdv(pThis, cbThisRead);
+ }
+ else
+ break;
+ } while (cbRead > 0);
+
+ *pcbRead = cbReadAll;
+ /* Kick the I/O thread if there is nothing to read to recalculate the poll flags. */
+ if (!drvHostSerialReadBufGetRead(pThis, NULL))
+ rc = RTSerialPortEvtPollInterrupt(pThis->hSerialPort);
+
+ STAM_COUNTER_ADD(&pThis->StatBytesRead, cbReadAll);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMISERIALCONNECTOR,pfnChgParams}
+ */
+static DECLCALLBACK(int) drvHostSerialChgParams(PPDMISERIALCONNECTOR pInterface, uint32_t uBps,
+ PDMSERIALPARITY enmParity, unsigned cDataBits,
+ PDMSERIALSTOPBITS enmStopBits)
+{
+ PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
+ RTSERIALPORTCFG Cfg;
+
+ Cfg.uBaudRate = uBps;
+
+ switch (enmParity)
+ {
+ case PDMSERIALPARITY_EVEN:
+ Cfg.enmParity = RTSERIALPORTPARITY_EVEN;
+ break;
+ case PDMSERIALPARITY_ODD:
+ Cfg.enmParity = RTSERIALPORTPARITY_ODD;
+ break;
+ case PDMSERIALPARITY_NONE:
+ Cfg.enmParity = RTSERIALPORTPARITY_NONE;
+ break;
+ case PDMSERIALPARITY_MARK:
+ Cfg.enmParity = RTSERIALPORTPARITY_MARK;
+ break;
+ case PDMSERIALPARITY_SPACE:
+ Cfg.enmParity = RTSERIALPORTPARITY_SPACE;
+ break;
+ default:
+ AssertMsgFailed(("Unsupported parity setting %d\n", enmParity)); /* Should not happen. */
+ Cfg.enmParity = RTSERIALPORTPARITY_NONE;
+ }
+
+ switch (cDataBits)
+ {
+ case 5:
+ Cfg.enmDataBitCount = RTSERIALPORTDATABITS_5BITS;
+ break;
+ case 6:
+ Cfg.enmDataBitCount = RTSERIALPORTDATABITS_6BITS;
+ break;
+ case 7:
+ Cfg.enmDataBitCount = RTSERIALPORTDATABITS_7BITS;
+ break;
+ case 8:
+ Cfg.enmDataBitCount = RTSERIALPORTDATABITS_8BITS;
+ break;
+ default:
+ AssertMsgFailed(("Unsupported data bit count %u\n", cDataBits)); /* Should not happen. */
+ Cfg.enmDataBitCount = RTSERIALPORTDATABITS_8BITS;
+ }
+
+ switch (enmStopBits)
+ {
+ case PDMSERIALSTOPBITS_ONE:
+ Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONE;
+ break;
+ case PDMSERIALSTOPBITS_ONEPOINTFIVE:
+ Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONEPOINTFIVE;
+ break;
+ case PDMSERIALSTOPBITS_TWO:
+ Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_TWO;
+ break;
+ default:
+ AssertMsgFailed(("Unsupported stop bit count %d\n", enmStopBits)); /* Should not happen. */
+ Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONE;
+ }
+
+ return RTSerialPortCfgSet(pThis->hSerialPort, &Cfg, NULL);
+}
+
+
+/**
+ * @interface_method_impl{PDMISERIALCONNECTOR,pfnChgModemLines}
+ */
+static DECLCALLBACK(int) drvHostSerialChgModemLines(PPDMISERIALCONNECTOR pInterface, bool fRts, bool fDtr)
+{
+ PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
+
+ uint32_t fClear = 0;
+ uint32_t fSet = 0;
+
+ if (fRts)
+ fSet |= RTSERIALPORT_CHG_STS_LINES_F_RTS;
+ else
+ fClear |= RTSERIALPORT_CHG_STS_LINES_F_RTS;
+
+ if (fDtr)
+ fSet |= RTSERIALPORT_CHG_STS_LINES_F_DTR;
+ else
+ fClear |= RTSERIALPORT_CHG_STS_LINES_F_DTR;
+
+ return RTSerialPortChgStatusLines(pThis->hSerialPort, fClear, fSet);
+}
+
+
+/**
+ * @interface_method_impl{PDMISERIALCONNECTOR,pfnChgBrk}
+ */
+static DECLCALLBACK(int) drvHostSerialChgBrk(PPDMISERIALCONNECTOR pInterface, bool fBrk)
+{
+ PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
+
+ return RTSerialPortChgBreakCondition(pThis->hSerialPort, fBrk);
+}
+
+
+/**
+ * @interface_method_impl{PDMISERIALCONNECTOR,pfnQueryStsLines}
+ */
+static DECLCALLBACK(int) drvHostSerialQueryStsLines(PPDMISERIALCONNECTOR pInterface, uint32_t *pfStsLines)
+{
+ PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
+
+ return RTSerialPortQueryStatusLines(pThis->hSerialPort, pfStsLines);
+}
+
+
+/**
+ * @callback_method_impl{PDMISERIALCONNECTOR,pfnQueuesFlush}
+ */
+static DECLCALLBACK(int) drvHostSerialQueuesFlush(PPDMISERIALCONNECTOR pInterface, bool fQueueRecv, bool fQueueXmit)
+{
+ RT_NOREF(fQueueXmit);
+ LogFlowFunc(("pInterface=%#p fQueueRecv=%RTbool fQueueXmit=%RTbool\n", pInterface, fQueueRecv, fQueueXmit));
+ int rc = VINF_SUCCESS;
+ PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
+
+ if (fQueueRecv)
+ {
+ size_t cbOld = ASMAtomicXchgZ(&pThis->cbReadBuf, 0);
+ if (cbOld) /* Kick the I/O thread to fetch new data. */
+ rc = RTSerialPortEvtPollInterrupt(pThis->hSerialPort);
+ }
+
+ LogFlowFunc(("-> %Rrc\n", rc));
+ return VINF_SUCCESS;
+}
+
+
+/* -=-=-=-=- I/O thread -=-=-=-=- */
+
+/**
+ * I/O thread loop.
+ *
+ * @returns VINF_SUCCESS.
+ * @param pDrvIns PDM driver instance data.
+ * @param pThread The PDM thread data.
+ */
+static DECLCALLBACK(int) drvHostSerialIoThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
+
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ return VINF_SUCCESS;
+
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ uint32_t fEvtFlags = RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED | RTSERIALPORT_EVT_F_BREAK_DETECTED;
+
+ if (!pThis->fAvailWrInt)
+ pThis->fAvailWrInt = ASMAtomicXchgBool(&pThis->fAvailWrExt, false);
+
+ /* Wait until there is room again if there is anyting to send. */
+ if ( pThis->fAvailWrInt
+ || pThis->cbTxUsed)
+ fEvtFlags |= RTSERIALPORT_EVT_F_DATA_TX;
+
+ /* Try to receive more if there is still room. */
+ if (drvHostSerialReadBufGetWrite(pThis, NULL) > 0)
+ fEvtFlags |= RTSERIALPORT_EVT_F_DATA_RX;
+
+ uint32_t fEvtsRecv = 0;
+ int rc = RTSerialPortEvtPoll(pThis->hSerialPort, fEvtFlags, &fEvtsRecv, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ if (fEvtsRecv & RTSERIALPORT_EVT_F_DATA_TX)
+ {
+ if (pThis->fAvailWrInt)
+ {
+ /* Stuff as much data into the TX buffer as we can. */
+ size_t cbToFetch = RT_ELEMENTS(pThis->abTxBuf) - pThis->cbTxUsed;
+ size_t cbFetched = 0;
+ rc = pThis->pDrvSerialPort->pfnReadWr(pThis->pDrvSerialPort, &pThis->abTxBuf[pThis->cbTxUsed], cbToFetch,
+ &cbFetched);
+ AssertRC(rc);
+
+ if (cbFetched > 0)
+ pThis->cbTxUsed += cbFetched;
+ else
+ {
+ /* There is no data available anymore. */
+ pThis->fAvailWrInt = false;
+ }
+ }
+
+ if (pThis->cbTxUsed)
+ {
+ size_t cbProcessed = 0;
+ rc = RTSerialPortWriteNB(pThis->hSerialPort, &pThis->abTxBuf[0], pThis->cbTxUsed, &cbProcessed);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->cbTxUsed -= cbProcessed;
+ if (pThis->cbTxUsed)
+ {
+ /* Move the data in the TX buffer to the front to fill the end again. */
+ memmove(&pThis->abTxBuf[0], &pThis->abTxBuf[cbProcessed], pThis->cbTxUsed);
+ }
+ else
+ pThis->pDrvSerialPort->pfnDataSentNotify(pThis->pDrvSerialPort);
+ STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbProcessed);
+ }
+ else
+ {
+ LogRelMax(10, ("HostSerial#%d: Sending data failed even though the serial port is marked as writeable (rc=%Rrc)\n",
+ pThis->pDrvIns->iInstance, rc));
+ break;
+ }
+ }
+ }
+
+ if (fEvtsRecv & RTSERIALPORT_EVT_F_DATA_RX)
+ {
+ void *pvDst = NULL;
+ size_t cbToRead = drvHostSerialReadBufGetWrite(pThis, &pvDst);
+ size_t cbRead = 0;
+ rc = RTSerialPortReadNB(pThis->hSerialPort, pvDst, cbToRead, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ drvHostSerialReadBufWriteAdv(pThis, cbRead);
+ /* Notify the device/driver above. */
+ rc = pThis->pDrvSerialPort->pfnDataAvailRdrNotify(pThis->pDrvSerialPort, cbRead);
+ AssertRC(rc);
+ }
+ else
+ LogRelMax(10, ("HostSerial#%d: Reading data failed even though the serial port is marked as readable (rc=%Rrc)\n",
+ pThis->pDrvIns->iInstance, rc));
+ }
+
+ if (fEvtsRecv & RTSERIALPORT_EVT_F_BREAK_DETECTED)
+ pThis->pDrvSerialPort->pfnNotifyBrk(pThis->pDrvSerialPort);
+
+ if (fEvtsRecv & RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED)
+ {
+ /* The status lines have changed. Notify the device. */
+ uint32_t fStsLines = 0;
+ rc = RTSerialPortQueryStatusLines(pThis->hSerialPort, &fStsLines);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fPdmStsLines = 0;
+
+ if (fStsLines & RTSERIALPORT_STS_LINE_DCD)
+ fPdmStsLines |= PDMISERIALPORT_STS_LINE_DCD;
+ if (fStsLines & RTSERIALPORT_STS_LINE_RI)
+ fPdmStsLines |= PDMISERIALPORT_STS_LINE_RI;
+ if (fStsLines & RTSERIALPORT_STS_LINE_DSR)
+ fPdmStsLines |= PDMISERIALPORT_STS_LINE_DSR;
+ if (fStsLines & RTSERIALPORT_STS_LINE_CTS)
+ fPdmStsLines |= PDMISERIALPORT_STS_LINE_CTS;
+
+ rc = pThis->pDrvSerialPort->pfnNotifyStsLinesChanged(pThis->pDrvSerialPort, fPdmStsLines);
+ if (RT_FAILURE(rc))
+ {
+ /* Notifying device failed, continue but log it */
+ LogRelMax(10, ("HostSerial#%d: Notifying device about changed status lines failed with error %Rrc; continuing.\n",
+ pDrvIns->iInstance, rc));
+ }
+ }
+ else
+ LogRelMax(10, ("HostSerial#%d: Getting status lines state failed with error %Rrc; continuing.\n", pDrvIns->iInstance, rc));
+ }
+
+ if (fEvtsRecv & RTSERIALPORT_EVT_F_STATUS_LINE_MONITOR_FAILED)
+ LogRel(("HostSerial#%d: Status line monitoring failed at a lower level and is disabled\n", pDrvIns->iInstance));
+ }
+ else if (rc == VERR_TIMEOUT || rc == VERR_INTERRUPTED)
+ {
+ /* Getting interrupted or running into a timeout are no error conditions. */
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Unblock the send thread so it can respond to a state change.
+ *
+ * @returns a VBox status code.
+ * @param pDrvIns The driver instance.
+ * @param pThread The send thread.
+ */
+static DECLCALLBACK(int) drvHostSerialWakeupIoThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pThread);
+ PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
+
+ return RTSerialPortEvtPollInterrupt(pThis->hSerialPort);
+}
+
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+/**
+ * Destruct a char driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that
+ * any non-VM resources can be freed correctly.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvHostSerialDestruct(PPDMDRVINS pDrvIns)
+{
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+ PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
+ LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
+
+ if (pThis->hSerialPort != NIL_RTSERIALPORT)
+ {
+ RTSerialPortClose(pThis->hSerialPort);
+ pThis->hSerialPort = NIL_RTSERIALPORT;
+ }
+
+ if (pThis->pszDevicePath)
+ {
+ MMR3HeapFree(pThis->pszDevicePath);
+ pThis->pszDevicePath = NULL;
+ }
+}
+
+
+/**
+ * Construct a char driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvHostSerialConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF1(fFlags);
+ PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
+ LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+
+ /*
+ * Init basic data members and interfaces.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->hSerialPort = NIL_RTSERIALPORT;
+ pThis->fAvailWrExt = false;
+ pThis->fAvailWrInt = false;
+ pThis->cbTxUsed = 0;
+ pThis->offWrite = 0;
+ pThis->offRead = 0;
+ pThis->cbReadBuf = 0;
+ /* IBase. */
+ pDrvIns->IBase.pfnQueryInterface = drvHostSerialQueryInterface;
+ /* ISerialConnector. */
+ pThis->ISerialConnector.pfnDataAvailWrNotify = drvHostSerialDataAvailWrNotify;
+ pThis->ISerialConnector.pfnReadRdr = drvHostSerialReadRdr;
+ pThis->ISerialConnector.pfnChgParams = drvHostSerialChgParams;
+ pThis->ISerialConnector.pfnChgModemLines = drvHostSerialChgModemLines;
+ pThis->ISerialConnector.pfnChgBrk = drvHostSerialChgBrk;
+ pThis->ISerialConnector.pfnQueryStsLines = drvHostSerialQueryStsLines;
+ pThis->ISerialConnector.pfnQueuesFlush = drvHostSerialQueuesFlush;
+
+ /*
+ * Query configuration.
+ */
+ /* Device */
+ int rc = CFGMR3QueryStringAlloc(pCfg, "DevicePath", &pThis->pszDevicePath);
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: query for \"DevicePath\" string returned %Rra.\n", rc));
+ return rc;
+ }
+
+ /*
+ * Open the device
+ */
+ uint32_t fOpenFlags = RTSERIALPORT_OPEN_F_READ
+ | RTSERIALPORT_OPEN_F_WRITE
+ | RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING
+ | RTSERIALPORT_OPEN_F_DETECT_BREAK_CONDITION;
+ rc = RTSerialPortOpen(&pThis->hSerialPort, pThis->pszDevicePath, fOpenFlags);
+ if (rc == VERR_NOT_SUPPORTED)
+ {
+ /*
+ * For certain devices (or pseudo terminals) status line monitoring does not work
+ * so try again without it.
+ */
+ fOpenFlags &= ~RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING;
+ rc = RTSerialPortOpen(&pThis->hSerialPort, pThis->pszDevicePath, fOpenFlags);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Could not open host device %s, rc=%Rrc\n", pThis->pszDevicePath, rc));
+ switch (rc)
+ {
+ case VERR_ACCESS_DENIED:
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
+ N_("Cannot open host device '%s' for read/write access. Check the permissions "
+ "of that device ('/bin/ls -l %s'): Most probably you need to be member "
+ "of the device group. Make sure that you logout/login after changing "
+ "the group settings of the current user"),
+#else
+ N_("Cannot open host device '%s' for read/write access. Check the permissions "
+ "of that device"),
+#endif
+ pThis->pszDevicePath, pThis->pszDevicePath);
+ default:
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("Failed to open host device '%s'"),
+ pThis->pszDevicePath);
+ }
+ }
+
+ /*
+ * Get the ISerialPort interface of the above driver/device.
+ */
+ pThis->pDrvSerialPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMISERIALPORT);
+ if (!pThis->pDrvSerialPort)
+ return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE, RT_SRC_POS, N_("HostSerial#%d has no serial port interface above"), pDrvIns->iInstance);
+
+ /*
+ * Create the I/O thread.
+ */
+ rc = PDMDrvHlpThreadCreate(pDrvIns, &pThis->pIoThrd, pThis, drvHostSerialIoThread, drvHostSerialWakeupIoThread, 0, RTTHREADTYPE_IO, "SerIo");
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("HostSerial#%d cannot create I/O thread"), pDrvIns->iInstance);
+
+ /*
+ * Register release statistics.
+ */
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Nr of bytes written", "/Devices/HostSerial%d/Written", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
+ "Nr of bytes read", "/Devices/HostSerial%d/Read", pDrvIns->iInstance);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Char driver registration record.
+ */
+const PDMDRVREG g_DrvHostSerial =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "Host Serial",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Host serial driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_CHAR,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVHOSTSERIAL),
+ /* pfnConstruct */
+ drvHostSerialConstruct,
+ /* pfnDestruct */
+ drvHostSerialDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Serial/DrvNamedPipe.cpp b/src/VBox/Devices/Serial/DrvNamedPipe.cpp
new file mode 100644
index 00000000..40a395df
--- /dev/null
+++ b/src/VBox/Devices/Serial/DrvNamedPipe.cpp
@@ -0,0 +1,1110 @@
+ /* $Id: DrvNamedPipe.cpp $ */
+/** @file
+ * Named pipe / local socket stream driver.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_NAMEDPIPE
+#include <VBox/vmm/pdmdrv.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/stream.h>
+#include <iprt/alloc.h>
+#include <iprt/pipe.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/socket.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+
+#ifdef RT_OS_WINDOWS
+# include <iprt/win/windows.h>
+#else /* !RT_OS_WINDOWS */
+# include <errno.h>
+# include <unistd.h>
+# include <sys/types.h>
+# include <sys/socket.h>
+# include <sys/un.h>
+# ifndef SHUT_RDWR /* OS/2 */
+# define SHUT_RDWR 3
+# endif
+#endif /* !RT_OS_WINDOWS */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+#ifndef RT_OS_WINDOWS
+# define DRVNAMEDPIPE_POLLSET_ID_SOCKET 0
+# define DRVNAMEDPIPE_POLLSET_ID_WAKEUP 1
+#endif
+
+# define DRVNAMEDPIPE_WAKEUP_REASON_EXTERNAL 0
+# define DRVNAMEDPIPE_WAKEUP_REASON_NEW_CONNECTION 1
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Named pipe driver instance data.
+ *
+ * @implements PDMISTREAM
+ */
+typedef struct DRVNAMEDPIPE
+{
+ /** The stream interface. */
+ PDMISTREAM IStream;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the named pipe file name. (Freed by MM) */
+ char *pszLocation;
+ /** Flag whether VirtualBox represents the server or client side. */
+ bool fIsServer;
+#ifdef RT_OS_WINDOWS
+ /** File handle of the named pipe. */
+ HANDLE NamedPipe;
+ /** The wake event handle. */
+ HANDLE hEvtWake;
+ /** Overlapped structure for writes. */
+ OVERLAPPED OverlappedWrite;
+ /** Overlapped structure for reads. */
+ OVERLAPPED OverlappedRead;
+ /** Listen thread wakeup semaphore */
+ RTSEMEVENTMULTI ListenSem;
+ /** Read buffer. */
+ uint8_t abBufRead[32];
+ /** Write buffer. */
+ uint8_t abBufWrite[32];
+ /** Read buffer currently used. */
+ size_t cbReadBufUsed;
+ /** Size of the write buffer used. */
+ size_t cbWriteBufUsed;
+ /** Flag whether a wake operation was caused by an external trigger. */
+ volatile bool fWakeExternal;
+ /** Flag whether a read was started. */
+ bool fReadPending;
+#else /* !RT_OS_WINDOWS */
+ /** Poll set used to wait for I/O events. */
+ RTPOLLSET hPollSet;
+ /** Reading end of the wakeup pipe. */
+ RTPIPE hPipeWakeR;
+ /** Writing end of the wakeup pipe. */
+ RTPIPE hPipeWakeW;
+ /** Socket handle. */
+ RTSOCKET hSock;
+ /** Flag whether the socket is in the pollset. */
+ bool fSockInPollSet;
+ /** Socket handle of the local socket for server. */
+ int LocalSocketServer;
+#endif /* !RT_OS_WINDOWS */
+ /** Thread for listening for new connections. */
+ RTTHREAD ListenThread;
+ /** Flag to signal listening thread to shut down. */
+ bool volatile fShutdown;
+} DRVNAMEDPIPE, *PDRVNAMEDPIPE;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Kicks any possibly polling thread to get informed about changes.
+ *
+ * @returns VBOx status code.
+ * @param pThis The named pipe driver instance.
+ * @param bReason The reason code to handle.
+ */
+static int drvNamedPipePollerKick(PDRVNAMEDPIPE pThis, uint8_t bReason)
+{
+#ifdef RT_OS_WINDOWS
+ if (bReason == DRVNAMEDPIPE_WAKEUP_REASON_EXTERNAL)
+ ASMAtomicXchgBool(&pThis->fWakeExternal, true);
+ if (!SetEvent(pThis->hEvtWake))
+ return RTErrConvertFromWin32(GetLastError());
+
+ return VINF_SUCCESS;
+#else
+ size_t cbWritten = 0;
+ return RTPipeWrite(pThis->hPipeWakeW, &bReason, 1, &cbWritten);
+#endif
+}
+
+
+/** @interface_method_impl{PDMISTREAM,pfnPoll} */
+static DECLCALLBACK(int) drvNamedPipePoll(PPDMISTREAM pInterface, uint32_t fEvts, uint32_t *pfEvts, RTMSINTERVAL cMillies)
+{
+ int rc = VINF_SUCCESS;
+ PDRVNAMEDPIPE pThis = RT_FROM_MEMBER(pInterface, DRVNAMEDPIPE, IStream);
+
+ LogFlowFunc(("pInterface=%#p fEvts=%#x pfEvts=%#p cMillies=%u\n", pInterface, fEvts, pfEvts, cMillies));
+
+#ifdef RT_OS_WINDOWS
+ /* Immediately return if there is something to read or no write pending and the respective events are set. */
+ *pfEvts = 0;
+ if ( (fEvts & RTPOLL_EVT_READ)
+ && pThis->cbReadBufUsed > 0)
+ *pfEvts |= RTPOLL_EVT_READ;
+ if ( (fEvts & RTPOLL_EVT_WRITE)
+ && !pThis->cbWriteBufUsed)
+ *pfEvts |= RTPOLL_EVT_WRITE;
+
+ if (*pfEvts)
+ return VINF_SUCCESS;
+
+ while (RT_SUCCESS(rc))
+ {
+ /* Set up the waiting handles. */
+ HANDLE ahEvts[3];
+ unsigned cEvts = 0;
+
+ ahEvts[cEvts++] = pThis->hEvtWake;
+ if (fEvts & RTPOLL_EVT_WRITE)
+ {
+ Assert(pThis->cbWriteBufUsed);
+ ahEvts[cEvts++] = pThis->OverlappedWrite.hEvent;
+ }
+ if ( (fEvts & RTPOLL_EVT_READ)
+ && pThis->NamedPipe != INVALID_HANDLE_VALUE
+ && !pThis->fReadPending)
+ {
+ Assert(!pThis->cbReadBufUsed);
+
+ DWORD cbReallyRead;
+ pThis->OverlappedRead.Offset = 0;
+ pThis->OverlappedRead.OffsetHigh = 0;
+ if (!ReadFile(pThis->NamedPipe, &pThis->abBufRead[0], sizeof(pThis->abBufRead), &cbReallyRead, &pThis->OverlappedRead))
+ {
+ DWORD uError = GetLastError();
+
+ if (uError == ERROR_IO_PENDING)
+ {
+ uError = 0;
+ pThis->fReadPending = true;
+ }
+
+ if ( uError == ERROR_PIPE_LISTENING
+ || uError == ERROR_PIPE_NOT_CONNECTED)
+ {
+ /* No connection yet/anymore */
+ cbReallyRead = 0;
+ }
+ else
+ {
+ rc = RTErrConvertFromWin32(uError);
+ Log(("drvNamedPipePoll: ReadFile returned %d (%Rrc)\n", uError, rc));
+ }
+ }
+ else
+ {
+ LogFlowFunc(("Read completed: cbReallyRead=%u\n", cbReallyRead));
+ pThis->fReadPending = false;
+ pThis->cbReadBufUsed = cbReallyRead;
+ *pfEvts |= RTPOLL_EVT_READ;
+ return VINF_SUCCESS;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ Log(("drvNamedPipePoll: FileRead returned %Rrc fShutdown=%d\n", rc, pThis->fShutdown));
+ if ( !pThis->fShutdown
+ && ( rc == VERR_EOF
+ || rc == VERR_BROKEN_PIPE
+ )
+ )
+ {
+ FlushFileBuffers(pThis->NamedPipe);
+ DisconnectNamedPipe(pThis->NamedPipe);
+ if (!pThis->fIsServer)
+ {
+ CloseHandle(pThis->NamedPipe);
+ pThis->NamedPipe = INVALID_HANDLE_VALUE;
+ }
+ /* pretend success */
+ rc = VINF_SUCCESS;
+ }
+ cbReallyRead = 0;
+ }
+ }
+
+ if (pThis->fReadPending)
+ ahEvts[cEvts++] = pThis->OverlappedRead.hEvent;
+
+ DWORD dwMillies = cMillies == RT_INDEFINITE_WAIT ? INFINITE : cMillies;
+ DWORD uErr = WaitForMultipleObjects(cEvts, &ahEvts[0], FALSE /* bWaitAll */, dwMillies);
+ if (uErr == WAIT_TIMEOUT)
+ rc = VERR_TIMEOUT;
+ else if (uErr == WAIT_FAILED)
+ rc = RTErrConvertFromWin32(GetLastError());
+ else
+ {
+ /* Something triggered. */
+ unsigned idxEvt = uErr - WAIT_OBJECT_0;
+ Assert(idxEvt < cEvts);
+
+ LogFlowFunc(("Interrupted by pipe activity: idxEvt=%u\n", idxEvt));
+
+ if (idxEvt == 0)
+ {
+ /* The wakeup triggered. */
+ if (ASMAtomicXchgBool(&pThis->fWakeExternal, false))
+ rc = VERR_INTERRUPTED;
+ else
+ {
+ /*
+ * Internal event because there was a new connection from the listener thread,
+ * restart everything.
+ */
+ rc = VINF_SUCCESS;
+ }
+ }
+ else if (ahEvts[idxEvt] == pThis->OverlappedWrite.hEvent)
+ {
+ LogFlowFunc(("Write completed\n"));
+ /* Fetch the result of the write. */
+ DWORD cbWritten = 0;
+ if (GetOverlappedResult(pThis->NamedPipe, &pThis->OverlappedWrite, &cbWritten, TRUE) == FALSE)
+ {
+ uErr = GetLastError();
+ rc = RTErrConvertFromWin32(uErr);
+ Log(("drvNamedPipePoll: Write completed with %d (%Rrc)\n", uErr, rc));
+
+ if (RT_FAILURE(rc))
+ {
+ /** @todo WriteFile(pipe) has been observed to return ERROR_NO_DATA
+ * (VERR_NO_DATA) instead of ERROR_BROKEN_PIPE, when the pipe is
+ * disconnected. */
+ if ( rc == VERR_EOF
+ || rc == VERR_BROKEN_PIPE)
+ {
+ FlushFileBuffers(pThis->NamedPipe);
+ DisconnectNamedPipe(pThis->NamedPipe);
+ if (!pThis->fIsServer)
+ {
+ CloseHandle(pThis->NamedPipe);
+ pThis->NamedPipe = INVALID_HANDLE_VALUE;
+ }
+ /* pretend success */
+ rc = VINF_SUCCESS;
+ }
+ cbWritten = (DWORD)pThis->cbWriteBufUsed;
+ }
+ }
+
+ pThis->cbWriteBufUsed -= cbWritten;
+ if (!pThis->cbWriteBufUsed && (fEvts & RTPOLL_EVT_WRITE))
+ {
+ *pfEvts |= RTPOLL_EVT_WRITE;
+ break;
+ }
+ }
+ else
+ {
+ Assert(ahEvts[idxEvt] == pThis->OverlappedRead.hEvent);
+
+ DWORD cbRead = 0;
+ if (GetOverlappedResult(pThis->NamedPipe, &pThis->OverlappedRead, &cbRead, TRUE) == FALSE)
+ {
+ uErr = GetLastError();
+ rc = RTErrConvertFromWin32(uErr);
+ Log(("drvNamedPipePoll: Read completed with %d (%Rrc)\n", uErr, rc));
+
+ if (RT_FAILURE(rc))
+ {
+ /** @todo WriteFile(pipe) has been observed to return ERROR_NO_DATA
+ * (VERR_NO_DATA) instead of ERROR_BROKEN_PIPE, when the pipe is
+ * disconnected. */
+ if ( rc == VERR_EOF
+ || rc == VERR_BROKEN_PIPE)
+ {
+ FlushFileBuffers(pThis->NamedPipe);
+ DisconnectNamedPipe(pThis->NamedPipe);
+ if (!pThis->fIsServer)
+ {
+ CloseHandle(pThis->NamedPipe);
+ pThis->NamedPipe = INVALID_HANDLE_VALUE;
+ }
+ /* pretend success */
+ rc = VINF_SUCCESS;
+ }
+ cbRead = 0;
+ }
+ }
+
+ LogFlowFunc(("Read completed with cbRead=%u\n", cbRead));
+ pThis->fReadPending = false;
+ pThis->cbReadBufUsed = cbRead;
+ if (pThis->cbReadBufUsed && (fEvts & RTPOLL_EVT_READ))
+ {
+ *pfEvts |= RTPOLL_EVT_READ;
+ break;
+ }
+ }
+ }
+ }
+#else
+ if (pThis->hSock != NIL_RTSOCKET)
+ {
+ if (!pThis->fSockInPollSet)
+ {
+ rc = RTPollSetAddSocket(pThis->hPollSet, pThis->hSock,
+ fEvts, DRVNAMEDPIPE_POLLSET_ID_SOCKET);
+ if (RT_SUCCESS(rc))
+ pThis->fSockInPollSet = true;
+ }
+ else
+ {
+ /* Always include error event. */
+ fEvts |= RTPOLL_EVT_ERROR;
+ rc = RTPollSetEventsChange(pThis->hPollSet, DRVNAMEDPIPE_POLLSET_ID_SOCKET, fEvts);
+ AssertRC(rc);
+ }
+ }
+
+ while (RT_SUCCESS(rc))
+ {
+ uint32_t fEvtsRecv = 0;
+ uint32_t idHnd = 0;
+
+ rc = RTPoll(pThis->hPollSet, cMillies, &fEvtsRecv, &idHnd);
+ if (RT_SUCCESS(rc))
+ {
+ if (idHnd == DRVNAMEDPIPE_POLLSET_ID_WAKEUP)
+ {
+ /* We got woken up, drain the pipe and return. */
+ uint8_t bReason;
+ size_t cbRead = 0;
+ rc = RTPipeRead(pThis->hPipeWakeR, &bReason, 1, &cbRead);
+ AssertRC(rc);
+
+ if (bReason == DRVNAMEDPIPE_WAKEUP_REASON_EXTERNAL)
+ rc = VERR_INTERRUPTED;
+ else if (bReason == DRVNAMEDPIPE_WAKEUP_REASON_NEW_CONNECTION)
+ {
+ Assert(!pThis->fSockInPollSet);
+ rc = RTPollSetAddSocket(pThis->hPollSet, pThis->hSock,
+ fEvts, DRVNAMEDPIPE_POLLSET_ID_SOCKET);
+ if (RT_SUCCESS(rc))
+ pThis->fSockInPollSet = true;
+ }
+ else
+ AssertMsgFailed(("Unknown wakeup reason in pipe %u\n", bReason));
+ }
+ else
+ {
+ Assert(idHnd == DRVNAMEDPIPE_POLLSET_ID_SOCKET);
+
+ /* On error we close the socket here. */
+ if (fEvtsRecv & RTPOLL_EVT_ERROR)
+ {
+ rc = RTPollSetRemove(pThis->hPollSet, DRVNAMEDPIPE_POLLSET_ID_SOCKET);
+ AssertRC(rc);
+
+ RTSocketClose(pThis->hSock);
+ pThis->hSock = NIL_RTSOCKET;
+ pThis->fSockInPollSet = false;
+ /* Continue with polling. */
+ }
+ else
+ {
+ *pfEvts = fEvtsRecv;
+ break;
+ }
+ }
+ }
+ }
+#endif
+
+ LogFlowFunc(("returns %Rrc\n", rc));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMISTREAM,pfnPollInterrupt} */
+static DECLCALLBACK(int) drvNamedPipePollInterrupt(PPDMISTREAM pInterface)
+{
+ PDRVNAMEDPIPE pThis = RT_FROM_MEMBER(pInterface, DRVNAMEDPIPE, IStream);
+ return drvNamedPipePollerKick(pThis, DRVNAMEDPIPE_WAKEUP_REASON_EXTERNAL);
+}
+
+
+/** @interface_method_impl{PDMISTREAM,pfnRead} */
+static DECLCALLBACK(int) drvNamedPipeRead(PPDMISTREAM pInterface, void *pvBuf, size_t *pcbRead)
+{
+ int rc = VINF_SUCCESS;
+ PDRVNAMEDPIPE pThis = RT_FROM_MEMBER(pInterface, DRVNAMEDPIPE, IStream);
+ LogFlow(("%s: pvBuf=%p *pcbRead=%#x (%s)\n", __FUNCTION__, pvBuf, *pcbRead, pThis->pszLocation));
+
+ Assert(pvBuf);
+#ifdef RT_OS_WINDOWS
+ if (pThis->NamedPipe != INVALID_HANDLE_VALUE)
+ {
+ /* Check if there is something in the read buffer and return as much as we can. */
+ if (pThis->cbReadBufUsed)
+ {
+ size_t cbRead = RT_MIN(*pcbRead, pThis->cbReadBufUsed);
+
+ memcpy(pvBuf, &pThis->abBufRead[0], cbRead);
+ if (cbRead < pThis->cbReadBufUsed)
+ memmove(&pThis->abBufRead[0], &pThis->abBufRead[cbRead], pThis->cbReadBufUsed - cbRead);
+ pThis->cbReadBufUsed -= cbRead;
+ *pcbRead = cbRead;
+ }
+ else
+ *pcbRead = 0;
+ }
+#else /* !RT_OS_WINDOWS */
+ if (pThis->hSock != NIL_RTSOCKET)
+ {
+ size_t cbRead;
+ size_t cbBuf = *pcbRead;
+ rc = RTSocketReadNB(pThis->hSock, pvBuf, cbBuf, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ if (!cbRead && rc != VINF_TRY_AGAIN)
+ {
+ rc = RTPollSetRemove(pThis->hPollSet, DRVNAMEDPIPE_POLLSET_ID_SOCKET);
+ AssertRC(rc);
+
+ RTSocketClose(pThis->hSock);
+ pThis->hSock = NIL_RTSOCKET;
+ pThis->fSockInPollSet = false;
+ rc = VINF_SUCCESS;
+ }
+ *pcbRead = cbRead;
+ }
+ }
+#endif /* !RT_OS_WINDOWS */
+ else
+ {
+ RTThreadSleep(100);
+ *pcbRead = 0;
+ }
+
+ LogFlow(("%s: *pcbRead=%zu returns %Rrc\n", __FUNCTION__, *pcbRead, rc));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMISTREAM,pfnWrite} */
+static DECLCALLBACK(int) drvNamedPipeWrite(PPDMISTREAM pInterface, const void *pvBuf, size_t *pcbWrite)
+{
+ int rc = VINF_SUCCESS;
+ PDRVNAMEDPIPE pThis = RT_FROM_MEMBER(pInterface, DRVNAMEDPIPE, IStream);
+ LogFlow(("%s: pvBuf=%p *pcbWrite=%#x (%s)\n", __FUNCTION__, pvBuf, *pcbWrite, pThis->pszLocation));
+
+ Assert(pvBuf);
+#ifdef RT_OS_WINDOWS
+ if (pThis->NamedPipe != INVALID_HANDLE_VALUE)
+ {
+ /* Accept the data in case the write buffer is empty. */
+ if (!pThis->cbWriteBufUsed)
+ {
+ size_t cbWrite = RT_MIN(*pcbWrite, sizeof(pThis->cbWriteBufUsed));
+
+ memcpy(&pThis->abBufWrite[0], pvBuf, cbWrite);
+ pThis->cbWriteBufUsed += cbWrite;
+
+ /* Initiate the write. */
+ pThis->OverlappedWrite.Offset = 0;
+ pThis->OverlappedWrite.OffsetHigh = 0;
+ if (!WriteFile(pThis->NamedPipe, pvBuf, (DWORD)cbWrite, NULL, &pThis->OverlappedWrite))
+ {
+ DWORD uError = GetLastError();
+
+ if ( uError == ERROR_PIPE_LISTENING
+ || uError == ERROR_PIPE_NOT_CONNECTED)
+ {
+ /* No connection yet/anymore; just discard the write (pretending everything was written). */
+ pThis->cbWriteBufUsed = 0;
+ cbWrite = *pcbWrite;
+ }
+ else if (uError != ERROR_IO_PENDING) /* We wait for the write to complete in the poll callback. */
+ {
+ rc = RTErrConvertFromWin32(uError);
+ Log(("drvNamedPipeWrite: WriteFile returned %d (%Rrc)\n", uError, rc));
+ cbWrite = 0;
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ /** @todo WriteFile(pipe) has been observed to return ERROR_NO_DATA
+ * (VERR_NO_DATA) instead of ERROR_BROKEN_PIPE, when the pipe is
+ * disconnected. */
+ if ( rc == VERR_EOF
+ || rc == VERR_BROKEN_PIPE)
+ {
+ FlushFileBuffers(pThis->NamedPipe);
+ DisconnectNamedPipe(pThis->NamedPipe);
+ if (!pThis->fIsServer)
+ {
+ CloseHandle(pThis->NamedPipe);
+ pThis->NamedPipe = INVALID_HANDLE_VALUE;
+ }
+ /* pretend success */
+ rc = VINF_SUCCESS;
+ }
+ cbWrite = 0;
+ }
+
+ *pcbWrite = cbWrite;
+ }
+ else
+ *pcbWrite = 0;
+ }
+#else /* !RT_OS_WINDOWS */
+ if (pThis->hSock != NIL_RTSOCKET)
+ {
+ size_t cbBuf = *pcbWrite;
+ rc = RTSocketWriteNB(pThis->hSock, pvBuf, cbBuf, pcbWrite);
+ }
+ else
+ *pcbWrite = 0;
+#endif /* !RT_OS_WINDOWS */
+
+ LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvNamedPipeQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVNAMEDPIPE pThis = PDMINS_2_DATA(pDrvIns, PDRVNAMEDPIPE);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMISTREAM, &pThis->IStream);
+ return NULL;
+}
+
+
+/* -=-=-=-=- listen thread -=-=-=-=- */
+
+/**
+ * Receive thread loop.
+ *
+ * @returns 0 on success.
+ * @param hThreadSelf Thread handle to this thread.
+ * @param pvUser User argument.
+ */
+static DECLCALLBACK(int) drvNamedPipeListenLoop(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+ PDRVNAMEDPIPE pThis = (PDRVNAMEDPIPE)pvUser;
+ int rc = VINF_SUCCESS;
+#ifdef RT_OS_WINDOWS
+ HANDLE NamedPipe = pThis->NamedPipe;
+ HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, 0);
+#endif
+
+ while (RT_LIKELY(!pThis->fShutdown))
+ {
+#ifdef RT_OS_WINDOWS
+ OVERLAPPED overlapped;
+
+ memset(&overlapped, 0, sizeof(overlapped));
+ overlapped.hEvent = hEvent;
+
+ BOOL fConnected = ConnectNamedPipe(NamedPipe, &overlapped);
+ if ( !fConnected
+ && !pThis->fShutdown)
+ {
+ DWORD hrc = GetLastError();
+
+ if (hrc == ERROR_IO_PENDING)
+ {
+ DWORD dummy;
+
+ hrc = 0;
+ if (GetOverlappedResult(pThis->NamedPipe, &overlapped, &dummy, TRUE) == FALSE)
+ hrc = GetLastError();
+ else
+ drvNamedPipePollerKick(pThis, DRVNAMEDPIPE_WAKEUP_REASON_NEW_CONNECTION);
+ }
+
+ if (pThis->fShutdown)
+ break;
+
+ if (hrc == ERROR_PIPE_CONNECTED)
+ {
+ RTSemEventMultiWait(pThis->ListenSem, 250);
+ }
+ else if (hrc != ERROR_SUCCESS)
+ {
+ rc = RTErrConvertFromWin32(hrc);
+ LogRel(("NamedPipe%d: ConnectNamedPipe failed, rc=%Rrc\n", pThis->pDrvIns->iInstance, rc));
+ break;
+ }
+ }
+#else /* !RT_OS_WINDOWS */
+ if (listen(pThis->LocalSocketServer, 0) == -1)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ LogRel(("NamedPipe%d: listen failed, rc=%Rrc\n", pThis->pDrvIns->iInstance, rc));
+ break;
+ }
+ int s = accept(pThis->LocalSocketServer, NULL, NULL);
+ if (s == -1)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ LogRel(("NamedPipe%d: accept failed, rc=%Rrc\n", pThis->pDrvIns->iInstance, rc));
+ break;
+ }
+ if (pThis->hSock != NIL_RTSOCKET)
+ {
+ LogRel(("NamedPipe%d: only single connection supported\n", pThis->pDrvIns->iInstance));
+ close(s);
+ }
+ else
+ {
+ RTSOCKET hSockNew = NIL_RTSOCKET;
+ rc = RTSocketFromNative(&hSockNew, s);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->hSock = hSockNew;
+ /* Inform the poller about the new socket. */
+ drvNamedPipePollerKick(pThis, DRVNAMEDPIPE_WAKEUP_REASON_NEW_CONNECTION);
+ }
+ else
+ {
+ LogRel(("NamedPipe%d: Failed to wrap socket with %Rrc\n", pThis->pDrvIns->iInstance, rc));
+ close(s);
+ }
+ }
+#endif /* !RT_OS_WINDOWS */
+ }
+
+#ifdef RT_OS_WINDOWS
+ CloseHandle(hEvent);
+#endif
+ return VINF_SUCCESS;
+}
+
+/* -=-=-=-=- PDMDRVREG -=-=-=-=- */
+
+/**
+ * Common worker for drvNamedPipePowerOff and drvNamedPipeDestructor.
+ *
+ * @param pThis The instance data.
+ */
+static void drvNamedPipeShutdownListener(PDRVNAMEDPIPE pThis)
+{
+ /*
+ * Signal shutdown of the listener thread.
+ */
+ pThis->fShutdown = true;
+#ifdef RT_OS_WINDOWS
+ if ( pThis->fIsServer
+ && pThis->NamedPipe != INVALID_HANDLE_VALUE)
+ {
+ FlushFileBuffers(pThis->NamedPipe);
+ DisconnectNamedPipe(pThis->NamedPipe);
+
+ BOOL fRc = CloseHandle(pThis->NamedPipe);
+ Assert(fRc); NOREF(fRc);
+ pThis->NamedPipe = INVALID_HANDLE_VALUE;
+
+ /* Wake up listen thread */
+ if (pThis->ListenSem != NIL_RTSEMEVENT)
+ RTSemEventMultiSignal(pThis->ListenSem);
+ }
+#else
+ if ( pThis->fIsServer
+ && pThis->LocalSocketServer != -1)
+ {
+ int rc = shutdown(pThis->LocalSocketServer, SHUT_RDWR);
+ AssertRC(rc == 0); NOREF(rc);
+
+ rc = close(pThis->LocalSocketServer);
+ AssertRC(rc == 0);
+ pThis->LocalSocketServer = -1;
+ }
+#endif
+}
+
+
+/**
+ * Power off a named pipe stream driver instance.
+ *
+ * This does most of the destruction work, to avoid ordering dependencies.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvNamedPipePowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVNAMEDPIPE pThis = PDMINS_2_DATA(pDrvIns, PDRVNAMEDPIPE);
+ LogFlow(("%s: %s\n", __FUNCTION__, pThis->pszLocation));
+
+ drvNamedPipeShutdownListener(pThis);
+}
+
+
+/**
+ * Destruct a named pipe stream driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that
+ * any non-VM resources can be freed correctly.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvNamedPipeDestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVNAMEDPIPE pThis = PDMINS_2_DATA(pDrvIns, PDRVNAMEDPIPE);
+ LogFlow(("%s: %s\n", __FUNCTION__, pThis->pszLocation));
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+
+ drvNamedPipeShutdownListener(pThis);
+
+ /*
+ * While the thread exits, clean up as much as we can.
+ */
+#ifdef RT_OS_WINDOWS
+ if (pThis->NamedPipe != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(pThis->NamedPipe);
+ pThis->NamedPipe = INVALID_HANDLE_VALUE;
+ }
+ if (pThis->OverlappedRead.hEvent != NULL)
+ {
+ CloseHandle(pThis->OverlappedRead.hEvent);
+ pThis->OverlappedRead.hEvent = NULL;
+ }
+ if (pThis->OverlappedWrite.hEvent != NULL)
+ {
+ CloseHandle(pThis->OverlappedWrite.hEvent);
+ pThis->OverlappedWrite.hEvent = NULL;
+ }
+ if (pThis->hEvtWake != NULL)
+ {
+ CloseHandle(pThis->hEvtWake);
+ pThis->hEvtWake = NULL;
+ }
+#else /* !RT_OS_WINDOWS */
+ Assert(pThis->LocalSocketServer == -1);
+
+ if (pThis->hSock != NIL_RTSOCKET)
+ {
+ int rc = RTPollSetRemove(pThis->hPollSet, DRVNAMEDPIPE_POLLSET_ID_SOCKET);
+ AssertRC(rc);
+
+ rc = RTSocketShutdown(pThis->hSock, true /* fRead */, true /* fWrite */);
+ AssertRC(rc);
+
+ rc = RTSocketClose(pThis->hSock);
+ AssertRC(rc); RT_NOREF(rc);
+
+ pThis->hSock = NIL_RTSOCKET;
+ }
+
+ if (pThis->hPipeWakeR != NIL_RTPIPE)
+ {
+ int rc = RTPipeClose(pThis->hPipeWakeR);
+ AssertRC(rc);
+
+ pThis->hPipeWakeR = NIL_RTPIPE;
+ }
+
+ if (pThis->hPipeWakeW != NIL_RTPIPE)
+ {
+ int rc = RTPipeClose(pThis->hPipeWakeW);
+ AssertRC(rc);
+
+ pThis->hPipeWakeW = NIL_RTPIPE;
+ }
+
+ if (pThis->hPollSet != NIL_RTPOLLSET)
+ {
+ int rc = RTPollSetDestroy(pThis->hPollSet);
+ AssertRC(rc);
+
+ pThis->hPollSet = NIL_RTPOLLSET;
+ }
+
+ if ( pThis->fIsServer
+ && pThis->pszLocation)
+ RTFileDelete(pThis->pszLocation);
+#endif /* !RT_OS_WINDOWS */
+
+ MMR3HeapFree(pThis->pszLocation);
+ pThis->pszLocation = NULL;
+
+ /*
+ * Wait for the thread.
+ */
+ if (pThis->ListenThread != NIL_RTTHREAD)
+ {
+ int rc = RTThreadWait(pThis->ListenThread, 30000, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->ListenThread = NIL_RTTHREAD;
+ else
+ LogRel(("NamedPipe%d: listen thread did not terminate (%Rrc)\n", pDrvIns->iInstance, rc));
+ }
+
+ /*
+ * The last bits of cleanup.
+ */
+#ifdef RT_OS_WINDOWS
+ if (pThis->ListenSem != NIL_RTSEMEVENT)
+ {
+ RTSemEventMultiDestroy(pThis->ListenSem);
+ pThis->ListenSem = NIL_RTSEMEVENT;
+ }
+#endif
+}
+
+
+/**
+ * Construct a named pipe stream driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvNamedPipeConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVNAMEDPIPE pThis = PDMINS_2_DATA(pDrvIns, PDRVNAMEDPIPE);
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->pszLocation = NULL;
+ pThis->fIsServer = false;
+#ifdef RT_OS_WINDOWS
+ pThis->NamedPipe = INVALID_HANDLE_VALUE;
+ pThis->ListenSem = NIL_RTSEMEVENTMULTI;
+ pThis->OverlappedWrite.hEvent = NULL;
+ pThis->OverlappedRead.hEvent = NULL;
+ pThis->hEvtWake = NULL;
+#else /* !RT_OS_WINDOWS */
+ pThis->LocalSocketServer = -1;
+ pThis->hSock = NIL_RTSOCKET;
+
+ pThis->hPollSet = NIL_RTPOLLSET;
+ pThis->hPipeWakeR = NIL_RTPIPE;
+ pThis->hPipeWakeW = NIL_RTPIPE;
+ pThis->fSockInPollSet = false;
+#endif /* !RT_OS_WINDOWS */
+ pThis->ListenThread = NIL_RTTHREAD;
+ pThis->fShutdown = false;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvNamedPipeQueryInterface;
+ /* IStream */
+ pThis->IStream.pfnPoll = drvNamedPipePoll;
+ pThis->IStream.pfnPollInterrupt = drvNamedPipePollInterrupt;
+ pThis->IStream.pfnRead = drvNamedPipeRead;
+ pThis->IStream.pfnWrite = drvNamedPipeWrite;
+
+ /*
+ * Validate and read the configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "Location|IsServer", "");
+
+ int rc = CFGMR3QueryStringAlloc(pCfg, "Location", &pThis->pszLocation);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("Configuration error: querying \"Location\" resulted in %Rrc"), rc);
+ rc = CFGMR3QueryBool(pCfg, "IsServer", &pThis->fIsServer);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("Configuration error: querying \"IsServer\" resulted in %Rrc"), rc);
+
+ /*
+ * Create/Open the pipe.
+ */
+#ifdef RT_OS_WINDOWS
+ if (pThis->fIsServer)
+ {
+ pThis->NamedPipe = CreateNamedPipe(pThis->pszLocation,
+ PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
+ PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
+ 1, /*nMaxInstances*/
+ 32, /*nOutBufferSize*/
+ 32, /*nOutBufferSize*/
+ 10000, /*nDefaultTimeOut*/
+ NULL); /* lpSecurityAttributes*/
+ if (pThis->NamedPipe == INVALID_HANDLE_VALUE)
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ LogRel(("NamedPipe%d: CreateNamedPipe failed rc=%Rrc\n", pThis->pDrvIns->iInstance));
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("NamedPipe#%d failed to create named pipe %s"),
+ pDrvIns->iInstance, pThis->pszLocation);
+ }
+
+ rc = RTSemEventMultiCreate(&pThis->ListenSem);
+ AssertRCReturn(rc, rc);
+
+ rc = RTThreadCreate(&pThis->ListenThread, drvNamedPipeListenLoop, (void *)pThis, 0,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SerPipe");
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("NamedPipe#%d failed to create listening thread"),
+ pDrvIns->iInstance);
+
+ }
+ else
+ {
+ /* Connect to the named pipe. */
+ pThis->NamedPipe = CreateFile(pThis->pszLocation, GENERIC_READ | GENERIC_WRITE, 0, NULL,
+ OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
+ if (pThis->NamedPipe == INVALID_HANDLE_VALUE)
+ {
+ rc = RTErrConvertFromWin32(GetLastError());
+ LogRel(("NamedPipe%d: CreateFile failed rc=%Rrc\n", pThis->pDrvIns->iInstance));
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("NamedPipe#%d failed to connect to named pipe %s"),
+ pDrvIns->iInstance, pThis->pszLocation);
+ }
+ }
+
+ memset(&pThis->OverlappedWrite, 0, sizeof(pThis->OverlappedWrite));
+ pThis->OverlappedWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ AssertReturn(pThis->OverlappedWrite.hEvent != NULL, VERR_OUT_OF_RESOURCES);
+
+ memset(&pThis->OverlappedRead, 0, sizeof(pThis->OverlappedRead));
+ pThis->OverlappedRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ AssertReturn(pThis->OverlappedRead.hEvent != NULL, VERR_OUT_OF_RESOURCES);
+
+ pThis->hEvtWake = CreateEvent(NULL, FALSE, FALSE, NULL);
+ AssertReturn(pThis->hEvtWake != NULL, VERR_OUT_OF_RESOURCES);
+
+#else /* !RT_OS_WINDOWS */
+ rc = RTPipeCreate(&pThis->hPipeWakeR, &pThis->hPipeWakeW, 0 /* fFlags */);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("DrvTCP#%d: Failed to create wake pipe"), pDrvIns->iInstance);
+
+ rc = RTPollSetCreate(&pThis->hPollSet);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("DrvTCP#%d: Failed to create poll set"), pDrvIns->iInstance);
+
+ rc = RTPollSetAddPipe(pThis->hPollSet, pThis->hPipeWakeR,
+ RTPOLL_EVT_READ | RTPOLL_EVT_ERROR,
+ DRVNAMEDPIPE_POLLSET_ID_WAKEUP);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("DrvTCP#%d failed to add wakeup pipe for %s to poll set"),
+ pDrvIns->iInstance, pThis->pszLocation);
+
+ int s = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (s == -1)
+ return PDMDrvHlpVMSetError(pDrvIns, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("NamedPipe#%d failed to create local socket"), pDrvIns->iInstance);
+
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, pThis->pszLocation, sizeof(addr.sun_path) - 1);
+
+ if (pThis->fIsServer)
+ {
+ /* Bind address to the local socket. */
+ pThis->LocalSocketServer = s;
+ RTFileDelete(pThis->pszLocation);
+ if (bind(s, (struct sockaddr *)&addr, sizeof(addr)) == -1)
+ return PDMDrvHlpVMSetError(pDrvIns, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("NamedPipe#%d failed to bind to local socket %s"),
+ pDrvIns->iInstance, pThis->pszLocation);
+ rc = RTThreadCreate(&pThis->ListenThread, drvNamedPipeListenLoop, (void *)pThis, 0,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SerPipe");
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("NamedPipe#%d failed to create listening thread"), pDrvIns->iInstance);
+ }
+ else
+ {
+ /* Connect to the local socket. */
+ if (connect(s, (struct sockaddr *)&addr, sizeof(addr)) == -1)
+ return PDMDrvHlpVMSetError(pDrvIns, RTErrConvertFromErrno(errno), RT_SRC_POS,
+ N_("NamedPipe#%d failed to connect to local socket %s"),
+ pDrvIns->iInstance, pThis->pszLocation);
+
+ rc = RTSocketFromNative(&pThis->hSock, s);
+ if (RT_FAILURE(rc))
+ {
+ close(s);
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("NamedPipe#%d failed to wrap socket %Rrc"),
+ pDrvIns->iInstance, pThis->pszLocation);
+ }
+ }
+#endif /* !RT_OS_WINDOWS */
+
+ LogRel(("NamedPipe: location %s, %s\n", pThis->pszLocation, pThis->fIsServer ? "server" : "client"));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Named pipe driver registration record.
+ */
+const PDMDRVREG g_DrvNamedPipe =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "NamedPipe",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "Named Pipe stream driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_STREAM,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVNAMEDPIPE),
+ /* pfnConstruct */
+ drvNamedPipeConstruct,
+ /* pfnDestruct */
+ drvNamedPipeDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ drvNamedPipePowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Serial/DrvRawFile.cpp b/src/VBox/Devices/Serial/DrvRawFile.cpp
new file mode 100644
index 00000000..92263417
--- /dev/null
+++ b/src/VBox/Devices/Serial/DrvRawFile.cpp
@@ -0,0 +1,287 @@
+/* $Id: DrvRawFile.cpp $ */
+/** @file
+ * VBox stream drivers - Raw file output.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEFAULT
+#include <VBox/vmm/pdmdrv.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/poll.h>
+#include <iprt/semaphore.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Raw file output driver instance data.
+ *
+ * @implements PDMISTREAM
+ */
+typedef struct DRVRAWFILE
+{
+ /** The stream interface. */
+ PDMISTREAM IStream;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the file name. (Freed by MM) */
+ char *pszLocation;
+ /** File handle to write the data to. */
+ RTFILE hOutputFile;
+ /** Event semaphore for the poll interface. */
+ RTSEMEVENT hSemEvtPoll;
+} DRVRAWFILE, *PDRVRAWFILE;
+
+
+
+/* -=-=-=-=- PDMISTREAM -=-=-=-=- */
+
+/** @interface_method_impl{PDMISTREAM,pfnPoll} */
+static DECLCALLBACK(int) drvRawFilePoll(PPDMISTREAM pInterface, uint32_t fEvts, uint32_t *pfEvts, RTMSINTERVAL cMillies)
+{
+ PDRVRAWFILE pThis = RT_FROM_MEMBER(pInterface, DRVRAWFILE, IStream);
+
+ Assert(!(fEvts & RTPOLL_EVT_READ)); /* Reading is not supported here. */
+
+ /* Writing is always possible. */
+ if (fEvts & RTPOLL_EVT_WRITE)
+ {
+ *pfEvts = RTPOLL_EVT_WRITE;
+ return VINF_SUCCESS;
+ }
+
+ return RTSemEventWait(pThis->hSemEvtPoll, cMillies);
+}
+
+
+/** @interface_method_impl{PDMISTREAM,pfnPollInterrupt} */
+static DECLCALLBACK(int) drvRawFilePollInterrupt(PPDMISTREAM pInterface)
+{
+ PDRVRAWFILE pThis = RT_FROM_MEMBER(pInterface, DRVRAWFILE, IStream);
+ return RTSemEventSignal(pThis->hSemEvtPoll);
+}
+
+
+/** @interface_method_impl{PDMISTREAM,pfnWrite} */
+static DECLCALLBACK(int) drvRawFileWrite(PPDMISTREAM pInterface, const void *pvBuf, size_t *pcbWrite)
+{
+ int rc = VINF_SUCCESS;
+ PDRVRAWFILE pThis = RT_FROM_MEMBER(pInterface, DRVRAWFILE, IStream);
+ LogFlow(("%s: pvBuf=%p *pcbWrite=%#x (%s)\n", __FUNCTION__, pvBuf, *pcbWrite, pThis->pszLocation));
+
+ Assert(pvBuf);
+ if (pThis->hOutputFile != NIL_RTFILE)
+ {
+ size_t cbWritten;
+ rc = RTFileWrite(pThis->hOutputFile, pvBuf, *pcbWrite, &cbWritten);
+#if 0
+ /* don't flush here, takes too long and we will loose characters */
+ if (RT_SUCCESS(rc))
+ RTFileFlush(pThis->hOutputFile);
+#endif
+ *pcbWrite = cbWritten;
+ }
+
+ LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
+ return rc;
+}
+
+/* -=-=-=-=- PDMIBASE -=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvRawFileQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVRAWFILE pThis = PDMINS_2_DATA(pDrvIns, PDRVRAWFILE);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMISTREAM, &pThis->IStream);
+ return NULL;
+}
+
+/* -=-=-=-=- PDMDRVREG -=-=-=-=- */
+
+
+/**
+ * Power off a raw output stream driver instance.
+ *
+ * This does most of the destruction work, to avoid ordering dependencies.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvRawFilePowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVRAWFILE pThis = PDMINS_2_DATA(pDrvIns, PDRVRAWFILE);
+ LogFlow(("%s: %s\n", __FUNCTION__, pThis->pszLocation));
+
+ RTFileClose(pThis->hOutputFile);
+ pThis->hOutputFile = NIL_RTFILE;
+}
+
+
+/**
+ * Destruct a raw output stream driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that
+ * any non-VM resources can be freed correctly.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvRawFileDestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVRAWFILE pThis = PDMINS_2_DATA(pDrvIns, PDRVRAWFILE);
+ LogFlow(("%s: %s\n", __FUNCTION__, pThis->pszLocation));
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+
+ if (pThis->pszLocation)
+ MMR3HeapFree(pThis->pszLocation);
+
+ if (pThis->hOutputFile != NIL_RTFILE)
+ {
+ RTFileClose(pThis->hOutputFile);
+ pThis->hOutputFile = NIL_RTFILE;
+ }
+
+ if (pThis->hSemEvtPoll != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(pThis->hSemEvtPoll);
+ pThis->hSemEvtPoll = NIL_RTSEMEVENT;
+ }
+}
+
+
+/**
+ * Construct a raw output stream driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvRawFileConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVRAWFILE pThis = PDMINS_2_DATA(pDrvIns, PDRVRAWFILE);
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->pszLocation = NULL;
+ pThis->hOutputFile = NIL_RTFILE;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvRawFileQueryInterface;
+ /* IStream */
+ pThis->IStream.pfnPoll = drvRawFilePoll;
+ pThis->IStream.pfnPollInterrupt = drvRawFilePollInterrupt;
+ pThis->IStream.pfnRead = NULL;
+ pThis->IStream.pfnWrite = drvRawFileWrite;
+
+ /*
+ * Read the configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, "Location\0"))
+ AssertFailedReturn(VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES);
+
+ int rc = CFGMR3QueryStringAlloc(pCfg, "Location", &pThis->pszLocation);
+ if (RT_FAILURE(rc))
+ AssertMsgFailedReturn(("Configuration error: query \"Location\" resulted in %Rrc.\n", rc), rc);
+
+ rc = RTSemEventCreate(&pThis->hSemEvtPoll);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Open the raw file.
+ */
+ rc = RTFileOpen(&pThis->hOutputFile, pThis->pszLocation, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("RawFile%d: CreateFile failed rc=%Rrc\n", pDrvIns->iInstance, rc));
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("RawFile#%d failed to create the raw output file %s"), pDrvIns->iInstance, pThis->pszLocation);
+ }
+
+ LogFlow(("drvRawFileConstruct: location %s\n", pThis->pszLocation));
+ LogRel(("RawFile#%u: location %s\n", pDrvIns->iInstance, pThis->pszLocation));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Raw file driver registration record.
+ */
+const PDMDRVREG g_DrvRawFile =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "RawFile",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "RawFile stream driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_STREAM,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVRAWFILE),
+ /* pfnConstruct */
+ drvRawFileConstruct,
+ /* pfnDestruct */
+ drvRawFileDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ drvRawFilePowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Serial/DrvTCP.cpp b/src/VBox/Devices/Serial/DrvTCP.cpp
new file mode 100644
index 00000000..b7cf2fcb
--- /dev/null
+++ b/src/VBox/Devices/Serial/DrvTCP.cpp
@@ -0,0 +1,651 @@
+/* $Id: DrvTCP.cpp $ */
+/** @file
+ * TCP socket driver implementing the IStream interface.
+ */
+
+/*
+ * Contributed by Alexey Eromenko (derived from DrvNamedPipe).
+ *
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_TCP
+#include <VBox/vmm/pdmdrv.h>
+#include <iprt/assert.h>
+#include <iprt/file.h>
+#include <iprt/stream.h>
+#include <iprt/alloc.h>
+#include <iprt/pipe.h>
+#include <iprt/poll.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/socket.h>
+#include <iprt/tcp.h>
+#include <iprt/uuid.h>
+#include <stdlib.h>
+
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+#define DRVTCP_POLLSET_ID_SOCKET 0
+#define DRVTCP_POLLSET_ID_WAKEUP 1
+
+#define DRVTCP_WAKEUP_REASON_EXTERNAL 0
+#define DRVTCP_WAKEUP_REASON_NEW_CONNECTION 1
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * TCP driver instance data.
+ *
+ * @implements PDMISTREAM
+ */
+typedef struct DRVTCP
+{
+ /** The stream interface. */
+ PDMISTREAM IStream;
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the TCP server address:port or port only. (Freed by MM) */
+ char *pszLocation;
+ /** Flag whether VirtualBox represents the server or client side. */
+ bool fIsServer;
+
+ /** Handle of the TCP server for incoming connections. */
+ PRTTCPSERVER hTcpServ;
+ /** Socket handle of the TCP socket connection. */
+ RTSOCKET hTcpSock;
+
+ /** Poll set used to wait for I/O events. */
+ RTPOLLSET hPollSet;
+ /** Reading end of the wakeup pipe. */
+ RTPIPE hPipeWakeR;
+ /** Writing end of the wakeup pipe. */
+ RTPIPE hPipeWakeW;
+ /** Flag whether the socket is in the pollset. */
+ bool fTcpSockInPollSet;
+ /** Flag whether the send buffer is full nad it is required to wait for more
+ * space until there is room again. */
+ bool fXmitBufFull;
+
+ /** Thread for listening for new connections. */
+ RTTHREAD ListenThread;
+ /** Flag to signal listening thread to shut down. */
+ bool volatile fShutdown;
+} DRVTCP, *PDRVTCP;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Kicks any possibly polling thread to get informed about changes.
+ *
+ * @returns VBOx status code.
+ * @param pThis The TCP driver instance.
+ * @param bReason The reason code to handle.
+ */
+static int drvTcpPollerKick(PDRVTCP pThis, uint8_t bReason)
+{
+ size_t cbWritten = 0;
+ return RTPipeWrite(pThis->hPipeWakeW, &bReason, 1, &cbWritten);
+}
+
+
+/** @interface_method_impl{PDMISTREAM,pfnPoll} */
+static DECLCALLBACK(int) drvTcpPoll(PPDMISTREAM pInterface, uint32_t fEvts, uint32_t *pfEvts, RTMSINTERVAL cMillies)
+{
+ int rc = VINF_SUCCESS;
+ PDRVTCP pThis = RT_FROM_MEMBER(pInterface, DRVTCP, IStream);
+
+ if (pThis->hTcpSock != NIL_RTSOCKET)
+ {
+ if (!pThis->fTcpSockInPollSet)
+ {
+ rc = RTPollSetAddSocket(pThis->hPollSet, pThis->hTcpSock,
+ fEvts, DRVTCP_POLLSET_ID_SOCKET);
+ if (RT_SUCCESS(rc))
+ {
+ pThis->fTcpSockInPollSet = true;
+ pThis->fXmitBufFull = false;
+ }
+ }
+ else
+ {
+ /* Always include error event. */
+ fEvts |= RTPOLL_EVT_ERROR;
+ rc = RTPollSetEventsChange(pThis->hPollSet, DRVTCP_POLLSET_ID_SOCKET, fEvts);
+ AssertRC(rc);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ while (RT_SUCCESS(rc))
+ {
+ uint32_t fEvtsRecv = 0;
+ uint32_t idHnd = 0;
+
+ /*
+ * Just check for data available to be read if the send buffer wasn't full till now and
+ * the caller wants to check whether writing is possible with the event set.
+ *
+ * On Windows the write event is only posted after a send operation returned
+ * WSAEWOULDBLOCK. So without this we would block in the poll call below waiting
+ * for an event which would never happen if the buffer has space left.
+ */
+ if ( (fEvts & RTPOLL_EVT_WRITE)
+ && !pThis->fXmitBufFull
+ && pThis->fTcpSockInPollSet)
+ cMillies = 0;
+
+ rc = RTPoll(pThis->hPollSet, cMillies, &fEvtsRecv, &idHnd);
+ if (RT_SUCCESS(rc))
+ {
+ if (idHnd == DRVTCP_POLLSET_ID_WAKEUP)
+ {
+ /* We got woken up, drain the pipe and return. */
+ uint8_t bReason;
+ size_t cbRead = 0;
+ rc = RTPipeRead(pThis->hPipeWakeR, &bReason, 1, &cbRead);
+ AssertRC(rc);
+
+ if (bReason == DRVTCP_WAKEUP_REASON_EXTERNAL)
+ rc = VERR_INTERRUPTED;
+ else if (bReason == DRVTCP_WAKEUP_REASON_NEW_CONNECTION)
+ {
+ Assert(!pThis->fTcpSockInPollSet);
+ rc = RTPollSetAddSocket(pThis->hPollSet, pThis->hTcpSock,
+ fEvts, DRVTCP_POLLSET_ID_SOCKET);
+ if (RT_SUCCESS(rc))
+ pThis->fTcpSockInPollSet = true;
+ }
+ else
+ AssertMsgFailed(("Unknown wakeup reason in pipe %u\n", bReason));
+ }
+ else
+ {
+ Assert(idHnd == DRVTCP_POLLSET_ID_SOCKET);
+
+ /* On error we close the socket here. */
+ if (fEvtsRecv & RTPOLL_EVT_ERROR)
+ {
+ rc = RTPollSetRemove(pThis->hPollSet, DRVTCP_POLLSET_ID_SOCKET);
+ AssertRC(rc);
+
+ if (pThis->fIsServer)
+ RTTcpServerDisconnectClient2(pThis->hTcpSock);
+ else
+ RTSocketClose(pThis->hTcpSock);
+ pThis->hTcpSock = NIL_RTSOCKET;
+ pThis->fTcpSockInPollSet = false;
+ /* Continue with polling. */
+ }
+ else
+ {
+ if (fEvtsRecv & RTPOLL_EVT_WRITE)
+ pThis->fXmitBufFull = false;
+ else if (!pThis->fXmitBufFull)
+ fEvtsRecv |= RTPOLL_EVT_WRITE;
+ *pfEvts = fEvtsRecv;
+ break;
+ }
+ }
+ }
+ else if ( rc == VERR_TIMEOUT
+ && !pThis->fXmitBufFull)
+ {
+ *pfEvts = RTPOLL_EVT_WRITE;
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMISTREAM,pfnPollInterrupt} */
+static DECLCALLBACK(int) drvTcpPollInterrupt(PPDMISTREAM pInterface)
+{
+ PDRVTCP pThis = RT_FROM_MEMBER(pInterface, DRVTCP, IStream);
+ return drvTcpPollerKick(pThis, DRVTCP_WAKEUP_REASON_EXTERNAL);
+}
+
+
+/** @interface_method_impl{PDMISTREAM,pfnRead} */
+static DECLCALLBACK(int) drvTcpRead(PPDMISTREAM pInterface, void *pvBuf, size_t *pcbRead)
+{
+ int rc = VINF_SUCCESS;
+ PDRVTCP pThis = RT_FROM_MEMBER(pInterface, DRVTCP, IStream);
+ LogFlow(("%s: pvBuf=%p *pcbRead=%#x (%s)\n", __FUNCTION__, pvBuf, *pcbRead, pThis->pszLocation));
+
+ Assert(pvBuf);
+
+ if (pThis->hTcpSock != NIL_RTSOCKET)
+ {
+ size_t cbRead;
+ size_t cbBuf = *pcbRead;
+ rc = RTSocketReadNB(pThis->hTcpSock, pvBuf, cbBuf, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ if (!cbRead && rc != VINF_TRY_AGAIN)
+ {
+ rc = RTPollSetRemove(pThis->hPollSet, DRVTCP_POLLSET_ID_SOCKET);
+ AssertRC(rc);
+
+ if (pThis->fIsServer)
+ RTTcpServerDisconnectClient2(pThis->hTcpSock);
+ else
+ RTSocketClose(pThis->hTcpSock);
+ pThis->hTcpSock = NIL_RTSOCKET;
+ pThis->fTcpSockInPollSet = false;
+ rc = VINF_SUCCESS;
+ }
+ *pcbRead = cbRead;
+ }
+ }
+ else
+ {
+ RTThreadSleep(100);
+ *pcbRead = 0;
+ }
+
+ LogFlow(("%s: *pcbRead=%zu returns %Rrc\n", __FUNCTION__, *pcbRead, rc));
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMISTREAM,pfnWrite} */
+static DECLCALLBACK(int) drvTcpWrite(PPDMISTREAM pInterface, const void *pvBuf, size_t *pcbWrite)
+{
+ int rc = VINF_SUCCESS;
+ PDRVTCP pThis = RT_FROM_MEMBER(pInterface, DRVTCP, IStream);
+ LogFlow(("%s: pvBuf=%p *pcbWrite=%#x (%s)\n", __FUNCTION__, pvBuf, *pcbWrite, pThis->pszLocation));
+
+ Assert(pvBuf);
+ if (pThis->hTcpSock != NIL_RTSOCKET)
+ {
+ size_t cbBuf = *pcbWrite;
+ rc = RTSocketWriteNB(pThis->hTcpSock, pvBuf, cbBuf, pcbWrite);
+ if (rc == VINF_TRY_AGAIN)
+ {
+ Assert(*pcbWrite == 0);
+ pThis->fXmitBufFull = true;
+ rc = VERR_TIMEOUT;
+ }
+ }
+ else
+ *pcbWrite = 0;
+
+ LogFlow(("%s: returns %Rrc *pcbWrite=%zu\n", __FUNCTION__, rc, *pcbWrite));
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) drvTCPQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PDRVTCP pThis = PDMINS_2_DATA(pDrvIns, PDRVTCP);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMISTREAM, &pThis->IStream);
+ return NULL;
+}
+
+
+/* -=-=-=-=- listen thread -=-=-=-=- */
+
+/**
+ * Receive thread loop.
+ *
+ * @returns VINF_SUCCESS
+ * @param hThreadSelf Thread handle to this thread.
+ * @param pvUser User argument.
+ */
+static DECLCALLBACK(int) drvTCPListenLoop(RTTHREAD hThreadSelf, void *pvUser)
+{
+ RT_NOREF(hThreadSelf);
+ PDRVTCP pThis = (PDRVTCP)pvUser;
+
+ while (RT_LIKELY(!pThis->fShutdown))
+ {
+ RTSOCKET hTcpSockNew = NIL_RTSOCKET;
+ int rc = RTTcpServerListen2(pThis->hTcpServ, &hTcpSockNew);
+ if (RT_SUCCESS(rc))
+ {
+ if (pThis->hTcpSock != NIL_RTSOCKET)
+ {
+ LogRel(("DrvTCP%d: only single connection supported\n", pThis->pDrvIns->iInstance));
+ RTTcpServerDisconnectClient2(hTcpSockNew);
+ }
+ else
+ {
+ pThis->hTcpSock = hTcpSockNew;
+ /* Inform the poller about the new socket. */
+ drvTcpPollerKick(pThis, DRVTCP_WAKEUP_REASON_NEW_CONNECTION);
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/* -=-=-=-=- PDMDRVREG -=-=-=-=- */
+
+/**
+ * Common worker for drvTCPPowerOff and drvTCPDestructor.
+ *
+ * @param pThis The instance data.
+ */
+static void drvTCPShutdownListener(PDRVTCP pThis)
+{
+ /*
+ * Signal shutdown of the listener thread.
+ */
+ pThis->fShutdown = true;
+ if ( pThis->fIsServer
+ && pThis->hTcpServ != NULL)
+ {
+ int rc = RTTcpServerShutdown(pThis->hTcpServ);
+ AssertRC(rc);
+ pThis->hTcpServ = NULL;
+ }
+}
+
+
+/**
+ * Power off a TCP socket stream driver instance.
+ *
+ * This does most of the destruction work, to avoid ordering dependencies.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvTCPPowerOff(PPDMDRVINS pDrvIns)
+{
+ PDRVTCP pThis = PDMINS_2_DATA(pDrvIns, PDRVTCP);
+ LogFlow(("%s: %s\n", __FUNCTION__, pThis->pszLocation));
+
+ drvTCPShutdownListener(pThis);
+}
+
+
+/**
+ * Destruct a TCP socket stream driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that
+ * any non-VM resources can be freed correctly.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) drvTCPDestruct(PPDMDRVINS pDrvIns)
+{
+ PDRVTCP pThis = PDMINS_2_DATA(pDrvIns, PDRVTCP);
+ LogFlow(("%s: %s\n", __FUNCTION__, pThis->pszLocation));
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+
+ drvTCPShutdownListener(pThis);
+
+ /*
+ * While the thread exits, clean up as much as we can.
+ */
+ if (pThis->hTcpSock != NIL_RTSOCKET)
+ {
+ int rc = RTPollSetRemove(pThis->hPollSet, DRVTCP_POLLSET_ID_SOCKET);
+ AssertRC(rc);
+
+ rc = RTSocketShutdown(pThis->hTcpSock, true /* fRead */, true /* fWrite */);
+ AssertRC(rc);
+
+ rc = RTSocketClose(pThis->hTcpSock);
+ AssertRC(rc); RT_NOREF(rc);
+
+ pThis->hTcpSock = NIL_RTSOCKET;
+ }
+
+ if (pThis->hPipeWakeR != NIL_RTPIPE)
+ {
+ int rc = RTPipeClose(pThis->hPipeWakeR);
+ AssertRC(rc);
+
+ pThis->hPipeWakeR = NIL_RTPIPE;
+ }
+
+ if (pThis->hPipeWakeW != NIL_RTPIPE)
+ {
+ int rc = RTPipeClose(pThis->hPipeWakeW);
+ AssertRC(rc);
+
+ pThis->hPipeWakeW = NIL_RTPIPE;
+ }
+
+ if (pThis->hPollSet != NIL_RTPOLLSET)
+ {
+ int rc = RTPollSetDestroy(pThis->hPollSet);
+ AssertRC(rc);
+
+ pThis->hPollSet = NIL_RTPOLLSET;
+ }
+
+ MMR3HeapFree(pThis->pszLocation);
+ pThis->pszLocation = NULL;
+
+ /*
+ * Wait for the thread.
+ */
+ if (pThis->ListenThread != NIL_RTTHREAD)
+ {
+ int rc = RTThreadWait(pThis->ListenThread, 30000, NULL);
+ if (RT_SUCCESS(rc))
+ pThis->ListenThread = NIL_RTTHREAD;
+ else
+ LogRel(("DrvTCP%d: listen thread did not terminate (%Rrc)\n", pDrvIns->iInstance, rc));
+ }
+}
+
+
+/**
+ * Construct a TCP socket stream driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) drvTCPConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVTCP pThis = PDMINS_2_DATA(pDrvIns, PDRVTCP);
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->pszLocation = NULL;
+ pThis->fIsServer = false;
+
+ pThis->hTcpServ = NULL;
+ pThis->hTcpSock = NIL_RTSOCKET;
+
+ pThis->hPollSet = NIL_RTPOLLSET;
+ pThis->hPipeWakeR = NIL_RTPIPE;
+ pThis->hPipeWakeW = NIL_RTPIPE;
+ pThis->fTcpSockInPollSet = false;
+
+ pThis->ListenThread = NIL_RTTHREAD;
+ pThis->fShutdown = false;
+ /* IBase */
+ pDrvIns->IBase.pfnQueryInterface = drvTCPQueryInterface;
+ /* IStream */
+ pThis->IStream.pfnPoll = drvTcpPoll;
+ pThis->IStream.pfnPollInterrupt = drvTcpPollInterrupt;
+ pThis->IStream.pfnRead = drvTcpRead;
+ pThis->IStream.pfnWrite = drvTcpWrite;
+
+ /*
+ * Validate and read the configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "Location|IsServer", "");
+
+ int rc = CFGMR3QueryStringAlloc(pCfg, "Location", &pThis->pszLocation);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("Configuration error: querying \"Location\" resulted in %Rrc"), rc);
+ rc = CFGMR3QueryBool(pCfg, "IsServer", &pThis->fIsServer);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("Configuration error: querying \"IsServer\" resulted in %Rrc"), rc);
+
+ rc = RTPipeCreate(&pThis->hPipeWakeR, &pThis->hPipeWakeW, 0 /* fFlags */);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("DrvTCP#%d: Failed to create wake pipe"), pDrvIns->iInstance);
+
+ rc = RTPollSetCreate(&pThis->hPollSet);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("DrvTCP#%d: Failed to create poll set"), pDrvIns->iInstance);
+
+ rc = RTPollSetAddPipe(pThis->hPollSet, pThis->hPipeWakeR,
+ RTPOLL_EVT_READ | RTPOLL_EVT_ERROR,
+ DRVTCP_POLLSET_ID_WAKEUP);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("DrvTCP#%d failed to add wakeup pipe for %s to poll set"),
+ pDrvIns->iInstance, pThis->pszLocation);
+
+ /*
+ * Create/Open the socket.
+ */
+ if (pThis->fIsServer)
+ {
+ uint32_t uPort = 0;
+ rc = RTStrToUInt32Ex(pThis->pszLocation, NULL, 10, &uPort);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("DrvTCP#%d: The port part of the location is not a numerical value"),
+ pDrvIns->iInstance);
+
+ /** @todo Allow binding to distinct interfaces. */
+ rc = RTTcpServerCreateEx(NULL, uPort, &pThis->hTcpServ);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("DrvTCP#%d failed to create server socket"), pDrvIns->iInstance);
+
+ rc = RTThreadCreate(&pThis->ListenThread, drvTCPListenLoop, (void *)pThis, 0,
+ RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "DrvTCPStream");
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("DrvTCP#%d failed to create listening thread"), pDrvIns->iInstance);
+ }
+ else
+ {
+ char *pszPort = strchr(pThis->pszLocation, ':');
+ if (!pszPort)
+ return PDMDrvHlpVMSetError(pDrvIns, VERR_NOT_FOUND, RT_SRC_POS,
+ N_("DrvTCP#%d: The location misses the port to connect to"),
+ pDrvIns->iInstance);
+
+ *pszPort = '\0'; /* Overwrite temporarily to avoid copying the hostname into a temporary buffer. */
+ uint32_t uPort = 0;
+ rc = RTStrToUInt32Ex(pszPort + 1, NULL, 10, &uPort);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("DrvTCP#%d: The port part of the location is not a numerical value"),
+ pDrvIns->iInstance);
+
+ rc = RTTcpClientConnect(pThis->pszLocation, uPort, &pThis->hTcpSock);
+ *pszPort = ':'; /* Restore delimiter before checking the status. */
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("DrvTCP#%d failed to connect to socket %s"),
+ pDrvIns->iInstance, pThis->pszLocation);
+
+ rc = RTPollSetAddSocket(pThis->hPollSet, pThis->hTcpSock,
+ RTPOLL_EVT_READ | RTPOLL_EVT_WRITE | RTPOLL_EVT_ERROR,
+ DRVTCP_POLLSET_ID_SOCKET);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("DrvTCP#%d failed to add socket for %s to poll set"),
+ pDrvIns->iInstance, pThis->pszLocation);
+
+ pThis->fTcpSockInPollSet = true;
+ }
+
+ LogRel(("DrvTCP: %s, %s\n", pThis->pszLocation, pThis->fIsServer ? "server" : "client"));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * TCP stream driver registration record.
+ */
+const PDMDRVREG g_DrvTCP =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "TCP",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "TCP serial stream driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_STREAM,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(DRVTCP),
+ /* pfnConstruct */
+ drvTCPConstruct,
+ /* pfnDestruct */
+ drvTCPDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ drvTCPPowerOff,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
diff --git a/src/VBox/Devices/Serial/Makefile.kup b/src/VBox/Devices/Serial/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Devices/Serial/Makefile.kup
diff --git a/src/VBox/Devices/Serial/UartCore.cpp b/src/VBox/Devices/Serial/UartCore.cpp
new file mode 100644
index 00000000..fd3de211
--- /dev/null
+++ b/src/VBox/Devices/Serial/UartCore.cpp
@@ -0,0 +1,1878 @@
+/* $Id: UartCore.cpp $ */
+/** @file
+ * UartCore - UART (16550A up to 16950) emulation.
+ *
+ * The documentation for this device was taken from the PC16550D spec from TI.
+ */
+
+/*
+ * Copyright (C) 2018-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_SERIAL
+#include <VBox/vmm/tm.h>
+#include <iprt/log.h>
+#include <iprt/uuid.h>
+#include <iprt/assert.h>
+
+#include "VBoxDD.h"
+#include "UartCore.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** The RBR/DLL register index (from the base of the port range). */
+#define UART_REG_RBR_DLL_INDEX 0
+
+/** The THR/DLL register index (from the base of the port range). */
+#define UART_REG_THR_DLL_INDEX 0
+
+/** The IER/DLM register index (from the base of the port range). */
+#define UART_REG_IER_DLM_INDEX 1
+/** Enable received data available interrupt */
+# define UART_REG_IER_ERBFI RT_BIT(0)
+/** Enable transmitter holding register empty interrupt */
+# define UART_REG_IER_ETBEI RT_BIT(1)
+/** Enable receiver line status interrupt */
+# define UART_REG_IER_ELSI RT_BIT(2)
+/** Enable modem status interrupt. */
+# define UART_REG_IER_EDSSI RT_BIT(3)
+/** Sleep mode enable. */
+# define UART_REG_IER_SLEEP_MODE_EN RT_BIT(4)
+/** Low power mode enable. */
+# define UART_REG_IER_LP_MODE_EN RT_BIT(5)
+/** Mask of writeable bits. */
+# define UART_REG_IER_MASK_WR 0x0f
+/** Mask of writeable bits for 16750+. */
+# define UART_REG_IER_MASK_WR_16750 0x3f
+
+/** The IIR register index (from the base of the port range). */
+#define UART_REG_IIR_INDEX 2
+/** Interrupt Pending - high means no interrupt pending. */
+# define UART_REG_IIR_IP_NO_INT RT_BIT(0)
+/** Interrupt identification mask. */
+# define UART_REG_IIR_ID_MASK 0x0e
+/** Sets the interrupt identification to the given value. */
+# define UART_REG_IIR_ID_SET(a_Val) (((a_Val) << 1) & UART_REG_IIR_ID_MASK)
+/** Gets the interrupt identification from the given IIR register value. */
+# define UART_REG_IIR_ID_GET(a_Val) (((a_Val) & UART_REG_IIR_ID_MASK) >> 1)
+/** Receiver Line Status interrupt. */
+# define UART_REG_IIR_ID_RCL 0x3
+/** Received Data Available interrupt. */
+# define UART_REG_IIR_ID_RDA 0x2
+/** Character Timeou Indicator interrupt. */
+# define UART_REG_IIR_ID_CTI 0x6
+/** Transmitter Holding Register Empty interrupt. */
+# define UART_REG_IIR_ID_THRE 0x1
+/** Modem Status interrupt. */
+# define UART_REG_IIR_ID_MS 0x0
+/** 64 byte FIFOs enabled (15750+ only). */
+# define UART_REG_IIR_64BYTE_FIFOS_EN RT_BIT(5)
+/** FIFOs enabled. */
+# define UART_REG_IIR_FIFOS_EN 0xc0
+/** Bits relevant for checking whether the interrupt status has changed. */
+# define UART_REG_IIR_CHANGED_MASK 0x0f
+
+/** The FCR register index (from the base of the port range). */
+#define UART_REG_FCR_INDEX 2
+/** Enable the TX/RX FIFOs. */
+# define UART_REG_FCR_FIFO_EN RT_BIT(0)
+/** Reset the receive FIFO. */
+# define UART_REG_FCR_RCV_FIFO_RST RT_BIT(1)
+/** Reset the transmit FIFO. */
+# define UART_REG_FCR_XMIT_FIFO_RST RT_BIT(2)
+/** DMA Mode Select. */
+# define UART_REG_FCR_DMA_MODE_SEL RT_BIT(3)
+/** 64 Byte FIFO enable (15750+ only). */
+# define UART_REG_FCR_64BYTE_FIFO_EN RT_BIT(5)
+/** Receiver level interrupt trigger. */
+# define UART_REG_FCR_RCV_LVL_IRQ_MASK 0xc0
+/** Returns the receive level trigger value from the given FCR register. */
+# define UART_REG_FCR_RCV_LVL_IRQ_GET(a_Fcr) (((a_Fcr) & UART_REG_FCR_RCV_LVL_IRQ_MASK) >> 6)
+/** RCV Interrupt trigger level - 1 byte. */
+# define UART_REG_FCR_RCV_LVL_IRQ_1 0x0
+/** RCV Interrupt trigger level - 4 bytes. */
+# define UART_REG_FCR_RCV_LVL_IRQ_4 0x1
+/** RCV Interrupt trigger level - 8 bytes. */
+# define UART_REG_FCR_RCV_LVL_IRQ_8 0x2
+/** RCV Interrupt trigger level - 14 bytes. */
+# define UART_REG_FCR_RCV_LVL_IRQ_14 0x3
+/** Mask of writeable bits. */
+# define UART_REG_FCR_MASK_WR 0xcf
+/** Mask of sticky bits. */
+# define UART_REG_FCR_MASK_STICKY 0xe9
+
+/** The LCR register index (from the base of the port range). */
+#define UART_REG_LCR_INDEX 3
+/** Word Length Select Mask. */
+# define UART_REG_LCR_WLS_MASK 0x3
+/** Returns the WLS value form the given LCR register value. */
+# define UART_REG_LCR_WLS_GET(a_Lcr) ((a_Lcr) & UART_REG_LCR_WLS_MASK)
+/** Number of stop bits. */
+# define UART_REG_LCR_STB RT_BIT(2)
+/** Parity Enable. */
+# define UART_REG_LCR_PEN RT_BIT(3)
+/** Even Parity. */
+# define UART_REG_LCR_EPS RT_BIT(4)
+/** Stick parity. */
+# define UART_REG_LCR_PAR_STICK RT_BIT(5)
+/** Set Break. */
+# define UART_REG_LCR_BRK_SET RT_BIT(6)
+/** Divisor Latch Access Bit. */
+# define UART_REG_LCR_DLAB RT_BIT(7)
+
+/** The MCR register index (from the base of the port range). */
+#define UART_REG_MCR_INDEX 4
+/** Data Terminal Ready. */
+# define UART_REG_MCR_DTR RT_BIT(0)
+/** Request To Send. */
+# define UART_REG_MCR_RTS RT_BIT(1)
+/** Out1. */
+# define UART_REG_MCR_OUT1 RT_BIT(2)
+/** Out2. */
+# define UART_REG_MCR_OUT2 RT_BIT(3)
+/** Loopback connection. */
+# define UART_REG_MCR_LOOP RT_BIT(4)
+/** Flow Control Enable (15750+ only). */
+# define UART_REG_MCR_AFE RT_BIT(5)
+/** Mask of writeable bits (15450 and 15550A). */
+# define UART_REG_MCR_MASK_WR 0x1f
+/** Mask of writeable bits (15750+). */
+# define UART_REG_MCR_MASK_WR_15750 0x3f
+
+/** The LSR register index (from the base of the port range). */
+#define UART_REG_LSR_INDEX 5
+/** Data Ready. */
+# define UART_REG_LSR_DR RT_BIT(0)
+/** Overrun Error. */
+# define UART_REG_LSR_OE RT_BIT(1)
+/** Parity Error. */
+# define UART_REG_LSR_PE RT_BIT(2)
+/** Framing Error. */
+# define UART_REG_LSR_FE RT_BIT(3)
+/** Break Interrupt. */
+# define UART_REG_LSR_BI RT_BIT(4)
+/** Transmitter Holding Register. */
+# define UART_REG_LSR_THRE RT_BIT(5)
+/** Transmitter Empty. */
+# define UART_REG_LSR_TEMT RT_BIT(6)
+/** Error in receiver FIFO. */
+# define UART_REG_LSR_RCV_FIFO_ERR RT_BIT(7)
+/** The bits to check in this register when checking for the RCL interrupt. */
+# define UART_REG_LSR_BITS_IIR_RCL 0x1e
+
+/** The MSR register index (from the base of the port range). */
+#define UART_REG_MSR_INDEX 6
+/** Delta Clear to Send. */
+# define UART_REG_MSR_DCTS RT_BIT(0)
+/** Delta Data Set Ready. */
+# define UART_REG_MSR_DDSR RT_BIT(1)
+/** Trailing Edge Ring Indicator. */
+# define UART_REG_MSR_TERI RT_BIT(2)
+/** Delta Data Carrier Detect. */
+# define UART_REG_MSR_DDCD RT_BIT(3)
+/** Clear to Send. */
+# define UART_REG_MSR_CTS RT_BIT(4)
+/** Data Set Ready. */
+# define UART_REG_MSR_DSR RT_BIT(5)
+/** Ring Indicator. */
+# define UART_REG_MSR_RI RT_BIT(6)
+/** Data Carrier Detect. */
+# define UART_REG_MSR_DCD RT_BIT(7)
+/** The bits to check in this register when checking for the MS interrupt. */
+# define UART_REG_MSR_BITS_IIR_MS 0x0f
+
+/** The SCR register index (from the base of the port range). */
+#define UART_REG_SCR_INDEX 7
+
+/** Set the specified bits in the given register. */
+#define UART_REG_SET(a_Reg, a_Set) ((a_Reg) |= (a_Set))
+/** Clear the specified bits in the given register. */
+#define UART_REG_CLR(a_Reg, a_Clr) ((a_Reg) &= ~(a_Clr))
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+#ifdef IN_RING3
+/**
+ * FIFO ITL levels.
+ */
+static struct
+{
+ /** ITL level for a 16byte FIFO. */
+ uint8_t cbItl16;
+ /** ITL level for a 64byte FIFO. */
+ uint8_t cbItl64;
+} s_aFifoItl[] =
+{
+ /* cbItl16 cbItl64 */
+ { 1, 1 },
+ { 4, 16 },
+ { 8, 32 },
+ { 14, 56 }
+};
+
+
+/**
+ * String versions of the parity enum.
+ */
+static const char *s_aszParity[] =
+{
+ "INVALID",
+ "NONE",
+ "EVEN",
+ "ODD",
+ "MARK",
+ "SPACE",
+ "INVALID"
+};
+
+
+/**
+ * String versions of the stop bits enum.
+ */
+static const char *s_aszStopBits[] =
+{
+ "INVALID",
+ "1",
+ "1.5",
+ "2",
+ "INVALID"
+};
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Updates the IRQ state based on the current device state.
+ *
+ * @returns nothing.
+ * @param pThis The UART core instance.
+ */
+static void uartIrqUpdate(PUARTCORE pThis)
+{
+ LogFlowFunc(("pThis=%#p\n", pThis));
+
+ /*
+ * The interrupt uses a priority scheme, only the interrupt with the
+ * highest priority is indicated in the interrupt identification register.
+ *
+ * The priorities are as follows (high to low):
+ * * Receiver line status
+ * * Received data available
+ * * Character timeout indication (only in FIFO mode).
+ * * Transmitter holding register empty
+ * * Modem status change.
+ */
+ uint8_t uRegIirNew = UART_REG_IIR_IP_NO_INT;
+ if ( (pThis->uRegLsr & UART_REG_LSR_BITS_IIR_RCL)
+ && (pThis->uRegIer & UART_REG_IER_ELSI))
+ uRegIirNew = UART_REG_IIR_ID_SET(UART_REG_IIR_ID_RCL);
+ else if ( (pThis->uRegIer & UART_REG_IER_ERBFI)
+ && pThis->fIrqCtiPending)
+ uRegIirNew = UART_REG_IIR_ID_SET(UART_REG_IIR_ID_CTI);
+ else if ( (pThis->uRegLsr & UART_REG_LSR_DR)
+ && (pThis->uRegIer & UART_REG_IER_ERBFI)
+ && ( !(pThis->uRegFcr & UART_REG_FCR_FIFO_EN)
+ || pThis->FifoRecv.cbUsed >= pThis->FifoRecv.cbItl))
+ uRegIirNew = UART_REG_IIR_ID_SET(UART_REG_IIR_ID_RDA);
+ else if ( (pThis->uRegIer & UART_REG_IER_ETBEI)
+ && pThis->fThreEmptyPending)
+ uRegIirNew = UART_REG_IIR_ID_SET(UART_REG_IIR_ID_THRE);
+ else if ( (pThis->uRegMsr & UART_REG_MSR_BITS_IIR_MS)
+ && (pThis->uRegIer & UART_REG_IER_EDSSI))
+ uRegIirNew = UART_REG_IIR_ID_SET(UART_REG_IIR_ID_MS);
+
+ LogFlowFunc((" uRegIirNew=%#x uRegIir=%#x\n", uRegIirNew, pThis->uRegIir));
+
+ /* Change interrupt only if the interrupt status really changed from the previous value. */
+ if (uRegIirNew != (pThis->uRegIir & UART_REG_IIR_CHANGED_MASK))
+ {
+ LogFlow((" Interrupt source changed from %#x -> %#x (IRQ %d -> %d)\n",
+ pThis->uRegIir, uRegIirNew,
+ pThis->uRegIir == UART_REG_IIR_IP_NO_INT ? 0 : 1,
+ uRegIirNew == UART_REG_IIR_IP_NO_INT ? 0 : 1));
+ if (uRegIirNew == UART_REG_IIR_IP_NO_INT)
+ pThis->CTX_SUFF(pfnUartIrqReq)(pThis->CTX_SUFF(pDevIns), pThis, pThis->iLUN, 0);
+ else
+ pThis->CTX_SUFF(pfnUartIrqReq)(pThis->CTX_SUFF(pDevIns), pThis, pThis->iLUN, 1);
+ }
+ else
+ LogFlow((" No change in interrupt source\n"));
+
+ if (pThis->uRegFcr & UART_REG_FCR_FIFO_EN)
+ uRegIirNew |= UART_REG_IIR_FIFOS_EN;
+ if (pThis->uRegFcr & UART_REG_FCR_64BYTE_FIFO_EN)
+ uRegIirNew |= UART_REG_IIR_64BYTE_FIFOS_EN;
+
+ pThis->uRegIir = uRegIirNew;
+}
+
+
+/**
+ * Returns the amount of bytes stored in the given FIFO.
+ *
+ * @returns Amount of bytes stored in the FIFO.
+ * @param pFifo The FIFO.
+ */
+DECLINLINE(size_t) uartFifoUsedGet(PUARTFIFO pFifo)
+{
+ return pFifo->cbUsed;
+}
+
+
+/**
+ * Puts a new character into the given FIFO.
+ *
+ * @returns Flag whether the FIFO overflowed.
+ * @param pFifo The FIFO to put the data into.
+ * @param fOvrWr Flag whether to overwrite data if the FIFO is full.
+ * @param bData The data to add.
+ */
+DECLINLINE(bool) uartFifoPut(PUARTFIFO pFifo, bool fOvrWr, uint8_t bData)
+{
+ if (fOvrWr || pFifo->cbUsed < pFifo->cbMax)
+ {
+ pFifo->abBuf[pFifo->offWrite] = bData;
+ pFifo->offWrite = (pFifo->offWrite + 1) % pFifo->cbMax;
+ }
+
+ bool fOverFlow = false;
+ if (pFifo->cbUsed < pFifo->cbMax)
+ pFifo->cbUsed++;
+ else
+ {
+ fOverFlow = true;
+ if (fOvrWr) /* Advance the read position to account for the lost character. */
+ pFifo->offRead = (pFifo->offRead + 1) % pFifo->cbMax;
+ }
+
+ return fOverFlow;
+}
+
+
+/**
+ * Returns the next character in the FIFO.
+ *
+ * @return Next byte in the FIFO.
+ * @param pFifo The FIFO to get data from.
+ */
+DECLINLINE(uint8_t) uartFifoGet(PUARTFIFO pFifo)
+{
+ uint8_t bRet = 0;
+
+ if (pFifo->cbUsed)
+ {
+ bRet = pFifo->abBuf[pFifo->offRead];
+ pFifo->offRead = (pFifo->offRead + 1) % pFifo->cbMax;
+ pFifo->cbUsed--;
+ }
+
+ return bRet;
+}
+
+
+#ifdef IN_RING3
+/**
+ * Clears the given FIFO.
+ *
+ * @returns nothing.
+ * @param pFifo The FIFO to clear.
+ */
+DECLINLINE(void) uartFifoClear(PUARTFIFO pFifo)
+{
+ memset(&pFifo->abBuf[0], 0, sizeof(pFifo->abBuf));
+ pFifo->cbUsed = 0;
+ pFifo->offWrite = 0;
+ pFifo->offRead = 0;
+}
+
+
+/**
+ * Returns the amount of free bytes in the given FIFO.
+ *
+ * @returns The amount of bytes free in the given FIFO.
+ * @param pFifo The FIFO.
+ */
+DECLINLINE(size_t) uartFifoFreeGet(PUARTFIFO pFifo)
+{
+ return pFifo->cbMax - pFifo->cbUsed;
+}
+
+
+/**
+ * Tries to copy the requested amount of data from the given FIFO into the provided buffer.
+ *
+ * @returns Amount of bytes actually copied.
+ * @param pFifo The FIFO to copy data from.
+ * @param pvDst Where to copy the data to.
+ * @param cbCopy How much to copy.
+ */
+DECLINLINE(size_t) uartFifoCopyTo(PUARTFIFO pFifo, void *pvDst, size_t cbCopy)
+{
+ size_t cbCopied = 0;
+ uint8_t *pbDst = (uint8_t *)pvDst;
+
+ cbCopy = RT_MIN(cbCopy, pFifo->cbUsed);
+ while (cbCopy)
+ {
+ uint8_t cbThisCopy = (uint8_t)RT_MIN(cbCopy, (uint8_t)(pFifo->cbMax - pFifo->offRead));
+ memcpy(pbDst, &pFifo->abBuf[pFifo->offRead], cbThisCopy);
+
+ pFifo->offRead = (pFifo->offRead + cbThisCopy) % pFifo->cbMax;
+ pFifo->cbUsed -= cbThisCopy;
+ pbDst += cbThisCopy;
+ cbCopied += cbThisCopy;
+ cbCopy -= cbThisCopy;
+ }
+
+ return cbCopied;
+}
+
+
+#if 0 /* unused */
+/**
+ * Tries to copy the requested amount of data from the provided buffer into the given FIFO.
+ *
+ * @returns Amount of bytes actually copied.
+ * @param pFifo The FIFO to copy data to.
+ * @param pvSrc Where to copy the data from.
+ * @param cbCopy How much to copy.
+ */
+DECLINLINE(size_t) uartFifoCopyFrom(PUARTFIFO pFifo, void *pvSrc, size_t cbCopy)
+{
+ size_t cbCopied = 0;
+ uint8_t *pbSrc = (uint8_t *)pvSrc;
+
+ cbCopy = RT_MIN(cbCopy, uartFifoFreeGet(pFifo));
+ while (cbCopy)
+ {
+ uint8_t cbThisCopy = (uint8_t)RT_MIN(cbCopy, (uint8_t)(pFifo->cbMax - pFifo->offWrite));
+ memcpy(&pFifo->abBuf[pFifo->offWrite], pbSrc, cbThisCopy);
+
+ pFifo->offWrite = (pFifo->offWrite + cbThisCopy) % pFifo->cbMax;
+ pFifo->cbUsed += cbThisCopy;
+ pbSrc += cbThisCopy;
+ cbCopied += cbThisCopy;
+ cbCopy -= cbThisCopy;
+ }
+
+ return cbCopied;
+}
+#endif
+
+
+/**
+ * Updates the delta bits for the given MSR register value which has the status line
+ * bits set.
+ *
+ * @returns nothing.
+ * @param pThis The serial port instance.
+ * @param uMsrSts MSR value with the appropriate status bits set.
+ */
+static void uartR3MsrUpdate(PUARTCORE pThis, uint8_t uMsrSts)
+{
+ /* Compare current and new states and set remaining bits accordingly. */
+ if ((uMsrSts & UART_REG_MSR_CTS) != (pThis->uRegMsr & UART_REG_MSR_CTS))
+ uMsrSts |= UART_REG_MSR_DCTS;
+ if ((uMsrSts & UART_REG_MSR_DSR) != (pThis->uRegMsr & UART_REG_MSR_DSR))
+ uMsrSts |= UART_REG_MSR_DDSR;
+ if ((uMsrSts & UART_REG_MSR_RI) != 0 && (pThis->uRegMsr & UART_REG_MSR_RI) == 0)
+ uMsrSts |= UART_REG_MSR_TERI;
+ if ((uMsrSts & UART_REG_MSR_DCD) != (pThis->uRegMsr & UART_REG_MSR_DCD))
+ uMsrSts |= UART_REG_MSR_DDCD;
+
+ pThis->uRegMsr = uMsrSts;
+
+ uartIrqUpdate(pThis);
+}
+
+
+/**
+ * Updates the serial port parameters of the attached driver with the current configuration.
+ *
+ * @returns nothing.
+ * @param pThis The serial port instance.
+ */
+static void uartR3ParamsUpdate(PUARTCORE pThis)
+{
+ if ( pThis->uRegDivisor != 0
+ && pThis->pDrvSerial)
+ {
+ uint32_t uBps = 115200 / pThis->uRegDivisor; /* This is for PC compatible serial port with a 1.8432 MHz crystal. */
+ unsigned cDataBits = UART_REG_LCR_WLS_GET(pThis->uRegLcr) + 5;
+ uint32_t cFrameBits = cDataBits;
+ PDMSERIALSTOPBITS enmStopBits = PDMSERIALSTOPBITS_ONE;
+ PDMSERIALPARITY enmParity = PDMSERIALPARITY_NONE;
+
+ if (pThis->uRegLcr & UART_REG_LCR_STB)
+ {
+ enmStopBits = cDataBits == 5 ? PDMSERIALSTOPBITS_ONEPOINTFIVE : PDMSERIALSTOPBITS_TWO;
+ cFrameBits += 2;
+ }
+ else
+ cFrameBits++;
+
+ if (pThis->uRegLcr & UART_REG_LCR_PEN)
+ {
+ /* Select the correct parity mode based on the even and stick parity bits. */
+ switch (pThis->uRegLcr & (UART_REG_LCR_EPS | UART_REG_LCR_PAR_STICK))
+ {
+ case 0:
+ enmParity = PDMSERIALPARITY_ODD;
+ break;
+ case UART_REG_LCR_EPS:
+ enmParity = PDMSERIALPARITY_EVEN;
+ break;
+ case UART_REG_LCR_EPS | UART_REG_LCR_PAR_STICK:
+ enmParity = PDMSERIALPARITY_SPACE;
+ break;
+ case UART_REG_LCR_PAR_STICK:
+ enmParity = PDMSERIALPARITY_MARK;
+ break;
+ default:
+ /* We should never get here as all cases where caught earlier. */
+ AssertMsgFailed(("This shouldn't happen at all: %#x\n",
+ pThis->uRegLcr & (UART_REG_LCR_EPS | UART_REG_LCR_PAR_STICK)));
+ }
+
+ cFrameBits++;
+ }
+
+ uint64_t uTimerFreq = TMTimerGetFreq(pThis->CTX_SUFF(pTimerRcvFifoTimeout));
+ pThis->cSymbolXferTicks = (uTimerFreq / uBps) * cFrameBits;
+
+ LogFlowFunc(("Changing parameters to: %u,%s,%u,%s\n",
+ uBps, s_aszParity[enmParity], cDataBits, s_aszStopBits[enmStopBits]));
+
+ int rc = pThis->pDrvSerial->pfnChgParams(pThis->pDrvSerial, uBps, enmParity, cDataBits, enmStopBits);
+ if (RT_FAILURE(rc))
+ LogRelMax(10, ("Serial#%d: Failed to change parameters to %u,%s,%u,%s -> %Rrc\n",
+ pThis->pDevInsR3->iInstance, uBps, s_aszParity[enmParity], cDataBits, s_aszStopBits[enmStopBits], rc));
+
+ /* Changed parameters will flush all receive queues, so there won't be any data to read even if indicated. */
+ pThis->pDrvSerial->pfnQueuesFlush(pThis->pDrvSerial, true /*fQueueRecv*/, false /*fQueueXmit*/);
+ ASMAtomicWriteU32(&pThis->cbAvailRdr, 0);
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_DR);
+ }
+}
+
+
+/**
+ * Updates the internal device state with the given PDM status line states.
+ *
+ * @returns nothing.
+ * @param pThis The serial port instance.
+ * @param fStsLines The PDM status line states.
+ */
+static void uartR3StsLinesUpdate(PUARTCORE pThis, uint32_t fStsLines)
+{
+ uint8_t uRegMsrNew = 0; /* The new MSR value. */
+
+ if (fStsLines & PDMISERIALPORT_STS_LINE_DCD)
+ uRegMsrNew |= UART_REG_MSR_DCD;
+ if (fStsLines & PDMISERIALPORT_STS_LINE_RI)
+ uRegMsrNew |= UART_REG_MSR_RI;
+ if (fStsLines & PDMISERIALPORT_STS_LINE_DSR)
+ uRegMsrNew |= UART_REG_MSR_DSR;
+ if (fStsLines & PDMISERIALPORT_STS_LINE_CTS)
+ uRegMsrNew |= UART_REG_MSR_CTS;
+
+ uartR3MsrUpdate(pThis, uRegMsrNew);
+}
+
+
+/**
+ * Fills up the receive FIFO with as much data as possible.
+ *
+ * @returns nothing.
+ * @param pThis The serial port instance.
+ */
+static void uartR3RecvFifoFill(PUARTCORE pThis)
+{
+ LogFlowFunc(("pThis=%#p\n", pThis));
+
+ PUARTFIFO pFifo = &pThis->FifoRecv;
+ size_t cbFill = RT_MIN(uartFifoFreeGet(pFifo),
+ ASMAtomicReadU32(&pThis->cbAvailRdr));
+ size_t cbFilled = 0;
+
+ while (cbFilled < cbFill)
+ {
+ size_t cbThisRead = cbFill - cbFilled;
+
+ if (pFifo->offRead <= pFifo->offWrite)
+ cbThisRead = RT_MIN(cbThisRead, (uint8_t)(pFifo->cbMax - pFifo->offWrite));
+ else
+ cbThisRead = RT_MIN(cbThisRead, (uint8_t)(pFifo->offRead - pFifo->offWrite));
+
+ size_t cbRead = 0;
+ int rc = pThis->pDrvSerial->pfnReadRdr(pThis->pDrvSerial, &pFifo->abBuf[pFifo->offWrite], cbThisRead, &cbRead);
+ AssertRC(rc); Assert(cbRead <= UINT8_MAX); RT_NOREF(rc);
+
+ pFifo->offWrite = (pFifo->offWrite + (uint8_t)cbRead) % pFifo->cbMax;
+ pFifo->cbUsed += (uint8_t)cbRead;
+ cbFilled += cbRead;
+
+ if (cbRead < cbThisRead)
+ break;
+ }
+
+ if (cbFilled)
+ {
+ UART_REG_SET(pThis->uRegLsr, UART_REG_LSR_DR);
+ if (pFifo->cbUsed < pFifo->cbItl)
+ {
+ pThis->fIrqCtiPending = false;
+ TMTimerSetRelative(pThis->CTX_SUFF(pTimerRcvFifoTimeout), pThis->cSymbolXferTicks * 4, NULL);
+ }
+ uartIrqUpdate(pThis);
+ }
+
+ Assert(cbFilled <= (size_t)pThis->cbAvailRdr);
+ ASMAtomicSubU32(&pThis->cbAvailRdr, (uint32_t)cbFilled);
+}
+
+
+/**
+ * Fetches a single byte and writes it to RBR.
+ *
+ * @returns nothing.
+ * @param pThis The serial port instance.
+ */
+static void uartR3ByteFetch(PUARTCORE pThis)
+{
+ if (ASMAtomicReadU32(&pThis->cbAvailRdr))
+ {
+ AssertPtr(pThis->pDrvSerial);
+ size_t cbRead = 0;
+ int rc2 = pThis->pDrvSerial->pfnReadRdr(pThis->pDrvSerial, &pThis->uRegRbr, 1, &cbRead);
+ AssertMsg(RT_SUCCESS(rc2) && cbRead == 1, ("This shouldn't fail and always return one byte!\n")); RT_NOREF(rc2);
+ UART_REG_SET(pThis->uRegLsr, UART_REG_LSR_DR);
+ uartIrqUpdate(pThis);
+ }
+}
+
+
+/**
+ * Fetches a ready data based on the FIFO setting.
+ *
+ * @returns nothing.
+ * @param pThis The serial port instance.
+ */
+static void uartR3DataFetch(PUARTCORE pThis)
+{
+ if (pThis->uRegFcr & UART_REG_FCR_FIFO_EN)
+ uartR3RecvFifoFill(pThis);
+ else
+ uartR3ByteFetch(pThis);
+}
+
+
+/**
+ * Reset the transmit/receive related bits to the standard values
+ * (after a detach/attach/reset event).
+ *
+ * @returns nothing.
+ * @param pThis The serial port instance.
+ */
+static void uartR3XferReset(PUARTCORE pThis)
+{
+ pThis->uRegLsr = UART_REG_LSR_THRE | UART_REG_LSR_TEMT;
+ pThis->fThreEmptyPending = false;
+
+ uartFifoClear(&pThis->FifoXmit);
+ uartFifoClear(&pThis->FifoRecv);
+ uartR3ParamsUpdate(pThis);
+ uartIrqUpdate(pThis);
+
+ if (pThis->pDrvSerial)
+ {
+ /* Set the modem lines to reflect the current state. */
+ int rc = pThis->pDrvSerial->pfnChgModemLines(pThis->pDrvSerial, false /*fRts*/, false /*fDtr*/);
+ if (RT_FAILURE(rc))
+ LogRel(("Serial#%d: Failed to set modem lines with %Rrc during reset\n",
+ pThis->pDevInsR3->iInstance, rc));
+
+ uint32_t fStsLines = 0;
+ rc = pThis->pDrvSerial->pfnQueryStsLines(pThis->pDrvSerial, &fStsLines);
+ if (RT_SUCCESS(rc))
+ uartR3StsLinesUpdate(pThis, fStsLines);
+ else
+ LogRel(("Serial#%d: Failed to query status line status with %Rrc during reset\n",
+ pThis->pDevInsR3->iInstance, rc));
+ }
+
+}
+
+
+/**
+ * Tries to copy the specified amount of data from the active TX queue (register or FIFO).
+ *
+ * @returns nothing.
+ * @param pThis The serial port instance.
+ * @param pvBuf Where to store the data.
+ * @param cbRead How much to read from the TX queue.
+ * @param pcbRead Where to store the amount of data read.
+ */
+static void uartR3TxQueueCopyFrom(PUARTCORE pThis, void *pvBuf, size_t cbRead, size_t *pcbRead)
+{
+ if (pThis->uRegFcr & UART_REG_FCR_FIFO_EN)
+ {
+ *pcbRead = uartFifoCopyTo(&pThis->FifoXmit, pvBuf, cbRead);
+ if (!pThis->FifoXmit.cbUsed)
+ {
+ UART_REG_SET(pThis->uRegLsr, UART_REG_LSR_THRE);
+ pThis->fThreEmptyPending = true;
+ }
+ if (*pcbRead)
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_TEMT);
+ uartIrqUpdate(pThis);
+ }
+ else if (!(pThis->uRegLsr & UART_REG_LSR_THRE))
+ {
+ *(uint8_t *)pvBuf = pThis->uRegThr;
+ *pcbRead = 1;
+ UART_REG_SET(pThis->uRegLsr, UART_REG_LSR_THRE);
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_TEMT);
+ pThis->fThreEmptyPending = true;
+ uartIrqUpdate(pThis);
+ }
+ else
+ {
+ /*
+ * This can happen if there was data in the FIFO when the connection was closed,
+ * indicate this condition to the lower driver by returning 0 bytes.
+ */
+ *pcbRead = 0;
+ }
+}
+#endif
+
+
+/**
+ * Write handler for the THR/DLL register (depending on the DLAB bit in LCR).
+ *
+ * @returns VBox status code.
+ * @param pThis The serial port instance.
+ * @param uVal The value to write.
+ */
+DECLINLINE(int) uartRegThrDllWrite(PUARTCORE pThis, uint8_t uVal)
+{
+ int rc = VINF_SUCCESS;
+
+ /* A set DLAB causes a write to the lower 8bits of the divisor latch. */
+ if (pThis->uRegLcr & UART_REG_LCR_DLAB)
+ {
+ if (uVal != (pThis->uRegDivisor & 0xff))
+ {
+#ifndef IN_RING3
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#else
+ pThis->uRegDivisor = (pThis->uRegDivisor & 0xff00) | uVal;
+ uartR3ParamsUpdate(pThis);
+#endif
+ }
+ }
+ else
+ {
+ if (pThis->uRegFcr & UART_REG_FCR_FIFO_EN)
+ {
+#ifndef IN_RING3
+ if (!uartFifoUsedGet(&pThis->FifoXmit))
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+ else
+ {
+ uartFifoPut(&pThis->FifoXmit, true /*fOvrWr*/, uVal);
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_THRE | UART_REG_LSR_TEMT);
+ }
+#else
+ uartFifoPut(&pThis->FifoXmit, true /*fOvrWr*/, uVal);
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_THRE | UART_REG_LSR_TEMT);
+ pThis->fThreEmptyPending = false;
+ uartIrqUpdate(pThis);
+ if ( pThis->pDrvSerial
+ && uartFifoUsedGet(&pThis->FifoXmit) == 1)
+ {
+ int rc2 = pThis->pDrvSerial->pfnDataAvailWrNotify(pThis->pDrvSerial);
+ if (RT_FAILURE(rc2))
+ LogRelMax(10, ("Serial#%d: Failed to send data with %Rrc\n", pThis->pDevInsR3->iInstance, rc2));
+ }
+#endif
+ }
+ else
+ {
+ /* Notify the lower driver about available data only if the register was empty before. */
+ if (pThis->uRegLsr & UART_REG_LSR_THRE)
+ {
+#ifndef IN_RING3
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#else
+ pThis->uRegThr = uVal;
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_THRE | UART_REG_LSR_TEMT);
+ pThis->fThreEmptyPending = false;
+ uartIrqUpdate(pThis);
+ if (pThis->pDrvSerial)
+ {
+ int rc2 = pThis->pDrvSerial->pfnDataAvailWrNotify(pThis->pDrvSerial);
+ if (RT_FAILURE(rc2))
+ LogRelMax(10, ("Serial#%d: Failed to send data with %Rrc\n", pThis->pDevInsR3->iInstance, rc2));
+ }
+ else
+ TMTimerSetRelative(pThis->CTX_SUFF(pTimerTxUnconnected), pThis->cSymbolXferTicks, NULL);
+#endif
+ }
+ else
+ pThis->uRegThr = uVal;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Write handler for the IER/DLM register (depending on the DLAB bit in LCR).
+ *
+ * @returns VBox status code.
+ * @param pThis The serial port instance.
+ * @param uVal The value to write.
+ */
+DECLINLINE(int) uartRegIerDlmWrite(PUARTCORE pThis, uint8_t uVal)
+{
+ int rc = VINF_SUCCESS;
+
+ /* A set DLAB causes a write to the higher 8bits of the divisor latch. */
+ if (pThis->uRegLcr & UART_REG_LCR_DLAB)
+ {
+ if (uVal != (pThis->uRegDivisor & 0xff00) >> 8)
+ {
+#ifndef IN_RING3
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#else
+ pThis->uRegDivisor = (pThis->uRegDivisor & 0xff) | (uVal << 8);
+ uartR3ParamsUpdate(pThis);
+#endif
+ }
+ }
+ else
+ {
+ if (pThis->enmType < UARTTYPE_16750)
+ pThis->uRegIer = uVal & UART_REG_IER_MASK_WR;
+ else
+ pThis->uRegIer = uVal & UART_REG_IER_MASK_WR_16750;
+
+ if (pThis->uRegLsr & UART_REG_LSR_THRE)
+ pThis->fThreEmptyPending = true;
+
+ uartIrqUpdate(pThis);
+ }
+
+ return rc;
+}
+
+
+/**
+ * Write handler for the FCR register.
+ *
+ * @returns VBox status code.
+ * @param pThis The serial port instance.
+ * @param uVal The value to write.
+ */
+DECLINLINE(int) uartRegFcrWrite(PUARTCORE pThis, uint8_t uVal)
+{
+#ifndef IN_RING3
+ RT_NOREF(pThis, uVal);
+ return VINF_IOM_R3_IOPORT_WRITE;
+#else
+ int rc = VINF_SUCCESS;
+ if ( pThis->enmType >= UARTTYPE_16550A
+ && uVal != pThis->uRegFcr)
+ {
+ /* A change in the FIFO enable bit clears both FIFOs automatically. */
+ if ((uVal ^ pThis->uRegFcr) & UART_REG_FCR_FIFO_EN)
+ {
+ uartFifoClear(&pThis->FifoXmit);
+ uartFifoClear(&pThis->FifoRecv);
+
+ /*
+ * If the FIFO is about to be enabled and the DR bit is ready we have an unacknowledged
+ * byte in the RBR register which will be lost so we have to adjust the available bytes.
+ */
+ if ( ASMAtomicReadU32(&pThis->cbAvailRdr) > 0
+ && (uVal & UART_REG_FCR_FIFO_EN))
+ ASMAtomicDecU32(&pThis->cbAvailRdr);
+
+ /* Clear the DR bit too. */
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_DR);
+ }
+
+ if (rc == VINF_SUCCESS)
+ {
+ if (uVal & UART_REG_FCR_RCV_FIFO_RST)
+ {
+ TMTimerStop(pThis->CTX_SUFF(pTimerRcvFifoTimeout));
+ pThis->fIrqCtiPending = false;
+ uartFifoClear(&pThis->FifoRecv);
+ }
+ if (uVal & UART_REG_FCR_XMIT_FIFO_RST)
+ uartFifoClear(&pThis->FifoXmit);
+
+ /*
+ * The 64byte FIFO enable bit is only changeable for 16750
+ * and if the DLAB bit in LCR is set.
+ */
+ if ( pThis->enmType < UARTTYPE_16750
+ || !(pThis->uRegLcr & UART_REG_LCR_DLAB))
+ uVal &= ~UART_REG_FCR_64BYTE_FIFO_EN;
+ else /* Use previous value. */
+ uVal |= pThis->uRegFcr & UART_REG_FCR_64BYTE_FIFO_EN;
+
+ if (uVal & UART_REG_FCR_64BYTE_FIFO_EN)
+ {
+ pThis->FifoRecv.cbMax = 64;
+ pThis->FifoXmit.cbMax = 64;
+ }
+ else
+ {
+ pThis->FifoRecv.cbMax = 16;
+ pThis->FifoXmit.cbMax = 16;
+ }
+
+ if (uVal & UART_REG_FCR_FIFO_EN)
+ {
+ uint8_t idxItl = UART_REG_FCR_RCV_LVL_IRQ_GET(uVal);
+ if (uVal & UART_REG_FCR_64BYTE_FIFO_EN)
+ pThis->FifoRecv.cbItl = s_aFifoItl[idxItl].cbItl64;
+ else
+ pThis->FifoRecv.cbItl = s_aFifoItl[idxItl].cbItl16;
+ }
+
+ /* The FIFO reset bits are self clearing. */
+ pThis->uRegFcr = uVal & UART_REG_FCR_MASK_STICKY;
+ uartIrqUpdate(pThis);
+ }
+
+ /* Fill in the next data. */
+ if (ASMAtomicReadU32(&pThis->cbAvailRdr))
+ uartR3DataFetch(pThis);
+ }
+
+ return rc;
+#endif
+}
+
+
+/**
+ * Write handler for the LCR register.
+ *
+ * @returns VBox status code.
+ * @param pThis The serial port instance.
+ * @param uVal The value to write.
+ */
+DECLINLINE(int) uartRegLcrWrite(PUARTCORE pThis, uint8_t uVal)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Any change except the DLAB bit causes a switch to R3. */
+ if ((pThis->uRegLcr & ~UART_REG_LCR_DLAB) != (uVal & ~UART_REG_LCR_DLAB))
+ {
+#ifndef IN_RING3
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#else
+ /* Check whether the BREAK bit changed before updating the LCR value. */
+ bool fBrkEn = RT_BOOL(uVal & UART_REG_LCR_BRK_SET);
+ bool fBrkChg = fBrkEn != RT_BOOL(pThis->uRegLcr & UART_REG_LCR_BRK_SET);
+ pThis->uRegLcr = uVal;
+ uartR3ParamsUpdate(pThis);
+
+ if ( fBrkChg
+ && pThis->pDrvSerial)
+ pThis->pDrvSerial->pfnChgBrk(pThis->pDrvSerial, fBrkEn);
+#endif
+ }
+ else
+ pThis->uRegLcr = uVal;
+
+ return rc;
+}
+
+
+/**
+ * Write handler for the MCR register.
+ *
+ * @returns VBox status code.
+ * @param pThis The serial port instance.
+ * @param uVal The value to write.
+ */
+DECLINLINE(int) uartRegMcrWrite(PUARTCORE pThis, uint8_t uVal)
+{
+ int rc = VINF_SUCCESS;
+
+ if (pThis->enmType < UARTTYPE_16750)
+ uVal &= UART_REG_MCR_MASK_WR;
+ else
+ uVal &= UART_REG_MCR_MASK_WR_15750;
+ if (pThis->uRegMcr != uVal)
+ {
+#ifndef IN_RING3
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+#else
+ /*
+ * When loopback mode is activated the RTS, DTR, OUT1 and OUT2 lines are
+ * disconnected and looped back to MSR.
+ */
+ if ( (uVal & UART_REG_MCR_LOOP)
+ && !(pThis->uRegMcr & UART_REG_MCR_LOOP)
+ && pThis->pDrvSerial)
+ pThis->pDrvSerial->pfnChgModemLines(pThis->pDrvSerial, false /*fRts*/, false /*fDtr*/);
+
+ pThis->uRegMcr = uVal;
+ if (uVal & UART_REG_MCR_LOOP)
+ {
+ uint8_t uRegMsrSts = 0;
+
+ if (uVal & UART_REG_MCR_RTS)
+ uRegMsrSts |= UART_REG_MSR_CTS;
+ if (uVal & UART_REG_MCR_DTR)
+ uRegMsrSts |= UART_REG_MSR_DSR;
+ if (uVal & UART_REG_MCR_OUT1)
+ uRegMsrSts |= UART_REG_MSR_RI;
+ if (uVal & UART_REG_MCR_OUT2)
+ uRegMsrSts |= UART_REG_MSR_DCD;
+ uartR3MsrUpdate(pThis, uRegMsrSts);
+ }
+ else if (pThis->pDrvSerial)
+ pThis->pDrvSerial->pfnChgModemLines(pThis->pDrvSerial,
+ RT_BOOL(uVal & UART_REG_MCR_RTS),
+ RT_BOOL(uVal & UART_REG_MCR_DTR));
+#endif
+ }
+
+ return rc;
+}
+
+
+/**
+ * Read handler for the RBR/DLL register (depending on the DLAB bit in LCR).
+ *
+ * @returns VBox status code.
+ * @param pThis The serial port instance.
+ * @param puVal Where to store the read value on success.
+ */
+DECLINLINE(int) uartRegRbrDllRead(PUARTCORE pThis, uint32_t *puVal)
+{
+ int rc = VINF_SUCCESS;
+
+ /* A set DLAB causes a read from the lower 8bits of the divisor latch. */
+ if (pThis->uRegLcr & UART_REG_LCR_DLAB)
+ *puVal = pThis->uRegDivisor & 0xff;
+ else
+ {
+ if (pThis->uRegFcr & UART_REG_FCR_FIFO_EN)
+ {
+ /*
+ * Only go back to R3 if there is new data available for the FIFO
+ * and we would clear the interrupt to fill it up again.
+ */
+ if ( pThis->FifoRecv.cbUsed <= pThis->FifoRecv.cbItl
+ && ASMAtomicReadU32(&pThis->cbAvailRdr) > 0)
+ {
+#ifndef IN_RING3
+ rc = VINF_IOM_R3_IOPORT_READ;
+#else
+ uartR3RecvFifoFill(pThis);
+#endif
+ }
+
+ if (rc == VINF_SUCCESS)
+ {
+ *puVal = uartFifoGet(&pThis->FifoRecv);
+ pThis->fIrqCtiPending = false;
+ if (!pThis->FifoRecv.cbUsed)
+ {
+ TMTimerStop(pThis->CTX_SUFF(pTimerRcvFifoTimeout));
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_DR);
+ }
+ else if (pThis->FifoRecv.cbUsed < pThis->FifoRecv.cbItl)
+ TMTimerSetRelative(pThis->CTX_SUFF(pTimerRcvFifoTimeout), pThis->cSymbolXferTicks * 4, NULL);
+ uartIrqUpdate(pThis);
+ }
+ }
+ else
+ {
+ *puVal = pThis->uRegRbr;
+
+ if (pThis->uRegLsr & UART_REG_LSR_DR)
+ {
+ Assert(pThis->cbAvailRdr);
+ uint32_t cbAvail = ASMAtomicDecU32(&pThis->cbAvailRdr);
+ if (!cbAvail)
+ {
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_DR);
+ uartIrqUpdate(pThis);
+ }
+ else
+ {
+#ifndef IN_RING3
+ /* Restore state and go back to R3. */
+ ASMAtomicIncU32(&pThis->cbAvailRdr);
+ rc = VINF_IOM_R3_IOPORT_READ;
+#else
+ /* Fetch new data and keep the DR bit set. */
+ uartR3DataFetch(pThis);
+#endif
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Read handler for the IER/DLM register (depending on the DLAB bit in LCR).
+ *
+ * @returns VBox status code.
+ * @param pThis The serial port instance.
+ * @param puVal Where to store the read value on success.
+ */
+DECLINLINE(int) uartRegIerDlmRead(PUARTCORE pThis, uint32_t *puVal)
+{
+ int rc = VINF_SUCCESS;
+
+ /* A set DLAB causes a read from the upper 8bits of the divisor latch. */
+ if (pThis->uRegLcr & UART_REG_LCR_DLAB)
+ *puVal = (pThis->uRegDivisor & 0xff00) >> 8;
+ else
+ *puVal = pThis->uRegIer;
+
+ return rc;
+}
+
+
+/**
+ * Read handler for the IIR register.
+ *
+ * @returns VBox status code.
+ * @param pThis The serial port instance.
+ * @param puVal Where to store the read value on success.
+ */
+DECLINLINE(int) uartRegIirRead(PUARTCORE pThis, uint32_t *puVal)
+{
+ *puVal = pThis->uRegIir;
+ /* Reset the THRE empty interrupt id when this gets returned to the guest (see table 3 UART Reset configuration). */
+ if (UART_REG_IIR_ID_GET(pThis->uRegIir) == UART_REG_IIR_ID_THRE)
+ {
+ pThis->fThreEmptyPending = false;
+ uartIrqUpdate(pThis);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Read handler for the LSR register.
+ *
+ * @returns VBox status code.
+ * @param pThis The serial port instance.
+ * @param puVal Where to store the read value on success.
+ */
+DECLINLINE(int) uartRegLsrRead(PUARTCORE pThis, uint32_t *puVal)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Yield if configured and there is no data available. */
+ if ( !(pThis->uRegLsr & UART_REG_LSR_DR)
+ && (pThis->fFlags & UART_CORE_YIELD_ON_LSR_READ))
+ {
+#ifndef IN_RING3
+ return VINF_IOM_R3_IOPORT_READ;
+#else
+ RTThreadYield();
+#endif
+ }
+
+ *puVal = pThis->uRegLsr;
+ /*
+ * Reading this register clears the Overrun (OE), Parity (PE) and Framing (FE) error
+ * as well as the Break Interrupt (BI).
+ */
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_BITS_IIR_RCL);
+ uartIrqUpdate(pThis);
+
+ return rc;
+}
+
+
+/**
+ * Read handler for the MSR register.
+ *
+ * @returns VBox status code.
+ * @param pThis The serial port instance.
+ * @param puVal Where to store the read value on success.
+ */
+DECLINLINE(int) uartRegMsrRead(PUARTCORE pThis, uint32_t *puVal)
+{
+ *puVal = pThis->uRegMsr;
+
+ /* Clear any of the delta bits. */
+ UART_REG_CLR(pThis->uRegMsr, UART_REG_MSR_BITS_IIR_MS);
+ uartIrqUpdate(pThis);
+ return VINF_SUCCESS;
+}
+
+
+#ifdef LOG_ENABLED
+/**
+ * Converts the register index into a sensible memnonic.
+ *
+ * @returns Register memnonic.
+ * @param pThis The serial port instance.
+ * @param idxReg Register index.
+ * @param fWrite Flag whether the register gets written.
+ */
+DECLINLINE(const char *) uartRegIdx2Str(PUARTCORE pThis, uint8_t idxReg, bool fWrite)
+{
+ const char *psz = "INV";
+
+ switch (idxReg)
+ {
+ /*case UART_REG_THR_DLL_INDEX:*/
+ case UART_REG_RBR_DLL_INDEX:
+ if (pThis->uRegLcr & UART_REG_LCR_DLAB)
+ psz = "DLL";
+ else if (fWrite)
+ psz = "THR";
+ else
+ psz = "RBR";
+ break;
+ case UART_REG_IER_DLM_INDEX:
+ if (pThis->uRegLcr & UART_REG_LCR_DLAB)
+ psz = "DLM";
+ else
+ psz = "IER";
+ break;
+ /*case UART_REG_IIR_INDEX:*/
+ case UART_REG_FCR_INDEX:
+ if (fWrite)
+ psz = "FCR";
+ else
+ psz = "IIR";
+ break;
+ case UART_REG_LCR_INDEX:
+ psz = "LCR";
+ break;
+ case UART_REG_MCR_INDEX:
+ psz = "MCR";
+ break;
+ case UART_REG_LSR_INDEX:
+ psz = "LSR";
+ break;
+ case UART_REG_MSR_INDEX:
+ psz = "MSR";
+ break;
+ case UART_REG_SCR_INDEX:
+ psz = "SCR";
+ break;
+ }
+
+ return psz;
+}
+#endif
+
+
+DECLHIDDEN(int) uartRegWrite(PUARTCORE pThis, uint32_t uReg, uint32_t u32, size_t cb)
+{
+ AssertMsgReturn(cb == 1, ("uReg=%#x cb=%d u32=%#x\n", uReg, cb, u32), VINF_SUCCESS);
+
+ int rc = PDMCritSectEnter(&pThis->CritSect, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ uint8_t idxReg = uReg & 0x7;
+ LogFlowFunc(("pThis=%#p uReg=%u{%s} u32=%#x cb=%u\n",
+ pThis, uReg, uartRegIdx2Str(pThis, idxReg, true /*fWrite*/), u32, cb));
+
+ uint8_t uVal = (uint8_t)u32;
+ switch (idxReg)
+ {
+ case UART_REG_THR_DLL_INDEX:
+ rc = uartRegThrDllWrite(pThis, uVal);
+ break;
+ case UART_REG_IER_DLM_INDEX:
+ rc = uartRegIerDlmWrite(pThis, uVal);
+ break;
+ case UART_REG_FCR_INDEX:
+ rc = uartRegFcrWrite(pThis, uVal);
+ break;
+ case UART_REG_LCR_INDEX:
+ rc = uartRegLcrWrite(pThis, uVal);
+ break;
+ case UART_REG_MCR_INDEX:
+ rc = uartRegMcrWrite(pThis, uVal);
+ break;
+ case UART_REG_SCR_INDEX:
+ pThis->uRegScr = u32;
+ break;
+ default:
+ break;
+ }
+
+ PDMCritSectLeave(&pThis->CritSect);
+ LogFlowFunc(("-> %Rrc\n", rc));
+ return rc;
+}
+
+
+DECLHIDDEN(int) uartRegRead(PUARTCORE pThis, uint32_t uReg, uint32_t *pu32, size_t cb)
+{
+ if (cb != 1)
+ return VERR_IOM_IOPORT_UNUSED;
+
+ int rc = PDMCritSectEnter(&pThis->CritSect, VINF_IOM_R3_IOPORT_READ);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ uint8_t idxReg = uReg & 0x7;
+ switch (idxReg)
+ {
+ case UART_REG_RBR_DLL_INDEX:
+ rc = uartRegRbrDllRead(pThis, pu32);
+ break;
+ case UART_REG_IER_DLM_INDEX:
+ rc = uartRegIerDlmRead(pThis, pu32);
+ break;
+ case UART_REG_IIR_INDEX:
+ rc = uartRegIirRead(pThis, pu32);
+ break;
+ case UART_REG_LCR_INDEX:
+ *pu32 = pThis->uRegLcr;
+ break;
+ case UART_REG_MCR_INDEX:
+ *pu32 = pThis->uRegMcr;
+ break;
+ case UART_REG_LSR_INDEX:
+ rc = uartRegLsrRead(pThis, pu32);
+ break;
+ case UART_REG_MSR_INDEX:
+ rc = uartRegMsrRead(pThis, pu32);
+ break;
+ case UART_REG_SCR_INDEX:
+ *pu32 = pThis->uRegScr;
+ break;
+ default:
+ rc = VERR_IOM_IOPORT_UNUSED;
+ }
+
+ PDMCritSectLeave(&pThis->CritSect);
+ LogFlowFunc(("pThis=%#p uReg=%u{%s} u32=%#x cb=%u -> %Rrc\n",
+ pThis, uReg, uartRegIdx2Str(pThis, idxReg, false /*fWrite*/), *pu32, cb, rc));
+ return rc;
+}
+
+
+#ifdef IN_RING3
+
+/* -=-=-=-=-=-=-=-=- Timer callbacks -=-=-=-=-=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV, Fifo timer function.}
+ */
+static DECLCALLBACK(void) uartR3RcvFifoTimeoutTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ LogFlowFunc(("pDevIns=%#p pTimer=%#p pvUser=%#p\n", pDevIns, pTimer, pvUser));
+ RT_NOREF(pDevIns, pTimer);
+ PUARTCORE pThis = (PUARTCORE)pvUser;
+ PDMCritSectEnter(&pThis->CritSect, VERR_IGNORED);
+ if (pThis->FifoRecv.cbUsed < pThis->FifoRecv.cbItl)
+ {
+ pThis->fIrqCtiPending = true;
+ uartIrqUpdate(pThis);
+ }
+ PDMCritSectLeave(&pThis->CritSect);
+}
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV, TX timer function when there is no driver connected for draining the THR/FIFO.}
+ */
+static DECLCALLBACK(void) uartR3TxUnconnectedTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ LogFlowFunc(("pDevIns=%#p pTimer=%#p pvUser=%#p\n", pDevIns, pTimer, pvUser));
+ RT_NOREF(pDevIns, pTimer);
+ PUARTCORE pThis = (PUARTCORE)pvUser;
+ PDMCritSectEnter(&pThis->CritSect, VERR_IGNORED);
+ uint8_t bIgnore = 0;
+ size_t cbRead = 0;
+ uartR3TxQueueCopyFrom(pThis, &bIgnore, sizeof(uint8_t), &cbRead);
+ if (cbRead == 1)
+ TMTimerSetRelative(pThis->CTX_SUFF(pTimerTxUnconnected), pThis->cSymbolXferTicks, NULL);
+ PDMCritSectLeave(&pThis->CritSect);
+}
+
+
+/* -=-=-=-=-=-=-=-=- PDMISERIALPORT on LUN#0 -=-=-=-=-=-=-=-=- */
+
+
+/**
+ * @interface_method_impl{PDMISERIALPORT,pfnDataAvailRdrNotify}
+ */
+static DECLCALLBACK(int) uartR3DataAvailRdrNotify(PPDMISERIALPORT pInterface, size_t cbAvail)
+{
+ LogFlowFunc(("pInterface=%#p cbAvail=%zu\n", pInterface, cbAvail));
+ PUARTCORE pThis = RT_FROM_MEMBER(pInterface, UARTCORE, ISerialPort);
+
+ AssertMsg((uint32_t)cbAvail == cbAvail, ("Too much data available\n"));
+
+ PDMCritSectEnter(&pThis->CritSect, VERR_IGNORED);
+ uint32_t cbAvailOld = ASMAtomicAddU32(&pThis->cbAvailRdr, (uint32_t)cbAvail);
+ LogFlow((" cbAvailRdr=%u -> cbAvailRdr=%u\n", cbAvailOld, cbAvail + cbAvailOld));
+ if (pThis->uRegFcr & UART_REG_FCR_FIFO_EN)
+ uartR3RecvFifoFill(pThis);
+ else if (!cbAvailOld)
+ {
+ size_t cbRead = 0;
+ int rc = pThis->pDrvSerial->pfnReadRdr(pThis->pDrvSerial, &pThis->uRegRbr, 1, &cbRead);
+ AssertMsg(RT_SUCCESS(rc) && cbRead == 1, ("This shouldn't fail and always return one byte!\n")); RT_NOREF(rc);
+ UART_REG_SET(pThis->uRegLsr, UART_REG_LSR_DR);
+ uartIrqUpdate(pThis);
+ }
+ PDMCritSectLeave(&pThis->CritSect);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMISERIALPORT,pfnDataSentNotify}
+ */
+static DECLCALLBACK(int) uartR3DataSentNotify(PPDMISERIALPORT pInterface)
+{
+ LogFlowFunc(("pInterface=%#p\n", pInterface));
+ PUARTCORE pThis = RT_FROM_MEMBER(pInterface, UARTCORE, ISerialPort);
+
+ /* Set the transmitter empty bit because everything was sent. */
+ PDMCritSectEnter(&pThis->CritSect, VERR_IGNORED);
+ UART_REG_SET(pThis->uRegLsr, UART_REG_LSR_TEMT);
+ uartIrqUpdate(pThis);
+ PDMCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMISERIALPORT,pfnReadWr}
+ */
+static DECLCALLBACK(int) uartR3ReadWr(PPDMISERIALPORT pInterface, void *pvBuf, size_t cbRead, size_t *pcbRead)
+{
+ LogFlowFunc(("pInterface=%#p pvBuf=%#p cbRead=%zu pcbRead=%#p\n", pInterface, pvBuf, cbRead, pcbRead));
+ PUARTCORE pThis = RT_FROM_MEMBER(pInterface, UARTCORE, ISerialPort);
+
+ AssertReturn(cbRead > 0, VERR_INVALID_PARAMETER);
+
+ PDMCritSectEnter(&pThis->CritSect, VERR_IGNORED);
+ uartR3TxQueueCopyFrom(pThis, pvBuf, cbRead, pcbRead);
+ PDMCritSectLeave(&pThis->CritSect);
+
+ LogFlowFunc(("-> VINF_SUCCESS{*pcbRead=%zu}\n", *pcbRead));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMISERIALPORT,pfnNotifyStsLinesChanged}
+ */
+static DECLCALLBACK(int) uartR3NotifyStsLinesChanged(PPDMISERIALPORT pInterface, uint32_t fNewStatusLines)
+{
+ LogFlowFunc(("pInterface=%#p fNewStatusLines=%#x\n", pInterface, fNewStatusLines));
+ PUARTCORE pThis = RT_FROM_MEMBER(pInterface, UARTCORE, ISerialPort);
+
+ PDMCritSectEnter(&pThis->CritSect, VERR_IGNORED);
+ uartR3StsLinesUpdate(pThis, fNewStatusLines);
+ PDMCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMISERIALPORT,pfnNotifyBrk}
+ */
+static DECLCALLBACK(int) uartR3NotifyBrk(PPDMISERIALPORT pInterface)
+{
+ LogFlowFunc(("pInterface=%#p\n", pInterface));
+ PUARTCORE pThis = RT_FROM_MEMBER(pInterface, UARTCORE, ISerialPort);
+
+ PDMCritSectEnter(&pThis->CritSect, VERR_IGNORED);
+ UART_REG_SET(pThis->uRegLsr, UART_REG_LSR_BI);
+ uartIrqUpdate(pThis);
+ PDMCritSectLeave(&pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/* -=-=-=-=-=-=-=-=- PDMIBASE -=-=-=-=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) uartR3QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PUARTCORE pThis = RT_FROM_MEMBER(pInterface, UARTCORE, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMISERIALPORT, &pThis->ISerialPort);
+ return NULL;
+}
+
+
+DECLHIDDEN(int) uartR3SaveExec(PUARTCORE pThis, PSSMHANDLE pSSM)
+{
+ SSMR3PutU16(pSSM, pThis->uRegDivisor);
+ SSMR3PutU8(pSSM, pThis->uRegRbr);
+ SSMR3PutU8(pSSM, pThis->uRegThr);
+ SSMR3PutU8(pSSM, pThis->uRegIer);
+ SSMR3PutU8(pSSM, pThis->uRegIir);
+ SSMR3PutU8(pSSM, pThis->uRegFcr);
+ SSMR3PutU8(pSSM, pThis->uRegLcr);
+ SSMR3PutU8(pSSM, pThis->uRegMcr);
+ SSMR3PutU8(pSSM, pThis->uRegLsr);
+ SSMR3PutU8(pSSM, pThis->uRegMsr);
+ SSMR3PutU8(pSSM, pThis->uRegScr);
+ SSMR3PutBool(pSSM, pThis->fIrqCtiPending);
+ SSMR3PutBool(pSSM, pThis->fThreEmptyPending);
+ SSMR3PutU8(pSSM, pThis->FifoXmit.cbMax);
+ SSMR3PutU8(pSSM, pThis->FifoXmit.cbItl);
+ SSMR3PutU8(pSSM, pThis->FifoRecv.cbMax);
+ SSMR3PutU8(pSSM, pThis->FifoRecv.cbItl);
+
+ int rc = TMR3TimerSave(pThis->pTimerRcvFifoTimeoutR3, pSSM);
+ if (RT_SUCCESS(rc))
+ rc = TMR3TimerSave(pThis->pTimerTxUnconnectedR3, pSSM);
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) uartR3LoadExec(PUARTCORE pThis, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass,
+ uint8_t *pbIrq, RTIOPORT *pPortBase)
+{
+ RT_NOREF(uPass);
+ int rc;
+
+ if (uVersion > UART_SAVED_STATE_VERSION_LEGACY_CODE)
+ {
+ SSMR3GetU16(pSSM, &pThis->uRegDivisor);
+ SSMR3GetU8(pSSM, &pThis->uRegRbr);
+ SSMR3GetU8(pSSM, &pThis->uRegThr);
+ SSMR3GetU8(pSSM, &pThis->uRegIer);
+ SSMR3GetU8(pSSM, &pThis->uRegIir);
+ SSMR3GetU8(pSSM, &pThis->uRegFcr);
+ SSMR3GetU8(pSSM, &pThis->uRegLcr);
+ SSMR3GetU8(pSSM, &pThis->uRegMcr);
+ SSMR3GetU8(pSSM, &pThis->uRegLsr);
+ SSMR3GetU8(pSSM, &pThis->uRegMsr);
+ SSMR3GetU8(pSSM, &pThis->uRegScr);
+ SSMR3GetBool(pSSM, &pThis->fIrqCtiPending);
+ SSMR3GetBool(pSSM, &pThis->fThreEmptyPending);
+ SSMR3GetU8(pSSM, &pThis->FifoXmit.cbMax);
+ SSMR3GetU8(pSSM, &pThis->FifoXmit.cbItl);
+ SSMR3GetU8(pSSM, &pThis->FifoRecv.cbMax);
+ SSMR3GetU8(pSSM, &pThis->FifoRecv.cbItl);
+
+ rc = TMR3TimerLoad(pThis->pTimerRcvFifoTimeoutR3, pSSM);
+ if (uVersion > UART_SAVED_STATE_VERSION_PRE_UNCONNECTED_TX_TIMER)
+ rc = TMR3TimerLoad(pThis->pTimerTxUnconnectedR3, pSSM);
+ }
+ else
+ {
+ AssertPtr(pbIrq);
+ AssertPtr(pPortBase);
+ if (uVersion == UART_SAVED_STATE_VERSION_16450)
+ {
+ pThis->enmType = UARTTYPE_16450;
+ LogRel(("Serial#%d: falling back to 16450 mode from load state\n", pThis->pDevInsR3->iInstance));
+ }
+
+ SSMR3GetU16(pSSM, &pThis->uRegDivisor);
+ SSMR3GetU8(pSSM, &pThis->uRegRbr);
+ SSMR3GetU8(pSSM, &pThis->uRegIer);
+ SSMR3GetU8(pSSM, &pThis->uRegLcr);
+ SSMR3GetU8(pSSM, &pThis->uRegMcr);
+ SSMR3GetU8(pSSM, &pThis->uRegLsr);
+ SSMR3GetU8(pSSM, &pThis->uRegMsr);
+ SSMR3GetU8(pSSM, &pThis->uRegScr);
+ if (uVersion > UART_SAVED_STATE_VERSION_16450)
+ SSMR3GetU8(pSSM, &pThis->uRegFcr);
+
+ int32_t iTmp = 0;
+ SSMR3GetS32(pSSM, &iTmp);
+ pThis->fThreEmptyPending = RT_BOOL(iTmp);
+
+ rc = SSMR3GetS32(pSSM, &iTmp);
+ AssertRCReturn(rc, rc);
+ *pbIrq = (uint8_t)iTmp;
+
+ SSMR3Skip(pSSM, sizeof(int32_t)); /* was: last_break_enable */
+
+ uint32_t uPortBaseTmp = 0;
+ rc = SSMR3GetU32(pSSM, &uPortBaseTmp);
+ AssertRCReturn(rc, rc);
+ *pPortBase = (RTIOPORT)uPortBaseTmp;
+
+ rc = SSMR3Skip(pSSM, sizeof(bool)); /* was: msr_changed */
+ if ( RT_SUCCESS(rc)
+ && uVersion > UART_SAVED_STATE_VERSION_MISSING_BITS)
+ {
+ SSMR3GetU8(pSSM, &pThis->uRegThr);
+ SSMR3Skip(pSSM, sizeof(uint8_t)); /* The old transmit shift register, not used anymore. */
+ SSMR3GetU8(pSSM, &pThis->uRegIir);
+
+ int32_t iTimeoutPending = 0;
+ SSMR3GetS32(pSSM, &iTimeoutPending);
+ pThis->fIrqCtiPending = RT_BOOL(iTimeoutPending);
+
+ rc = TMR3TimerLoad(pThis->pTimerRcvFifoTimeoutR3, pSSM);
+ AssertRCReturn(rc, rc);
+
+ bool fWasActiveIgn;
+ rc = TMR3TimerSkip(pSSM, &fWasActiveIgn); /* was: transmit_timerR3 */
+ AssertRCReturn(rc, rc);
+
+ SSMR3GetU8(pSSM, &pThis->FifoRecv.cbItl);
+ rc = SSMR3GetU8(pSSM, &pThis->FifoRecv.cbItl);
+ }
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(int) uartR3LoadDone(PUARTCORE pThis, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+
+ uartR3ParamsUpdate(pThis);
+ uartIrqUpdate(pThis);
+
+ if (pThis->pDrvSerial)
+ {
+ /* Set the modem lines to reflect the current state. */
+ int rc = pThis->pDrvSerial->pfnChgModemLines(pThis->pDrvSerial,
+ RT_BOOL(pThis->uRegMcr & UART_REG_MCR_RTS),
+ RT_BOOL(pThis->uRegMcr & UART_REG_MCR_DTR));
+ if (RT_FAILURE(rc))
+ LogRel(("Serial#%d: Failed to set modem lines with %Rrc during saved state load\n",
+ pThis->pDevInsR3->iInstance, rc));
+
+ uint32_t fStsLines = 0;
+ rc = pThis->pDrvSerial->pfnQueryStsLines(pThis->pDrvSerial, &fStsLines);
+ if (RT_SUCCESS(rc))
+ uartR3StsLinesUpdate(pThis, fStsLines);
+ else
+ LogRel(("Serial#%d: Failed to query status line status with %Rrc during reset\n",
+ pThis->pDevInsR3->iInstance, rc));
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+DECLHIDDEN(void) uartR3Relocate(PUARTCORE pThis, RTGCINTPTR offDelta)
+{
+ RT_NOREF(offDelta);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pThis->pDevInsR3);
+ pThis->pTimerRcvFifoTimeoutRC = TMTimerRCPtr(pThis->pTimerRcvFifoTimeoutR3);
+ pThis->pTimerTxUnconnectedRC = TMTimerRCPtr(pThis->pTimerTxUnconnectedR3);
+}
+
+
+DECLHIDDEN(void) uartR3Reset(PUARTCORE pThis)
+{
+ pThis->uRegDivisor = 0x0c; /* Default to 9600 Baud. */
+ pThis->uRegRbr = 0;
+ pThis->uRegThr = 0;
+ pThis->uRegIer = 0;
+ pThis->uRegIir = UART_REG_IIR_IP_NO_INT;
+ pThis->uRegFcr = 0;
+ pThis->uRegLcr = 0; /* 5 data bits, no parity, 1 stop bit. */
+ pThis->uRegMcr = 0;
+ pThis->uRegLsr = UART_REG_LSR_THRE | UART_REG_LSR_TEMT;
+ pThis->uRegMsr = 0; /* Updated below. */
+ pThis->uRegScr = 0;
+ pThis->fIrqCtiPending = false;
+ pThis->fThreEmptyPending = true;
+
+ /* Standard FIFO size for 15550A. */
+ pThis->FifoXmit.cbMax = 16;
+ pThis->FifoRecv.cbMax = 16;
+ pThis->FifoRecv.cbItl = 1;
+
+ uartR3XferReset(pThis);
+}
+
+
+DECLHIDDEN(int) uartR3Attach(PUARTCORE pThis, unsigned iLUN)
+{
+ int rc = PDMDevHlpDriverAttach(pThis->pDevInsR3, iLUN, &pThis->IBase, &pThis->pDrvBase, "Serial Char");
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pDrvSerial = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMISERIALCONNECTOR);
+ if (!pThis->pDrvSerial)
+ {
+ AssertLogRelMsgFailed(("Configuration error: instance %d has no serial interface!\n", pThis->pDevInsR3->iInstance));
+ return VERR_PDM_MISSING_INTERFACE;
+ }
+ uartR3XferReset(pThis);
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ pThis->pDrvBase = NULL;
+ pThis->pDrvSerial = NULL;
+ rc = VINF_SUCCESS;
+ uartR3XferReset(pThis);
+ LogRel(("Serial#%d: no unit\n", pThis->pDevInsR3->iInstance));
+ }
+ else /* Don't call VMSetError here as we assume that the driver already set an appropriate error */
+ LogRel(("Serial#%d: Failed to attach to serial driver. rc=%Rrc\n", pThis->pDevInsR3->iInstance, rc));
+
+ return rc;
+}
+
+
+DECLHIDDEN(void) uartR3Detach(PUARTCORE pThis)
+{
+ /* Zero out important members. */
+ pThis->pDrvBase = NULL;
+ pThis->pDrvSerial = NULL;
+ uartR3XferReset(pThis);
+}
+
+
+DECLHIDDEN(void) uartR3Destruct(PUARTCORE pThis)
+{
+ PDMR3CritSectDelete(&pThis->CritSect);
+}
+
+
+DECLHIDDEN(int) uartR3Init(PUARTCORE pThis, PPDMDEVINS pDevInsR3, UARTTYPE enmType, unsigned iLUN, uint32_t fFlags,
+ R3PTRTYPE(PFNUARTCOREIRQREQ) pfnUartIrqReqR3, R0PTRTYPE(PFNUARTCOREIRQREQ) pfnUartIrqReqR0,
+ RCPTRTYPE(PFNUARTCOREIRQREQ) pfnUartIrqReqRC)
+{
+ int rc = VINF_SUCCESS;
+
+ /*
+ * Initialize the instance data.
+ * (Do this early or the destructor might choke on something!)
+ */
+ pThis->pDevInsR3 = pDevInsR3;
+ pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevInsR3);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevInsR3);
+ pThis->iLUN = iLUN;
+ pThis->enmType = enmType;
+ pThis->fFlags = fFlags;
+ pThis->pfnUartIrqReqR3 = pfnUartIrqReqR3;
+ pThis->pfnUartIrqReqR0 = pfnUartIrqReqR0;
+ pThis->pfnUartIrqReqRC = pfnUartIrqReqRC;
+
+ /* IBase */
+ pThis->IBase.pfnQueryInterface = uartR3QueryInterface;
+
+ /* ISerialPort */
+ pThis->ISerialPort.pfnDataAvailRdrNotify = uartR3DataAvailRdrNotify;
+ pThis->ISerialPort.pfnDataSentNotify = uartR3DataSentNotify;
+ pThis->ISerialPort.pfnReadWr = uartR3ReadWr;
+ pThis->ISerialPort.pfnNotifyStsLinesChanged = uartR3NotifyStsLinesChanged;
+ pThis->ISerialPort.pfnNotifyBrk = uartR3NotifyBrk;
+
+ rc = PDMDevHlpCritSectInit(pDevInsR3, &pThis->CritSect, RT_SRC_POS, "Uart{%s#%d}#%d",
+ pDevInsR3->pReg->szName, pDevInsR3->iInstance, iLUN);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Attach the char driver and get the interfaces.
+ */
+ rc = PDMDevHlpDriverAttach(pDevInsR3, iLUN, &pThis->IBase, &pThis->pDrvBase, "UART");
+ if (RT_SUCCESS(rc))
+ {
+ pThis->pDrvSerial = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMISERIALCONNECTOR);
+ if (!pThis->pDrvSerial)
+ {
+ AssertLogRelMsgFailed(("Configuration error: instance %d has no serial interface!\n", iLUN));
+ return VERR_PDM_MISSING_INTERFACE;
+ }
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ pThis->pDrvBase = NULL;
+ pThis->pDrvSerial = NULL;
+ LogRel(("Serial#%d: no unit\n", iLUN));
+ }
+ else
+ {
+ AssertLogRelMsgFailed(("Serial#%d: Failed to attach to char driver. rc=%Rrc\n", iLUN, rc));
+ /* Don't call VMSetError here as we assume that the driver already set an appropriate error */
+ return rc;
+ }
+
+ /*
+ * Create the receive FIFO character timeout indicator timer.
+ */
+ rc = PDMDevHlpTMTimerCreate(pDevInsR3, TMCLOCK_VIRTUAL, uartR3RcvFifoTimeoutTimer, pThis,
+ TMTIMER_FLAGS_NO_CRIT_SECT, "UART Rcv FIFO Timer",
+ &pThis->pTimerRcvFifoTimeoutR3);
+ AssertRCReturn(rc, rc);
+
+ rc = TMR3TimerSetCritSect(pThis->pTimerRcvFifoTimeoutR3, &pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ pThis->pTimerRcvFifoTimeoutR0 = TMTimerR0Ptr(pThis->pTimerRcvFifoTimeoutR3);
+ pThis->pTimerRcvFifoTimeoutRC = TMTimerRCPtr(pThis->pTimerRcvFifoTimeoutR3);
+
+ /*
+ * Create the transmit timer when no device is connected.
+ */
+ rc = PDMDevHlpTMTimerCreate(pDevInsR3, TMCLOCK_VIRTUAL, uartR3TxUnconnectedTimer, pThis,
+ TMTIMER_FLAGS_NO_CRIT_SECT, "UART TX uncon. Timer",
+ &pThis->pTimerTxUnconnectedR3);
+ AssertRCReturn(rc, rc);
+
+ rc = TMR3TimerSetCritSect(pThis->pTimerTxUnconnectedR3, &pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ pThis->pTimerTxUnconnectedR0 = TMTimerR0Ptr(pThis->pTimerTxUnconnectedR3);
+ pThis->pTimerTxUnconnectedRC = TMTimerRCPtr(pThis->pTimerTxUnconnectedR3);
+
+ uartR3Reset(pThis);
+ return VINF_SUCCESS;
+}
+
+#endif /* IN_RING3 */
+
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
diff --git a/src/VBox/Devices/Serial/UartCore.h b/src/VBox/Devices/Serial/UartCore.h
new file mode 100644
index 00000000..7ec2e6e1
--- /dev/null
+++ b/src/VBox/Devices/Serial/UartCore.h
@@ -0,0 +1,348 @@
+/* $Id: UartCore.h $ */
+/** @file
+ * UartCore - UART (16550A up to 16950) emulation.
+ *
+ * The documentation for this device was taken from the PC16550D spec from TI.
+ */
+
+/*
+ * Copyright (C) 2018-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#ifndef VBOX_INCLUDED_SRC_Serial_UartCore_h
+#define VBOX_INCLUDED_SRC_Serial_UartCore_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/types.h>
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmserialifs.h>
+#include <VBox/vmm/ssm.h>
+#include <iprt/assert.h>
+
+RT_C_DECLS_BEGIN
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** The current serial code saved state version. */
+#define UART_SAVED_STATE_VERSION 7
+/** Saved state version before the TX timer for the connected device case was added. */
+#define UART_SAVED_STATE_VERSION_PRE_UNCONNECTED_TX_TIMER 6
+/** Saved state version of the legacy code which got replaced after 5.2. */
+#define UART_SAVED_STATE_VERSION_LEGACY_CODE 5
+/** Includes some missing bits from the previous saved state. */
+#define UART_SAVED_STATE_VERSION_MISSING_BITS 4
+/** Saved state version when only the 16450 variant was implemented. */
+#define UART_SAVED_STATE_VERSION_16450 3
+
+/** Maximum size of a FIFO. */
+#define UART_FIFO_LENGTH_MAX 128
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** Pointer to the UART core state. */
+typedef struct UARTCORE *PUARTCORE;
+
+
+/**
+ * UART core IRQ request callback to let the core instance raise/clear interrupt requests.
+ *
+ * @returns nothing.
+ * @param pDevIns The owning device instance.
+ * @param pThis The UART core instance.
+ * @param iLUN The LUN associated with the UART core.
+ * @param iLvl The interrupt level.
+ */
+typedef DECLCALLBACK(void) FNUARTCOREIRQREQ(PPDMDEVINS pDevIns, PUARTCORE pThis, unsigned iLUN, int iLvl);
+/** Pointer to a UART core IRQ request callback. */
+typedef FNUARTCOREIRQREQ *PFNUARTCOREIRQREQ;
+
+
+/**
+ * UART type.
+ */
+typedef enum UARTTYPE
+{
+ /** Invalid UART type. */
+ UARTTYPE_INVALID = 0,
+ /** 16450 UART type. */
+ UARTTYPE_16450,
+ /** 16550A UART type. */
+ UARTTYPE_16550A,
+ /** 16750 UART type. */
+ UARTTYPE_16750,
+ /** 32bit hack. */
+ UARTTYPE_32BIT_HACK = 0x7fffffff
+} UARTTYPE;
+
+
+/**
+ * UART FIFO.
+ */
+typedef struct UARTFIFO
+{
+ /** Fifo size configured. */
+ uint8_t cbMax;
+ /** Current amount of bytes used. */
+ uint8_t cbUsed;
+ /** Next index to write to. */
+ uint8_t offWrite;
+ /** Next index to read from. */
+ uint8_t offRead;
+ /** The interrupt trigger level (only used for the receive FIFO). */
+ uint8_t cbItl;
+ /** The data in the FIFO. */
+ uint8_t abBuf[UART_FIFO_LENGTH_MAX];
+ /** Alignment to a 4 byte boundary. */
+ uint8_t au8Alignment0[3];
+} UARTFIFO;
+/** Pointer to a FIFO. */
+typedef UARTFIFO *PUARTFIFO;
+
+
+/**
+ * UART core device.
+ *
+ * @implements PDMIBASE
+ * @implements PDMISERIALPORT
+ */
+typedef struct UARTCORE
+{
+ /** Access critical section. */
+ PDMCRITSECT CritSect;
+ /** Pointer to the device instance - R3 Ptr. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** Pointer to the device instance - R0 Ptr. */
+ PPDMDEVINSR0 pDevInsR0;
+ /** Pointer to the device instance - RC Ptr. */
+ PPDMDEVINSRC pDevInsRC;
+ /** The LUN on the owning device instance for this core. */
+ uint32_t iLUN;
+ /** LUN\#0: The base interface. */
+ PDMIBASE IBase;
+ /** LUN\#0: The serial port interface. */
+ PDMISERIALPORT ISerialPort;
+ /** Pointer to the attached base driver. */
+ R3PTRTYPE(PPDMIBASE) pDrvBase;
+ /** Pointer to the attached serial driver. */
+ R3PTRTYPE(PPDMISERIALCONNECTOR) pDrvSerial;
+ /** Configuration flags. */
+ uint32_t fFlags;
+ /** The selected UART type. */
+ UARTTYPE enmType;
+
+ /** R3 timer pointer for the character timeout indication. */
+ PTMTIMERR3 pTimerRcvFifoTimeoutR3;
+ /** R3 timer pointer for the send loop if no driver is connected. */
+ PTMTIMERR3 pTimerTxUnconnectedR3;
+ /** R3 interrupt request callback of the owning device. */
+ R3PTRTYPE(PFNUARTCOREIRQREQ) pfnUartIrqReqR3;
+ /** R0 timer pointer fo the character timeout indication. */
+ PTMTIMERR0 pTimerRcvFifoTimeoutR0;
+ /** R0 timer pointer for the send loop if no driver is connected. */
+ PTMTIMERR0 pTimerTxUnconnectedR0;
+ /** R0 interrupt request callback of the owning device. */
+ R0PTRTYPE(PFNUARTCOREIRQREQ) pfnUartIrqReqR0;
+ /** RC timer pointer fo the character timeout indication. */
+ PTMTIMERRC pTimerRcvFifoTimeoutRC;
+ /** RC timer pointer for the send loop if no driver is connected. */
+ PTMTIMERRC pTimerTxUnconnectedRC;
+ /** RC interrupt request callback of the owning device. */
+ RCPTRTYPE(PFNUARTCOREIRQREQ) pfnUartIrqReqRC;
+ /** Alignment */
+ uint32_t u32Alignment;
+
+ /** The divisor register (DLAB = 1). */
+ uint16_t uRegDivisor;
+ /** The Receiver Buffer Register (RBR, DLAB = 0). */
+ uint8_t uRegRbr;
+ /** The Transmitter Holding Register (THR, DLAB = 0). */
+ uint8_t uRegThr;
+ /** The Interrupt Enable Register (IER, DLAB = 0). */
+ uint8_t uRegIer;
+ /** The Interrupt Identification Register (IIR). */
+ uint8_t uRegIir;
+ /** The FIFO Control Register (FCR). */
+ uint8_t uRegFcr;
+ /** The Line Control Register (LCR). */
+ uint8_t uRegLcr;
+ /** The Modem Control Register (MCR). */
+ uint8_t uRegMcr;
+ /** The Line Status Register (LSR). */
+ uint8_t uRegLsr;
+ /** The Modem Status Register (MSR). */
+ uint8_t uRegMsr;
+ /** The Scratch Register (SCR). */
+ uint8_t uRegScr;
+
+ /** Flag whether a character timeout interrupt is pending
+ * (no symbols were inserted or removed from the receive FIFO
+ * during an 4 times the character transmit/receive period and the FIFO
+ * is not empty). */
+ bool fIrqCtiPending;
+ /** Flag whether the transmitter holding register went empty since last time the
+ * IIR register was read. This gets reset when IIR is read so the guest will get this
+ * interrupt ID only once. */
+ bool fThreEmptyPending;
+ /** Alignment. */
+ bool afAlignment[2];
+ /** The transmit FIFO. */
+ UARTFIFO FifoXmit;
+ /** The receive FIFO. */
+ UARTFIFO FifoRecv;
+
+ /** Time it takes to transmit/receive a single symbol in timer ticks. */
+ uint64_t cSymbolXferTicks;
+ /** Number of bytes available for reading from the layer below. */
+ volatile uint32_t cbAvailRdr;
+
+#if defined(IN_RC) || HC_ARCH_BITS == 32
+ uint32_t uAlignment;
+#endif
+} UARTCORE;
+
+AssertCompileSizeAlignment(UARTCORE, 8);
+
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+/** Flag whether to yield the CPU on an LSR read. */
+#define UART_CORE_YIELD_ON_LSR_READ RT_BIT_32(0)
+
+/**
+ * Performs a register write to the given register offset.
+ *
+ * @returns VBox status code.
+ * @param pThis The UART core instance.
+ * @param uReg The register offset (byte offset) to start writing to.
+ * @param u32 The value to write.
+ * @param cb Number of bytes to write.
+ */
+DECLHIDDEN(int) uartRegWrite(PUARTCORE pThis, uint32_t uReg, uint32_t u32, size_t cb);
+
+/**
+ * Performs a register read from the given register offset.
+ *
+ * @returns VBox status code.
+ * @param pThis The UART core instance.
+ * @param uReg The register offset (byte offset) to start reading from.
+ * @param pu32 Where to store the read value.
+ * @param cb Number of bytes to read.
+ */
+DECLHIDDEN(int) uartRegRead(PUARTCORE pThis, uint32_t uReg, uint32_t *pu32, size_t cb);
+
+# ifdef IN_RING3
+/**
+ * Initializes the given UART core instance using the provided configuration.
+ *
+ * @returns VBox status code.
+ * @param pThis The UART core instance to initialize.
+ * @param pDevInsR3 The R3 device instance pointer.
+ * @param enmType The type of UART emulated.
+ * @param iLUN The LUN the UART should look for attached drivers.
+ * @param fFlags Additional flags controlling device behavior.
+ * @param pfnUartIrqReqR3 Pointer to the R3 interrupt request callback.
+ * @param pfnUartIrqReqR0 Pointer to the R0 interrupt request callback.
+ * @param pfnUartIrqReqRC Pointer to the RC interrupt request callback.
+ */
+DECLHIDDEN(int) uartR3Init(PUARTCORE pThis, PPDMDEVINS pDevInsR3, UARTTYPE enmType, unsigned iLUN, uint32_t fFlags,
+ R3PTRTYPE(PFNUARTCOREIRQREQ) pfnUartIrqReqR3, R0PTRTYPE(PFNUARTCOREIRQREQ) pfnUartIrqReqR0,
+ RCPTRTYPE(PFNUARTCOREIRQREQ) pfnUartIrqReqRC);
+
+/**
+ * Destroys the given UART core instance freeing all allocated resources.
+ *
+ * @returns nothing.
+ * @param pThis The UART core instance.
+ */
+DECLHIDDEN(void) uartR3Destruct(PUARTCORE pThis);
+
+/**
+ * Detaches any attached driver from the given UART core instance.
+ *
+ * @returns nothing.
+ * @param pThis The UART core instance.
+ */
+DECLHIDDEN(void) uartR3Detach(PUARTCORE pThis);
+
+/**
+ * Attaches the given UART core instance to the drivers at the given LUN.
+ *
+ * @returns VBox status code.
+ * @param pThis The UART core instance.
+ * @param iLUN The LUN being attached.
+ */
+DECLHIDDEN(int) uartR3Attach(PUARTCORE pThis, unsigned iLUN);
+
+/**
+ * Resets the given UART core instance.
+ *
+ * @returns nothing.
+ * @param pThis The UART core instance.
+ */
+DECLHIDDEN(void) uartR3Reset(PUARTCORE pThis);
+
+/**
+ * Relocates an RC pointers of the given UART core instance
+ *
+ * @returns nothing.
+ * @param pThis The UART core instance.
+ * @param offDelta The delta to relocate RC pointers with.
+ */
+DECLHIDDEN(void) uartR3Relocate(PUARTCORE pThis, RTGCINTPTR offDelta);
+
+/**
+ * Saves the UART state to the given SSM handle.
+ *
+ * @returns VBox status code.
+ * @param pThis The UART core instance.
+ * @param pSSM The SSM handle to save to.
+ */
+DECLHIDDEN(int) uartR3SaveExec(PUARTCORE pThis, PSSMHANDLE pSSM);
+
+/**
+ * Loads the UART state from the given SSM handle.
+ *
+ * @returns VBox status code.
+ * @param pThis The UART core instance.
+ * @param pSSM The SSM handle to load from.
+ * @param uVersion Saved state version.
+ * @param uPass The SSM pass the call is done in.
+ * @param puIrq Where to store the IRQ value for legacy
+ * saved states - optional.
+ * @param pPortBase Where to store the I/O port base for legacy
+ * saved states - optional.
+ */
+DECLHIDDEN(int) uartR3LoadExec(PUARTCORE pThis, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass,
+ uint8_t *puIrq, RTIOPORT *pPortBase);
+
+/**
+ * Called when loading the state completed, updates the parameters of any driver underneath.
+ *
+ * @returns VBox status code.
+ * @param pThis The UART core instance.
+ * @param pSSM The SSM handle.
+ */
+DECLHIDDEN(int) uartR3LoadDone(PUARTCORE pThis, PSSMHANDLE pSSM);
+
+# endif
+
+#endif
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_Serial_UartCore_h */