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