summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/Serial
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-11 08:17:27 +0000
commitf215e02bf85f68d3a6106c2a1f4f7f063f819064 (patch)
tree6bb5b92c046312c4e95ac2620b10ddf482d3fa8b /src/VBox/Devices/Serial
parentInitial commit. (diff)
downloadvirtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.tar.xz
virtualbox-f215e02bf85f68d3a6106c2a1f4f7f063f819064.zip
Adding upstream version 7.0.14-dfsg.upstream/7.0.14-dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/Serial')
-rw-r--r--src/VBox/Devices/Serial/DevOxPcie958.cpp710
-rw-r--r--src/VBox/Devices/Serial/DevSerial.cpp545
-rw-r--r--src/VBox/Devices/Serial/DrvChar.cpp491
-rw-r--r--src/VBox/Devices/Serial/DrvHostSerial.cpp953
-rw-r--r--src/VBox/Devices/Serial/DrvNamedPipe.cpp1124
-rw-r--r--src/VBox/Devices/Serial/DrvRawFile.cpp297
-rw-r--r--src/VBox/Devices/Serial/DrvTCP.cpp741
-rw-r--r--src/VBox/Devices/Serial/Makefile.kup0
-rw-r--r--src/VBox/Devices/Serial/UartCore.cpp2132
-rw-r--r--src/VBox/Devices/Serial/UartCore.h292
10 files changed, 7285 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..e9cccd0b
--- /dev/null
+++ b/src/VBox/Devices/Serial/DevOxPcie958.cpp
@@ -0,0 +1,710 @@
+/* $Id: DevOxPcie958.cpp $ */
+/** @file
+ * DevOxPcie958 - Oxford Semiconductor OXPCIe958 PCI Express bridge to octal serial port emulation
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @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/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
+
+
+/**
+ * Shared 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 shared OXPCIe958 UART core. */
+typedef OX958UART *POX958UART;
+
+/**
+ * Ring-3 OXPCIe958 UART core.
+ */
+typedef struct OX958UARTR3
+{
+ /** The ring-3 UART core. */
+ UARTCORER3 UartCore;
+} OX958UARTR3;
+/** Pointer to a ring-3 OXPCIe958 UART core. */
+typedef OX958UARTR3 *POX958UARTR3;
+
+/**
+ * Ring-0 OXPCIe958 UART core.
+ */
+typedef struct OX958UARTR0
+{
+ /** The ring-0 UART core. */
+ UARTCORER0 UartCore;
+} OX958UARTR0;
+/** Pointer to a ring-0 OXPCIe958 UART core. */
+typedef OX958UARTR0 *POX958UARTR0;
+
+
+/**
+ * Raw-mode OXPCIe958 UART core.
+ */
+typedef struct OX958UARTRC
+{
+ /** The raw-mode UART core. */
+ UARTCORERC UartCore;
+} OX958UARTRC;
+/** Pointer to a raw-mode OXPCIe958 UART core. */
+typedef OX958UARTRC *POX958UARTRC;
+
+/** Current context OXPCIe958 UART core. */
+typedef CTX_SUFF(OX958UART) OX958UARTCC;
+/** Pointer to a current context OXPCIe958 UART core. */
+typedef CTX_SUFF(POX958UART) POX958UARTCC;
+
+
+/**
+ * Shared OXPCIe958 device instance data.
+ */
+typedef struct DEVOX958
+{
+ /** 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;
+ /** Handle to the MMIO region (PCI region \#0). */
+ IOMMMIOHANDLE hMmio;
+ /** The UARTs. */
+ OX958UART aUarts[OX958_UARTS_MAX];
+} DEVOX958;
+/** Pointer to shared OXPCIe958 device instance data. */
+typedef DEVOX958 *PDEVOX958;
+
+/**
+ * Ring-3 OXPCIe958 device instance data.
+ */
+typedef struct DEVOX958R3
+{
+ /** The UARTs. */
+ OX958UARTR3 aUarts[OX958_UARTS_MAX];
+} DEVOX958R3;
+/** Pointer to ring-3 OXPCIe958 device instance data. */
+typedef DEVOX958R3 *PDEVOX958R3;
+
+/**
+ * Ring-0 OXPCIe958 device instance data.
+ */
+typedef struct DEVOX958R0
+{
+ /** The UARTs. */
+ OX958UARTR0 aUarts[OX958_UARTS_MAX];
+} DEVOX958R0;
+/** Pointer to ring-0 OXPCIe958 device instance data. */
+typedef DEVOX958R0 *PDEVOX958R0;
+
+/**
+ * Raw-mode OXPCIe958 device instance data.
+ */
+typedef struct DEVOX958RC
+{
+ /** The UARTs. */
+ OX958UARTRC aUarts[OX958_UARTS_MAX];
+} DEVOX958RC;
+/** Pointer to raw-mode OXPCIe958 device instance data. */
+typedef DEVOX958RC *PDEVOX958RC;
+
+/** Current context OXPCIe958 device instance data. */
+typedef CTX_SUFF(DEVOX958) DEVOX958CC;
+/** Pointer to current context OXPCIe958 device instance data. */
+typedef CTX_SUFF(PDEVOX958) PDEVOX958CC;
+
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+
+/**
+ * Update IRQ status of the device.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared OXPCIe958 device instance data.
+ */
+static void ox958IrqUpdate(PPDMDEVINS pDevIns, PDEVOX958 pThis)
+{
+ uint32_t u32IrqSts = ASMAtomicReadU32(&pThis->u32RegIrqStsGlob);
+ uint32_t u32IrqEn = ASMAtomicReadU32(&pThis->u32RegIrqEnGlob);
+
+ if (u32IrqSts & u32IrqEn)
+ PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_HIGH);
+ else
+ PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_LOW);
+}
+
+
+/**
+ * Performs a register read from the given UART.
+ *
+ * @returns Strict VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared OXPCIe958 device instance data.
+ * @param pUart The UART accessed, shared bits.
+ * @param pUartCC The UART accessed, current context bits.
+ * @param offUartReg Offset of the register being read.
+ * @param pv Where to store the read data.
+ * @param cb Number of bytes to read.
+ */
+static VBOXSTRICTRC ox958UartRegRead(PPDMDEVINS pDevIns, PDEVOX958 pThis, POX958UART pUart, POX958UARTCC pUartCC,
+ uint32_t offUartReg, void *pv, unsigned cb)
+{
+ VBOXSTRICTRC rc;
+ RT_NOREF(pThis);
+
+ if (offUartReg >= OX958_REG_UART_DMA_REGION_OFFSET)
+ {
+ /* Access to the DMA registers. */
+ rc = VINF_SUCCESS;
+ }
+ else /* Access UART registers. */
+ rc = uartRegRead(pDevIns, &pUart->UartCore, &pUartCC->UartCore, offUartReg, (uint32_t *)pv, cb);
+
+ return rc;
+}
+
+
+/**
+ * Performs a register write to the given UART.
+ *
+ * @returns Strict VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared OXPCIe958 device instance data.
+ * @param pUart The UART accessed, shared bits.
+ * @param pUartCC The UART accessed, current context bits.
+ * @param offUartReg Offset of the register being written.
+ * @param pv The data to write.
+ * @param cb Number of bytes to write.
+ */
+static VBOXSTRICTRC ox958UartRegWrite(PPDMDEVINS pDevIns, PDEVOX958 pThis, POX958UART pUart, POX958UARTCC pUartCC,
+ uint32_t offUartReg, const void *pv, unsigned cb)
+{
+ VBOXSTRICTRC rc;
+ RT_NOREF(pThis);
+
+ if (offUartReg >= OX958_REG_UART_DMA_REGION_OFFSET)
+ {
+ /* Access to the DMA registers. */
+ rc = VINF_SUCCESS;
+ }
+ else /* Access UART registers. */
+ rc = uartRegWrite(pDevIns, &pUart->UartCore, &pUartCC->UartCore, offUartReg, *(const uint32_t *)pv, cb);
+
+ return rc;
+}
+
+
+/**
+ * UART core IRQ request callback.
+ *
+ * @param pDevIns The device instance.
+ * @param pUart The UART requesting an IRQ update.
+ * @param iLUN The UART index.
+ * @param iLvl IRQ level requested.
+ */
+static DECLCALLBACK(void) ox958IrqReq(PPDMDEVINS pDevIns, PUARTCORE pUart, unsigned iLUN, int iLvl)
+{
+ RT_NOREF(pUart);
+ PDEVOX958 pThis = PDMDEVINS_2_DATA(pDevIns, PDEVOX958);
+
+ if (iLvl)
+ ASMAtomicOrU32(&pThis->u32RegIrqStsGlob, RT_BIT_32(iLUN));
+ else
+ ASMAtomicAndU32(&pThis->u32RegIrqStsGlob, ~RT_BIT_32(iLUN));
+ ox958IrqUpdate(pDevIns, pThis);
+}
+
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWREAD}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) ox958MmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb)
+{
+ PDEVOX958 pThis = PDMDEVINS_2_DATA(pDevIns, PDEVOX958);
+ PDEVOX958CC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVOX958CC);
+ VBOXSTRICTRC rc = VINF_SUCCESS;
+ RT_NOREF(pvUser);
+
+ if (off < OX958_REG_UART_REGION_OFFSET)
+ {
+ uint32_t *pu32 = (uint32_t *)pv;
+ Assert(cb == 4);
+
+ switch ((uint32_t)off)
+ {
+ 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. */
+ off -= OX958_REG_UART_REGION_OFFSET;
+ uint32_t iUart = (uint32_t)off / OX958_REG_UART_REGION_SIZE;
+ uint32_t offUartReg = (uint32_t)off % OX958_REG_UART_REGION_SIZE;
+ if (iUart < RT_MIN(pThis->cUarts, RT_ELEMENTS(pThis->aUarts)))
+ {
+ POX958UART pUart = &pThis->aUarts[iUart];
+ POX958UARTCC pUartCC = &pThisCC->aUarts[iUart];
+ rc = ox958UartRegRead(pDevIns, pThis, pUart, pUartCC, 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;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWWRITE}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) ox958MmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb)
+{
+ PDEVOX958 pThis = PDMDEVINS_2_DATA(pDevIns, PDEVOX958);
+ PDEVOX958CC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVOX958CC);
+ VBOXSTRICTRC rc = VINF_SUCCESS;
+ RT_NOREF1(pvUser);
+
+ if (off < OX958_REG_UART_REGION_OFFSET)
+ {
+ const uint32_t u32 = *(const uint32_t *)pv;
+ Assert(cb == 4);
+
+ switch ((uint32_t)off)
+ {
+ case OX958_REG_UART_IRQ_ENABLE:
+ ASMAtomicOrU32(&pThis->u32RegIrqEnGlob, u32);
+ ox958IrqUpdate(pDevIns, pThis);
+ break;
+ case OX958_REG_UART_IRQ_DISABLE:
+ ASMAtomicAndU32(&pThis->u32RegIrqEnGlob, ~u32);
+ ox958IrqUpdate(pDevIns, 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:
+ break;
+ }
+ }
+ else
+ {
+ /* Figure out the UART accessed from the offset. */
+ off -= OX958_REG_UART_REGION_OFFSET;
+ uint32_t iUart = (uint32_t)off / OX958_REG_UART_REGION_SIZE;
+ uint32_t offUartReg = (uint32_t)off % OX958_REG_UART_REGION_SIZE;
+ if (iUart < RT_MIN(pThis->cUarts, RT_ELEMENTS(pThis->aUarts)))
+ {
+ POX958UART pUart = &pThis->aUarts[iUart];
+ POX958UARTCC pUartCC = &pThisCC->aUarts[iUart];
+ rc = ox958UartRegWrite(pDevIns, pThis, pUart, pUartCC, offUartReg, pv, cb);
+ if (rc == VINF_IOM_R3_IOPORT_WRITE)
+ rc = VINF_IOM_R3_MMIO_WRITE;
+ }
+ }
+
+ return rc;
+}
+
+
+#ifdef IN_RING3
+
+/** @interface_method_impl{PDMDEVREG,pfnDetach} */
+static DECLCALLBACK(void) ox958R3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PDEVOX958 pThis = PDMDEVINS_2_DATA(pDevIns, PDEVOX958);
+ PDEVOX958CC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVOX958CC);
+ AssertReturnVoid(iLUN >= pThis->cUarts);
+
+ RT_NOREF(fFlags);
+
+ return uartR3Detach(pDevIns, &pThis->aUarts[iLUN].UartCore, &pThisCC->aUarts[iLUN].UartCore);
+}
+
+
+/** @interface_method_impl{PDMDEVREG,pfnAttach} */
+static DECLCALLBACK(int) ox958R3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PDEVOX958 pThis = PDMDEVINS_2_DATA(pDevIns, PDEVOX958);
+ PDEVOX958CC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVOX958CC);
+
+ RT_NOREF(fFlags);
+
+ if (iLUN >= RT_MIN(pThis->cUarts, RT_ELEMENTS(pThis->aUarts)))
+ return VERR_PDM_LUN_NOT_FOUND;
+
+ return uartR3Attach(pDevIns, &pThis->aUarts[iLUN].UartCore, &pThisCC->aUarts[iLUN].UartCore, iLUN);
+}
+
+
+/** @interface_method_impl{PDMDEVREG,pfnReset} */
+static DECLCALLBACK(void) ox958R3Reset(PPDMDEVINS pDevIns)
+{
+ PDEVOX958 pThis = PDMDEVINS_2_DATA(pDevIns, PDEVOX958);
+ PDEVOX958CC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVOX958CC);
+
+ pThis->u32RegIrqStsGlob = 0x00;
+ pThis->u32RegIrqEnGlob = 0x00;
+ pThis->u32RegIrqEnWake = 0x00;
+
+ uint32_t const cUarts = RT_MIN(pThis->cUarts, RT_ELEMENTS(pThis->aUarts));
+ for (uint32_t i = 0; i < cUarts; i++)
+ uartR3Reset(pDevIns, &pThis->aUarts[i].UartCore, &pThisCC->aUarts[i].UartCore);
+}
+
+
+/** @interface_method_impl{PDMDEVREG,pfnDestruct} */
+static DECLCALLBACK(int) ox958R3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+ PDEVOX958 pThis = PDMDEVINS_2_DATA(pDevIns, PDEVOX958);
+
+ uint32_t const cUarts = RT_MIN(pThis->cUarts, RT_ELEMENTS(pThis->aUarts));
+ for (uint32_t i = 0; i < cUarts; i++)
+ uartR3Destruct(pDevIns, &pThis->aUarts[i].UartCore);
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{PDMDEVREG,pfnConstruct} */
+static DECLCALLBACK(int) ox958R3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ RT_NOREF(iInstance);
+ PDEVOX958 pThis = PDMDEVINS_2_DATA(pDevIns, PDEVOX958);
+ PDEVOX958R3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVOX958CC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ bool fMsiXSupported = false;
+ int rc;
+
+ /*
+ * Init instance data.
+ */
+ rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Validate and read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "MsiXSupported|UartCount", "");
+
+ rc = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryU32Def(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);
+
+ /*
+ * Fill PCI config space.
+ */
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev);
+
+ PDMPciDevSetVendorId(pPciDev, OX958_PCI_VENDOR_ID);
+ PDMPciDevSetDeviceId(pPciDev, OX958_PCI_DEVICE_ID);
+ PDMPciDevSetCommand(pPciDev, 0x0000);
+# ifdef VBOX_WITH_MSI_DEVICES
+ PDMPciDevSetStatus(pPciDev, VBOX_PCI_STATUS_CAP_LIST);
+ PDMPciDevSetCapabilityList(pPciDev, OX958_PCI_MSI_CAP_OFS);
+# else
+ PDMPciDevSetCapabilityList(pPciDev, 0x70);
+# endif
+ PDMPciDevSetRevisionId(pPciDev, 0x00);
+ PDMPciDevSetClassBase(pPciDev, 0x07); /* Communication controller. */
+ PDMPciDevSetClassSub(pPciDev, 0x00); /* Serial controller. */
+ PDMPciDevSetClassProg(pPciDev, 0x02); /* 16550. */
+
+ PDMPciDevSetRevisionId(pPciDev, 0x00);
+ PDMPciDevSetSubSystemVendorId(pPciDev, OX958_PCI_VENDOR_ID);
+ PDMPciDevSetSubSystemId(pPciDev, OX958_PCI_DEVICE_ID);
+
+ PDMPciDevSetInterruptLine(pPciDev, 0x00);
+ PDMPciDevSetInterruptPin(pPciDev, 0x01);
+ /** @todo More Capabilities. */
+
+ /*
+ * Register PCI device and I/O region.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, pPciDev);
+ 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))
+ {
+ PDMPciDevSetCapabilityList(pPciDev, 0x0);
+ /* That's OK, we can work without MSI */
+ }
+# endif
+
+ rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0 /*iPciRegion*/, _16K, PCI_ADDRESS_SPACE_MEM,
+ ox958MmioWrite, ox958MmioRead, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_PASSTHRU | IOMMMIO_FLAGS_WRITE_PASSTHRU,
+ "OxPCIe958", &pThis->hMmio);
+ AssertRCReturn(rc, rc);
+
+
+ /*
+ * Initialize the UARTs.
+ */
+ for (uint32_t i = 0; i < pThis->cUarts; i++)
+ {
+ POX958UART pUart = &pThis->aUarts[i];
+ POX958UARTCC pUartCC = &pThisCC->aUarts[i];
+ rc = uartR3Init(pDevIns, &pUart->UartCore, &pUartCC->UartCore, UARTTYPE_16550A, i, 0, ox958IrqReq);
+ 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;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) ox958RZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PDEVOX958 pThis = PDMDEVINS_2_DATA(pDevIns, PDEVOX958);
+ PDEVOX958CC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVOX958CC);
+
+ int rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, ox958MmioWrite, ox958MmioRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ uint32_t const cUarts = RT_MIN(pThis->cUarts, RT_ELEMENTS(pThis->aUarts));
+ for (uint32_t i = 0; i < cUarts; i++)
+ {
+ POX958UARTCC pUartCC = &pThisCC->aUarts[i];
+ rc = uartRZInit(&pUartCC->UartCore, ox958IrqReq);
+ AssertRCReturn(rc, rc);
+ }
+
+ return VINF_SUCCESS;
+}
+
+#endif /* !IN_RING3 */
+
+
+const PDMDEVREG g_DeviceOxPcie958 =
+{
+ /* .u32version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "oxpcie958uart",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE,
+ /* .fClass = */ PDM_DEVREG_CLASS_SERIAL,
+ /* .cMaxInstances = */ ~0U,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(DEVOX958),
+ /* .cbInstanceCC = */ sizeof(DEVOX958CC),
+ /* .cbInstanceRC = */ sizeof(DEVOX958RC),
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ VBOX_MSIX_MAX_ENTRIES,
+ /* .pszDescription = */ "OXPCIe958 based UART controller.\n",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ ox958R3Construct,
+ /* .pfnDestruct = */ ox958R3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ ox958R3Reset,
+ /* .pfnSuspend = */ NULL,
+ /* .pfnResume = */ NULL,
+ /* .pfnAttach = */ ox958R3Attach,
+ /* .pfnDetach = */ ox958R3Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ NULL,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ ox958RZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ ox958RZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+#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..afb7771f
--- /dev/null
+++ b/src/VBox/Devices/Serial/DevSerial.cpp
@@ -0,0 +1,545 @@
+/* $Id: DevSerial.cpp $ */
+/** @file
+ * DevSerial - 16550A UART emulation.
+ *
+ * The documentation for this device was taken from the PC16550D spec from TI.
+ */
+
+/*
+ * Copyright (C) 2018-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_SERIAL
+#include <VBox/vmm/pdmdev.h>
+#include <VBox/vmm/pdmserialifs.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"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * Shared serial device state.
+ */
+typedef struct DEVSERIAL
+{
+ /** The IRQ value. */
+ uint8_t uIrq;
+ uint8_t bAlignment;
+ /** The base I/O port the device is registered at. */
+ RTIOPORT PortBase;
+ /** The I/O ports registration. */
+ IOMIOPORTHANDLE hIoPorts;
+
+ /** The UART core. */
+ UARTCORE UartCore;
+} DEVSERIAL;
+/** Pointer to the shared serial device state. */
+typedef DEVSERIAL *PDEVSERIAL;
+
+
+/**
+ * Serial device state for ring-3.
+ */
+typedef struct DEVSERIALR3
+{
+ /** The UART core. */
+ UARTCORER3 UartCore;
+} DEVSERIALR3;
+/** Pointer to the serial device state for ring-3. */
+typedef DEVSERIALR3 *PDEVSERIALR3;
+
+
+/**
+ * Serial device state for ring-0.
+ */
+typedef struct DEVSERIALR0
+{
+ /** The UART core. */
+ UARTCORER0 UartCore;
+} DEVSERIALR0;
+/** Pointer to the serial device state for ring-0. */
+typedef DEVSERIALR0 *PDEVSERIALR0;
+
+
+/**
+ * Serial device state for raw-mode.
+ */
+typedef struct DEVSERIALRC
+{
+ /** The UART core. */
+ UARTCORERC UartCore;
+} DEVSERIALRC;
+/** Pointer to the serial device state for raw-mode. */
+typedef DEVSERIALRC *PDEVSERIALRC;
+
+/** The serial device state for the current context. */
+typedef CTX_SUFF(DEVSERIAL) DEVSERIALCC;
+/** Pointer to the serial device state for the current context. */
+typedef CTX_SUFF(PDEVSERIAL) PDEVSERIALCC;
+
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+
+static DECLCALLBACK(void) serialIrqReq(PPDMDEVINS pDevIns, PUARTCORE pUart, unsigned iLUN, int iLvl)
+{
+ RT_NOREF(pUart, iLUN);
+ PDEVSERIAL pThis = PDMDEVINS_2_DATA(pDevIns, PDEVSERIAL);
+ PDMDevHlpISASetIrqNoWait(pDevIns, pThis->uIrq, iLvl);
+}
+
+
+/* -=-=-=-=-=-=-=-=- I/O Port Access Handlers -=-=-=-=-=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWOUT}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+serialIoPortWrite(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t u32, unsigned cb)
+{
+ PDEVSERIAL pThis = PDMDEVINS_2_DATA(pDevIns, PDEVSERIAL);
+ PDEVSERIALCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVSERIALCC);
+ RT_NOREF_PV(pvUser);
+
+ return uartRegWrite(pDevIns, &pThis->UartCore, &pThisCC->UartCore, offPort, u32, cb);
+}
+
+
+/**
+ * @callback_method_impl{FNIOMIOPORTNEWIN}
+ */
+static DECLCALLBACK(VBOXSTRICTRC)
+serialIoPortRead(PPDMDEVINS pDevIns, void *pvUser, RTIOPORT offPort, uint32_t *pu32, unsigned cb)
+{
+ PDEVSERIAL pThis = PDMDEVINS_2_DATA(pDevIns, PDEVSERIAL);
+ PDEVSERIALCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVSERIALCC);
+ RT_NOREF_PV(pvUser);
+
+ return uartRegRead(pDevIns, &pThis->UartCore, &pThisCC->UartCore, offPort, 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)
+{
+ PDEVSERIAL pThis = PDMDEVINS_2_DATA(pDevIns, PDEVSERIAL);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ RT_NOREF(uPass);
+
+ pHlp->pfnSSMPutU8(pSSM, pThis->uIrq);
+ pHlp->pfnSSMPutIOPort(pSSM, pThis->PortBase);
+ pHlp->pfnSSMPutU32(pSSM, pThis->UartCore.enmType);
+
+ return VINF_SSM_DONT_CALL_AGAIN;
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) serialR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PDEVSERIAL pThis = PDMDEVINS_2_DATA(pDevIns, PDEVSERIAL);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ pHlp->pfnSSMPutU8( pSSM, pThis->uIrq);
+ pHlp->pfnSSMPutIOPort(pSSM, pThis->PortBase);
+ pHlp->pfnSSMPutU32( pSSM, pThis->UartCore.enmType);
+
+ uartR3SaveExec(pDevIns, &pThis->UartCore, pSSM);
+ return pHlp->pfnSSMPutU32(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 = PDMDEVINS_2_DATA(pDevIns, PDEVSERIAL);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ 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)
+ {
+ pHlp->pfnSSMGetU8( pSSM, &bIrq);
+ pHlp->pfnSSMGetIOPort(pSSM, &PortBase);
+ PDMDEVHLP_SSM_GET_ENUM32_RET(pHlp, pSSM, enmType, UARTTYPE);
+ if (uPass == SSM_PASS_FINAL)
+ {
+ rc = uartR3LoadExec(pDevIns, &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;
+ pHlp->pfnSSMGetS32(pSSM, &iIrqTmp);
+ uint32_t uPortBaseTmp;
+ rc = pHlp->pfnSSMGetU32(pSSM, &uPortBaseTmp);
+ AssertRCReturn(rc, rc);
+
+ bIrq = (uint8_t)iIrqTmp;
+ PortBase = (uint32_t)uPortBaseTmp;
+ }
+ else
+ {
+ rc = uartR3LoadExec(pDevIns, &pThis->UartCore, pSSM, uVersion, uPass, &bIrq, &PortBase);
+ AssertRCReturn(rc, rc);
+ }
+ }
+
+ if (uPass == SSM_PASS_FINAL)
+ {
+ /* The marker. */
+ uint32_t u32;
+ rc = pHlp->pfnSSMGetU32(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 pHlp->pfnSSMSetCfgError(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 = PDMDEVINS_2_DATA(pDevIns, PDEVSERIAL);
+ PDEVSERIALCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVSERIALCC);
+ return uartR3LoadDone(pDevIns, &pThis->UartCore, &pThisCC->UartCore, pSSM);
+}
+
+
+/* -=-=-=-=-=-=-=-=- PDMDEVREG -=-=-=-=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ */
+static DECLCALLBACK(void) serialR3Reset(PPDMDEVINS pDevIns)
+{
+ PDEVSERIAL pThis = PDMDEVINS_2_DATA(pDevIns, PDEVSERIAL);
+ PDEVSERIALCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVSERIALCC);
+ uartR3Reset(pDevIns, &pThis->UartCore, &pThisCC->UartCore);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnAttach}
+ */
+static DECLCALLBACK(int) serialR3Attach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PDEVSERIAL pThis = PDMDEVINS_2_DATA(pDevIns, PDEVSERIAL);
+ PDEVSERIALCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVSERIALCC);
+ RT_NOREF(fFlags);
+ AssertReturn(iLUN == 0, VERR_PDM_LUN_NOT_FOUND);
+
+ return uartR3Attach(pDevIns, &pThis->UartCore, &pThisCC->UartCore, iLUN);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDetach}
+ */
+static DECLCALLBACK(void) serialR3Detach(PPDMDEVINS pDevIns, unsigned iLUN, uint32_t fFlags)
+{
+ PDEVSERIAL pThis = PDMDEVINS_2_DATA(pDevIns, PDEVSERIAL);
+ PDEVSERIALCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVSERIALCC);
+ RT_NOREF(fFlags);
+ AssertReturnVoid(iLUN == 0);
+
+ uartR3Detach(pDevIns, &pThis->UartCore, &pThisCC->UartCore);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) serialR3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+ PDEVSERIAL pThis = PDMDEVINS_2_DATA(pDevIns, PDEVSERIAL);
+
+ uartR3Destruct(pDevIns, &pThis->UartCore);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct}
+ */
+static DECLCALLBACK(int) serialR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PDEVSERIAL pThis = PDMDEVINS_2_DATA(pDevIns, PDEVSERIAL);
+ PDEVSERIALCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVSERIALCC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ int rc;
+
+ Assert(iInstance < 4);
+
+ /*
+ * Validate and read the configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "IRQ|IOBase|YieldOnLSRRead|UartType", "");
+
+ bool fYieldOnLSRRead = false;
+ rc = pHlp->pfnCFGMQueryBoolDef(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 = pHlp->pfnCFGMQueryU8(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 = pHlp->pfnCFGMQueryU16(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 szUartType[32];
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "UartType", szUartType, sizeof(szUartType), "16550A");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("Configuration error: failed to read \"UartType\" as string"));
+
+ UARTTYPE enmUartType = serialR3GetUartTypeFromString(szUartType);
+ if (enmUartType != UARTTYPE_INVALID)
+ LogRel(("Serial#%d: emulating %s (IOBase: %04x IRQ: %u)\n", pDevIns->iInstance, szUartType, uIoBase, uIrq));
+ else
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("Configuration error: Invalid \"UartType\" type value: %s"), szUartType);
+
+ pThis->uIrq = uIrq;
+ pThis->PortBase = uIoBase;
+
+ /*
+ * Init locks, using explicit locking where necessary.
+ */
+ rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register the I/O ports.
+ */
+ rc = PDMDevHlpIoPortCreateAndMap(pDevIns, uIoBase, 8 /*cPorts*/, serialIoPortWrite, serialIoPortRead,
+ "SERIAL", NULL /*paExtDescs*/, &pThis->hIoPorts);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Saved state.
+ */
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, UART_SAVED_STATE_VERSION, sizeof(*pThis), NULL,
+ NULL, serialR3LiveExec, NULL,
+ NULL, serialR3SaveExec, NULL,
+ NULL, serialR3LoadExec, serialR3LoadDone);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Init the UART core structure.
+ */
+ rc = uartR3Init(pDevIns, &pThis->UartCore, &pThisCC->UartCore, enmUartType, 0,
+ fYieldOnLSRRead ? UART_CORE_YIELD_ON_LSR_READ : 0, serialIrqReq);
+ AssertRCReturn(rc, rc);
+
+ serialR3Reset(pDevIns);
+ return VINF_SUCCESS;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) serialRZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PDEVSERIAL pThis = PDMDEVINS_2_DATA(pDevIns, PDEVSERIAL);
+ PDEVSERIALCC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PDEVSERIALCC);
+
+ int rc = PDMDevHlpSetDeviceCritSect(pDevIns, PDMDevHlpCritSectGetNop(pDevIns));
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpIoPortSetUpContext(pDevIns, pThis->hIoPorts, serialIoPortWrite, serialIoPortRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ rc = uartRZInit(&pThisCC->UartCore, serialIrqReq);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* !IN_RING3 */
+
+/**
+ * The device registration structure.
+ */
+const PDMDEVREG g_DeviceSerialPort =
+{
+ /* .u32Version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "serial",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE,
+ /* .fClass = */ PDM_DEVREG_CLASS_SERIAL,
+ /* .cMaxInstances = */ UINT32_MAX,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(DEVSERIAL),
+ /* .cbInstanceCC = */ sizeof(DEVSERIALCC),
+ /* .cbInstanceRC = */ sizeof(DEVSERIALRC),
+ /* .cMaxPciDevices = */ 0,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "Serial Communication Port",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ serialR3Construct,
+ /* .pfnDestruct = */ serialR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ serialR3Reset,
+ /* .pfnSuspend = */ NULL,
+ /* .pfnResume = */ NULL,
+ /* .pfnAttach = */ serialR3Attach,
+ /* .pfnDetach = */ serialR3Detach,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ NULL,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ serialRZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ serialRZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+#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..aa7e50c0
--- /dev/null
+++ b/src/VBox/Devices/Serial/DrvChar.cpp
@@ -0,0 +1,491 @@
+/* $Id: DrvChar.cpp $ */
+/** @file
+ * Driver that adapts PDMISTREAM into PDMISERIALCONNECTOR / PDMISERIALPORT.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* 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)
+{
+ /* Always carrier detect, data set read and clear to send. */
+ *pfStsLines = PDMISERIALPORT_STS_LINE_DCD | PDMISERIALPORT_STS_LINE_DSR | PDMISERIALPORT_STS_LINE_CTS;
+ 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 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
+ && pThis->cbTxUsed < RT_ELEMENTS(pThis->abTxBuf))
+ {
+ /* 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
+ && cbProcessed)
+ {
+ /* 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{PDMDRVREG,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..2d69c8cd
--- /dev/null
+++ b/src/VBox/Devices/Serial/DrvHostSerial.cpp
@@ -0,0 +1,953 @@
+/* $Id: DrvHostSerial.cpp $ */
+/** @file
+ * VBox serial devices: Host serial driver
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+
+/*********************************************************************************************************************************
+* 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;
+ /** The active config of the serial port. */
+ RTSERIALPORTCFG Cfg;
+
+ /** 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;
+
+ /* Flag whether the host device ran into a fatal error condition and I/O is suspended
+ * until the nuext VM suspend/resume cycle where we will try again. */
+ volatile bool fIoFatalErr;
+ /** Event semaphore the I/O thread is waiting on */
+ RTSEMEVENT hSemEvtIoFatalErr;
+
+ /** Read/write statistics */
+ STAMCOUNTER StatBytesRead;
+ STAMCOUNTER StatBytesWritten;
+} DRVHOSTSERIAL, *PDRVHOSTSERIAL;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Resets the read buffer.
+ *
+ * @returns Number of bytes which were queued in the read buffer before reset.
+ * @param pThis The host serial driver instance.
+ */
+DECLINLINE(size_t) drvHostSerialReadBufReset(PDRVHOSTSERIAL pThis)
+{
+ size_t cbOld = ASMAtomicXchgZ(&pThis->cbReadBuf, 0);
+ ASMAtomicWriteU32(&pThis->offWrite, 0);
+ ASMAtomicWriteU32(&pThis->offRead, 0);
+
+ return cbOld;
+}
+
+
+/**
+ * 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.
+ *
+ * @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.
+ *
+ * @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);
+}
+
+
+/**
+ * Wakes up the serial port I/O thread.
+ *
+ * @returns VBox status code.
+ * @param pThis The host serial driver instance.
+ */
+static int drvHostSerialWakeupIoThread(PDRVHOSTSERIAL pThis)
+{
+
+ if (RT_UNLIKELY(pThis->fIoFatalErr))
+ return RTSemEventSignal(pThis->hSemEvtIoFatalErr);
+
+ return RTSerialPortEvtPollInterrupt(pThis->hSerialPort);
+}
+
+
+/* -=-=-=-=- 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 = drvHostSerialWakeupIoThread(pThis);
+
+ 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 = drvHostSerialWakeupIoThread(pThis);
+
+ 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);
+
+ pThis->Cfg.uBaudRate = uBps;
+
+ switch (enmParity)
+ {
+ case PDMSERIALPARITY_EVEN:
+ pThis->Cfg.enmParity = RTSERIALPORTPARITY_EVEN;
+ break;
+ case PDMSERIALPARITY_ODD:
+ pThis->Cfg.enmParity = RTSERIALPORTPARITY_ODD;
+ break;
+ case PDMSERIALPARITY_NONE:
+ pThis->Cfg.enmParity = RTSERIALPORTPARITY_NONE;
+ break;
+ case PDMSERIALPARITY_MARK:
+ pThis->Cfg.enmParity = RTSERIALPORTPARITY_MARK;
+ break;
+ case PDMSERIALPARITY_SPACE:
+ pThis->Cfg.enmParity = RTSERIALPORTPARITY_SPACE;
+ break;
+ default:
+ AssertMsgFailed(("Unsupported parity setting %d\n", enmParity)); /* Should not happen. */
+ pThis->Cfg.enmParity = RTSERIALPORTPARITY_NONE;
+ }
+
+ switch (cDataBits)
+ {
+ case 5:
+ pThis->Cfg.enmDataBitCount = RTSERIALPORTDATABITS_5BITS;
+ break;
+ case 6:
+ pThis->Cfg.enmDataBitCount = RTSERIALPORTDATABITS_6BITS;
+ break;
+ case 7:
+ pThis->Cfg.enmDataBitCount = RTSERIALPORTDATABITS_7BITS;
+ break;
+ case 8:
+ pThis->Cfg.enmDataBitCount = RTSERIALPORTDATABITS_8BITS;
+ break;
+ default:
+ AssertMsgFailed(("Unsupported data bit count %u\n", cDataBits)); /* Should not happen. */
+ pThis->Cfg.enmDataBitCount = RTSERIALPORTDATABITS_8BITS;
+ }
+
+ switch (enmStopBits)
+ {
+ case PDMSERIALSTOPBITS_ONE:
+ pThis->Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONE;
+ break;
+ case PDMSERIALSTOPBITS_ONEPOINTFIVE:
+ pThis->Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONEPOINTFIVE;
+ break;
+ case PDMSERIALSTOPBITS_TWO:
+ pThis->Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_TWO;
+ break;
+ default:
+ AssertMsgFailed(("Unsupported stop bit count %d\n", enmStopBits)); /* Should not happen. */
+ pThis->Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONE;
+ }
+
+ return RTSerialPortCfgSet(pThis->hSerialPort, &pThis->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 = drvHostSerialReadBufReset(pThis);
+ if (cbOld) /* Kick the I/O thread to fetch new data. */
+ rc = drvHostSerialWakeupIoThread(pThis);
+ }
+
+ LogFlowFunc(("-> %Rrc\n", rc));
+ return VINF_SUCCESS;
+}
+
+
+/* -=-=-=-=- I/O thread -=-=-=-=- */
+
+/**
+ * The normal I/O loop.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns Pointer to the driver instance data.
+ * @param pThis Host serial driver instance data.
+ * @param pThread Thread instance data.
+ */
+static int drvHostSerialIoLoopNormal(PPDMDRVINS pDrvIns, PDRVHOSTSERIAL pThis, PPDMTHREAD pThread)
+{
+ int rc = VINF_SUCCESS;
+ while ( pThread->enmState == PDMTHREADSTATE_RUNNING
+ && RT_SUCCESS(rc))
+ {
+ 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;
+ rc = RTSerialPortEvtPoll(pThis->hSerialPort, fEvtFlags, &fEvtsRecv, RT_INDEFINITE_WAIT);
+ if (RT_SUCCESS(rc))
+ {
+ if (fEvtsRecv & RTSERIALPORT_EVT_F_DATA_TX)
+ {
+ if ( pThis->fAvailWrInt
+ && pThis->cbTxUsed < RT_ELEMENTS(pThis->abTxBuf))
+ {
+ /* 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
+ && cbProcessed)
+ {
+ /* 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);
+ /*
+ * No data being available while the port is marked as readable can happen
+ * if another thread changed the settings of the port inbetween the poll and
+ * the read call because it can flush all the buffered data (seen on Windows).
+ */
+ if (rc != VINF_TRY_AGAIN)
+ {
+ 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));
+ rc = VINF_SUCCESS;
+ }
+ }
+ else
+ {
+ LogRelMax(10, ("HostSerial#%d: Getting status lines state failed with error %Rrc; continuing.\n", pDrvIns->iInstance, rc));
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ if (fEvtsRecv & RTSERIALPORT_EVT_F_STATUS_LINE_MONITOR_FAILED)
+ {
+ LogRel(("HostSerial#%d: Status line monitoring failed at a lower level with rc=%Rrc and is disabled\n", pDrvIns->iInstance, rc));
+ rc = VINF_SUCCESS;
+ }
+ }
+ else if (rc == VERR_TIMEOUT || rc == VERR_INTERRUPTED)
+ {
+ /* Getting interrupted or running into a timeout are no error conditions. */
+ rc = VINF_SUCCESS;
+ }
+ }
+
+ LogRel(("HostSerial#%d: The underlying host device run into a fatal error condition %Rrc, any data transfer is disabled\n",
+ pDrvIns->iInstance, rc));
+
+ return rc;
+}
+
+
+/**
+ * The error I/O loop.
+ *
+ * @param pThis Host serial driver instance data.
+ * @param pThread Thread instance data.
+ */
+static void drvHostSerialIoLoopError(PDRVHOSTSERIAL pThis, PPDMTHREAD pThread)
+{
+ ASMAtomicXchgBool(&pThis->fIoFatalErr, true);
+
+ PDMDrvHlpVMSetRuntimeError(pThis->pDrvIns, 0 /*fFlags*/, "SerialPortIoError",
+ N_("The host serial port \"%s\" encountered a fatal error and stopped functioning. "
+ "This can be caused by bad cabling or USB to serial converters being unplugged by accident. "
+ "To restart I/O transfers suspend and resume the VM after fixing the underlying issue."),
+ pThis->pszDevicePath);
+
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ /*
+ * We have to discard any data which is going to be send (the error
+ * mode resembles the "someone just pulled the plug on the serial port" situation)
+ */
+ RTSemEventWait(pThis->hSemEvtIoFatalErr, RT_INDEFINITE_WAIT);
+
+ if (ASMAtomicXchgBool(&pThis->fAvailWrExt, false))
+ {
+ size_t cbFetched = 0;
+
+ do
+ {
+ /* Stuff as much data into the TX buffer as we can. */
+ uint8_t abDiscard[64];
+ int rc = pThis->pDrvSerialPort->pfnReadWr(pThis->pDrvSerialPort, &abDiscard, sizeof(abDiscard),
+ &cbFetched);
+ AssertRC(rc);
+ } while (cbFetched > 0);
+
+ /* Acknowledge the sent data. */
+ pThis->pDrvSerialPort->pfnDataSentNotify(pThis->pDrvSerialPort);
+
+ /*
+ * Sleep a bit to avoid excessive I/O loop CPU usage, timing is not important in
+ * this mode.
+ */
+ PDMDrvHlpThreadSleep(pThis->pDrvIns, pThread, 100);
+ }
+ }
+}
+
+
+/**
+ * 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;
+
+ int rc = VINF_SUCCESS;
+ if (!pThis->fIoFatalErr)
+ rc = drvHostSerialIoLoopNormal(pDrvIns, pThis, pThread);
+
+ if ( RT_FAILURE(rc)
+ || pThis->fIoFatalErr)
+ drvHostSerialIoLoopError(pThis, pThread);
+
+ 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 drvHostSerialWakeupIoThread(pThis);
+}
+
+
+/* -=-=-=-=- driver interface -=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNPDMDRVRESUME}
+ */
+static DECLCALLBACK(void) drvHostSerialResume(PPDMDRVINS pDrvIns)
+{
+ PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
+
+ if (RT_UNLIKELY(pThis->fIoFatalErr))
+ {
+ /* Try to reopen the device and set the old config. */
+ uint32_t fOpenFlags = RTSERIALPORT_OPEN_F_READ
+ | RTSERIALPORT_OPEN_F_WRITE
+ | RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING
+ | RTSERIALPORT_OPEN_F_DETECT_BREAK_CONDITION;
+ int 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_SUCCESS(rc))
+ {
+ /* Set the config which is currently active. */
+ rc = RTSerialPortCfgSet(pThis->hSerialPort, &pThis->Cfg, NULL);
+ if (RT_FAILURE(rc))
+ LogRelMax(10, ("HostSerial#%d: Setting the active serial port config failed with error %Rrc during VM resume; continuing.\n", pDrvIns->iInstance, rc));
+ /* Reset the I/O error flag on success to resume the normal I/O thread loop. */
+ ASMAtomicXchgBool(&pThis->fIoFatalErr, false);
+ }
+ }
+}
+
+
+/**
+ * @callback_method_impl{FNPDMDRVSUSPEND}
+ */
+static DECLCALLBACK(void) drvHostSerialSuspend(PPDMDRVINS pDrvIns)
+{
+ PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
+
+ if (RT_UNLIKELY(pThis->fIoFatalErr))
+ {
+ /* Close the device and try reopening it on resume. */
+ if (pThis->hSerialPort != NIL_RTSERIALPORT)
+ {
+ RTSerialPortClose(pThis->hSerialPort);
+ pThis->hSerialPort = NIL_RTSERIALPORT;
+ }
+ }
+}
+
+
+/**
+ * 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->hSemEvtIoFatalErr != NIL_RTSEMEVENT)
+ {
+ RTSemEventDestroy(pThis->hSemEvtIoFatalErr);
+ pThis->hSemEvtIoFatalErr = NIL_RTSEMEVENT;
+ }
+
+ if (pThis->pszDevicePath)
+ {
+ PDMDrvHlpMMHeapFree(pDrvIns, 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);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
+
+ /*
+ * 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;
+ pThis->fIoFatalErr = false;
+ pThis->hSemEvtIoFatalErr = NIL_RTSEMEVENT;
+ /* 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;
+
+ /*
+ * Validate the config.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "DevicePath", "");
+
+ /*
+ * Query configuration.
+ */
+ /* Device */
+ int rc = pHlp->pfnCFGMQueryStringAlloc(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);
+ }
+ }
+
+ rc = RTSemEventCreate(&pThis->hSemEvtIoFatalErr);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("HostSerial#%d failed to create event semaphore"), pDrvIns->iInstance);
+
+ /*
+ * 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 */
+ drvHostSerialSuspend,
+ /* pfnResume */
+ drvHostSerialResume,
+ /* 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..72dda7a2
--- /dev/null
+++ b/src/VBox/Devices/Serial/DrvNamedPipe.cpp
@@ -0,0 +1,1124 @@
+ /* $Id: DrvNamedPipe.cpp $ */
+/** @file
+ * Named pipe / local socket stream driver.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* 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 */
+
+ PDMDrvHlpMMHeapFree(pDrvIns, 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);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ /*
+ * 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 = pHlp->pfnCFGMQueryStringAlloc(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 = pHlp->pfnCFGMQueryBool(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)
+ {
+ close(s);
+ 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..389da56a
--- /dev/null
+++ b/src/VBox/Devices/Serial/DrvRawFile.cpp
@@ -0,0 +1,297 @@
+/* $Id: DrvRawFile.cpp $ */
+/** @file
+ * VBox stream drivers - Raw file output.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* 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)
+ PDMDrvHlpMMHeapFree(pDrvIns, 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);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ /*
+ * 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.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "Location", "");
+
+ int rc = pHlp->pfnCFGMQueryStringAlloc(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..ad28a8bb
--- /dev/null
+++ b/src/VBox/Devices/Serial/DrvTCP.cpp
@@ -0,0 +1,741 @@
+/* $Id: DrvTCP.cpp $ */
+/** @file
+ * TCP socket driver implementing the IStream interface.
+ */
+
+/*
+ * Contributed by Alexey Eromenko (derived from DrvNamedPipe).
+ *
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* 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 send buffer is full nad it is required to wait for more
+ * space until there is room again. */
+ bool fXmitBufFull;
+
+ /** Number of connections active. */
+ volatile uint32_t cConnections;
+ /** Thread for listening for new connections. */
+ RTTHREAD ListenThread;
+ /** Flag to signal listening thread to shut down. */
+ bool volatile fShutdown;
+ /** Flag to signal whether the thread was woken up from external. */
+ bool volatile fWokenUp;
+} DRVTCP, *PDRVTCP;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Kicks any possibly polling thread to get informed about changes - extended version
+ * sending additional data along with the wakeup reason.
+ *
+ * @returns VBOx status code.
+ * @param pThis The TCP driver instance.
+ * @param bReason The reason code to handle.
+ * @param pvData The additional to send along with the wakeup reason.
+ * @param cbData Number of bytes to send along.
+ */
+static int drvTcpPollerKickEx(PDRVTCP pThis, uint8_t bReason, const void *pvData, size_t cbData)
+{
+ size_t cbWritten = 0;
+ int rc = RTPipeWriteBlocking(pThis->hPipeWakeW, &bReason, 1, &cbWritten);
+ if (RT_SUCCESS(rc))
+ rc = RTPipeWriteBlocking(pThis->hPipeWakeW, pvData, cbData, &cbWritten);
+ return rc;
+}
+
+
+/**
+ * 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 RTPipeWriteBlocking(pThis->hPipeWakeW, &bReason, 1, &cbWritten);
+}
+
+
+/**
+ * Closes the connection.
+ *
+ * @param pThis The TCP driver instance.
+ */
+static void drvTcpConnectionClose(PDRVTCP pThis)
+{
+ Assert(pThis->hTcpSock != NIL_RTSOCKET);
+
+ int rc = RTPollSetRemove(pThis->hPollSet, DRVTCP_POLLSET_ID_SOCKET);
+ AssertRC(rc);
+
+ if (pThis->fIsServer)
+ RTTcpServerDisconnectClient2(pThis->hTcpSock);
+ else
+ RTSocketClose(pThis->hTcpSock);
+ pThis->hTcpSock = NIL_RTSOCKET;
+ ASMAtomicDecU32(&pThis->cConnections);
+}
+
+
+/**
+ * Checks the wakeup pipe for events.
+ *
+ * @returns VBox status code.
+ * @param pThis The TCP driver instance.
+ * @param fEvts Event mask to set if a new connection arrived.
+ */
+static int drvTcpWakeupPipeCheckForRequest(PDRVTCP pThis, uint32_t fEvts)
+{
+ int rc = VINF_SUCCESS;
+
+ while ( RT_SUCCESS(rc)
+ || rc == VERR_INTERRUPTED)
+ {
+ uint8_t bReason;
+ size_t cbRead = 0;
+ int rc2 = RTPipeRead(pThis->hPipeWakeR, &bReason, 1, &cbRead);
+ if (rc2 == VINF_TRY_AGAIN) /* Nothing there so we are done here. */
+ break;
+ else if (RT_SUCCESS(rc2))
+ {
+ if (bReason == DRVTCP_WAKEUP_REASON_EXTERNAL)
+ {
+ ASMAtomicXchgBool(&pThis->fWokenUp, false);
+ rc = VERR_INTERRUPTED;
+ }
+ else if (bReason == DRVTCP_WAKEUP_REASON_NEW_CONNECTION)
+ {
+ Assert(pThis->hTcpSock == NIL_RTSOCKET);
+
+ /* Read the socket handle. */
+ RTSOCKET hTcpSockNew = NIL_RTSOCKET;
+ rc = RTPipeReadBlocking(pThis->hPipeWakeR, &hTcpSockNew, sizeof(hTcpSockNew), NULL);
+ AssertRC(rc);
+
+ /* Always include error event. */
+ fEvts |= RTPOLL_EVT_ERROR;
+ rc = RTPollSetAddSocket(pThis->hPollSet, hTcpSockNew,
+ fEvts, DRVTCP_POLLSET_ID_SOCKET);
+ if (RT_SUCCESS(rc))
+ pThis->hTcpSock = hTcpSockNew;
+ }
+ else
+ AssertMsgFailed(("Unknown wakeup reason in pipe %u\n", bReason));
+ }
+ }
+
+ return rc;
+}
+
+
+/** @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)
+ {
+ Assert(ASMAtomicReadU32(&pThis->cConnections) > 0);
+
+ /* Always include error event. */
+ fEvts |= RTPOLL_EVT_ERROR;
+ rc = RTPollSetEventsChange(pThis->hPollSet, DRVTCP_POLLSET_ID_SOCKET, fEvts);
+ AssertRC(rc);
+ }
+ else
+ {
+ /*
+ * Check whether new connection arrived first so we don't miss it in case
+ * the guest is constantly writing data and we always end up here.
+ */
+ rc = drvTcpWakeupPipeCheckForRequest(pThis, fEvts);
+ if ( pThis->hTcpSock == NIL_RTSOCKET
+ && (fEvts & RTPOLL_EVT_WRITE))
+ {
+ /*
+ * Just pretend we can always write to not fill up any buffers and block the guest
+ * from sending data.
+ */
+ *pfEvts |= RTPOLL_EVT_WRITE;
+ return rc;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ while (RT_SUCCESS(rc))
+ {
+ uint32_t fEvtsRecv = 0;
+ uint32_t idHnd = 0;
+ uint64_t tsStartMs = RTTimeMilliTS();
+ RTMSINTERVAL cThisWaitMs = cMillies;
+
+ /*
+ * 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->hTcpSock != NIL_RTSOCKET)
+ cThisWaitMs = 0;
+
+ rc = RTPoll(pThis->hPollSet, cThisWaitMs, &fEvtsRecv, &idHnd);
+
+ /* Adjust remaining time to wait. */
+ uint64_t tsPollSpanMs = RTTimeMilliTS() - tsStartMs;
+ cMillies -= RT_MIN(cMillies, tsPollSpanMs);
+ if (RT_SUCCESS(rc))
+ {
+ if (idHnd == DRVTCP_POLLSET_ID_WAKEUP)
+ {
+ /* We got woken up, drain the pipe and return. */
+ rc = drvTcpWakeupPipeCheckForRequest(pThis, fEvts);
+ }
+ else
+ {
+ Assert(idHnd == DRVTCP_POLLSET_ID_SOCKET);
+
+ /* On error we close the socket here. */
+ if (fEvtsRecv & RTPOLL_EVT_ERROR)
+ drvTcpConnectionClose(pThis); /* Continue with polling afterwards. */
+ 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)
+{
+ int rc = VINF_SUCCESS;
+ PDRVTCP pThis = RT_FROM_MEMBER(pInterface, DRVTCP, IStream);
+
+ if (!ASMAtomicXchgBool(&pThis->fWokenUp, true))
+ rc = drvTcpPollerKick(pThis, DRVTCP_WAKEUP_REASON_EXTERNAL);
+
+ return rc;
+}
+
+
+/** @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)
+ {
+ drvTcpConnectionClose(pThis);
+ 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 Just pretend we wrote everything to not block. */
+
+ 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 (ASMAtomicReadU32(&pThis->cConnections) > 0)
+ {
+ LogRel(("DrvTCP%d: only single connection supported\n", pThis->pDrvIns->iInstance));
+ RTTcpServerDisconnectClient2(hTcpSockNew);
+ }
+ else
+ {
+ ASMAtomicIncU32(&pThis->cConnections);
+
+ /* Inform the poller about the new socket. */
+ drvTcpPollerKickEx(pThis, DRVTCP_WAKEUP_REASON_NEW_CONNECTION, &hTcpSockNew, sizeof(hTcpSockNew));
+ }
+ }
+ }
+
+ 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;
+ }
+
+ PDMDrvHlpMMHeapFree(pDrvIns, 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);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ /*
+ * Init the static parts.
+ */
+ pThis->pDrvIns = pDrvIns;
+ pThis->pszLocation = NULL;
+ pThis->fIsServer = false;
+ pThis->fXmitBufFull = false;
+ pThis->cConnections = 0;
+
+ pThis->hTcpServ = NULL;
+ pThis->hTcpSock = NIL_RTSOCKET;
+
+ pThis->hPollSet = NIL_RTPOLLSET;
+ pThis->hPipeWakeR = NIL_RTPIPE;
+ pThis->hPipeWakeW = NIL_RTPIPE;
+
+ pThis->ListenThread = NIL_RTTHREAD;
+ pThis->fShutdown = false;
+ pThis->fWokenUp = 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 = pHlp->pfnCFGMQueryStringAlloc(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 = pHlp->pfnCFGMQueryBool(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);
+
+ ASMAtomicIncU32(&pThis->cConnections);
+ }
+
+ 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..5e7fa536
--- /dev/null
+++ b/src/VBox/Devices/Serial/UartCore.cpp
@@ -0,0 +1,2132 @@
+/* $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-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* 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.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ */
+static void uartIrqUpdate(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC)
+{
+ 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));
+
+ 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));
+ else
+ LogFlow((" No change in interrupt source\n"));
+
+ /*
+ * Set interrupt value accordingly. As this is an ISA device most guests
+ * configure the IRQ as edge triggered instead of level triggered.
+ * So this needs to be done everytime, even if the internal interrupt state
+ * doesn't change in order to avoid the guest losing interrupts (reading one byte at
+ * a time from the FIFO for instance which doesn't change the interrupt source).
+ */
+ if (uRegIirNew == UART_REG_IIR_IP_NO_INT)
+ pThisCC->pfnUartIrqReq(pDevIns, pThis, pThis->iLUN, 0);
+ else
+ pThisCC->pfnUartIrqReq(pDevIns, pThis, pThis->iLUN, 1);
+
+ 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.
+ *
+ * @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.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param uMsrSts MSR value with the appropriate status bits set.
+ */
+static void uartR3MsrUpdate(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, 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(pDevIns, pThis, pThisCC);
+}
+
+
+/**
+ * Updates the serial port parameters of the attached driver with the current configuration.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ */
+static void uartR3ParamsUpdate(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC)
+{
+ if ( pThis->uRegDivisor != 0
+ && pThisCC->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 = PDMDevHlpTimerGetFreq(pDevIns, pThis->hTimerRcvFifoTimeout);
+ pThis->cSymbolXferTicks = (uTimerFreq / uBps) * cFrameBits;
+
+ LogFlowFunc(("Changing parameters to: %u,%s,%u,%s\n",
+ uBps, s_aszParity[enmParity], cDataBits, s_aszStopBits[enmStopBits]));
+
+ int rc = pThisCC->pDrvSerial->pfnChgParams(pThisCC->pDrvSerial, uBps, enmParity, cDataBits, enmStopBits);
+ if (RT_FAILURE(rc))
+ LogRelMax(10, ("Serial#%d: Failed to change parameters to %u,%s,%u,%s -> %Rrc\n",
+ pDevIns->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. */
+ pThisCC->pDrvSerial->pfnQueuesFlush(pThisCC->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.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param fStsLines The PDM status line states.
+ */
+static void uartR3StsLinesUpdate(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, 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(pDevIns, pThis, pThisCC, uRegMsrNew);
+}
+
+
+/**
+ * Fills up the receive FIFO with as much data as possible.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ */
+static void uartR3RecvFifoFill(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC)
+{
+ 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 = pThisCC->pDrvSerial->pfnReadRdr(pThisCC->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;
+ PDMDevHlpTimerSetRelative(pDevIns, pThis->hTimerRcvFifoTimeout, pThis->cSymbolXferTicks * 4, NULL);
+ }
+ uartIrqUpdate(pDevIns, pThis, pThisCC);
+ }
+
+ Assert(cbFilled <= (size_t)pThis->cbAvailRdr);
+ ASMAtomicSubU32(&pThis->cbAvailRdr, (uint32_t)cbFilled);
+}
+
+
+/**
+ * Fetches a single byte and writes it to RBR.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ */
+static void uartR3ByteFetch(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC)
+{
+ if (ASMAtomicReadU32(&pThis->cbAvailRdr))
+ {
+ size_t cbRead = 0;
+ int rc2 = pThisCC->pDrvSerial->pfnReadRdr(pThisCC->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(pDevIns, pThis, pThisCC);
+ }
+}
+
+
+/**
+ * Fetches a ready data based on the FIFO setting.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ */
+static void uartR3DataFetch(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC)
+{
+ AssertPtrReturnVoid(pThisCC->pDrvSerial);
+
+ if (pThis->uRegFcr & UART_REG_FCR_FIFO_EN)
+ uartR3RecvFifoFill(pDevIns, pThis, pThisCC);
+ else
+ uartR3ByteFetch(pDevIns, pThis, pThisCC);
+}
+
+
+/**
+ * Reset the transmit/receive related bits to the standard values
+ * (after a detach/attach/reset event).
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ */
+static void uartR3XferReset(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC)
+{
+ PDMDevHlpTimerStop(pDevIns, pThis->hTimerRcvFifoTimeout);
+ PDMDevHlpTimerStop(pDevIns, pThis->hTimerTxUnconnected);
+ pThis->uRegLsr = UART_REG_LSR_THRE | UART_REG_LSR_TEMT;
+ pThis->fThreEmptyPending = false;
+
+ uartFifoClear(&pThis->FifoXmit);
+ uartFifoClear(&pThis->FifoRecv);
+ uartR3ParamsUpdate(pDevIns, pThis, pThisCC);
+ uartIrqUpdate(pDevIns, pThis, pThisCC);
+
+ if (pThisCC->pDrvSerial)
+ {
+ /* Set the modem lines to reflect the current state. */
+ int rc = pThisCC->pDrvSerial->pfnChgModemLines(pThisCC->pDrvSerial, false /*fRts*/, false /*fDtr*/);
+ if (RT_FAILURE(rc))
+ LogRel(("Serial#%d: Failed to set modem lines with %Rrc during reset\n",
+ pDevIns->iInstance, rc));
+
+ uint32_t fStsLines = 0;
+ rc = pThisCC->pDrvSerial->pfnQueryStsLines(pThisCC->pDrvSerial, &fStsLines);
+ if (RT_SUCCESS(rc))
+ uartR3StsLinesUpdate(pDevIns, pThis, pThisCC, fStsLines);
+ else
+ LogRel(("Serial#%d: Failed to query status line status with %Rrc during reset\n",
+ pDevIns->iInstance, rc));
+ }
+
+}
+
+
+/**
+ * Tries to copy the specified amount of data from the active TX queue (register or FIFO).
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @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(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC,
+ 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(pDevIns, pThis, pThisCC);
+ }
+ 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(pDevIns, pThis, pThisCC);
+ }
+ 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 /* IN_RING3 */
+
+
+/**
+ * Transmits the given byte.
+ *
+ * @returns Strict VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param bVal Byte to transmit.
+ */
+static VBOXSTRICTRC uartXmit(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, uint8_t bVal)
+{
+ int rc = VINF_SUCCESS;
+#ifdef IN_RING3
+ bool fNotifyDrv = false;
+#endif
+
+ if (pThis->uRegFcr & UART_REG_FCR_FIFO_EN)
+ {
+#ifndef IN_RING3
+ RT_NOREF(pDevIns, pThisCC);
+ if (!uartFifoUsedGet(&pThis->FifoXmit))
+ rc = VINF_IOM_R3_IOPORT_WRITE;
+ else
+ {
+ uartFifoPut(&pThis->FifoXmit, true /*fOvrWr*/, bVal);
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_THRE | UART_REG_LSR_TEMT);
+ }
+#else
+ uartFifoPut(&pThis->FifoXmit, true /*fOvrWr*/, bVal);
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_THRE | UART_REG_LSR_TEMT);
+ pThis->fThreEmptyPending = false;
+ uartIrqUpdate(pDevIns, pThis, pThisCC);
+ if (uartFifoUsedGet(&pThis->FifoXmit) == 1)
+ fNotifyDrv = true;
+#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 = bVal;
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_THRE | UART_REG_LSR_TEMT);
+ pThis->fThreEmptyPending = false;
+ uartIrqUpdate(pDevIns, pThis, pThisCC);
+ fNotifyDrv = true;
+#endif
+ }
+ else
+ pThis->uRegThr = bVal;
+ }
+
+#ifdef IN_RING3
+ if (fNotifyDrv)
+ {
+ /* Leave the device critical section before calling into the lower driver. */
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect);
+
+ if ( pThisCC->pDrvSerial
+ && !(pThis->uRegMcr & UART_REG_MCR_LOOP))
+ {
+ int rc2 = pThisCC->pDrvSerial->pfnDataAvailWrNotify(pThisCC->pDrvSerial);
+ if (RT_FAILURE(rc2))
+ LogRelMax(10, ("Serial#%d: Failed to send data with %Rrc\n", pDevIns->iInstance, rc2));
+ }
+ else
+ PDMDevHlpTimerSetRelative(pDevIns, pThis->hTimerTxUnconnected, pThis->cSymbolXferTicks, NULL);
+
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_SUCCESS);
+ }
+#endif
+
+ return rc;
+}
+
+
+/**
+ * Write handler for the THR/DLL register (depending on the DLAB bit in LCR).
+ *
+ * @returns Strict VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param uVal The value to write.
+ */
+DECLINLINE(VBOXSTRICTRC) uartRegThrDllWrite(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, uint8_t uVal)
+{
+ VBOXSTRICTRC 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(pDevIns, pThis, pThisCC);
+#endif
+ }
+ }
+ else
+ rc = uartXmit(pDevIns, pThis, pThisCC, uVal);
+
+ return rc;
+}
+
+
+/**
+ * Write handler for the IER/DLM register (depending on the DLAB bit in LCR).
+ *
+ * @returns Strict VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param uVal The value to write.
+ */
+DECLINLINE(VBOXSTRICTRC) uartRegIerDlmWrite(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, uint8_t uVal)
+{
+ /* 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
+ return VINF_IOM_R3_IOPORT_WRITE;
+#else
+ pThis->uRegDivisor = (pThis->uRegDivisor & 0xff) | (uVal << 8);
+ uartR3ParamsUpdate(pDevIns, pThis, pThisCC);
+#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(pDevIns, pThis, pThisCC);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Write handler for the FCR register.
+ *
+ * @returns Strict VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param uVal The value to write.
+ */
+DECLINLINE(VBOXSTRICTRC) uartRegFcrWrite(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, uint8_t uVal)
+{
+#ifndef IN_RING3
+ RT_NOREF(pDevIns, pThis, pThisCC, uVal);
+ return VINF_IOM_R3_IOPORT_WRITE;
+#else /* IN_RING3 */
+ 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);
+ }
+
+ /** @todo r=bird: Why was this here: if (rc == VINF_SUCCESS) */
+ {
+ if (uVal & UART_REG_FCR_RCV_FIFO_RST)
+ {
+ PDMDevHlpTimerStop(pDevIns, pThis->hTimerRcvFifoTimeout);
+ 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(pDevIns, pThis, pThisCC);
+ }
+
+ /* Fill in the next data. */
+ if (ASMAtomicReadU32(&pThis->cbAvailRdr))
+ uartR3DataFetch(pDevIns, pThis, pThisCC);
+ }
+
+ return VINF_SUCCESS;
+#endif /* IN_RING3 */
+}
+
+
+/**
+ * Write handler for the LCR register.
+ *
+ * @returns Strict VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param uVal The value to write.
+ */
+DECLINLINE(VBOXSTRICTRC) uartRegLcrWrite(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, uint8_t uVal)
+{
+ /* 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
+ RT_NOREF(pThisCC, pDevIns);
+ return 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(pDevIns, pThis, pThisCC);
+
+ if ( fBrkChg
+ && pThisCC->pDrvSerial)
+ pThisCC->pDrvSerial->pfnChgBrk(pThisCC->pDrvSerial, fBrkEn);
+#endif
+ }
+ else
+ pThis->uRegLcr = uVal;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Write handler for the MCR register.
+ *
+ * @returns Strict VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param uVal The value to write.
+ */
+DECLINLINE(VBOXSTRICTRC) uartRegMcrWrite(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, uint8_t uVal)
+{
+ 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
+ RT_NOREF(pThisCC, pDevIns);
+ return 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)
+ && pThisCC->pDrvSerial)
+ pThisCC->pDrvSerial->pfnChgModemLines(pThisCC->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(pDevIns, pThis, pThisCC, uRegMsrSts);
+ }
+ else if (pThisCC->pDrvSerial)
+ {
+ pThisCC->pDrvSerial->pfnChgModemLines(pThisCC->pDrvSerial,
+ RT_BOOL(uVal & UART_REG_MCR_RTS),
+ RT_BOOL(uVal & UART_REG_MCR_DTR));
+
+ uint32_t fStsLines = 0;
+ int rc = pThisCC->pDrvSerial->pfnQueryStsLines(pThisCC->pDrvSerial, &fStsLines);
+ if (RT_SUCCESS(rc))
+ uartR3StsLinesUpdate(pDevIns, pThis, pThisCC, fStsLines);
+ else
+ LogRelMax(10, ("Serial#%d: Failed to query status line status with %Rrc during reset\n",
+ pDevIns->iInstance, rc));
+ }
+ else /* Loopback mode got disabled and no driver attached, fake presence. */
+ uartR3MsrUpdate(pDevIns, pThis, pThisCC, UART_REG_MSR_DCD | UART_REG_MSR_CTS | UART_REG_MSR_DSR);
+#endif
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Read handler for the RBR/DLL register (depending on the DLAB bit in LCR).
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param puVal Where to store the read value on success.
+ */
+DECLINLINE(VBOXSTRICTRC) uartRegRbrDllRead(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, uint32_t *puVal)
+{
+ VBOXSTRICTRC 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(pDevIns, pThis, pThisCC);
+#endif
+ }
+
+ if (rc == VINF_SUCCESS)
+ {
+ *puVal = uartFifoGet(&pThis->FifoRecv);
+ pThis->fIrqCtiPending = false;
+ if (!pThis->FifoRecv.cbUsed)
+ {
+ PDMDevHlpTimerStop(pDevIns, pThis->hTimerRcvFifoTimeout);
+ UART_REG_CLR(pThis->uRegLsr, UART_REG_LSR_DR);
+ }
+ else if (pThis->FifoRecv.cbUsed < pThis->FifoRecv.cbItl)
+ PDMDevHlpTimerSetRelative(pDevIns, pThis->hTimerRcvFifoTimeout,
+ pThis->cSymbolXferTicks * 4, NULL);
+ uartIrqUpdate(pDevIns, pThis, pThisCC);
+ }
+ }
+ 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(pDevIns, pThis, pThisCC);
+ }
+ 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(pDevIns, pThis, pThisCC);
+#endif
+ }
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Read handler for the IER/DLM register (depending on the DLAB bit in LCR).
+ *
+ * @param pThis The shared serial port instance data.
+ * @param puVal Where to store the read value on success.
+ */
+DECLINLINE(void) uartRegIerDlmRead(PUARTCORE pThis, uint32_t *puVal)
+{
+ /* 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;
+}
+
+
+/**
+ * Read handler for the IIR register.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param puVal Where to store the read value on success.
+ */
+DECLINLINE(void) uartRegIirRead(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, 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(pDevIns, pThis, pThisCC);
+ }
+}
+
+
+/**
+ * Read handler for the LSR register.
+ *
+ * @returns Strict VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param puVal Where to store the read value on success.
+ */
+DECLINLINE(VBOXSTRICTRC) uartRegLsrRead(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, uint32_t *puVal)
+{
+ /* 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(pDevIns, pThis, pThisCC);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Read handler for the MSR register.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param puVal Where to store the read value on success.
+ */
+DECLINLINE(void) uartRegMsrRead(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, uint32_t *puVal)
+{
+ *puVal = pThis->uRegMsr;
+
+ /* Clear any of the delta bits. */
+ UART_REG_CLR(pThis->uRegMsr, UART_REG_MSR_BITS_IIR_MS);
+ uartIrqUpdate(pDevIns, pThis, pThisCC);
+}
+
+
+#ifdef LOG_ENABLED
+/**
+ * Converts the register index into a sensible memnonic.
+ *
+ * @returns Register memnonic.
+ * @param pThis The shared serial port instance data.
+ * @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
+
+
+/**
+ * Performs a register write to the given register offset.
+ *
+ * @returns Strict VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared UART core instance data.
+ * @param pThisCC The current context UART core instance data.
+ * @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(VBOXSTRICTRC) uartRegWrite(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC,
+ uint32_t uReg, uint32_t u32, size_t cb)
+{
+ AssertMsgReturn(cb == 1, ("uReg=%#x cb=%d u32=%#x\n", uReg, cb, u32), VINF_SUCCESS);
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_IOM_R3_IOPORT_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ 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(pDevIns, pThis, pThisCC, uVal);
+ break;
+ case UART_REG_IER_DLM_INDEX:
+ rc = uartRegIerDlmWrite(pDevIns, pThis, pThisCC, uVal);
+ break;
+ case UART_REG_FCR_INDEX:
+ rc = uartRegFcrWrite(pDevIns, pThis, pThisCC, uVal);
+ break;
+ case UART_REG_LCR_INDEX:
+ rc = uartRegLcrWrite(pDevIns, pThis, pThisCC, uVal);
+ break;
+ case UART_REG_MCR_INDEX:
+ rc = uartRegMcrWrite(pDevIns, pThis, pThisCC, uVal);
+ break;
+ case UART_REG_SCR_INDEX:
+ pThis->uRegScr = uVal;
+ break;
+ default:
+ break;
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect);
+ }
+ LogFlowFunc(("-> %Rrc\n", VBOXSTRICTRC_VAL(rc)));
+ return rc;
+}
+
+
+/**
+ * Performs a register read from the given register offset.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared UART core instance data.
+ * @param pThisCC The current context UART core instance data.
+ * @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(VBOXSTRICTRC) uartRegRead(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC,
+ uint32_t uReg, uint32_t *pu32, size_t cb)
+{
+ if (cb != 1)
+ return VERR_IOM_IOPORT_UNUSED;
+
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VINF_IOM_R3_IOPORT_READ);
+ if (rc == VINF_SUCCESS)
+ {
+ uint8_t idxReg = uReg & 0x7;
+ switch (idxReg)
+ {
+ case UART_REG_RBR_DLL_INDEX:
+ rc = uartRegRbrDllRead(pDevIns, pThis, pThisCC, pu32);
+ break;
+ case UART_REG_IER_DLM_INDEX:
+ uartRegIerDlmRead(pThis, pu32);
+ break;
+ case UART_REG_IIR_INDEX:
+ uartRegIirRead(pDevIns, pThis, pThisCC, 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(pDevIns, pThis, pThisCC, pu32);
+ break;
+ case UART_REG_MSR_INDEX:
+ uartRegMsrRead(pDevIns, pThis, pThisCC, pu32);
+ break;
+ case UART_REG_SCR_INDEX:
+ *pu32 = pThis->uRegScr;
+ break;
+ default:
+ rc = VERR_IOM_IOPORT_UNUSED;
+ }
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect);
+ LogFlowFunc(("pThis=%#p uReg=%u{%s} u32=%#x cb=%u -> %Rrc\n",
+ pThis, uReg, uartRegIdx2Str(pThis, idxReg, false /*fWrite*/), *pu32, cb, VBOXSTRICTRC_VAL(rc)));
+ }
+ else
+ LogFlowFunc(("-> %Rrc\n", VBOXSTRICTRC_VAL(rc)));
+ return rc;
+}
+
+
+#ifdef IN_RING3
+
+/* -=-=-=-=-=-=-=-=- Timer callbacks -=-=-=-=-=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV, Fifo timer function.}
+ */
+static DECLCALLBACK(void) uartR3RcvFifoTimeoutTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ LogFlowFunc(("pDevIns=%#p hTimer=%#p pvUser=%#p\n", pDevIns, hTimer, pvUser));
+ PUARTCORER3 pThisCC = (PUARTCORECC)pvUser;
+ PUARTCORE pThis = pThisCC->pShared;
+ RT_NOREF(hTimer);
+
+ if (pThis->FifoRecv.cbUsed < pThis->FifoRecv.cbItl)
+ {
+ pThis->fIrqCtiPending = true;
+ uartIrqUpdate(pDevIns, pThis, pThisCC);
+ }
+}
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV,
+ * TX timer function when there is no driver connected for
+ * draining the THR/FIFO.}
+ */
+static DECLCALLBACK(void) uartR3TxUnconnectedTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ LogFlowFunc(("pDevIns=%#p hTimer=%#p pvUser=%#p\n", pDevIns, hTimer, pvUser));
+ PUARTCORER3 pThisCC = (PUARTCORECC)pvUser;
+ PUARTCORE pThis = pThisCC->pShared;
+ Assert(hTimer == pThis->hTimerTxUnconnected);
+
+ VBOXSTRICTRC rc1 = PDMDevHlpTimerLockClock2(pDevIns, hTimer, &pThis->CritSect, VINF_SUCCESS /* must get it */);
+ AssertRCReturnVoid(VBOXSTRICTRC_VAL(rc1));
+
+ uint8_t bVal = 0;
+ size_t cbRead = 0;
+ uartR3TxQueueCopyFrom(pDevIns, pThis, pThisCC, &bVal, sizeof(bVal), &cbRead);
+ if (pThis->uRegMcr & UART_REG_MCR_LOOP)
+ {
+ /* Loopback mode is active, feed in the data at the receiving end. */
+ uint32_t cbAvailOld = ASMAtomicAddU32(&pThis->cbAvailRdr, 1);
+ if (pThis->uRegFcr & UART_REG_FCR_FIFO_EN)
+ {
+ PUARTFIFO pFifo = &pThis->FifoRecv;
+ if (uartFifoFreeGet(pFifo) > 0)
+ {
+ pFifo->abBuf[pFifo->offWrite] = bVal;
+ pFifo->offWrite = (pFifo->offWrite + 1) % pFifo->cbMax;
+ pFifo->cbUsed++;
+
+ UART_REG_SET(pThis->uRegLsr, UART_REG_LSR_DR);
+ if (pFifo->cbUsed < pFifo->cbItl)
+ {
+ pThis->fIrqCtiPending = false;
+ PDMDevHlpTimerSetRelative(pDevIns, pThis->hTimerRcvFifoTimeout,
+ pThis->cSymbolXferTicks * 4, NULL);
+ }
+ uartIrqUpdate(pDevIns, pThis, pThisCC);
+ }
+
+ ASMAtomicSubU32(&pThis->cbAvailRdr, 1);
+ }
+ else if (!cbAvailOld)
+ {
+ pThis->uRegRbr = bVal;
+ UART_REG_SET(pThis->uRegLsr, UART_REG_LSR_DR);
+ uartIrqUpdate(pDevIns, pThis, pThisCC);
+ }
+ else
+ ASMAtomicSubU32(&pThis->cbAvailRdr, 1);
+ }
+
+ if (cbRead == 1)
+ PDMDevHlpTimerSetRelative(pDevIns, hTimer, pThis->cSymbolXferTicks, NULL);
+ else
+ {
+ /* NO data left, set the transmitter holding register as empty. */
+ UART_REG_SET(pThis->uRegLsr, UART_REG_LSR_TEMT);
+ }
+
+ PDMDevHlpTimerUnlockClock2(pDevIns, hTimer, &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));
+ PUARTCORECC pThisCC = RT_FROM_MEMBER(pInterface, UARTCORECC, ISerialPort);
+ PUARTCORE pThis = pThisCC->pShared;
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+
+ AssertMsg((uint32_t)cbAvail == cbAvail, ("Too much data available\n"));
+
+ int rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_IGNORED);
+ AssertRCReturn(rcLock, rcLock);
+
+ 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(pDevIns, pThis, pThisCC);
+ else if (!cbAvailOld)
+ {
+ size_t cbRead = 0;
+ int rc = pThisCC->pDrvSerial->pfnReadRdr(pThisCC->pDrvSerial, &pThis->uRegRbr, 1, &cbRead);
+ AssertRC(rc);
+
+ if (cbRead)
+ {
+ UART_REG_SET(pThis->uRegLsr, UART_REG_LSR_DR);
+ uartIrqUpdate(pDevIns, pThis, pThisCC);
+ }
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMISERIALPORT,pfnDataSentNotify}
+ */
+static DECLCALLBACK(int) uartR3DataSentNotify(PPDMISERIALPORT pInterface)
+{
+ LogFlowFunc(("pInterface=%#p\n", pInterface));
+ PUARTCORECC pThisCC = RT_FROM_MEMBER(pInterface, UARTCORECC, ISerialPort);
+ PUARTCORE pThis = pThisCC->pShared;
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+
+ /* Set the transmitter empty bit because everything was sent. */
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_IGNORED);
+ AssertRCReturn(rcLock, rcLock);
+
+ UART_REG_SET(pThis->uRegLsr, UART_REG_LSR_TEMT);
+ uartIrqUpdate(pDevIns, pThis, pThisCC);
+
+ PDMDevHlpCritSectLeave(pDevIns, &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));
+ PUARTCORECC pThisCC = RT_FROM_MEMBER(pInterface, UARTCORECC, ISerialPort);
+ PUARTCORE pThis = pThisCC->pShared;
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+
+ AssertReturn(cbRead > 0, VERR_INVALID_PARAMETER);
+
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_IGNORED);
+ AssertRCReturn(rcLock, rcLock);
+
+ uartR3TxQueueCopyFrom(pDevIns, pThis, pThisCC, pvBuf, cbRead, pcbRead);
+
+ PDMDevHlpCritSectLeave(pDevIns, &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));
+ PUARTCORECC pThisCC = RT_FROM_MEMBER(pInterface, UARTCORECC, ISerialPort);
+ PUARTCORE pThis = pThisCC->pShared;
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_IGNORED);
+ AssertRCReturn(rcLock, rcLock);
+
+ uartR3StsLinesUpdate(pDevIns, pThis, pThisCC, fNewStatusLines);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMISERIALPORT,pfnNotifyBrk}
+ */
+static DECLCALLBACK(int) uartR3NotifyBrk(PPDMISERIALPORT pInterface)
+{
+ LogFlowFunc(("pInterface=%#p\n", pInterface));
+ PUARTCORECC pThisCC = RT_FROM_MEMBER(pInterface, UARTCORECC, ISerialPort);
+ PUARTCORE pThis = pThisCC->pShared;
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_IGNORED);
+ AssertRCReturn(rcLock, rcLock);
+
+ UART_REG_SET(pThis->uRegLsr, UART_REG_LSR_BI);
+ uartIrqUpdate(pDevIns, pThis, pThisCC);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect);
+ return VINF_SUCCESS;
+}
+
+
+/* -=-=-=-=-=-=-=-=- PDMIBASE -=-=-=-=-=-=-=-=- */
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) uartR3QueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PUARTCORECC pThisCC = RT_FROM_MEMBER(pInterface, UARTCORECC, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMISERIALPORT, &pThisCC->ISerialPort);
+ return NULL;
+}
+
+
+/**
+ * Saves the UART state to the given SSM handle.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The UART core instance.
+ * @param pSSM The SSM handle to save to.
+ */
+DECLHIDDEN(int) uartR3SaveExec(PPDMDEVINS pDevIns, PUARTCORE pThis, PSSMHANDLE pSSM)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+
+ pHlp->pfnSSMPutU16(pSSM, pThis->uRegDivisor);
+ pHlp->pfnSSMPutU8(pSSM, pThis->uRegRbr);
+ pHlp->pfnSSMPutU8(pSSM, pThis->uRegThr);
+ pHlp->pfnSSMPutU8(pSSM, pThis->uRegIer);
+ pHlp->pfnSSMPutU8(pSSM, pThis->uRegIir);
+ pHlp->pfnSSMPutU8(pSSM, pThis->uRegFcr);
+ pHlp->pfnSSMPutU8(pSSM, pThis->uRegLcr);
+ pHlp->pfnSSMPutU8(pSSM, pThis->uRegMcr);
+ pHlp->pfnSSMPutU8(pSSM, pThis->uRegLsr);
+ pHlp->pfnSSMPutU8(pSSM, pThis->uRegMsr);
+ pHlp->pfnSSMPutU8(pSSM, pThis->uRegScr);
+ pHlp->pfnSSMPutBool(pSSM, pThis->fIrqCtiPending);
+ pHlp->pfnSSMPutBool(pSSM, pThis->fThreEmptyPending);
+ pHlp->pfnSSMPutU8(pSSM, pThis->FifoXmit.cbMax);
+ pHlp->pfnSSMPutU8(pSSM, pThis->FifoXmit.cbItl);
+ pHlp->pfnSSMPutU8(pSSM, pThis->FifoRecv.cbMax);
+ pHlp->pfnSSMPutU8(pSSM, pThis->FifoRecv.cbItl);
+
+ int rc = PDMDevHlpTimerSave(pDevIns, pThis->hTimerRcvFifoTimeout, pSSM);
+ if (RT_SUCCESS(rc))
+ rc = PDMDevHlpTimerSave(pDevIns, pThis->hTimerTxUnconnected, pSSM);
+
+ return rc;
+}
+
+
+/**
+ * Loads the UART state from the given SSM handle.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @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 pbIrq 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(PPDMDEVINS pDevIns, PUARTCORE pThis, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass,
+ uint8_t *pbIrq, RTIOPORT *pPortBase)
+{
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ int rc;
+ RT_NOREF(uPass);
+
+ if (uVersion > UART_SAVED_STATE_VERSION_LEGACY_CODE)
+ {
+ pHlp->pfnSSMGetU16(pSSM, &pThis->uRegDivisor);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegRbr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegThr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegIer);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegIir);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegFcr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegLcr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegMcr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegLsr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegMsr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegScr);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->fIrqCtiPending);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->fThreEmptyPending);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->FifoXmit.cbMax);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->FifoXmit.cbItl);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->FifoRecv.cbMax);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->FifoRecv.cbItl);
+
+ rc = PDMDevHlpTimerLoad(pDevIns, pThis->hTimerRcvFifoTimeout, pSSM);
+ if (uVersion > UART_SAVED_STATE_VERSION_PRE_UNCONNECTED_TX_TIMER)
+ rc = PDMDevHlpTimerLoad(pDevIns, pThis->hTimerTxUnconnected, 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", pDevIns->iInstance));
+ }
+
+ pHlp->pfnSSMGetU16(pSSM, &pThis->uRegDivisor);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegRbr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegIer);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegLcr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegMcr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegLsr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegMsr);
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegScr);
+ if (uVersion > UART_SAVED_STATE_VERSION_16450)
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegFcr);
+
+ int32_t iTmp = 0;
+ pHlp->pfnSSMGetS32(pSSM, &iTmp);
+ pThis->fThreEmptyPending = RT_BOOL(iTmp);
+
+ rc = pHlp->pfnSSMGetS32(pSSM, &iTmp);
+ AssertRCReturn(rc, rc);
+ *pbIrq = (uint8_t)iTmp;
+
+ pHlp->pfnSSMSkip(pSSM, sizeof(int32_t)); /* was: last_break_enable */
+
+ uint32_t uPortBaseTmp = 0;
+ rc = pHlp->pfnSSMGetU32(pSSM, &uPortBaseTmp);
+ AssertRCReturn(rc, rc);
+ *pPortBase = (RTIOPORT)uPortBaseTmp;
+
+ rc = pHlp->pfnSSMSkip(pSSM, sizeof(bool)); /* was: msr_changed */
+ if ( RT_SUCCESS(rc)
+ && uVersion > UART_SAVED_STATE_VERSION_MISSING_BITS)
+ {
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegThr);
+ pHlp->pfnSSMSkip(pSSM, sizeof(uint8_t)); /* The old transmit shift register, not used anymore. */
+ pHlp->pfnSSMGetU8(pSSM, &pThis->uRegIir);
+
+ int32_t iTimeoutPending = 0;
+ pHlp->pfnSSMGetS32(pSSM, &iTimeoutPending);
+ pThis->fIrqCtiPending = RT_BOOL(iTimeoutPending);
+
+ rc = PDMDevHlpTimerLoad(pDevIns, pThis->hTimerRcvFifoTimeout, pSSM);
+ AssertRCReturn(rc, rc);
+
+ bool fWasActiveIgn;
+ rc = pHlp->pfnTimerSkipLoad(pSSM, &fWasActiveIgn); /* was: transmit_timerR3 */
+ AssertRCReturn(rc, rc);
+
+ pHlp->pfnSSMGetU8(pSSM, &pThis->FifoRecv.cbItl);
+ rc = pHlp->pfnSSMGetU8(pSSM, &pThis->FifoRecv.cbItl);
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * Called when loading the state completed, updates the parameters of any driver underneath.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param pSSM The SSM handle.
+ */
+DECLHIDDEN(int) uartR3LoadDone(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+
+ uartR3ParamsUpdate(pDevIns, pThis, pThisCC);
+ uartIrqUpdate(pDevIns, pThis, pThisCC);
+
+ if (pThisCC->pDrvSerial)
+ {
+ /* Set the modem lines to reflect the current state. */
+ int rc = pThisCC->pDrvSerial->pfnChgModemLines(pThisCC->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",
+ pDevIns->iInstance, rc));
+
+ uint32_t fStsLines = 0;
+ rc = pThisCC->pDrvSerial->pfnQueryStsLines(pThisCC->pDrvSerial, &fStsLines);
+ if (RT_SUCCESS(rc))
+ uartR3StsLinesUpdate(pDevIns, pThis, pThisCC, fStsLines);
+ else
+ LogRel(("Serial#%d: Failed to query status line status with %Rrc during reset\n",
+ pDevIns->iInstance, rc));
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Resets the given UART core instance.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ */
+DECLHIDDEN(void) uartR3Reset(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC)
+{
+ 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 = UART_REG_MSR_DCD | UART_REG_MSR_CTS | UART_REG_MSR_DSR | UART_REG_MSR_DCTS | UART_REG_MSR_DDSR | UART_REG_MSR_DDCD;
+ 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(pDevIns, pThis, pThisCC);
+}
+
+
+/**
+ * Attaches the given UART core instance to the drivers at the given LUN.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param iLUN The LUN being attached.
+ */
+DECLHIDDEN(int) uartR3Attach(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, unsigned iLUN)
+{
+ int rc = PDMDevHlpDriverAttach(pDevIns, iLUN, &pThisCC->IBase, &pThisCC->pDrvBase, "Serial Char");
+ if (RT_SUCCESS(rc))
+ {
+ pThisCC->pDrvSerial = PDMIBASE_QUERY_INTERFACE(pThisCC->pDrvBase, PDMISERIALCONNECTOR);
+ if (!pThisCC->pDrvSerial)
+ {
+ AssertLogRelMsgFailed(("Configuration error: instance %d has no serial interface!\n", pDevIns->iInstance));
+ return VERR_PDM_MISSING_INTERFACE;
+ }
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_IGNORED);
+ if (RT_SUCCESS(rc))
+ {
+ uartR3XferReset(pDevIns, pThis, pThisCC);
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect);
+ }
+ }
+ else if (rc == VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ pThisCC->pDrvBase = NULL;
+ pThisCC->pDrvSerial = NULL;
+ rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_IGNORED);
+ if (RT_SUCCESS(rc))
+ {
+ uartR3XferReset(pDevIns, pThis, pThisCC);
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect);
+ }
+ LogRel(("Serial#%d: no unit\n", pDevIns->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", pDevIns->iInstance, rc));
+
+ return rc;
+}
+
+
+/**
+ * Detaches any attached driver from the given UART core instance.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared serial port instance data.
+ * @param pThisCC The serial port instance data for the current context.
+ */
+DECLHIDDEN(void) uartR3Detach(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC)
+{
+ /* Zero out important members. */
+ pThisCC->pDrvBase = NULL;
+ pThisCC->pDrvSerial = NULL;
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->CritSect, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->CritSect, rcLock);
+
+ uartR3XferReset(pDevIns, pThis, pThisCC);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect);
+}
+
+
+/**
+ * Destroys the given UART core instance freeing all allocated resources.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared UART core instance data..
+ */
+DECLHIDDEN(void) uartR3Destruct(PPDMDEVINS pDevIns, PUARTCORE pThis)
+{
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->CritSect);
+}
+
+
+/**
+ * Initializes the given UART core instance using the provided configuration.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance pointer.
+ * @param pThis The shared UART core instance data to
+ * initialize.
+ * @param pThisCC The ring-3 UART core instance data to
+ * initialize.
+ * @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 pfnUartIrqReq Pointer to the interrupt request callback.
+ */
+DECLHIDDEN(int) uartR3Init(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC,
+ UARTTYPE enmType, unsigned iLUN, uint32_t fFlags, PFNUARTCOREIRQREQ pfnUartIrqReq)
+{
+ /*
+ * Initialize the instance data.
+ * (Do this early or the destructor might choke on something!)
+ */
+ pThis->iLUN = iLUN;
+ pThis->enmType = enmType;
+ pThis->fFlags = fFlags;
+
+ pThisCC->iLUN = iLUN;
+ pThisCC->pDevIns = pDevIns;
+ pThisCC->pShared = pThis;
+ pThisCC->pfnUartIrqReq = pfnUartIrqReq;
+
+ /* IBase */
+ pThisCC->IBase.pfnQueryInterface = uartR3QueryInterface;
+
+ /* ISerialPort */
+ pThisCC->ISerialPort.pfnDataAvailRdrNotify = uartR3DataAvailRdrNotify;
+ pThisCC->ISerialPort.pfnDataSentNotify = uartR3DataSentNotify;
+ pThisCC->ISerialPort.pfnReadWr = uartR3ReadWr;
+ pThisCC->ISerialPort.pfnNotifyStsLinesChanged = uartR3NotifyStsLinesChanged;
+ pThisCC->ISerialPort.pfnNotifyBrk = uartR3NotifyBrk;
+
+ int rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CritSect, RT_SRC_POS, "Uart{%s#%d}#%d",
+ pDevIns->pReg->szName, pDevIns->iInstance, iLUN);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Attach the char driver and get the interfaces.
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, iLUN, &pThisCC->IBase, &pThisCC->pDrvBase, "UART");
+ if (RT_SUCCESS(rc))
+ {
+ pThisCC->pDrvSerial = PDMIBASE_QUERY_INTERFACE(pThisCC->pDrvBase, PDMISERIALCONNECTOR);
+ if (!pThisCC->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)
+ {
+ pThisCC->pDrvBase = NULL;
+ pThisCC->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 = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, uartR3RcvFifoTimeoutTimer, pThisCC,
+ TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_RING0, "UART Rcv FIFO",
+ &pThis->hTimerRcvFifoTimeout);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDevHlpTimerSetCritSect(pDevIns, pThis->hTimerRcvFifoTimeout, &pThis->CritSect);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create the transmit timer when no device is connected.
+ */
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL_SYNC, uartR3TxUnconnectedTimer, pThisCC,
+ TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_NO_RING0, "UART TX unconnect",
+ &pThis->hTimerTxUnconnected);
+ AssertRCReturn(rc, rc);
+
+ uartR3Reset(pDevIns, pThis, pThisCC);
+ return VINF_SUCCESS;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * Initializes the ring-0 / raw-mode instance data.
+ *
+ * @returns VBox status code.
+ * @param pThisCC The serial port instance data for the current context.
+ * @param pfnUartIrqReq Pointer to the interrupt request callback.
+ */
+DECLHIDDEN(int) uartRZInit(PUARTCORECC pThisCC, PFNUARTCOREIRQREQ pfnUartIrqReq)
+{
+ AssertPtrReturn(pfnUartIrqReq, VERR_INVALID_POINTER);
+ AssertPtrReturn(pThisCC, VERR_INVALID_POINTER);
+ pThisCC->pfnUartIrqReq = pfnUartIrqReq;
+ 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..25f63287
--- /dev/null
+++ b/src/VBox/Devices/Serial/UartCore.h
@@ -0,0 +1,292 @@
+/* $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-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#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.
+ *
+ * @param pDevIns The owning device instance.
+ * @param pThis The shared UART core instance data.
+ * @param iLUN The LUN associated with the UART core.
+ * @param iLvl The interrupt level.
+ */
+typedef DECLCALLBACKTYPE(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;
+
+
+/**
+ * Shared UART core device state.
+ *
+ * @implements PDMIBASE
+ * @implements PDMISERIALPORT
+ */
+typedef struct UARTCORE
+{
+ /** Access critical section. */
+ PDMCRITSECT CritSect;
+ /** The LUN on the owning device instance for this core. */
+ uint32_t iLUN;
+ /** Configuration flags. */
+ uint32_t fFlags;
+ /** The selected UART type. */
+ UARTTYPE enmType;
+
+ /** 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;
+
+ /** Timer handle for the character timeout indication. */
+ TMTIMERHANDLE hTimerRcvFifoTimeout;
+ /** Timer handle for the send loop if no driver is connected/loopback mode is active. */
+ TMTIMERHANDLE hTimerTxUnconnected;
+
+ /** 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;
+ /** Explicit alignment. */
+ bool afAlignment1[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;
+ /** Explicit alignment. */
+ uint32_t u32Alignment2;
+} UARTCORE;
+AssertCompileSizeAlignment(UARTCORE, 8);
+
+
+/**
+ * Ring-3 UART core device state.
+ *
+ * @implements PDMIBASE
+ * @implements PDMISERIALPORT
+ */
+typedef struct UARTCORER3
+{
+ /** The LUN on the owning device instance for this core. */
+ uint32_t iLUN;
+ uint32_t u32Padding;
+ /** 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;
+
+ /** Interrupt request callback of the owning device. */
+ R3PTRTYPE(PFNUARTCOREIRQREQ) pfnUartIrqReq;
+
+ /** Pointer to the shared data - for timers callbacks and interface methods
+ * only. */
+ R3PTRTYPE(PUARTCORE) pShared;
+ /** Pointer to the device instance - only for getting our bearings in
+ * interface methods. */
+ PPDMDEVINS pDevIns;
+} UARTCORER3;
+/** Pointer to the core ring-3 UART device state. */
+typedef UARTCORER3 *PUARTCORER3;
+
+
+/**
+ * Ring-0 UART core device state.
+ */
+typedef struct UARTCORER0
+{
+ /** Interrupt request callback of the owning device. */
+ R0PTRTYPE(PFNUARTCOREIRQREQ) pfnUartIrqReq;
+} UARTCORER0;
+/** Pointer to the core ring-0 UART device state. */
+typedef UARTCORER0 *PUARTCORER0;
+
+
+/**
+ * Raw-mode UART core device state.
+ */
+typedef struct UARTCORERC
+{
+ /** Interrupt request callback of the owning device. */
+ R0PTRTYPE(PFNUARTCOREIRQREQ) pfnUartIrqReq;
+} UARTCORERC;
+/** Pointer to the core raw-mode UART device state. */
+typedef UARTCORERC *PUARTCORERC;
+
+
+/** Current context UAR core device state. */
+typedef CTX_SUFF(UARTCORE) UARTCORECC;
+/** Pointer to the current context UAR core device state. */
+typedef CTX_SUFF(PUARTCORE) PUARTCORECC;
+
+
+#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)
+
+DECLHIDDEN(VBOXSTRICTRC) uartRegWrite(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC,
+ uint32_t uReg, uint32_t u32, size_t cb);
+DECLHIDDEN(VBOXSTRICTRC) uartRegRead(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC,
+ uint32_t uReg, uint32_t *pu32, size_t cb);
+
+# ifdef IN_RING3
+DECLHIDDEN(int) uartR3Init(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC,
+ UARTTYPE enmType, unsigned iLUN, uint32_t fFlags, PFNUARTCOREIRQREQ pfnUartIrqReq);
+DECLHIDDEN(void) uartR3Destruct(PPDMDEVINS pDevIns, PUARTCORE pThis);
+DECLHIDDEN(void) uartR3Detach(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC);
+DECLHIDDEN(int) uartR3Attach(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, unsigned iLUN);
+DECLHIDDEN(void) uartR3Reset(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC);
+DECLHIDDEN(int) uartR3SaveExec(PPDMDEVINS pDevIns, PUARTCORE pThis, PSSMHANDLE pSSM);
+DECLHIDDEN(int) uartR3LoadExec(PPDMDEVINS pDevIns, PUARTCORE pThis, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass,
+ uint8_t *puIrq, RTIOPORT *pPortBase);
+DECLHIDDEN(int) uartR3LoadDone(PPDMDEVINS pDevIns, PUARTCORE pThis, PUARTCORECC pThisCC, PSSMHANDLE pSSM);
+
+# endif /* IN_RING3 */
+# if !defined(IN_RING3) || defined(DOXYGEN_RUNNING)
+DECLHIDDEN(int) uartRZInit(PUARTCORECC pThisCC, PFNUARTCOREIRQREQ pfnUartIrqReq);
+# endif
+
+#endif
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_Serial_UartCore_h */