VirtualBox

source: vbox/trunk/src/VBox/Devices/Serial/DrvHostSerial.cpp@ 99739

Last change on this file since 99739 was 99739, checked in by vboxsync, 13 months ago

*: doxygen corrections (mostly about removing @returns from functions returning void).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.9 KB
Line 
1/* $Id: DrvHostSerial.cpp 99739 2023-05-11 01:01:08Z vboxsync $ */
2/** @file
3 * VBox serial devices: Host serial driver
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/*********************************************************************************************************************************
31* Header Files *
32*********************************************************************************************************************************/
33#define LOG_GROUP LOG_GROUP_DRV_HOST_SERIAL
34#include <VBox/vmm/pdm.h>
35#include <VBox/vmm/pdmserialifs.h>
36#include <VBox/err.h>
37
38#include <VBox/log.h>
39#include <iprt/asm.h>
40#include <iprt/assert.h>
41#include <iprt/file.h>
42#include <iprt/mem.h>
43#include <iprt/pipe.h>
44#include <iprt/semaphore.h>
45#include <iprt/uuid.h>
46#include <iprt/serialport.h>
47
48#include "VBoxDD.h"
49
50
51/*********************************************************************************************************************************
52* Structures and Typedefs *
53*********************************************************************************************************************************/
54
55/**
56 * Char driver instance data.
57 *
58 * @implements PDMISERIALCONNECTOR
59 */
60typedef struct DRVHOSTSERIAL
61{
62 /** Pointer to the driver instance structure. */
63 PPDMDRVINS pDrvIns;
64 /** Pointer to the serial port interface of the driver/device above us. */
65 PPDMISERIALPORT pDrvSerialPort;
66 /** Our serial interface. */
67 PDMISERIALCONNECTOR ISerialConnector;
68 /** I/O thread. */
69 PPDMTHREAD pIoThrd;
70 /** The serial port handle. */
71 RTSERIALPORT hSerialPort;
72 /** the device path */
73 char *pszDevicePath;
74 /** The active config of the serial port. */
75 RTSERIALPORTCFG Cfg;
76
77 /** Flag whether data is available from the device/driver above as notified by the driver. */
78 volatile bool fAvailWrExt;
79 /** Internal copy of the flag which gets reset when there is no data anymore. */
80 bool fAvailWrInt;
81 /** Small send buffer. */
82 uint8_t abTxBuf[16];
83 /** Amount of data in the buffer. */
84 size_t cbTxUsed;
85
86 /** The read queue. */
87 uint8_t abReadBuf[256];
88 /** Current offset to write to next. */
89 volatile uint32_t offWrite;
90 /** Current offset into the read buffer. */
91 volatile uint32_t offRead;
92 /** Current amount of data in the buffer. */
93 volatile size_t cbReadBuf;
94
95 /* Flag whether the host device ran into a fatal error condition and I/O is suspended
96 * until the nuext VM suspend/resume cycle where we will try again. */
97 volatile bool fIoFatalErr;
98 /** Event semaphore the I/O thread is waiting on */
99 RTSEMEVENT hSemEvtIoFatalErr;
100
101 /** Read/write statistics */
102 STAMCOUNTER StatBytesRead;
103 STAMCOUNTER StatBytesWritten;
104} DRVHOSTSERIAL, *PDRVHOSTSERIAL;
105
106
107/*********************************************************************************************************************************
108* Global Variables *
109*********************************************************************************************************************************/
110
111
112/*********************************************************************************************************************************
113* Internal Functions *
114*********************************************************************************************************************************/
115
116
117/**
118 * Resets the read buffer.
119 *
120 * @returns Number of bytes which were queued in the read buffer before reset.
121 * @param pThis The host serial driver instance.
122 */
123DECLINLINE(size_t) drvHostSerialReadBufReset(PDRVHOSTSERIAL pThis)
124{
125 size_t cbOld = ASMAtomicXchgZ(&pThis->cbReadBuf, 0);
126 ASMAtomicWriteU32(&pThis->offWrite, 0);
127 ASMAtomicWriteU32(&pThis->offRead, 0);
128
129 return cbOld;
130}
131
132
133/**
134 * Returns number of bytes free in the read buffer and pointer to the start of the free space
135 * in the read buffer.
136 *
137 * @returns Number of bytes free in the buffer.
138 * @param pThis The host serial driver instance.
139 * @param ppv Where to return the pointer if there is still free space.
140 */
141DECLINLINE(size_t) drvHostSerialReadBufGetWrite(PDRVHOSTSERIAL pThis, void **ppv)
142{
143 if (ppv)
144 *ppv = &pThis->abReadBuf[pThis->offWrite];
145
146 size_t cbFree = sizeof(pThis->abReadBuf) - ASMAtomicReadZ(&pThis->cbReadBuf);
147 if (cbFree)
148 cbFree = RT_MIN(cbFree, sizeof(pThis->abReadBuf) - pThis->offWrite);
149
150 return cbFree;
151}
152
153
154/**
155 * Returns number of bytes used in the read buffer and pointer to the next byte to read.
156 *
157 * @returns Number of bytes free in the buffer.
158 * @param pThis The host serial driver instance.
159 * @param ppv Where to return the pointer to the next data to read.
160 */
161DECLINLINE(size_t) drvHostSerialReadBufGetRead(PDRVHOSTSERIAL pThis, void **ppv)
162{
163 if (ppv)
164 *ppv = &pThis->abReadBuf[pThis->offRead];
165
166 size_t cbUsed = ASMAtomicReadZ(&pThis->cbReadBuf);
167 if (cbUsed)
168 cbUsed = RT_MIN(cbUsed, sizeof(pThis->abReadBuf) - pThis->offRead);
169
170 return cbUsed;
171}
172
173
174/**
175 * Advances the write position of the read buffer by the given amount of bytes.
176 *
177 * @param pThis The host serial driver instance.
178 * @param cbAdv Number of bytes to advance.
179 */
180DECLINLINE(void) drvHostSerialReadBufWriteAdv(PDRVHOSTSERIAL pThis, size_t cbAdv)
181{
182 uint32_t offWrite = ASMAtomicReadU32(&pThis->offWrite);
183 offWrite = (offWrite + cbAdv) % sizeof(pThis->abReadBuf);
184 ASMAtomicWriteU32(&pThis->offWrite, offWrite);
185 ASMAtomicAddZ(&pThis->cbReadBuf, cbAdv);
186}
187
188
189/**
190 * Advances the read position of the read buffer by the given amount of bytes.
191 *
192 * @param pThis The host serial driver instance.
193 * @param cbAdv Number of bytes to advance.
194 */
195DECLINLINE(void) drvHostSerialReadBufReadAdv(PDRVHOSTSERIAL pThis, size_t cbAdv)
196{
197 uint32_t offRead = ASMAtomicReadU32(&pThis->offRead);
198 offRead = (offRead + cbAdv) % sizeof(pThis->abReadBuf);
199 ASMAtomicWriteU32(&pThis->offRead, offRead);
200 ASMAtomicSubZ(&pThis->cbReadBuf, cbAdv);
201}
202
203
204/**
205 * Wakes up the serial port I/O thread.
206 *
207 * @returns VBox status code.
208 * @param pThis The host serial driver instance.
209 */
210static int drvHostSerialWakeupIoThread(PDRVHOSTSERIAL pThis)
211{
212
213 if (RT_UNLIKELY(pThis->fIoFatalErr))
214 return RTSemEventSignal(pThis->hSemEvtIoFatalErr);
215
216 return RTSerialPortEvtPollInterrupt(pThis->hSerialPort);
217}
218
219
220/* -=-=-=-=- IBase -=-=-=-=- */
221
222/**
223 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
224 */
225static DECLCALLBACK(void *) drvHostSerialQueryInterface(PPDMIBASE pInterface, const char *pszIID)
226{
227 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
228 PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
229
230 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
231 PDMIBASE_RETURN_INTERFACE(pszIID, PDMISERIALCONNECTOR, &pThis->ISerialConnector);
232 return NULL;
233}
234
235
236/* -=-=-=-=- ISerialConnector -=-=-=-=- */
237
238/** @interface_method_impl{PDMISERIALCONNECTOR,pfnDataAvailWrNotify} */
239static DECLCALLBACK(int) drvHostSerialDataAvailWrNotify(PPDMISERIALCONNECTOR pInterface)
240{
241 PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
242
243 int rc = VINF_SUCCESS;
244 bool fAvailOld = ASMAtomicXchgBool(&pThis->fAvailWrExt, true);
245 if (!fAvailOld)
246 rc = drvHostSerialWakeupIoThread(pThis);
247
248 return rc;
249}
250
251
252/**
253 * @interface_method_impl{PDMISERIALCONNECTOR,pfnReadRdr}
254 */
255static DECLCALLBACK(int) drvHostSerialReadRdr(PPDMISERIALCONNECTOR pInterface, void *pvBuf,
256 size_t cbRead, size_t *pcbRead)
257{
258 PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
259 int rc = VINF_SUCCESS;
260 uint8_t *pbDst = (uint8_t *)pvBuf;
261 size_t cbReadAll = 0;
262
263 do
264 {
265 void *pvSrc = NULL;
266 size_t cbThisRead = RT_MIN(drvHostSerialReadBufGetRead(pThis, &pvSrc), cbRead);
267 if (cbThisRead)
268 {
269 memcpy(pbDst, pvSrc, cbThisRead);
270 cbRead -= cbThisRead;
271 pbDst += cbThisRead;
272 cbReadAll += cbThisRead;
273 drvHostSerialReadBufReadAdv(pThis, cbThisRead);
274 }
275 else
276 break;
277 } while (cbRead > 0);
278
279 *pcbRead = cbReadAll;
280 /* Kick the I/O thread if there is nothing to read to recalculate the poll flags. */
281 if (!drvHostSerialReadBufGetRead(pThis, NULL))
282 rc = drvHostSerialWakeupIoThread(pThis);
283
284 STAM_COUNTER_ADD(&pThis->StatBytesRead, cbReadAll);
285 return rc;
286}
287
288
289/**
290 * @interface_method_impl{PDMISERIALCONNECTOR,pfnChgParams}
291 */
292static DECLCALLBACK(int) drvHostSerialChgParams(PPDMISERIALCONNECTOR pInterface, uint32_t uBps,
293 PDMSERIALPARITY enmParity, unsigned cDataBits,
294 PDMSERIALSTOPBITS enmStopBits)
295{
296 PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
297
298 pThis->Cfg.uBaudRate = uBps;
299
300 switch (enmParity)
301 {
302 case PDMSERIALPARITY_EVEN:
303 pThis->Cfg.enmParity = RTSERIALPORTPARITY_EVEN;
304 break;
305 case PDMSERIALPARITY_ODD:
306 pThis->Cfg.enmParity = RTSERIALPORTPARITY_ODD;
307 break;
308 case PDMSERIALPARITY_NONE:
309 pThis->Cfg.enmParity = RTSERIALPORTPARITY_NONE;
310 break;
311 case PDMSERIALPARITY_MARK:
312 pThis->Cfg.enmParity = RTSERIALPORTPARITY_MARK;
313 break;
314 case PDMSERIALPARITY_SPACE:
315 pThis->Cfg.enmParity = RTSERIALPORTPARITY_SPACE;
316 break;
317 default:
318 AssertMsgFailed(("Unsupported parity setting %d\n", enmParity)); /* Should not happen. */
319 pThis->Cfg.enmParity = RTSERIALPORTPARITY_NONE;
320 }
321
322 switch (cDataBits)
323 {
324 case 5:
325 pThis->Cfg.enmDataBitCount = RTSERIALPORTDATABITS_5BITS;
326 break;
327 case 6:
328 pThis->Cfg.enmDataBitCount = RTSERIALPORTDATABITS_6BITS;
329 break;
330 case 7:
331 pThis->Cfg.enmDataBitCount = RTSERIALPORTDATABITS_7BITS;
332 break;
333 case 8:
334 pThis->Cfg.enmDataBitCount = RTSERIALPORTDATABITS_8BITS;
335 break;
336 default:
337 AssertMsgFailed(("Unsupported data bit count %u\n", cDataBits)); /* Should not happen. */
338 pThis->Cfg.enmDataBitCount = RTSERIALPORTDATABITS_8BITS;
339 }
340
341 switch (enmStopBits)
342 {
343 case PDMSERIALSTOPBITS_ONE:
344 pThis->Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONE;
345 break;
346 case PDMSERIALSTOPBITS_ONEPOINTFIVE:
347 pThis->Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONEPOINTFIVE;
348 break;
349 case PDMSERIALSTOPBITS_TWO:
350 pThis->Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_TWO;
351 break;
352 default:
353 AssertMsgFailed(("Unsupported stop bit count %d\n", enmStopBits)); /* Should not happen. */
354 pThis->Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONE;
355 }
356
357 return RTSerialPortCfgSet(pThis->hSerialPort, &pThis->Cfg, NULL);
358}
359
360
361/**
362 * @interface_method_impl{PDMISERIALCONNECTOR,pfnChgModemLines}
363 */
364static DECLCALLBACK(int) drvHostSerialChgModemLines(PPDMISERIALCONNECTOR pInterface, bool fRts, bool fDtr)
365{
366 PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
367
368 uint32_t fClear = 0;
369 uint32_t fSet = 0;
370
371 if (fRts)
372 fSet |= RTSERIALPORT_CHG_STS_LINES_F_RTS;
373 else
374 fClear |= RTSERIALPORT_CHG_STS_LINES_F_RTS;
375
376 if (fDtr)
377 fSet |= RTSERIALPORT_CHG_STS_LINES_F_DTR;
378 else
379 fClear |= RTSERIALPORT_CHG_STS_LINES_F_DTR;
380
381 return RTSerialPortChgStatusLines(pThis->hSerialPort, fClear, fSet);
382}
383
384
385/**
386 * @interface_method_impl{PDMISERIALCONNECTOR,pfnChgBrk}
387 */
388static DECLCALLBACK(int) drvHostSerialChgBrk(PPDMISERIALCONNECTOR pInterface, bool fBrk)
389{
390 PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
391
392 return RTSerialPortChgBreakCondition(pThis->hSerialPort, fBrk);
393}
394
395
396/**
397 * @interface_method_impl{PDMISERIALCONNECTOR,pfnQueryStsLines}
398 */
399static DECLCALLBACK(int) drvHostSerialQueryStsLines(PPDMISERIALCONNECTOR pInterface, uint32_t *pfStsLines)
400{
401 PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
402
403 return RTSerialPortQueryStatusLines(pThis->hSerialPort, pfStsLines);
404}
405
406
407/**
408 * @callback_method_impl{PDMISERIALCONNECTOR,pfnQueuesFlush}
409 */
410static DECLCALLBACK(int) drvHostSerialQueuesFlush(PPDMISERIALCONNECTOR pInterface, bool fQueueRecv, bool fQueueXmit)
411{
412 RT_NOREF(fQueueXmit);
413 LogFlowFunc(("pInterface=%#p fQueueRecv=%RTbool fQueueXmit=%RTbool\n", pInterface, fQueueRecv, fQueueXmit));
414 int rc = VINF_SUCCESS;
415 PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ISerialConnector);
416
417 if (fQueueRecv)
418 {
419 size_t cbOld = drvHostSerialReadBufReset(pThis);
420 if (cbOld) /* Kick the I/O thread to fetch new data. */
421 rc = drvHostSerialWakeupIoThread(pThis);
422 }
423
424 LogFlowFunc(("-> %Rrc\n", rc));
425 return VINF_SUCCESS;
426}
427
428
429/* -=-=-=-=- I/O thread -=-=-=-=- */
430
431/**
432 * The normal I/O loop.
433 *
434 * @returns VBox status code.
435 * @param pDrvIns Pointer to the driver instance data.
436 * @param pThis Host serial driver instance data.
437 * @param pThread Thread instance data.
438 */
439static int drvHostSerialIoLoopNormal(PPDMDRVINS pDrvIns, PDRVHOSTSERIAL pThis, PPDMTHREAD pThread)
440{
441 int rc = VINF_SUCCESS;
442 while ( pThread->enmState == PDMTHREADSTATE_RUNNING
443 && RT_SUCCESS(rc))
444 {
445 uint32_t fEvtFlags = RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED | RTSERIALPORT_EVT_F_BREAK_DETECTED;
446
447 if (!pThis->fAvailWrInt)
448 pThis->fAvailWrInt = ASMAtomicXchgBool(&pThis->fAvailWrExt, false);
449
450 /* Wait until there is room again if there is anyting to send. */
451 if ( pThis->fAvailWrInt
452 || pThis->cbTxUsed)
453 fEvtFlags |= RTSERIALPORT_EVT_F_DATA_TX;
454
455 /* Try to receive more if there is still room. */
456 if (drvHostSerialReadBufGetWrite(pThis, NULL) > 0)
457 fEvtFlags |= RTSERIALPORT_EVT_F_DATA_RX;
458
459 uint32_t fEvtsRecv = 0;
460 rc = RTSerialPortEvtPoll(pThis->hSerialPort, fEvtFlags, &fEvtsRecv, RT_INDEFINITE_WAIT);
461 if (RT_SUCCESS(rc))
462 {
463 if (fEvtsRecv & RTSERIALPORT_EVT_F_DATA_TX)
464 {
465 if ( pThis->fAvailWrInt
466 && pThis->cbTxUsed < RT_ELEMENTS(pThis->abTxBuf))
467 {
468 /* Stuff as much data into the TX buffer as we can. */
469 size_t cbToFetch = RT_ELEMENTS(pThis->abTxBuf) - pThis->cbTxUsed;
470 size_t cbFetched = 0;
471 rc = pThis->pDrvSerialPort->pfnReadWr(pThis->pDrvSerialPort, &pThis->abTxBuf[pThis->cbTxUsed], cbToFetch,
472 &cbFetched);
473 AssertRC(rc);
474
475 if (cbFetched > 0)
476 pThis->cbTxUsed += cbFetched;
477 else
478 {
479 /* There is no data available anymore. */
480 pThis->fAvailWrInt = false;
481 }
482 }
483
484 if (pThis->cbTxUsed)
485 {
486 size_t cbProcessed = 0;
487 rc = RTSerialPortWriteNB(pThis->hSerialPort, &pThis->abTxBuf[0], pThis->cbTxUsed, &cbProcessed);
488 if (RT_SUCCESS(rc))
489 {
490 pThis->cbTxUsed -= cbProcessed;
491 if ( pThis->cbTxUsed
492 && cbProcessed)
493 {
494 /* Move the data in the TX buffer to the front to fill the end again. */
495 memmove(&pThis->abTxBuf[0], &pThis->abTxBuf[cbProcessed], pThis->cbTxUsed);
496 }
497 else
498 pThis->pDrvSerialPort->pfnDataSentNotify(pThis->pDrvSerialPort);
499 STAM_COUNTER_ADD(&pThis->StatBytesWritten, cbProcessed);
500 }
501 else
502 {
503 LogRelMax(10, ("HostSerial#%d: Sending data failed even though the serial port is marked as writeable (rc=%Rrc)\n",
504 pThis->pDrvIns->iInstance, rc));
505 break;
506 }
507 }
508 }
509
510 if (fEvtsRecv & RTSERIALPORT_EVT_F_DATA_RX)
511 {
512 void *pvDst = NULL;
513 size_t cbToRead = drvHostSerialReadBufGetWrite(pThis, &pvDst);
514 size_t cbRead = 0;
515 rc = RTSerialPortReadNB(pThis->hSerialPort, pvDst, cbToRead, &cbRead);
516 /*
517 * No data being available while the port is marked as readable can happen
518 * if another thread changed the settings of the port inbetween the poll and
519 * the read call because it can flush all the buffered data (seen on Windows).
520 */
521 if (rc != VINF_TRY_AGAIN)
522 {
523 if (RT_SUCCESS(rc))
524 {
525 drvHostSerialReadBufWriteAdv(pThis, cbRead);
526 /* Notify the device/driver above. */
527 rc = pThis->pDrvSerialPort->pfnDataAvailRdrNotify(pThis->pDrvSerialPort, cbRead);
528 AssertRC(rc);
529 }
530 else
531 LogRelMax(10, ("HostSerial#%d: Reading data failed even though the serial port is marked as readable (rc=%Rrc)\n",
532 pThis->pDrvIns->iInstance, rc));
533 }
534 }
535
536 if (fEvtsRecv & RTSERIALPORT_EVT_F_BREAK_DETECTED)
537 pThis->pDrvSerialPort->pfnNotifyBrk(pThis->pDrvSerialPort);
538
539 if (fEvtsRecv & RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED)
540 {
541 /* The status lines have changed. Notify the device. */
542 uint32_t fStsLines = 0;
543 rc = RTSerialPortQueryStatusLines(pThis->hSerialPort, &fStsLines);
544 if (RT_SUCCESS(rc))
545 {
546 uint32_t fPdmStsLines = 0;
547
548 if (fStsLines & RTSERIALPORT_STS_LINE_DCD)
549 fPdmStsLines |= PDMISERIALPORT_STS_LINE_DCD;
550 if (fStsLines & RTSERIALPORT_STS_LINE_RI)
551 fPdmStsLines |= PDMISERIALPORT_STS_LINE_RI;
552 if (fStsLines & RTSERIALPORT_STS_LINE_DSR)
553 fPdmStsLines |= PDMISERIALPORT_STS_LINE_DSR;
554 if (fStsLines & RTSERIALPORT_STS_LINE_CTS)
555 fPdmStsLines |= PDMISERIALPORT_STS_LINE_CTS;
556
557 rc = pThis->pDrvSerialPort->pfnNotifyStsLinesChanged(pThis->pDrvSerialPort, fPdmStsLines);
558 if (RT_FAILURE(rc))
559 {
560 /* Notifying device failed, continue but log it */
561 LogRelMax(10, ("HostSerial#%d: Notifying device about changed status lines failed with error %Rrc; continuing.\n",
562 pDrvIns->iInstance, rc));
563 rc = VINF_SUCCESS;
564 }
565 }
566 else
567 {
568 LogRelMax(10, ("HostSerial#%d: Getting status lines state failed with error %Rrc; continuing.\n", pDrvIns->iInstance, rc));
569 rc = VINF_SUCCESS;
570 }
571 }
572
573 if (fEvtsRecv & RTSERIALPORT_EVT_F_STATUS_LINE_MONITOR_FAILED)
574 {
575 LogRel(("HostSerial#%d: Status line monitoring failed at a lower level with rc=%Rrc and is disabled\n", pDrvIns->iInstance, rc));
576 rc = VINF_SUCCESS;
577 }
578 }
579 else if (rc == VERR_TIMEOUT || rc == VERR_INTERRUPTED)
580 {
581 /* Getting interrupted or running into a timeout are no error conditions. */
582 rc = VINF_SUCCESS;
583 }
584 }
585
586 LogRel(("HostSerial#%d: The underlying host device run into a fatal error condition %Rrc, any data transfer is disabled\n",
587 pDrvIns->iInstance, rc));
588
589 return rc;
590}
591
592
593/**
594 * The error I/O loop.
595 *
596 * @param pThis Host serial driver instance data.
597 * @param pThread Thread instance data.
598 */
599static void drvHostSerialIoLoopError(PDRVHOSTSERIAL pThis, PPDMTHREAD pThread)
600{
601 ASMAtomicXchgBool(&pThis->fIoFatalErr, true);
602
603 PDMDrvHlpVMSetRuntimeError(pThis->pDrvIns, 0 /*fFlags*/, "SerialPortIoError",
604 N_("The host serial port \"%s\" encountered a fatal error and stopped functioning. "
605 "This can be caused by bad cabling or USB to serial converters being unplugged by accident. "
606 "To restart I/O transfers suspend and resume the VM after fixing the underlying issue."),
607 pThis->pszDevicePath);
608
609 while (pThread->enmState == PDMTHREADSTATE_RUNNING)
610 {
611 /*
612 * We have to discard any data which is going to be send (the error
613 * mode resembles the "someone just pulled the plug on the serial port" situation)
614 */
615 RTSemEventWait(pThis->hSemEvtIoFatalErr, RT_INDEFINITE_WAIT);
616
617 if (ASMAtomicXchgBool(&pThis->fAvailWrExt, false))
618 {
619 size_t cbFetched = 0;
620
621 do
622 {
623 /* Stuff as much data into the TX buffer as we can. */
624 uint8_t abDiscard[64];
625 int rc = pThis->pDrvSerialPort->pfnReadWr(pThis->pDrvSerialPort, &abDiscard, sizeof(abDiscard),
626 &cbFetched);
627 AssertRC(rc);
628 } while (cbFetched > 0);
629
630 /* Acknowledge the sent data. */
631 pThis->pDrvSerialPort->pfnDataSentNotify(pThis->pDrvSerialPort);
632
633 /*
634 * Sleep a bit to avoid excessive I/O loop CPU usage, timing is not important in
635 * this mode.
636 */
637 PDMDrvHlpThreadSleep(pThis->pDrvIns, pThread, 100);
638 }
639 }
640}
641
642
643/**
644 * I/O thread loop.
645 *
646 * @returns VINF_SUCCESS.
647 * @param pDrvIns PDM driver instance data.
648 * @param pThread The PDM thread data.
649 */
650static DECLCALLBACK(int) drvHostSerialIoThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
651{
652 PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
653
654 if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
655 return VINF_SUCCESS;
656
657 int rc = VINF_SUCCESS;
658 if (!pThis->fIoFatalErr)
659 rc = drvHostSerialIoLoopNormal(pDrvIns, pThis, pThread);
660
661 if ( RT_FAILURE(rc)
662 || pThis->fIoFatalErr)
663 drvHostSerialIoLoopError(pThis, pThread);
664
665 return VINF_SUCCESS;
666}
667
668
669/**
670 * Unblock the send thread so it can respond to a state change.
671 *
672 * @returns a VBox status code.
673 * @param pDrvIns The driver instance.
674 * @param pThread The send thread.
675 */
676static DECLCALLBACK(int) drvHostSerialWakeupIoThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
677{
678 RT_NOREF(pThread);
679 PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
680
681 return drvHostSerialWakeupIoThread(pThis);
682}
683
684
685/* -=-=-=-=- driver interface -=-=-=-=- */
686
687/**
688 * @callback_method_impl{FNPDMDRVRESUME}
689 */
690static DECLCALLBACK(void) drvHostSerialResume(PPDMDRVINS pDrvIns)
691{
692 PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
693
694 if (RT_UNLIKELY(pThis->fIoFatalErr))
695 {
696 /* Try to reopen the device and set the old config. */
697 uint32_t fOpenFlags = RTSERIALPORT_OPEN_F_READ
698 | RTSERIALPORT_OPEN_F_WRITE
699 | RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING
700 | RTSERIALPORT_OPEN_F_DETECT_BREAK_CONDITION;
701 int rc = RTSerialPortOpen(&pThis->hSerialPort, pThis->pszDevicePath, fOpenFlags);
702 if (rc == VERR_NOT_SUPPORTED)
703 {
704 /*
705 * For certain devices (or pseudo terminals) status line monitoring does not work
706 * so try again without it.
707 */
708 fOpenFlags &= ~RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING;
709 rc = RTSerialPortOpen(&pThis->hSerialPort, pThis->pszDevicePath, fOpenFlags);
710 }
711
712 if (RT_SUCCESS(rc))
713 {
714 /* Set the config which is currently active. */
715 rc = RTSerialPortCfgSet(pThis->hSerialPort, &pThis->Cfg, NULL);
716 if (RT_FAILURE(rc))
717 LogRelMax(10, ("HostSerial#%d: Setting the active serial port config failed with error %Rrc during VM resume; continuing.\n", pDrvIns->iInstance, rc));
718 /* Reset the I/O error flag on success to resume the normal I/O thread loop. */
719 ASMAtomicXchgBool(&pThis->fIoFatalErr, false);
720 }
721 }
722}
723
724
725/**
726 * @callback_method_impl{FNPDMDRVSUSPEND}
727 */
728static DECLCALLBACK(void) drvHostSerialSuspend(PPDMDRVINS pDrvIns)
729{
730 PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
731
732 if (RT_UNLIKELY(pThis->fIoFatalErr))
733 {
734 /* Close the device and try reopening it on resume. */
735 if (pThis->hSerialPort != NIL_RTSERIALPORT)
736 {
737 RTSerialPortClose(pThis->hSerialPort);
738 pThis->hSerialPort = NIL_RTSERIALPORT;
739 }
740 }
741}
742
743
744/**
745 * Destruct a char driver instance.
746 *
747 * Most VM resources are freed by the VM. This callback is provided so that
748 * any non-VM resources can be freed correctly.
749 *
750 * @param pDrvIns The driver instance data.
751 */
752static DECLCALLBACK(void) drvHostSerialDestruct(PPDMDRVINS pDrvIns)
753{
754 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
755 PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
756 LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
757
758 if (pThis->hSerialPort != NIL_RTSERIALPORT)
759 {
760 RTSerialPortClose(pThis->hSerialPort);
761 pThis->hSerialPort = NIL_RTSERIALPORT;
762 }
763
764 if (pThis->hSemEvtIoFatalErr != NIL_RTSEMEVENT)
765 {
766 RTSemEventDestroy(pThis->hSemEvtIoFatalErr);
767 pThis->hSemEvtIoFatalErr = NIL_RTSEMEVENT;
768 }
769
770 if (pThis->pszDevicePath)
771 {
772 PDMDrvHlpMMHeapFree(pDrvIns, pThis->pszDevicePath);
773 pThis->pszDevicePath = NULL;
774 }
775}
776
777
778/**
779 * Construct a char driver instance.
780 *
781 * @copydoc FNPDMDRVCONSTRUCT
782 */
783static DECLCALLBACK(int) drvHostSerialConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
784{
785 RT_NOREF1(fFlags);
786 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
787 PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
788 PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
789
790 LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
791
792 /*
793 * Init basic data members and interfaces.
794 */
795 pThis->pDrvIns = pDrvIns;
796 pThis->hSerialPort = NIL_RTSERIALPORT;
797 pThis->fAvailWrExt = false;
798 pThis->fAvailWrInt = false;
799 pThis->cbTxUsed = 0;
800 pThis->offWrite = 0;
801 pThis->offRead = 0;
802 pThis->cbReadBuf = 0;
803 pThis->fIoFatalErr = false;
804 pThis->hSemEvtIoFatalErr = NIL_RTSEMEVENT;
805 /* IBase. */
806 pDrvIns->IBase.pfnQueryInterface = drvHostSerialQueryInterface;
807 /* ISerialConnector. */
808 pThis->ISerialConnector.pfnDataAvailWrNotify = drvHostSerialDataAvailWrNotify;
809 pThis->ISerialConnector.pfnReadRdr = drvHostSerialReadRdr;
810 pThis->ISerialConnector.pfnChgParams = drvHostSerialChgParams;
811 pThis->ISerialConnector.pfnChgModemLines = drvHostSerialChgModemLines;
812 pThis->ISerialConnector.pfnChgBrk = drvHostSerialChgBrk;
813 pThis->ISerialConnector.pfnQueryStsLines = drvHostSerialQueryStsLines;
814 pThis->ISerialConnector.pfnQueuesFlush = drvHostSerialQueuesFlush;
815
816 /*
817 * Validate the config.
818 */
819 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "DevicePath", "");
820
821 /*
822 * Query configuration.
823 */
824 /* Device */
825 int rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "DevicePath", &pThis->pszDevicePath);
826 if (RT_FAILURE(rc))
827 {
828 AssertMsgFailed(("Configuration error: query for \"DevicePath\" string returned %Rra.\n", rc));
829 return rc;
830 }
831
832 /*
833 * Open the device
834 */
835 uint32_t fOpenFlags = RTSERIALPORT_OPEN_F_READ
836 | RTSERIALPORT_OPEN_F_WRITE
837 | RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING
838 | RTSERIALPORT_OPEN_F_DETECT_BREAK_CONDITION;
839 rc = RTSerialPortOpen(&pThis->hSerialPort, pThis->pszDevicePath, fOpenFlags);
840 if (rc == VERR_NOT_SUPPORTED)
841 {
842 /*
843 * For certain devices (or pseudo terminals) status line monitoring does not work
844 * so try again without it.
845 */
846 fOpenFlags &= ~RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING;
847 rc = RTSerialPortOpen(&pThis->hSerialPort, pThis->pszDevicePath, fOpenFlags);
848 }
849
850 if (RT_FAILURE(rc))
851 {
852 AssertMsgFailed(("Could not open host device %s, rc=%Rrc\n", pThis->pszDevicePath, rc));
853 switch (rc)
854 {
855 case VERR_ACCESS_DENIED:
856 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
857#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
858 N_("Cannot open host device '%s' for read/write access. Check the permissions "
859 "of that device ('/bin/ls -l %s'): Most probably you need to be member "
860 "of the device group. Make sure that you logout/login after changing "
861 "the group settings of the current user"),
862#else
863 N_("Cannot open host device '%s' for read/write access. Check the permissions "
864 "of that device"),
865#endif
866 pThis->pszDevicePath, pThis->pszDevicePath);
867 default:
868 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
869 N_("Failed to open host device '%s'"),
870 pThis->pszDevicePath);
871 }
872 }
873
874 rc = RTSemEventCreate(&pThis->hSemEvtIoFatalErr);
875 if (RT_FAILURE(rc))
876 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("HostSerial#%d failed to create event semaphore"), pDrvIns->iInstance);
877
878 /*
879 * Get the ISerialPort interface of the above driver/device.
880 */
881 pThis->pDrvSerialPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMISERIALPORT);
882 if (!pThis->pDrvSerialPort)
883 return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE, RT_SRC_POS, N_("HostSerial#%d has no serial port interface above"), pDrvIns->iInstance);
884
885 /*
886 * Create the I/O thread.
887 */
888 rc = PDMDrvHlpThreadCreate(pDrvIns, &pThis->pIoThrd, pThis, drvHostSerialIoThread, drvHostSerialWakeupIoThread, 0, RTTHREADTYPE_IO, "SerIo");
889 if (RT_FAILURE(rc))
890 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("HostSerial#%d cannot create I/O thread"), pDrvIns->iInstance);
891
892 /*
893 * Register release statistics.
894 */
895 PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
896 "Nr of bytes written", "/Devices/HostSerial%d/Written", pDrvIns->iInstance);
897 PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES,
898 "Nr of bytes read", "/Devices/HostSerial%d/Read", pDrvIns->iInstance);
899
900 return VINF_SUCCESS;
901}
902
903/**
904 * Char driver registration record.
905 */
906const PDMDRVREG g_DrvHostSerial =
907{
908 /* u32Version */
909 PDM_DRVREG_VERSION,
910 /* szName */
911 "Host Serial",
912 /* szRCMod */
913 "",
914 /* szR0Mod */
915 "",
916 /* pszDescription */
917 "Host serial driver.",
918 /* fFlags */
919 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
920 /* fClass. */
921 PDM_DRVREG_CLASS_CHAR,
922 /* cMaxInstances */
923 ~0U,
924 /* cbInstance */
925 sizeof(DRVHOSTSERIAL),
926 /* pfnConstruct */
927 drvHostSerialConstruct,
928 /* pfnDestruct */
929 drvHostSerialDestruct,
930 /* pfnRelocate */
931 NULL,
932 /* pfnIOCtl */
933 NULL,
934 /* pfnPowerOn */
935 NULL,
936 /* pfnReset */
937 NULL,
938 /* pfnSuspend */
939 drvHostSerialSuspend,
940 /* pfnResume */
941 drvHostSerialResume,
942 /* pfnAttach */
943 NULL,
944 /* pfnDetach */
945 NULL,
946 /* pfnPowerOff */
947 NULL,
948 /* pfnSoftReset */
949 NULL,
950 /* u32EndVersion */
951 PDM_DRVREG_VERSION
952};
953
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use