VirtualBox

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

Last change on this file since 98103 was 98103, checked in by vboxsync, 22 months ago

Copyright year updates by scm.

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