/* $Id: DrvHostParallel.cpp $ */ /** @file * VirtualBox Host Parallel Port Driver. * * Initial Linux-only code contributed by: Alexander Eichner */ /* * 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 . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DRV_HOST_PARALLEL #include #include #include #include #include #include #include #include #include #include #include #ifdef RT_OS_LINUX # include # include # include # include # include # include # include # include # include #endif /** @def VBOX_WITH_WIN_PARPORT_SUP * Indicates whether to use the generic direct hardware access or host specific * code to access the parallel port. */ #if defined(DOXYGEN_RUNNING) # define VBOX_WITH_WIN_PARPORT_SUP #endif #if defined(RT_OS_LINUX) # undef VBOX_WITH_WIN_PARPORT_SUP #elif defined(RT_OS_WINDOWS) #else # error "Not ported" #endif #if defined(VBOX_WITH_WIN_PARPORT_SUP) && defined(IN_RING0) # include #endif #if defined(VBOX_WITH_WIN_PARPORT_SUP) && defined(IN_RING3) # include # include # include # include # include # include # include #endif #include "VBoxDD.h" /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Host parallel port driver instance data. * @implements PDMIHOSTPARALLELCONNECTOR */ typedef struct DRVHOSTPARALLEL { /** Pointer to the driver instance structure. */ PPDMDRVINS pDrvIns; /** Pointer to the driver instance. */ PPDMDRVINSR3 pDrvInsR3; PPDMDRVINSR0 pDrvInsR0; /** Pointer to the char port interface of the driver/device above us. */ PPDMIHOSTPARALLELPORT pDrvHostParallelPort; /** Our host device interface. */ PDMIHOSTPARALLELCONNECTOR IHostParallelConnector; /** Our host device interface. */ PDMIHOSTPARALLELCONNECTOR IHostParallelConnectorR3; /** Device Path */ char *pszDevicePath; /** Device Handle */ RTFILE hFileDevice; #ifndef VBOX_WITH_WIN_PARPORT_SUP /** Thread waiting for interrupts. */ PPDMTHREAD pMonitorThread; /** Wakeup pipe read end. */ RTPIPE hWakeupPipeR; /** Wakeup pipe write end. */ RTPIPE hWakeupPipeW; /** Current mode the parallel port is in. */ PDMPARALLELPORTMODE enmModeCur; #endif #ifdef VBOX_WITH_WIN_PARPORT_SUP /** Data register. */ RTIOPORT PortDirectData; /** Status register. */ RTIOPORT PortDirectStatus; /** Control register. */ RTIOPORT PortDirectControl; /** Control read result buffer. */ uint8_t bReadInControl; /** Status read result buffer. */ uint8_t bReadInStatus; /** Data buffer for reads and writes. */ uint8_t abDataBuf[32]; #endif /* VBOX_WITH_WIN_PARPORT_SUP */ } DRVHOSTPARALLEL, *PDRVHOSTPARALLEL; /** * Ring-0 operations. */ typedef enum DRVHOSTPARALLELR0OP { /** Invalid zero value. */ DRVHOSTPARALLELR0OP_INVALID = 0, /** Perform R0 initialization. */ DRVHOSTPARALLELR0OP_INITR0STUFF, /** Read data into the data buffer (abDataBuf). */ DRVHOSTPARALLELR0OP_READ, /** Read status register. */ DRVHOSTPARALLELR0OP_READSTATUS, /** Read control register. */ DRVHOSTPARALLELR0OP_READCONTROL, /** Write data from the data buffer (abDataBuf). */ DRVHOSTPARALLELR0OP_WRITE, /** Write control register. */ DRVHOSTPARALLELR0OP_WRITECONTROL, /** Set port direction. */ DRVHOSTPARALLELR0OP_SETPORTDIRECTION } DRVHOSTPARALLELR0OP; /** Converts a pointer to DRVHOSTPARALLEL::IHostDeviceConnector to a PDRHOSTPARALLEL. */ #define PDMIHOSTPARALLELCONNECTOR_2_DRVHOSTPARALLEL(pInterface) ( (PDRVHOSTPARALLEL)((uintptr_t)pInterface - RT_UOFFSETOF(DRVHOSTPARALLEL, CTX_SUFF(IHostParallelConnector))) ) /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ #define CTRL_REG_OFFSET 2 #define STATUS_REG_OFFSET 1 #define LPT_CONTROL_ENABLE_BIDIRECT 0x20 #ifdef VBOX_WITH_WIN_PARPORT_SUP # ifdef IN_RING0 /** * R0 mode function to write byte value to data port. * * @returns VBox status code. * @param pThis Pointer to the instance data. * @param u64Arg The number of bytes to write (from abDataBuf). */ static int drvR0HostParallelReqWrite(PDRVHOSTPARALLEL pThis, uint64_t u64Arg) { LogFlowFunc(("write %#RX64 bytes to data (%#x)\n", u64Arg, pThis->PortDirectData)); AssertReturn(u64Arg > 0 && u64Arg <= sizeof(pThis->abDataBuf), VERR_OUT_OF_RANGE); uint8_t const *pbSrc = pThis->abDataBuf; while (u64Arg-- > 0) { ASMOutU8(pThis->PortDirectData, *pbSrc); pbSrc++; } return VINF_SUCCESS; } /** * R0 mode function to write byte value to parallel port control register. * * @returns VBox status code. * @param pThis Pointer to the instance data. * @param u64Arg Data to be written to control register. */ static int drvR0HostParallelReqWriteControl(PDRVHOSTPARALLEL pThis, uint64_t u64Arg) { LogFlowFunc(("write to ctrl port=%#x val=%#x\n", pThis->PortDirectControl, u64Arg)); ASMOutU8(pThis->PortDirectControl, (uint8_t)(u64Arg)); return VINF_SUCCESS; } /** * R0 mode function to ready byte value from the parallel port data register. * * @returns VBox status code. * @param pThis Pointer to the instance data. * @param u64Arg The number of bytes to read into abDataBuf. */ static int drvR0HostParallelReqRead(PDRVHOSTPARALLEL pThis, uint64_t u64Arg) { LogFlowFunc(("read %#RX64 bytes to data (%#x)\n", u64Arg, pThis->PortDirectData)); AssertReturn(u64Arg > 0 && u64Arg <= sizeof(pThis->abDataBuf), VERR_OUT_OF_RANGE); uint8_t *pbDst = pThis->abDataBuf; while (u64Arg-- > 0) *pbDst++ = ASMInU8(pThis->PortDirectData); return VINF_SUCCESS; } /** * R0 mode function to ready byte value from the parallel port control register. * * @returns VBox status code. * @param pThis Pointer to the instance data. */ static int drvR0HostParallelReqReadControl(PDRVHOSTPARALLEL pThis) { uint8_t u8Data = ASMInU8(pThis->PortDirectControl); LogFlowFunc(("read from ctrl port=%#x val=%#x\n", pThis->PortDirectControl, u8Data)); pThis->bReadInControl = u8Data; return VINF_SUCCESS; } /** * R0 mode function to ready byte value from the parallel port status register. * * @returns VBox status code. * @param pThis Pointer to the instance data. */ static int drvR0HostParallelReqReadStatus(PDRVHOSTPARALLEL pThis) { uint8_t u8Data = ASMInU8(pThis->PortDirectStatus); LogFlowFunc(("read from status port=%#x val=%#x\n", pThis->PortDirectStatus, u8Data)); pThis->bReadInStatus = u8Data; return VINF_SUCCESS; } /** * R0 mode function to set the direction of parallel port - * operate in bidirectional mode or single direction. * * @returns VBox status code. * @param pThis Pointer to the instance data. * @param u64Arg Mode. */ static int drvR0HostParallelReqSetPortDir(PDRVHOSTPARALLEL pThis, uint64_t u64Arg) { uint8_t bCtl = ASMInU8(pThis->PortDirectControl); if (u64Arg) bCtl |= LPT_CONTROL_ENABLE_BIDIRECT; /* enable input direction */ else bCtl &= ~LPT_CONTROL_ENABLE_BIDIRECT; /* disable input direction */ ASMOutU8(pThis->PortDirectControl, bCtl); return VINF_SUCCESS; } /** * @callback_method_impl{FNPDMDRVREQHANDLERR0} */ PDMBOTHCBDECL(int) drvR0HostParallelReqHandler(PPDMDRVINS pDrvIns, uint32_t uOperation, uint64_t u64Arg) { PDRVHOSTPARALLEL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPARALLEL); int rc; LogFlowFuncEnter(); if (pThis->PortDirectData != 0) { switch ((DRVHOSTPARALLELR0OP)uOperation) { case DRVHOSTPARALLELR0OP_READ: rc = drvR0HostParallelReqRead(pThis, u64Arg); break; case DRVHOSTPARALLELR0OP_READSTATUS: rc = drvR0HostParallelReqReadStatus(pThis); break; case DRVHOSTPARALLELR0OP_READCONTROL: rc = drvR0HostParallelReqReadControl(pThis); break; case DRVHOSTPARALLELR0OP_WRITE: rc = drvR0HostParallelReqWrite(pThis, u64Arg); break; case DRVHOSTPARALLELR0OP_WRITECONTROL: rc = drvR0HostParallelReqWriteControl(pThis, u64Arg); break; case DRVHOSTPARALLELR0OP_SETPORTDIRECTION: rc = drvR0HostParallelReqSetPortDir(pThis, u64Arg); break; default: rc = VERR_INVALID_FUNCTION; break; } } else rc = VERR_WRONG_ORDER; LogFlowFuncLeaveRC(rc); return rc; } # endif /* IN_RING0 */ #endif /* VBOX_WITH_WIN_PARPORT_SUP */ #ifdef IN_RING3 # ifdef VBOX_WITH_WIN_PARPORT_SUP /** * Find IO port range for the parallel port and return the lower address. * * @returns Parallel base I/O port. * @param DevInst Device instance (dword/handle) for the parallel port. */ static RTIOPORT drvHostParallelGetWinHostIoPortsSub(const DEVINST DevInst) { RTIOPORT PortBase = 0; /* Get handle of the first logical configuration. */ LOG_CONF hFirstLogConf; CONFIGRET rcCm = CM_Get_First_Log_Conf(&hFirstLogConf, DevInst, ALLOC_LOG_CONF); if (rcCm != CR_SUCCESS) rcCm = CM_Get_First_Log_Conf(&hFirstLogConf, DevInst, BOOT_LOG_CONF); if (rcCm == CR_SUCCESS) { /* * This loop is based on the "fact" that only one I/O resource is assigned * to the LPT port. Should there ever be multiple resources, we'll pick * the last one for some silly reason. */ /* Get the first resource descriptor handle. */ LOG_CONF hCurLogConf = 0; rcCm = CM_Get_Next_Res_Des(&hCurLogConf, hFirstLogConf, ResType_IO, 0, 0); if (rcCm == CR_SUCCESS) { for (;;) { ULONG cbData; rcCm = CM_Get_Res_Des_Data_Size(&cbData, hCurLogConf, 0); if (rcCm != CR_SUCCESS) cbData = 0; cbData = RT_MAX(cbData, sizeof(IO_DES)); IO_DES *pIoDesc = (IO_DES *)RTMemAllocZ(cbData); if (pIoDesc) { rcCm = CM_Get_Res_Des_Data(hCurLogConf, pIoDesc, cbData, 0L); if (rcCm == CR_SUCCESS) { LogRel(("drvHostParallelGetWinHostIoPortsSub: Count=%#u Type=%#x Base=%#RX64 End=%#RX64 Flags=%#x\n", pIoDesc->IOD_Count, pIoDesc->IOD_Type, (uint64_t)pIoDesc->IOD_Alloc_Base, (uint64_t)pIoDesc->IOD_Alloc_End, pIoDesc->IOD_DesFlags)); PortBase = (RTIOPORT)pIoDesc->IOD_Alloc_Base; } else LogRel(("drvHostParallelGetWinHostIoPortsSub: CM_Get_Res_Des_Data(,,%u,0) failed: %u\n", cbData, rcCm)); RTMemFree(pIoDesc); } else LogRel(("drvHostParallelGetWinHostIoPortsSub: failed to allocate %#x bytes\n", cbData)); /* Next */ RES_DES hFreeResDesc = hCurLogConf; rcCm = CM_Get_Next_Res_Des(&hCurLogConf, hCurLogConf, ResType_IO, 0, 0); CM_Free_Res_Des_Handle(hFreeResDesc); if (rcCm != CR_SUCCESS) { if (rcCm != CR_NO_MORE_RES_DES) LogRel(("drvHostParallelGetWinHostIoPortsSub: CM_Get_Next_Res_Des failed: %u\n", rcCm)); break; } } } else LogRel(("drvHostParallelGetWinHostIoPortsSub: Initial CM_Get_Next_Res_Des failed: %u\n", rcCm)); CM_Free_Log_Conf_Handle(hFirstLogConf); } LogFlowFunc(("return PortBase=%#x", PortBase)); return PortBase; } /** * Get Parallel port address and update the shared data structure. * * @returns VBox status code. * @param pThis The host parallel port instance data. */ static int drvHostParallelGetWinHostIoPorts(PDRVHOSTPARALLEL pThis) { /* * Assume the host device path is on the form "\\.\PIPE\LPT1", then get the "LPT1" part. */ const char * const pszCfgPortName = RTPathFilename(pThis->pszDevicePath); AssertReturn(pszCfgPortName, VERR_INTERNAL_ERROR_3); size_t const cchCfgPortName = strlen(pszCfgPortName); if ( cchCfgPortName != 4 || RTStrNICmp(pszCfgPortName, "LPT", 3) != 0 || !RT_C_IS_DIGIT(pszCfgPortName[3]) ) { LogRel(("drvHostParallelGetWinHostIoPorts: The configured device name '%s' is not on the expected 'LPTx' form!\n", pszCfgPortName)); return VERR_INVALID_NAME; } /* * Get a list of devices then enumerate it looking for the LPT port we're using. */ HDEVINFO hDevInfo = SetupDiGetClassDevs(NULL, 0, 0, DIGCF_PRESENT | DIGCF_ALLCLASSES); if (hDevInfo == INVALID_HANDLE_VALUE) { DWORD dwErr = GetLastError(); LogRel(("drvHostParallelGetWinHostIoPorts: SetupDiGetClassDevs failed: %u\n", dwErr)); return RTErrConvertFromWin32(dwErr); } int rc = VINF_SUCCESS; char *pszBuf = NULL; DWORD cbBuf = 0; for (int32_t idxDevInfo = 0;; idxDevInfo++) { /* * Query the next device info. */ SP_DEVINFO_DATA DeviceInfoData; DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA); if (!SetupDiEnumDeviceInfo(hDevInfo, idxDevInfo, &DeviceInfoData)) { DWORD dwErr = GetLastError(); if (dwErr != ERROR_NO_MORE_ITEMS && dwErr != NO_ERROR) { LogRel(("drvHostParallelGetWinHostIoPorts: SetupDiEnumDeviceInfo failed: %u\n", dwErr)); rc = RTErrConvertFromWin32(dwErr); } break; } /* Get the friendly name of the device. */ DWORD dwDataType; DWORD cbBufActual; BOOL fOk; while (!(fOk = SetupDiGetDeviceRegistryProperty(hDevInfo, &DeviceInfoData, SPDRP_FRIENDLYNAME, &dwDataType, (PBYTE)pszBuf, cbBuf, &cbBufActual))) { DWORD dwErr = GetLastError(); if (dwErr == ERROR_INSUFFICIENT_BUFFER) { LogFlow(("ERROR_INSUFF_BUFF = %d. dwBufSz = %d\n", dwErr, cbBuf)); cbBuf = RT_MAX(RT_ALIGN_Z(cbBufActual + 16, 64), 256); void *pvNew = RTMemRealloc(pszBuf, cbBuf); if (pvNew) pszBuf = (char *)pvNew; else { LogFlow(("GetDevProp Error = %d & cbBufActual = %d\n", dwErr, cbBufActual)); break; } } else { /* No need to bother about this error (in most cases its errno=13, * INVALID_DATA . Just break from here and proceed to next device * enumerated item */ LogFlow(("GetDevProp Error = %d & cbBufActual = %d\n", dwErr, cbBufActual)); break; } } if ( fOk && pszBuf) { pszBuf[cbBuf - 1] = '\0'; /* * Does this look like the port name we're looking for. * * We're expecting either "Parallel Port (LPT1)" or just "LPT1", though we'll make do * with anything that includes the name we're looking for as a separate word. */ char *pszMatch; do pszMatch = RTStrIStr(pszBuf, pszCfgPortName); while ( pszMatch != NULL && !( ( pszMatch == pszBuf || pszMatch[-1] == '(' || RT_C_IS_BLANK(pszMatch[-1])) && ( pszMatch[cchCfgPortName] == '\0' || pszMatch[cchCfgPortName] == ')' || RT_C_IS_BLANK(pszMatch[cchCfgPortName])) ) ); if (pszMatch != NULL) { RTIOPORT Port = drvHostParallelGetWinHostIoPortsSub(DeviceInfoData.DevInst); if (Port != 0) { pThis->PortDirectData = Port; pThis->PortDirectControl = Port + CTRL_REG_OFFSET; pThis->PortDirectStatus = Port + STATUS_REG_OFFSET; break; } LogRel(("drvHostParallelGetWinHostIoPorts: Addr not found for '%s'\n", pszBuf)); } } } /* Cleanup. */ RTMemFree(pszBuf); SetupDiDestroyDeviceInfoList(hDevInfo); return rc; } # endif /* VBOX_WITH_WIN_PARPORT_SUP */ /** * Changes the current mode of the host parallel port. * * @returns VBox status code. * @param pThis The host parallel port instance data. * @param enmMode The mode to change the port to. */ static int drvHostParallelSetMode(PDRVHOSTPARALLEL pThis, PDMPARALLELPORTMODE enmMode) { LogFlowFunc(("mode=%d\n", enmMode)); # ifndef VBOX_WITH_WIN_PARPORT_SUP int rc = VINF_SUCCESS; int iMode = 0; int rcLnx; if (pThis->enmModeCur != enmMode) { switch (enmMode) { case PDM_PARALLEL_PORT_MODE_SPP: iMode = IEEE1284_MODE_COMPAT; break; case PDM_PARALLEL_PORT_MODE_EPP_DATA: iMode = IEEE1284_MODE_EPP | IEEE1284_DATA; break; case PDM_PARALLEL_PORT_MODE_EPP_ADDR: iMode = IEEE1284_MODE_EPP | IEEE1284_ADDR; break; case PDM_PARALLEL_PORT_MODE_ECP: case PDM_PARALLEL_PORT_MODE_INVALID: default: return VERR_NOT_SUPPORTED; } rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPSETMODE, &iMode); if (RT_UNLIKELY(rcLnx < 0)) rc = RTErrConvertFromErrno(errno); else pThis->enmModeCur = enmMode; } return rc; # else /* VBOX_WITH_WIN_PARPORT_SUP */ RT_NOREF(pThis, enmMode); return VINF_SUCCESS; # endif /* VBOX_WITH_WIN_PARPORT_SUP */ } /* -=-=-=-=- IBase -=-=-=-=- */ /** * @interface_method_impl{PDMIBASE,pfnQueryInterface} */ static DECLCALLBACK(void *) drvHostParallelQueryInterface(PPDMIBASE pInterface, const char *pszIID) { PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); PDRVHOSTPARALLEL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPARALLEL); PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); PDMIBASE_RETURN_INTERFACE(pszIID, PDMIHOSTPARALLELCONNECTOR, &pThis->CTX_SUFF(IHostParallelConnector)); return NULL; } /* -=-=-=-=- IHostDeviceConnector -=-=-=-=- */ /** * @interface_method_impl{PDMIHOSTPARALLELCONNECTOR,pfnWrite} */ static DECLCALLBACK(int) drvHostParallelWrite(PPDMIHOSTPARALLELCONNECTOR pInterface, const void *pvBuf, size_t cbWrite, PDMPARALLELPORTMODE enmMode) { PDRVHOSTPARALLEL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPARALLEL, CTX_SUFF(IHostParallelConnector)); int rc = VINF_SUCCESS; LogFlowFunc(("pvBuf=%#p cbWrite=%d\n", pvBuf, cbWrite)); rc = drvHostParallelSetMode(pThis, enmMode); if (RT_FAILURE(rc)) return rc; # ifndef VBOX_WITH_WIN_PARPORT_SUP int rcLnx = 0; if (enmMode == PDM_PARALLEL_PORT_MODE_SPP) { /* Set the data lines directly. */ rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPWDATA, pvBuf); } else { /* Use write interface. */ rcLnx = write(RTFileToNative(pThis->hFileDevice), pvBuf, cbWrite); } if (RT_UNLIKELY(rcLnx < 0)) rc = RTErrConvertFromErrno(errno); # else /* VBOX_WITH_WIN_PARPORT_SUP */ if (pThis->PortDirectData != 0) { while (cbWrite > 0) { size_t cbToWrite = RT_MIN(cbWrite, sizeof(pThis->abDataBuf)); LogFlowFunc(("Calling R0 to write %#zu bytes of data\n", cbToWrite)); memcpy(pThis->abDataBuf, pvBuf, cbToWrite); rc = PDMDrvHlpCallR0(pThis->CTX_SUFF(pDrvIns), DRVHOSTPARALLELR0OP_WRITE, cbToWrite); AssertRC(rc); pvBuf = (uint8_t const *)pvBuf + cbToWrite; cbWrite -= cbToWrite; } } # endif /* VBOX_WITH_WIN_PARPORT_SUP */ return rc; } /** * @interface_method_impl{PDMIHOSTPARALLELCONNECTOR,pfnRead} */ static DECLCALLBACK(int) drvHostParallelRead(PPDMIHOSTPARALLELCONNECTOR pInterface, void *pvBuf, size_t cbRead, PDMPARALLELPORTMODE enmMode) { PDRVHOSTPARALLEL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPARALLEL, CTX_SUFF(IHostParallelConnector)); int rc = VINF_SUCCESS; # ifndef VBOX_WITH_WIN_PARPORT_SUP int rcLnx = 0; LogFlowFunc(("pvBuf=%#p cbRead=%d\n", pvBuf, cbRead)); rc = drvHostParallelSetMode(pThis, enmMode); if (RT_FAILURE(rc)) return rc; if (enmMode == PDM_PARALLEL_PORT_MODE_SPP) { /* Set the data lines directly. */ rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPWDATA, pvBuf); } else { /* Use write interface. */ rcLnx = read(RTFileToNative(pThis->hFileDevice), pvBuf, cbRead); } if (RT_UNLIKELY(rcLnx < 0)) rc = RTErrConvertFromErrno(errno); # else /* VBOX_WITH_WIN_PARPORT_SUP */ RT_NOREF(enmMode); if (pThis->PortDirectData != 0) { while (cbRead > 0) { size_t cbToRead = RT_MIN(cbRead, sizeof(pThis->abDataBuf)); LogFlowFunc(("Calling R0 to read %#zu bytes of data\n", cbToRead)); memset(pThis->abDataBuf, 0, cbToRead); rc = PDMDrvHlpCallR0(pThis->CTX_SUFF(pDrvIns), DRVHOSTPARALLELR0OP_READ, cbToRead); AssertRC(rc); memcpy(pvBuf, pThis->abDataBuf, cbToRead); pvBuf = (uint8_t *)pvBuf + cbToRead; cbRead -= cbToRead; } } # endif /* VBOX_WITH_WIN_PARPORT_SUP */ return rc; } /** * @interface_method_impl{PDMIHOSTPARALLELCONNECTOR,pfnSetPortDirection} */ static DECLCALLBACK(int) drvHostParallelSetPortDirection(PPDMIHOSTPARALLELCONNECTOR pInterface, bool fForward) { PDRVHOSTPARALLEL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPARALLEL, CTX_SUFF(IHostParallelConnector)); int rc = VINF_SUCCESS; int iMode = 0; if (!fForward) iMode = 1; # ifndef VBOX_WITH_WIN_PARPORT_SUP int rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPDATADIR, &iMode); if (RT_UNLIKELY(rcLnx < 0)) rc = RTErrConvertFromErrno(errno); # else /* VBOX_WITH_WIN_PARPORT_SUP */ if (pThis->PortDirectData != 0) { LogFlowFunc(("calling R0 to write CTRL, data=%#x\n", iMode)); rc = PDMDrvHlpCallR0(pThis->CTX_SUFF(pDrvIns), DRVHOSTPARALLELR0OP_SETPORTDIRECTION, iMode); AssertRC(rc); } # endif /* VBOX_WITH_WIN_PARPORT_SUP */ return rc; } /** * @interface_method_impl{PDMIHOSTPARALLELCONNECTOR,pfnWriteControl} */ static DECLCALLBACK(int) drvHostParallelWriteControl(PPDMIHOSTPARALLELCONNECTOR pInterface, uint8_t fReg) { PDRVHOSTPARALLEL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPARALLEL, CTX_SUFF(IHostParallelConnector)); int rc = VINF_SUCCESS; LogFlowFunc(("fReg=%#x\n", fReg)); # ifndef VBOX_WITH_WIN_PARPORT_SUP int rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPWCONTROL, &fReg); if (RT_UNLIKELY(rcLnx < 0)) rc = RTErrConvertFromErrno(errno); # else /* VBOX_WITH_WIN_PARPORT_SUP */ if (pThis->PortDirectData != 0) { LogFlowFunc(("calling R0 to write CTRL, data=%#x\n", fReg)); rc = PDMDrvHlpCallR0(pThis->CTX_SUFF(pDrvIns), DRVHOSTPARALLELR0OP_WRITECONTROL, fReg); AssertRC(rc); } # endif /* VBOX_WITH_WIN_PARPORT_SUP */ return rc; } /** * @interface_method_impl{PDMIHOSTPARALLELCONNECTOR,pfnReadControl} */ static DECLCALLBACK(int) drvHostParallelReadControl(PPDMIHOSTPARALLELCONNECTOR pInterface, uint8_t *pfReg) { PDRVHOSTPARALLEL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPARALLEL, CTX_SUFF(IHostParallelConnector)); int rc = VINF_SUCCESS; # ifndef VBOX_WITH_WIN_PARPORT_SUP uint8_t fReg = 0; int rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPRCONTROL, &fReg); if (RT_UNLIKELY(rcLnx < 0)) rc = RTErrConvertFromErrno(errno); else { LogFlowFunc(("fReg=%#x\n", fReg)); *pfReg = fReg; } # else /* VBOX_WITH_WIN_PARPORT_SUP */ *pfReg = 0; /* Initialize the buffer*/ if (pThis->PortDirectData != 0) { LogFlowFunc(("calling R0 to read control from parallel port\n")); rc = PDMDrvHlpCallR0(pThis-> CTX_SUFF(pDrvIns), DRVHOSTPARALLELR0OP_READCONTROL, 0); AssertRC(rc); *pfReg = pThis->bReadInControl; } # endif /* VBOX_WITH_WIN_PARPORT_SUP */ return rc; } /** * @interface_method_impl{PDMIHOSTPARALLELCONNECTOR,pfnReadStatus} */ static DECLCALLBACK(int) drvHostParallelReadStatus(PPDMIHOSTPARALLELCONNECTOR pInterface, uint8_t *pfReg) { PDRVHOSTPARALLEL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTPARALLEL, CTX_SUFF(IHostParallelConnector)); int rc = VINF_SUCCESS; # ifndef VBOX_WITH_WIN_PARPORT_SUP uint8_t fReg = 0; int rcLnx = ioctl(RTFileToNative(pThis->hFileDevice), PPRSTATUS, &fReg); if (RT_UNLIKELY(rcLnx < 0)) rc = RTErrConvertFromErrno(errno); else { LogFlowFunc(("fReg=%#x\n", fReg)); *pfReg = fReg; } # else /* VBOX_WITH_WIN_PARPORT_SUP */ *pfReg = 0; /* Intialize the buffer. */ if (pThis->PortDirectData != 0) { LogFlowFunc(("calling R0 to read status from parallel port\n")); rc = PDMDrvHlpCallR0(pThis->CTX_SUFF(pDrvIns), DRVHOSTPARALLELR0OP_READSTATUS, 0); AssertRC(rc); *pfReg = pThis->bReadInStatus; } # endif /* VBOX_WITH_WIN_PARPORT_SUP */ return rc; } # ifndef VBOX_WITH_WIN_PARPORT_SUP static DECLCALLBACK(int) drvHostParallelMonitorThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread) { PDRVHOSTPARALLEL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPARALLEL); struct pollfd aFDs[2]; /* * We can wait for interrupts using poll on linux hosts. */ while (pThread->enmState == PDMTHREADSTATE_RUNNING) { int rc; aFDs[0].fd = RTFileToNative(pThis->hFileDevice); aFDs[0].events = POLLIN; aFDs[0].revents = 0; aFDs[1].fd = RTPipeToNative(pThis->hWakeupPipeR); aFDs[1].events = POLLIN | POLLERR | POLLHUP; aFDs[1].revents = 0; rc = poll(aFDs, RT_ELEMENTS(aFDs), -1); if (rc < 0) { AssertMsgFailed(("poll failed with rc=%d\n", RTErrConvertFromErrno(errno))); return RTErrConvertFromErrno(errno); } if (pThread->enmState != PDMTHREADSTATE_RUNNING) break; if (rc > 0 && aFDs[1].revents) { if (aFDs[1].revents & (POLLHUP | POLLERR | POLLNVAL)) break; /* notification to terminate -- drain the pipe */ char ch; size_t cbRead; RTPipeRead(pThis->hWakeupPipeR, &ch, 1, &cbRead); continue; } /* Interrupt occurred. */ rc = pThis->pDrvHostParallelPort->pfnNotifyInterrupt(pThis->pDrvHostParallelPort); AssertRC(rc); } return VINF_SUCCESS; } /** * Unblock the monitor 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) drvHostParallelWakeupMonitorThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread) { RT_NOREF(pThread); PDRVHOSTPARALLEL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPARALLEL); size_t cbIgnored; return RTPipeWrite(pThis->hWakeupPipeW, "", 1, &cbIgnored); } # endif /* VBOX_WITH_WIN_PARPORT_SUP */ /** * Destruct a host parallel 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) drvHostParallelDestruct(PPDMDRVINS pDrvIns) { PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); LogFlowFunc(("iInstance=%d\n", pDrvIns->iInstance)); #ifndef VBOX_WITH_WIN_PARPORT_SUP PDRVHOSTPARALLEL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPARALLEL); if (pThis->hFileDevice != NIL_RTFILE) ioctl(RTFileToNative(pThis->hFileDevice), PPRELEASE); if (pThis->hWakeupPipeW != NIL_RTPIPE) { int rc = RTPipeClose(pThis->hWakeupPipeW); AssertRC(rc); pThis->hWakeupPipeW = NIL_RTPIPE; } if (pThis->hWakeupPipeR != NIL_RTPIPE) { int rc = RTPipeClose(pThis->hWakeupPipeR); AssertRC(rc); pThis->hWakeupPipeR = NIL_RTPIPE; } if (pThis->hFileDevice != NIL_RTFILE) { int rc = RTFileClose(pThis->hFileDevice); AssertRC(rc); pThis->hFileDevice = NIL_RTFILE; } if (pThis->pszDevicePath) { PDMDrvHlpMMHeapFree(pDrvIns, pThis->pszDevicePath); pThis->pszDevicePath = NULL; } #endif /* !VBOX_WITH_WIN_PARPORT_SUP */ } /** * Construct a host parallel driver instance. * * @copydoc FNPDMDRVCONSTRUCT */ static DECLCALLBACK(int) drvHostParallelConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) { RT_NOREF(fFlags); PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); PDRVHOSTPARALLEL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTPARALLEL); PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3; LogFlowFunc(("iInstance=%d\n", pDrvIns->iInstance)); /* * Init basic data members and interfaces. * * Must be done before returning any failure because we've got a destructor. */ pThis->hFileDevice = NIL_RTFILE; #ifndef VBOX_WITH_WIN_PARPORT_SUP pThis->hWakeupPipeR = NIL_RTPIPE; pThis->hWakeupPipeW = NIL_RTPIPE; #endif pThis->pDrvInsR3 = pDrvIns; #ifdef VBOX_WITH_DRVINTNET_IN_R0 pThis->pDrvInsR0 = PDMDRVINS_2_R0PTR(pDrvIns); #endif /* IBase. */ pDrvIns->IBase.pfnQueryInterface = drvHostParallelQueryInterface; /* IHostParallelConnector. */ pThis->IHostParallelConnectorR3.pfnWrite = drvHostParallelWrite; pThis->IHostParallelConnectorR3.pfnRead = drvHostParallelRead; pThis->IHostParallelConnectorR3.pfnSetPortDirection = drvHostParallelSetPortDirection; pThis->IHostParallelConnectorR3.pfnWriteControl = drvHostParallelWriteControl; pThis->IHostParallelConnectorR3.pfnReadControl = drvHostParallelReadControl; pThis->IHostParallelConnectorR3.pfnReadStatus = drvHostParallelReadStatus; /* * Validate the config. */ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "DevicePath", ""); /* * Query configuration. */ /* Device */ int rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "DevicePath", &pThis->pszDevicePath); if (RT_FAILURE(rc)) { AssertMsgFailed(("Configuration error: query for \"DevicePath\" string returned %Rra.\n", rc)); return rc; } /* * Open the device */ /** @todo exclusive access on windows? */ rc = RTFileOpen(&pThis->hFileDevice, pThis->pszDevicePath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); if (RT_FAILURE(rc)) return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("Parallel#%d could not open '%s'"), pDrvIns->iInstance, pThis->pszDevicePath); #ifndef VBOX_WITH_WIN_PARPORT_SUP /* * Try to get exclusive access to parallel port */ rc = ioctl(RTFileToNative(pThis->hFileDevice), PPEXCL); if (rc < 0) return PDMDrvHlpVMSetError(pDrvIns, RTErrConvertFromErrno(errno), RT_SRC_POS, N_("Parallel#%d could not get exclusive access for parallel port '%s'" "Be sure that no other process or driver accesses this port"), pDrvIns->iInstance, pThis->pszDevicePath); /* * Claim the parallel port */ rc = ioctl(RTFileToNative(pThis->hFileDevice), PPCLAIM); if (rc < 0) return PDMDrvHlpVMSetError(pDrvIns, RTErrConvertFromErrno(errno), RT_SRC_POS, N_("Parallel#%d could not claim parallel port '%s'" "Be sure that no other process or driver accesses this port"), pDrvIns->iInstance, pThis->pszDevicePath); /* * Get the IHostParallelPort interface of the above driver/device. */ pThis->pDrvHostParallelPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIHOSTPARALLELPORT); if (!pThis->pDrvHostParallelPort) return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE, RT_SRC_POS, N_("Parallel#%d has no parallel port interface above"), pDrvIns->iInstance); /* * Create wakeup pipe. */ rc = RTPipeCreate(&pThis->hWakeupPipeR, &pThis->hWakeupPipeW, 0 /*fFlags*/); AssertRCReturn(rc, rc); /* * Start in SPP mode. */ pThis->enmModeCur = PDM_PARALLEL_PORT_MODE_INVALID; rc = drvHostParallelSetMode(pThis, PDM_PARALLEL_PORT_MODE_SPP); if (RT_FAILURE(rc)) return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("HostParallel#%d cannot change mode of parallel mode to SPP"), pDrvIns->iInstance); /* * Start waiting for interrupts. */ rc = PDMDrvHlpThreadCreate(pDrvIns, &pThis->pMonitorThread, pThis, drvHostParallelMonitorThread, drvHostParallelWakeupMonitorThread, 0, RTTHREADTYPE_IO, "ParMon"); if (RT_FAILURE(rc)) return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("HostParallel#%d cannot create monitor thread"), pDrvIns->iInstance); #else /* VBOX_WITH_WIN_PARPORT_SUP */ pThis->PortDirectData = 0; pThis->PortDirectControl = 0; pThis->PortDirectStatus = 0; rc = drvHostParallelGetWinHostIoPorts(pThis); if (RT_FAILURE(rc)) return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("HostParallel#%d: Could not get direct access to the host parallel port!! (rc=%Rrc)"), pDrvIns->iInstance, rc); #endif /* VBOX_WITH_WIN_PARPORT_SUP */ return VINF_SUCCESS; } /** * Char driver registration record. */ const PDMDRVREG g_DrvHostParallel = { /* u32Version */ PDM_DRVREG_VERSION, /* szName */ "HostParallel", /* szRCMod */ "", /* szR0Mod */ "VBoxDDR0.r0", /* pszDescription */ "Parallel host driver.", /* fFlags */ # if defined(VBOX_WITH_WIN_PARPORT_SUP) PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT | PDM_DRVREG_FLAGS_R0, # else PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, # endif /* fClass. */ PDM_DRVREG_CLASS_CHAR, /* cMaxInstances */ ~0U, /* cbInstance */ sizeof(DRVHOSTPARALLEL), /* pfnConstruct */ drvHostParallelConstruct, /* pfnDestruct */ drvHostParallelDestruct, /* pfnRelocate */ NULL, /* pfnIOCtl */ NULL, /* pfnPowerOn */ NULL, /* pfnReset */ NULL, /* pfnSuspend */ NULL, /* pfnResume */ NULL, /* pfnAttach */ NULL, /* pfnDetach */ NULL, /* pfnPowerOff */ NULL, /* pfnSoftReset */ NULL, /* u32EndVersion */ PDM_DRVREG_VERSION }; #endif /*IN_RING3*/