VirtualBox

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

Last change on this file was 106061, checked in by vboxsync, 3 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.9 KB
Line 
1/* $Id: DrvChar.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
2/** @file
3 * Driver that adapts PDMISTREAM into PDMISERIALCONNECTOR / PDMISERIALPORT.
4 */
5
6/*
7 * Copyright (C) 2006-2024 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 if (RT_FAILURE(rc))
335 {
336 LogRel(("Char#%d: Notifying upper driver about available data failed with %Rrc\n",
337 pDrvIns->iInstance, rc));
338 break;
339 }
340 }
341 }
342 }
343 else if (rc != VERR_INTERRUPTED)
344 LogRelMax(10, ("Char#%d: Polling failed with %Rrc\n", pDrvIns->iInstance, rc));
345 }
346
347 return VINF_SUCCESS;
348}
349
350
351/**
352 * Unblock the send worker thread so it can respond to a state change.
353 *
354 * @returns VBox status code.
355 * @param pDrvIns The char driver instance.
356 * @param pThread The worker thread.
357 */
358static DECLCALLBACK(int) drvCharIoLoopWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
359{
360 PDRVCHAR pThis = (PDRVCHAR)pThread->pvUser;
361
362 RT_NOREF(pDrvIns);
363 return pThis->pDrvStream->pfnPollInterrupt(pThis->pDrvStream);
364}
365
366
367/* -=-=-=-=- driver interface -=-=-=-=- */
368
369/**
370 * @interface_method_impl{PDMDRVREG,pfnReset}
371 */
372static DECLCALLBACK(void) drvCharReset(PPDMDRVINS pDrvIns)
373{
374 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
375 PDRVCHAR pThis = PDMINS_2_DATA(pDrvIns, PDRVCHAR);
376
377 /* Reset TX and RX buffers. */
378 pThis->fAvailWrExt = false;
379 pThis->fAvailWrInt = false;
380 pThis->cbTxUsed = 0;
381 pThis->cbRemaining = 0;
382}
383
384
385/**
386 * Construct a char driver instance.
387 *
388 * @copydoc FNPDMDRVCONSTRUCT
389 */
390static DECLCALLBACK(int) drvCharConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
391{
392 RT_NOREF(pCfg);
393 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
394 PDRVCHAR pThis = PDMINS_2_DATA(pDrvIns, PDRVCHAR);
395 LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
396
397 /*
398 * Init basic data members and interfaces.
399 */
400 pThis->pDrvIns = pDrvIns;
401 pThis->pThrdIo = NIL_RTTHREAD;
402 /* IBase. */
403 pDrvIns->IBase.pfnQueryInterface = drvCharQueryInterface;
404 /* ISerialConnector. */
405 pThis->ISerialConnector.pfnDataAvailWrNotify = drvCharDataAvailWrNotify;
406 pThis->ISerialConnector.pfnReadRdr = drvCharReadRdr;
407 pThis->ISerialConnector.pfnChgParams = drvCharChgParams;
408 pThis->ISerialConnector.pfnChgModemLines = drvCharChgModemLines;
409 pThis->ISerialConnector.pfnChgBrk = drvCharChgBrk;
410 pThis->ISerialConnector.pfnQueryStsLines = drvCharQueryStsLines;
411 pThis->ISerialConnector.pfnQueuesFlush = drvCharQueuesFlush;
412
413 /*
414 * Get the ISerialPort interface of the above driver/device.
415 */
416 pThis->pDrvSerialPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMISERIALPORT);
417 if (!pThis->pDrvSerialPort)
418 return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE, RT_SRC_POS,
419 N_("Char#%d has no serial port interface above"), pDrvIns->iInstance);
420
421 /*
422 * Attach driver below and query its stream interface.
423 */
424 PPDMIBASE pBase;
425 int rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pBase);
426 if (RT_FAILURE(rc))
427 return rc; /* Don't call PDMDrvHlpVMSetError here as we assume that the driver already set an appropriate error */
428 pThis->pDrvStream = PDMIBASE_QUERY_INTERFACE(pBase, PDMISTREAM);
429 if (!pThis->pDrvStream)
430 return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW, RT_SRC_POS,
431 N_("Char#%d has no stream interface below"), pDrvIns->iInstance);
432
433 rc = PDMDrvHlpThreadCreate(pThis->pDrvIns, &pThis->pThrdIo, pThis, drvCharIoLoop,
434 drvCharIoLoopWakeup, 0, RTTHREADTYPE_IO, "CharIo");
435 if (RT_FAILURE(rc))
436 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("Char#%d cannot create I/O thread"), pDrvIns->iInstance);
437
438
439 PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
440 "Nr of bytes written", "/Devices/Char%d/Written", pDrvIns->iInstance);
441 PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
442 "Nr of bytes read", "/Devices/Char%d/Read", pDrvIns->iInstance);
443
444 return VINF_SUCCESS;
445}
446
447
448/**
449 * Char driver registration record.
450 */
451const PDMDRVREG g_DrvChar =
452{
453 /* u32Version */
454 PDM_DRVREG_VERSION,
455 /* szName */
456 "Char",
457 /* szRCMod */
458 "",
459 /* szR0Mod */
460 "",
461 /* pszDescription */
462 "Generic char driver.",
463 /* fFlags */
464 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
465 /* fClass. */
466 PDM_DRVREG_CLASS_CHAR,
467 /* cMaxInstances */
468 ~0U,
469 /* cbInstance */
470 sizeof(DRVCHAR),
471 /* pfnConstruct */
472 drvCharConstruct,
473 /* pfnDestruct */
474 NULL,
475 /* pfnRelocate */
476 NULL,
477 /* pfnIOCtl */
478 NULL,
479 /* pfnPowerOn */
480 NULL,
481 /* pfnReset */
482 drvCharReset,
483 /* pfnSuspend */
484 NULL,
485 /* pfnResume */
486 NULL,
487 /* pfnAttach */
488 NULL,
489 /* pfnDetach */
490 NULL,
491 /* pfnPowerOff */
492 NULL,
493 /* pfnSoftReset */
494 NULL,
495 /* u32EndVersion */
496 PDM_DRVREG_VERSION
497};
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