diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
commit | 16f504a9dca3fe3b70568f67b7d41241ae485288 (patch) | |
tree | c60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Devices/Serial | |
parent | Initial commit. (diff) | |
download | virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.tar.xz virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.zip |
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
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.cpp | 712 | ||||
-rw-r--r-- | src/VBox/Devices/Serial/DevSerial.cpp | 545 | ||||
-rw-r--r-- | src/VBox/Devices/Serial/DrvChar.cpp | 491 | ||||
-rw-r--r-- | src/VBox/Devices/Serial/DrvHostSerial.cpp | 956 | ||||
-rw-r--r-- | src/VBox/Devices/Serial/DrvNamedPipe.cpp | 1124 | ||||
-rw-r--r-- | src/VBox/Devices/Serial/DrvRawFile.cpp | 297 | ||||
-rw-r--r-- | src/VBox/Devices/Serial/DrvTCP.cpp | 742 | ||||
-rw-r--r-- | src/VBox/Devices/Serial/Makefile.kup | 0 | ||||
-rw-r--r-- | src/VBox/Devices/Serial/UartCore.cpp | 2145 | ||||
-rw-r--r-- | src/VBox/Devices/Serial/UartCore.h | 293 |
10 files changed, 7305 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..d0dda1eb --- /dev/null +++ b/src/VBox/Devices/Serial/DevOxPcie958.cpp @@ -0,0 +1,712 @@ +/* $Id: DevOxPcie958.cpp $ */ +/** @file + * DevOxPcie958 - Oxford Semiconductor OXPCIe958 PCI Express bridge to octal serial port emulation + */ + +/* + * Copyright (C) 2018-2022 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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @param pDevIns The device instance. + * @param pUart The UART requesting an IRQ update. + * @param iLUN The UART index. + * @param iLvl IRQ level requested. + */ +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..c44f0f95 --- /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-2022 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..0880259b --- /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-2022 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..597aeadc --- /dev/null +++ b/src/VBox/Devices/Serial/DrvHostSerial.cpp @@ -0,0 +1,956 @@ +/* $Id: DrvHostSerial.cpp $ */ +/** @file + * VBox serial devices: Host serial driver + */ + +/* + * Copyright (C) 2006-2022 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. + * + * @returns nothing. + * @param pThis The host serial driver instance. + * @param cbAdv Number of bytes to advance. + */ +DECLINLINE(void) drvHostSerialReadBufWriteAdv(PDRVHOSTSERIAL pThis, size_t cbAdv) +{ + uint32_t offWrite = ASMAtomicReadU32(&pThis->offWrite); + offWrite = (offWrite + cbAdv) % sizeof(pThis->abReadBuf); + ASMAtomicWriteU32(&pThis->offWrite, offWrite); + ASMAtomicAddZ(&pThis->cbReadBuf, cbAdv); +} + + +/** + * Advances the read position of the read buffer by the given amount of bytes. + * + * @returns nothing. + * @param pThis The host serial driver instance. + * @param cbAdv Number of bytes to advance. + */ +DECLINLINE(void) drvHostSerialReadBufReadAdv(PDRVHOSTSERIAL pThis, size_t cbAdv) +{ + uint32_t offRead = ASMAtomicReadU32(&pThis->offRead); + offRead = (offRead + cbAdv) % sizeof(pThis->abReadBuf); + ASMAtomicWriteU32(&pThis->offRead, offRead); + ASMAtomicSubZ(&pThis->cbReadBuf, cbAdv); +} + + +/** + * 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. + * + * @returns VBox status code. + * @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..e29c884f --- /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-2022 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..693fe39c --- /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-2022 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..04526a3c --- /dev/null +++ b/src/VBox/Devices/Serial/DrvTCP.cpp @@ -0,0 +1,742 @@ +/* $Id: DrvTCP.cpp $ */ +/** @file + * TCP socket driver implementing the IStream interface. + */ + +/* + * Contributed by Alexey Eromenko (derived from DrvNamedPipe). + * + * Copyright (C) 2006-2022 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. + * + * @returns nothing. + * @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..f391e9b6 --- /dev/null +++ b/src/VBox/Devices/Serial/UartCore.cpp @@ -0,0 +1,2145 @@ +/* $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-2022 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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @param pFifo The FIFO to clear. + */ +DECLINLINE(void) uartFifoClear(PUARTFIFO pFifo) +{ + memset(&pFifo->abBuf[0], 0, sizeof(pFifo->abBuf)); + pFifo->cbUsed = 0; + pFifo->offWrite = 0; + pFifo->offRead = 0; +} + + +/** + * Returns the amount of free bytes in the given FIFO. + * + * @returns The amount of bytes free in the given FIFO. + * @param pFifo The FIFO. + */ +DECLINLINE(size_t) uartFifoFreeGet(PUARTFIFO pFifo) +{ + return pFifo->cbMax - pFifo->cbUsed; +} + + +/** + * Tries to copy the requested amount of data from the given FIFO into the provided buffer. + * + * @returns Amount of bytes actually copied. + * @param pFifo The FIFO to copy data from. + * @param pvDst Where to copy the data to. + * @param cbCopy How much to copy. + */ +DECLINLINE(size_t) uartFifoCopyTo(PUARTFIFO pFifo, void *pvDst, size_t cbCopy) +{ + size_t cbCopied = 0; + uint8_t *pbDst = (uint8_t *)pvDst; + + cbCopy = RT_MIN(cbCopy, pFifo->cbUsed); + while (cbCopy) + { + uint8_t cbThisCopy = (uint8_t)RT_MIN(cbCopy, (uint8_t)(pFifo->cbMax - pFifo->offRead)); + memcpy(pbDst, &pFifo->abBuf[pFifo->offRead], cbThisCopy); + + pFifo->offRead = (pFifo->offRead + cbThisCopy) % pFifo->cbMax; + pFifo->cbUsed -= cbThisCopy; + pbDst += cbThisCopy; + cbCopied += cbThisCopy; + cbCopy -= cbThisCopy; + } + + return cbCopied; +} + + +#if 0 /* unused */ +/** + * Tries to copy the requested amount of data from the provided buffer into the given FIFO. + * + * @returns Amount of bytes actually copied. + * @param pFifo The FIFO to copy data to. + * @param pvSrc Where to copy the data from. + * @param cbCopy How much to copy. + */ +DECLINLINE(size_t) uartFifoCopyFrom(PUARTFIFO pFifo, void *pvSrc, size_t cbCopy) +{ + size_t cbCopied = 0; + uint8_t *pbSrc = (uint8_t *)pvSrc; + + cbCopy = RT_MIN(cbCopy, uartFifoFreeGet(pFifo)); + while (cbCopy) + { + uint8_t cbThisCopy = (uint8_t)RT_MIN(cbCopy, (uint8_t)(pFifo->cbMax - pFifo->offWrite)); + memcpy(&pFifo->abBuf[pFifo->offWrite], pbSrc, cbThisCopy); + + pFifo->offWrite = (pFifo->offWrite + cbThisCopy) % pFifo->cbMax; + pFifo->cbUsed += cbThisCopy; + pbSrc += cbThisCopy; + cbCopied += cbThisCopy; + cbCopy -= cbThisCopy; + } + + return cbCopied; +} +#endif + + +/** + * Updates the delta bits for the given MSR register value which has the status line + * bits set. + * + * @returns nothing. + * @param 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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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). + * + * @returns nothing. + * @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). + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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. + * + * @returns nothing. + * @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..a8e038a5 --- /dev/null +++ b/src/VBox/Devices/Serial/UartCore.h @@ -0,0 +1,293 @@ +/* $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-2022 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. + * + * @returns nothing. + * @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 */ |