diff options
Diffstat (limited to 'src/VBox/Devices/Serial/DrvChar.cpp')
-rw-r--r-- | src/VBox/Devices/Serial/DrvChar.cpp | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/src/VBox/Devices/Serial/DrvChar.cpp b/src/VBox/Devices/Serial/DrvChar.cpp new file mode 100644 index 00000000..aa7e50c0 --- /dev/null +++ b/src/VBox/Devices/Serial/DrvChar.cpp @@ -0,0 +1,491 @@ +/* $Id: DrvChar.cpp $ */ +/** @file + * Driver that adapts PDMISTREAM into PDMISERIALCONNECTOR / PDMISERIALPORT. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_CHAR +#include <VBox/vmm/pdmdrv.h> +#include <VBox/vmm/pdmserialifs.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/poll.h> +#include <iprt/stream.h> +#include <iprt/critsect.h> +#include <iprt/semaphore.h> +#include <iprt/uuid.h> + +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Char driver instance data. + * + * @implements PDMISERIALCONNECTOR + */ +typedef struct DRVCHAR +{ + /** Pointer to the driver instance structure. */ + PPDMDRVINS pDrvIns; + /** Pointer to the char port interface of the driver/device above us. */ + PPDMISERIALPORT pDrvSerialPort; + /** Pointer to the stream interface of the driver below us. */ + PPDMISTREAM pDrvStream; + /** Our serial interface. */ + PDMISERIALCONNECTOR ISerialConnector; + /** Flag to notify the receive thread it should terminate. */ + volatile bool fShutdown; + /** Flag whether data is available from the device/driver above as notified by the driver. */ + volatile bool fAvailWrExt; + /** Internal copy of the flag which gets reset when there is no data anymore. */ + bool fAvailWrInt; + /** I/O thread. */ + PPDMTHREAD pThrdIo; + + /** Small send buffer. */ + uint8_t abTxBuf[16]; + /** Amount of data in the buffer. */ + size_t cbTxUsed; + + /** Receive buffer. */ + uint8_t abBuffer[256]; + /** Number of bytes remaining in the receive buffer. */ + volatile size_t cbRemaining; + /** Current position into the read buffer. */ + uint8_t *pbBuf; + +#if HC_ARCH_BITS == 32 + uint32_t uAlignment0; +#endif + + /** Read/write statistics */ + STAMCOUNTER StatBytesRead; + STAMCOUNTER StatBytesWritten; +} DRVCHAR, *PDRVCHAR; +AssertCompileMemberAlignment(DRVCHAR, StatBytesRead, 8); + + + + +/* -=-=-=-=- IBase -=-=-=-=- */ + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) drvCharQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PDRVCHAR pThis = PDMINS_2_DATA(pDrvIns, PDRVCHAR); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMISERIALCONNECTOR, &pThis->ISerialConnector); + return NULL; +} + + +/* -=-=-=-=- ISerialConnector -=-=-=-=- */ + + +/** + * @interface_method_impl{PDMISERIALCONNECTOR,pfnDataAvailWrNotify} + */ +static DECLCALLBACK(int) drvCharDataAvailWrNotify(PPDMISERIALCONNECTOR pInterface) +{ + LogFlowFunc(("pInterface=%#p\n", pInterface)); + PDRVCHAR pThis = RT_FROM_MEMBER(pInterface, DRVCHAR, ISerialConnector); + + int rc = VINF_SUCCESS; + bool fAvailOld = ASMAtomicXchgBool(&pThis->fAvailWrExt, true); + if (!fAvailOld) + rc = pThis->pDrvStream->pfnPollInterrupt(pThis->pDrvStream); + + return rc; +} + + +/** + * @interface_method_impl{PDMISERIALCONNECTOR,pfnReadRdr} + */ +static DECLCALLBACK(int) drvCharReadRdr(PPDMISERIALCONNECTOR pInterface, void *pvBuf, size_t cbRead, size_t *pcbRead) +{ + LogFlowFunc(("pInterface=%#p pvBuf=%#p cbRead=%zu pcbRead=%#p\n", pInterface, pvBuf, cbRead, pcbRead)); + PDRVCHAR pThis = RT_FROM_MEMBER(pInterface, DRVCHAR, ISerialConnector); + int rc = VINF_SUCCESS; + + AssertReturn(pThis->cbRemaining, VERR_INVALID_STATE); + size_t cbToRead = RT_MIN(cbRead, pThis->cbRemaining); + memcpy(pvBuf, pThis->pbBuf, cbToRead); + + pThis->pbBuf += cbToRead; + *pcbRead = cbToRead; + size_t cbOld = ASMAtomicSubZ(&pThis->cbRemaining, cbToRead); + if (!(cbOld - cbToRead)) /* Kick the I/O thread to fetch new data. */ + rc = pThis->pDrvStream->pfnPollInterrupt(pThis->pDrvStream); + STAM_COUNTER_ADD(&pThis->StatBytesRead, cbToRead); + + LogFlowFunc(("-> %Rrc\n", rc)); + return rc; +} + + +/** + * @interface_method_impl{PDMISERIALCONNECTOR,pfnChgParams} + */ +static DECLCALLBACK(int) drvCharChgParams(PPDMISERIALCONNECTOR pInterface, uint32_t uBps, + PDMSERIALPARITY enmParity, unsigned cDataBits, + PDMSERIALSTOPBITS enmStopBits) +{ + /* Nothing to do here. */ + RT_NOREF(pInterface, uBps, enmParity, cDataBits, enmStopBits); + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{PDMISERIALCONNECTOR,pfnChgModemLines} + */ +static DECLCALLBACK(int) drvCharChgModemLines(PPDMISERIALCONNECTOR pInterface, bool fRts, bool fDtr) +{ + /* Nothing to do here. */ + RT_NOREF(pInterface, fRts, fDtr); + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{PDMISERIALCONNECTOR,pfnChgBrk} + */ +static DECLCALLBACK(int) drvCharChgBrk(PPDMISERIALCONNECTOR pInterface, bool fBrk) +{ + /* Nothing to do here. */ + RT_NOREF(pInterface, fBrk); + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{PDMISERIALCONNECTOR,pfnQueryStsLines} + */ +static DECLCALLBACK(int) drvCharQueryStsLines(PPDMISERIALCONNECTOR pInterface, uint32_t *pfStsLines) +{ + /* Always carrier detect, data set read and clear to send. */ + *pfStsLines = PDMISERIALPORT_STS_LINE_DCD | PDMISERIALPORT_STS_LINE_DSR | PDMISERIALPORT_STS_LINE_CTS; + RT_NOREF(pInterface); + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{PDMISERIALCONNECTOR,pfnQueuesFlush} + */ +static DECLCALLBACK(int) drvCharQueuesFlush(PPDMISERIALCONNECTOR pInterface, bool fQueueRecv, bool fQueueXmit) +{ + RT_NOREF(fQueueXmit); + LogFlowFunc(("pInterface=%#p fQueueRecv=%RTbool fQueueXmit=%RTbool\n", pInterface, fQueueRecv, fQueueXmit)); + int rc = VINF_SUCCESS; + PDRVCHAR pThis = RT_FROM_MEMBER(pInterface, DRVCHAR, ISerialConnector); + + if (fQueueRecv) + { + size_t cbOld = 0; + cbOld = ASMAtomicXchgZ(&pThis->cbRemaining, 0); + if (cbOld) /* Kick the I/O thread to fetch new data. */ + rc = pThis->pDrvStream->pfnPollInterrupt(pThis->pDrvStream); + } + + LogFlowFunc(("-> %Rrc\n", rc)); + return rc; +} + + +/* -=-=-=-=- I/O thread -=-=-=-=- */ + +/** + * Send thread loop - pushes data down thru the driver chain. + * + * @returns VBox status code. + * @param pDrvIns The char driver instance. + * @param pThread The worker thread. + */ +static DECLCALLBACK(int) drvCharIoLoop(PPDMDRVINS pDrvIns, PPDMTHREAD pThread) +{ + RT_NOREF(pDrvIns); + PDRVCHAR pThis = (PDRVCHAR)pThread->pvUser; + + if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) + return VINF_SUCCESS; + + while (pThread->enmState == PDMTHREADSTATE_RUNNING) + { + uint32_t fEvts = 0; + + if (!pThis->fAvailWrInt) + pThis->fAvailWrInt = ASMAtomicXchgBool(&pThis->fAvailWrExt, false); + + if ( !pThis->cbRemaining + && pThis->pDrvStream->pfnRead) + fEvts |= RTPOLL_EVT_READ; + if ( pThis->fAvailWrInt + || pThis->cbTxUsed) + fEvts |= RTPOLL_EVT_WRITE; + + uint32_t fEvtsRecv = 0; + int rc = pThis->pDrvStream->pfnPoll(pThis->pDrvStream, fEvts, &fEvtsRecv, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + if (fEvtsRecv & RTPOLL_EVT_WRITE) + { + if ( pThis->fAvailWrInt + && pThis->cbTxUsed < RT_ELEMENTS(pThis->abTxBuf)) + { + /* Stuff as much data into the TX buffer as we can. */ + size_t cbToFetch = RT_ELEMENTS(pThis->abTxBuf) - pThis->cbTxUsed; + size_t cbFetched = 0; + rc = pThis->pDrvSerialPort->pfnReadWr(pThis->pDrvSerialPort, &pThis->abTxBuf[pThis->cbTxUsed], cbToFetch, + &cbFetched); + AssertRC(rc); + + if (cbFetched > 0) + pThis->cbTxUsed += cbFetched; + else + { + /* There is no data available anymore. */ + pThis->fAvailWrInt = false; + } + } + + if (pThis->cbTxUsed) + { + size_t cbProcessed = pThis->cbTxUsed; + rc = pThis->pDrvStream->pfnWrite(pThis->pDrvStream, &pThis->abTxBuf[0], &cbProcessed); + if (RT_SUCCESS(rc)) + { + pThis->cbTxUsed -= cbProcessed; + if ( pThis->cbTxUsed + && cbProcessed) + { + /* Move the data in the TX buffer to the front to fill the end again. */ + memmove(&pThis->abTxBuf[0], &pThis->abTxBuf[cbProcessed], pThis->cbTxUsed); + } + else + pThis->pDrvSerialPort->pfnDataSentNotify(pThis->pDrvSerialPort); + STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbProcessed); + } + else if (rc != VERR_TIMEOUT) + { + LogRel(("Char#%d: Write failed with %Rrc; skipping\n", pDrvIns->iInstance, rc)); + break; + } + } + } + + if (fEvtsRecv & RTPOLL_EVT_READ) + { + AssertPtr(pThis->pDrvStream->pfnRead); + Assert(!pThis->cbRemaining); + + size_t cbRead = sizeof(pThis->abBuffer); + rc = pThis->pDrvStream->pfnRead(pThis->pDrvStream, &pThis->abBuffer[0], &cbRead); + if (RT_FAILURE(rc)) + { + LogFlow(("Read failed with %Rrc\n", rc)); + break; + } + + if (cbRead) + { + pThis->pbBuf = &pThis->abBuffer[0]; + ASMAtomicWriteZ(&pThis->cbRemaining, cbRead); + /* Notify the upper device/driver. */ + rc = pThis->pDrvSerialPort->pfnDataAvailRdrNotify(pThis->pDrvSerialPort, cbRead); + } + } + } + else if (rc != VERR_INTERRUPTED) + LogRelMax(10, ("Char#%d: Polling failed with %Rrc\n", pDrvIns->iInstance, rc)); + } + + return VINF_SUCCESS; +} + + +/** + * Unblock the send worker thread so it can respond to a state change. + * + * @returns VBox status code. + * @param pDrvIns The char driver instance. + * @param pThread The worker thread. + */ +static DECLCALLBACK(int) drvCharIoLoopWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread) +{ + PDRVCHAR pThis = (PDRVCHAR)pThread->pvUser; + + RT_NOREF(pDrvIns); + return pThis->pDrvStream->pfnPollInterrupt(pThis->pDrvStream); +} + + +/* -=-=-=-=- driver interface -=-=-=-=- */ + +/** + * @interface_method_impl{PDMDRVREG,pfnReset} + */ +static DECLCALLBACK(void) drvCharReset(PPDMDRVINS pDrvIns) +{ + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + PDRVCHAR pThis = PDMINS_2_DATA(pDrvIns, PDRVCHAR); + + /* Reset TX and RX buffers. */ + pThis->fAvailWrExt = false; + pThis->fAvailWrInt = false; + pThis->cbTxUsed = 0; + pThis->cbRemaining = 0; +} + + +/** + * Construct a char driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) drvCharConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(pCfg); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PDRVCHAR pThis = PDMINS_2_DATA(pDrvIns, PDRVCHAR); + LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance)); + + /* + * Init basic data members and interfaces. + */ + pThis->pDrvIns = pDrvIns; + pThis->pThrdIo = NIL_RTTHREAD; + /* IBase. */ + pDrvIns->IBase.pfnQueryInterface = drvCharQueryInterface; + /* ISerialConnector. */ + pThis->ISerialConnector.pfnDataAvailWrNotify = drvCharDataAvailWrNotify; + pThis->ISerialConnector.pfnReadRdr = drvCharReadRdr; + pThis->ISerialConnector.pfnChgParams = drvCharChgParams; + pThis->ISerialConnector.pfnChgModemLines = drvCharChgModemLines; + pThis->ISerialConnector.pfnChgBrk = drvCharChgBrk; + pThis->ISerialConnector.pfnQueryStsLines = drvCharQueryStsLines; + pThis->ISerialConnector.pfnQueuesFlush = drvCharQueuesFlush; + + /* + * Get the ISerialPort interface of the above driver/device. + */ + pThis->pDrvSerialPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMISERIALPORT); + if (!pThis->pDrvSerialPort) + return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE, RT_SRC_POS, + N_("Char#%d has no serial port interface above"), pDrvIns->iInstance); + + /* + * Attach driver below and query its stream interface. + */ + PPDMIBASE pBase; + int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pBase); + if (RT_FAILURE(rc)) + return rc; /* Don't call PDMDrvHlpVMSetError here as we assume that the driver already set an appropriate error */ + pThis->pDrvStream = PDMIBASE_QUERY_INTERFACE(pBase, PDMISTREAM); + if (!pThis->pDrvStream) + return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW, RT_SRC_POS, + N_("Char#%d has no stream interface below"), pDrvIns->iInstance); + + rc = PDMDrvHlpThreadCreate(pThis->pDrvIns, &pThis->pThrdIo, pThis, drvCharIoLoop, + drvCharIoLoopWakeup, 0, RTTHREADTYPE_IO, "CharIo"); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("Char#%d cannot create I/O thread"), pDrvIns->iInstance); + + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Nr of bytes written", "/Devices/Char%d/Written", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES, + "Nr of bytes read", "/Devices/Char%d/Read", pDrvIns->iInstance); + + return VINF_SUCCESS; +} + + +/** + * Char driver registration record. + */ +const PDMDRVREG g_DrvChar = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "Char", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "Generic char driver.", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_CHAR, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(DRVCHAR), + /* pfnConstruct */ + drvCharConstruct, + /* pfnDestruct */ + NULL, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + drvCharReset, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; |