VirtualBox

source: vbox/trunk/src/VBox/Devices/Serial/DrvChar.cpp@ 82798

Last change on this file since 82798 was 82798, checked in by vboxsync, 5 years ago

Devices/Serial: Some fixes for unconnected mode where the application inside the guest accessing the serial port could hang

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 16.3 KB
Line 
1/* $Id: DrvChar.cpp 82798 2020-01-20 15:35:50Z vboxsync $ */
2/** @file
3 * Driver that adapts PDMISTREAM into PDMISERIALCONNECTOR / PDMISERIALPORT.
4 */
5
6/*
7 * Copyright (C) 2006-2019 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_DRV_CHAR
23#include <VBox/vmm/pdmdrv.h>
24#include <VBox/vmm/pdmserialifs.h>
25#include <iprt/asm.h>
26#include <iprt/assert.h>
27#include <iprt/poll.h>
28#include <iprt/stream.h>
29#include <iprt/critsect.h>
30#include <iprt/semaphore.h>
31#include <iprt/uuid.h>
32
33#include "VBoxDD.h"
34
35
36/*********************************************************************************************************************************
37* Defined Constants And Macros *
38*********************************************************************************************************************************/
39
40
41/*********************************************************************************************************************************
42* Structures and Typedefs *
43*********************************************************************************************************************************/
44/**
45 * Char driver instance data.
46 *
47 * @implements PDMISERIALCONNECTOR
48 */
49typedef struct DRVCHAR
50{
51 /** Pointer to the driver instance structure. */
52 PPDMDRVINS pDrvIns;
53 /** Pointer to the char port interface of the driver/device above us. */
54 PPDMISERIALPORT pDrvSerialPort;
55 /** Pointer to the stream interface of the driver below us. */
56 PPDMISTREAM pDrvStream;
57 /** Our serial interface. */
58 PDMISERIALCONNECTOR ISerialConnector;
59 /** Flag to notify the receive thread it should terminate. */
60 volatile bool fShutdown;
61 /** Flag whether data is available from the device/driver above as notified by the driver. */
62 volatile bool fAvailWrExt;
63 /** Internal copy of the flag which gets reset when there is no data anymore. */
64 bool fAvailWrInt;
65 /** I/O thread. */
66 PPDMTHREAD pThrdIo;
67
68 /** Small send buffer. */
69 uint8_t abTxBuf[16];
70 /** Amount of data in the buffer. */
71 size_t cbTxUsed;
72
73 /** Receive buffer. */
74 uint8_t abBuffer[256];
75 /** Number of bytes remaining in the receive buffer. */
76 volatile size_t cbRemaining;
77 /** Current position into the read buffer. */
78 uint8_t *pbBuf;
79
80#if HC_ARCH_BITS == 32
81 uint32_t uAlignment0;
82#endif
83
84 /** Read/write statistics */
85 STAMCOUNTER StatBytesRead;
86 STAMCOUNTER StatBytesWritten;
87} DRVCHAR, *PDRVCHAR;
88AssertCompileMemberAlignment(DRVCHAR, StatBytesRead, 8);
89
90
91
92
93/* -=-=-=-=- IBase -=-=-=-=- */
94
95/**
96 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
97 */
98static DECLCALLBACK(void *) drvCharQueryInterface(PPDMIBASE pInterface, const char *pszIID)
99{
100 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
101 PDRVCHAR pThis = PDMINS_2_DATA(pDrvIns, PDRVCHAR);
102
103 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
104 PDMIBASE_RETURN_INTERFACE(pszIID, PDMISERIALCONNECTOR, &pThis->ISerialConnector);
105 return NULL;
106}
107
108
109/* -=-=-=-=- ISerialConnector -=-=-=-=- */
110
111
112/**
113 * @interface_method_impl{PDMISERIALCONNECTOR,pfnDataAvailWrNotify}
114 */
115static DECLCALLBACK(int) drvCharDataAvailWrNotify(PPDMISERIALCONNECTOR pInterface)
116{
117 LogFlowFunc(("pInterface=%#p\n", pInterface));
118 PDRVCHAR pThis = RT_FROM_MEMBER(pInterface, DRVCHAR, ISerialConnector);
119
120 int rc = VINF_SUCCESS;
121 bool fAvailOld = ASMAtomicXchgBool(&pThis->fAvailWrExt, true);
122 if (!fAvailOld)
123 rc = pThis->pDrvStream->pfnPollInterrupt(pThis->pDrvStream);
124
125 return rc;
126}
127
128
129/**
130 * @interface_method_impl{PDMISERIALCONNECTOR,pfnReadRdr}
131 */
132static DECLCALLBACK(int) drvCharReadRdr(PPDMISERIALCONNECTOR pInterface, void *pvBuf, size_t cbRead, size_t *pcbRead)
133{
134 LogFlowFunc(("pInterface=%#p pvBuf=%#p cbRead=%zu pcbRead=%#p\n", pInterface, pvBuf, cbRead, pcbRead));
135 PDRVCHAR pThis = RT_FROM_MEMBER(pInterface, DRVCHAR, ISerialConnector);
136 int rc = VINF_SUCCESS;
137
138 AssertReturn(pThis->cbRemaining, VERR_INVALID_STATE);
139 size_t cbToRead = RT_MIN(cbRead, pThis->cbRemaining);
140 memcpy(pvBuf, pThis->pbBuf, cbToRead);
141
142 pThis->pbBuf += cbToRead;
143 *pcbRead = cbToRead;
144 size_t cbOld = ASMAtomicSubZ(&pThis->cbRemaining, cbToRead);
145 if (!(cbOld - cbToRead)) /* Kick the I/O thread to fetch new data. */
146 rc = pThis->pDrvStream->pfnPollInterrupt(pThis->pDrvStream);
147 STAM_COUNTER_ADD(&pThis->StatBytesRead, cbToRead);
148
149 LogFlowFunc(("-> %Rrc\n", rc));
150 return rc;
151}
152
153
154/**
155 * @interface_method_impl{PDMISERIALCONNECTOR,pfnChgParams}
156 */
157static DECLCALLBACK(int) drvCharChgParams(PPDMISERIALCONNECTOR pInterface, uint32_t uBps,
158 PDMSERIALPARITY enmParity, unsigned cDataBits,
159 PDMSERIALSTOPBITS enmStopBits)
160{
161 /* Nothing to do here. */
162 RT_NOREF(pInterface, uBps, enmParity, cDataBits, enmStopBits);
163 return VINF_SUCCESS;
164}
165
166
167/**
168 * @callback_method_impl{PDMISERIALCONNECTOR,pfnChgModemLines}
169 */
170static DECLCALLBACK(int) drvCharChgModemLines(PPDMISERIALCONNECTOR pInterface, bool fRts, bool fDtr)
171{
172 /* Nothing to do here. */
173 RT_NOREF(pInterface, fRts, fDtr);
174 return VINF_SUCCESS;
175}
176
177
178/**
179 * @callback_method_impl{PDMISERIALCONNECTOR,pfnChgBrk}
180 */
181static DECLCALLBACK(int) drvCharChgBrk(PPDMISERIALCONNECTOR pInterface, bool fBrk)
182{
183 /* Nothing to do here. */
184 RT_NOREF(pInterface, fBrk);
185 return VINF_SUCCESS;
186}
187
188
189/**
190 * @callback_method_impl{PDMISERIALCONNECTOR,pfnQueryStsLines}
191 */
192static DECLCALLBACK(int) drvCharQueryStsLines(PPDMISERIALCONNECTOR pInterface, uint32_t *pfStsLines)
193{
194 /* Always carrier detect, data set read and clear to send. */
195 *pfStsLines = PDMISERIALPORT_STS_LINE_DCD | PDMISERIALPORT_STS_LINE_DSR | PDMISERIALPORT_STS_LINE_CTS;
196 RT_NOREF(pInterface);
197 return VINF_SUCCESS;
198}
199
200
201/**
202 * @callback_method_impl{PDMISERIALCONNECTOR,pfnQueuesFlush}
203 */
204static DECLCALLBACK(int) drvCharQueuesFlush(PPDMISERIALCONNECTOR pInterface, bool fQueueRecv, bool fQueueXmit)
205{
206 RT_NOREF(fQueueXmit);
207 LogFlowFunc(("pInterface=%#p fQueueRecv=%RTbool fQueueXmit=%RTbool\n", pInterface, fQueueRecv, fQueueXmit));
208 int rc = VINF_SUCCESS;
209 PDRVCHAR pThis = RT_FROM_MEMBER(pInterface, DRVCHAR, ISerialConnector);
210
211 if (fQueueRecv)
212 {
213 size_t cbOld = 0;
214 cbOld = ASMAtomicXchgZ(&pThis->cbRemaining, 0);
215 if (cbOld) /* Kick the I/O thread to fetch new data. */
216 rc = pThis->pDrvStream->pfnPollInterrupt(pThis->pDrvStream);
217 }
218
219 LogFlowFunc(("-> %Rrc\n", rc));
220 return VINF_SUCCESS; /** @todo r=bird: return rc? */
221}
222
223
224/* -=-=-=-=- I/O thread -=-=-=-=- */
225
226/**
227 * Send thread loop - pushes data down thru the driver chain.
228 *
229 * @returns VBox status code.
230 * @param pDrvIns The char driver instance.
231 * @param pThread The worker thread.
232 */
233static DECLCALLBACK(int) drvCharIoLoop(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
234{
235 RT_NOREF(pDrvIns);
236 PDRVCHAR pThis = (PDRVCHAR)pThread->pvUser;
237
238 if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
239 return VINF_SUCCESS;
240
241 while (pThread->enmState == PDMTHREADSTATE_RUNNING)
242 {
243 uint32_t fEvts = 0;
244
245 if (!pThis->fAvailWrInt)
246 pThis->fAvailWrInt = ASMAtomicXchgBool(&pThis->fAvailWrExt, false);
247
248 if ( !pThis->cbRemaining
249 && pThis->pDrvStream->pfnRead)
250 fEvts |= RTPOLL_EVT_READ;
251 if ( pThis->fAvailWrInt
252 || pThis->cbTxUsed)
253 fEvts |= RTPOLL_EVT_WRITE;
254
255 uint32_t fEvtsRecv = 0;
256 int rc = pThis->pDrvStream->pfnPoll(pThis->pDrvStream, fEvts, &fEvtsRecv, RT_INDEFINITE_WAIT);
257 if (RT_SUCCESS(rc))
258 {
259 if (fEvtsRecv & RTPOLL_EVT_WRITE)
260 {
261 if ( pThis->fAvailWrInt
262 && pThis->cbTxUsed < RT_ELEMENTS(pThis->abTxBuf))
263 {
264 /* Stuff as much data into the TX buffer as we can. */
265 size_t cbToFetch = RT_ELEMENTS(pThis->abTxBuf) - pThis->cbTxUsed;
266 size_t cbFetched = 0;
267 rc = pThis->pDrvSerialPort->pfnReadWr(pThis->pDrvSerialPort, &pThis->abTxBuf[pThis->cbTxUsed], cbToFetch,
268 &cbFetched);
269 AssertRC(rc);
270
271 if (cbFetched > 0)
272 pThis->cbTxUsed += cbFetched;
273 else
274 {
275 /* There is no data available anymore. */
276 pThis->fAvailWrInt = false;
277 }
278 }
279
280 if (pThis->cbTxUsed)
281 {
282 size_t cbProcessed = pThis->cbTxUsed;
283 rc = pThis->pDrvStream->pfnWrite(pThis->pDrvStream, &pThis->abTxBuf[0], &cbProcessed);
284 if (RT_SUCCESS(rc))
285 {
286 pThis->cbTxUsed -= cbProcessed;
287 if ( pThis->cbTxUsed
288 && cbProcessed)
289 {
290 /* Move the data in the TX buffer to the front to fill the end again. */
291 memmove(&pThis->abTxBuf[0], &pThis->abTxBuf[cbProcessed], pThis->cbTxUsed);
292 }
293 else
294 pThis->pDrvSerialPort->pfnDataSentNotify(pThis->pDrvSerialPort);
295 STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbProcessed);
296 }
297 else if (rc != VERR_TIMEOUT)
298 {
299 LogRel(("Char#%d: Write failed with %Rrc; skipping\n", pDrvIns->iInstance, rc));
300 break;
301 }
302 }
303 }
304
305 if (fEvtsRecv & RTPOLL_EVT_READ)
306 {
307 AssertPtr(pThis->pDrvStream->pfnRead);
308 Assert(!pThis->cbRemaining);
309
310 size_t cbRead = sizeof(pThis->abBuffer);
311 rc = pThis->pDrvStream->pfnRead(pThis->pDrvStream, &pThis->abBuffer[0], &cbRead);
312 if (RT_FAILURE(rc))
313 {
314 LogFlow(("Read failed with %Rrc\n", rc));
315 break;
316 }
317
318 if (cbRead)
319 {
320 pThis->pbBuf = &pThis->abBuffer[0];
321 ASMAtomicWriteZ(&pThis->cbRemaining, cbRead);
322 /* Notify the upper device/driver. */
323 rc = pThis->pDrvSerialPort->pfnDataAvailRdrNotify(pThis->pDrvSerialPort, cbRead);
324 }
325 }
326 }
327 else if (rc != VERR_INTERRUPTED)
328 LogRelMax(10, ("Char#%d: Polling failed with %Rrc\n", pDrvIns->iInstance, rc));
329 }
330
331 return VINF_SUCCESS;
332}
333
334
335/**
336 * Unblock the send worker thread so it can respond to a state change.
337 *
338 * @returns VBox status code.
339 * @param pDrvIns The char driver instance.
340 * @param pThread The worker thread.
341 */
342static DECLCALLBACK(int) drvCharIoLoopWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
343{
344 PDRVCHAR pThis = (PDRVCHAR)pThread->pvUser;
345
346 RT_NOREF(pDrvIns);
347 return pThis->pDrvStream->pfnPollInterrupt(pThis->pDrvStream);
348}
349
350
351/* -=-=-=-=- driver interface -=-=-=-=- */
352
353/**
354 * @interface_method_impl{PDMDRVREG,pfnReset}
355 */
356static DECLCALLBACK(void) drvCharReset(PPDMDRVINS pDrvIns)
357{
358 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
359 PDRVCHAR pThis = PDMINS_2_DATA(pDrvIns, PDRVCHAR);
360
361 /* Reset TX and RX buffers. */
362 pThis->fAvailWrExt = false;
363 pThis->fAvailWrInt = false;
364 pThis->cbTxUsed = 0;
365 pThis->cbRemaining = 0;
366}
367
368
369/**
370 * Construct a char driver instance.
371 *
372 * @copydoc FNPDMDRVCONSTRUCT
373 */
374static DECLCALLBACK(int) drvCharConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
375{
376 RT_NOREF(pCfg);
377 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
378 PDRVCHAR pThis = PDMINS_2_DATA(pDrvIns, PDRVCHAR);
379 LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
380
381 /*
382 * Init basic data members and interfaces.
383 */
384 pThis->pDrvIns = pDrvIns;
385 pThis->pThrdIo = NIL_RTTHREAD;
386 /* IBase. */
387 pDrvIns->IBase.pfnQueryInterface = drvCharQueryInterface;
388 /* ISerialConnector. */
389 pThis->ISerialConnector.pfnDataAvailWrNotify = drvCharDataAvailWrNotify;
390 pThis->ISerialConnector.pfnReadRdr = drvCharReadRdr;
391 pThis->ISerialConnector.pfnChgParams = drvCharChgParams;
392 pThis->ISerialConnector.pfnChgModemLines = drvCharChgModemLines;
393 pThis->ISerialConnector.pfnChgBrk = drvCharChgBrk;
394 pThis->ISerialConnector.pfnQueryStsLines = drvCharQueryStsLines;
395 pThis->ISerialConnector.pfnQueuesFlush = drvCharQueuesFlush;
396
397 /*
398 * Get the ISerialPort interface of the above driver/device.
399 */
400 pThis->pDrvSerialPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMISERIALPORT);
401 if (!pThis->pDrvSerialPort)
402 return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE, RT_SRC_POS,
403 N_("Char#%d has no serial port interface above"), pDrvIns->iInstance);
404
405 /*
406 * Attach driver below and query its stream interface.
407 */
408 PPDMIBASE pBase;
409 int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pBase);
410 if (RT_FAILURE(rc))
411 return rc; /* Don't call PDMDrvHlpVMSetError here as we assume that the driver already set an appropriate error */
412 pThis->pDrvStream = PDMIBASE_QUERY_INTERFACE(pBase, PDMISTREAM);
413 if (!pThis->pDrvStream)
414 return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW, RT_SRC_POS,
415 N_("Char#%d has no stream interface below"), pDrvIns->iInstance);
416
417 rc = PDMDrvHlpThreadCreate(pThis->pDrvIns, &pThis->pThrdIo, pThis, drvCharIoLoop,
418 drvCharIoLoopWakeup, 0, RTTHREADTYPE_IO, "CharIo");
419 if (RT_FAILURE(rc))
420 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("Char#%d cannot create I/O thread"), pDrvIns->iInstance);
421
422
423 PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
424 "Nr of bytes written", "/Devices/Char%d/Written", pDrvIns->iInstance);
425 PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
426 "Nr of bytes read", "/Devices/Char%d/Read", pDrvIns->iInstance);
427
428 return VINF_SUCCESS;
429}
430
431
432/**
433 * Char driver registration record.
434 */
435const PDMDRVREG g_DrvChar =
436{
437 /* u32Version */
438 PDM_DRVREG_VERSION,
439 /* szName */
440 "Char",
441 /* szRCMod */
442 "",
443 /* szR0Mod */
444 "",
445 /* pszDescription */
446 "Generic char driver.",
447 /* fFlags */
448 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
449 /* fClass. */
450 PDM_DRVREG_CLASS_CHAR,
451 /* cMaxInstances */
452 ~0U,
453 /* cbInstance */
454 sizeof(DRVCHAR),
455 /* pfnConstruct */
456 drvCharConstruct,
457 /* pfnDestruct */
458 NULL,
459 /* pfnRelocate */
460 NULL,
461 /* pfnIOCtl */
462 NULL,
463 /* pfnPowerOn */
464 NULL,
465 /* pfnReset */
466 drvCharReset,
467 /* pfnSuspend */
468 NULL,
469 /* pfnResume */
470 NULL,
471 /* pfnAttach */
472 NULL,
473 /* pfnDetach */
474 NULL,
475 /* pfnPowerOff */
476 NULL,
477 /* pfnSoftReset */
478 NULL,
479 /* u32EndVersion */
480 PDM_DRVREG_VERSION
481};
Note: See TracBrowser for help on using the repository browser.

© 2024 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette