VirtualBox

source: vbox/trunk/src/VBox/Devices/Serial/DrvHostSerialNew.cpp@ 72056

Last change on this file since 72056 was 70800, checked in by vboxsync, 7 years ago

Devices/Serial: Rewrite the host serial driver to use the IPRT serial port API, done in a separate file for now until it is mature enough

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.3 KB
Line 
1/* $Id: DrvHostSerialNew.cpp 70800 2018-01-29 20:55:29Z vboxsync $ */
2/** @file
3 * VBox stream I/O devices: Host serial driver
4 */
5
6/*
7 * Copyright (C) 2006-2017 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/*********************************************************************************************************************************
21* Header Files *
22*********************************************************************************************************************************/
23#define LOG_GROUP LOG_GROUP_DRV_HOST_SERIAL
24#include <VBox/vmm/pdm.h>
25#include <VBox/err.h>
26
27#include <VBox/log.h>
28#include <iprt/asm.h>
29#include <iprt/assert.h>
30#include <iprt/file.h>
31#include <iprt/mem.h>
32#include <iprt/pipe.h>
33#include <iprt/semaphore.h>
34#include <iprt/uuid.h>
35#include <iprt/serialport.h>
36
37#include "VBoxDD.h"
38
39
40/*********************************************************************************************************************************
41* Structures and Typedefs *
42*********************************************************************************************************************************/
43
44/**
45 * Char driver instance data.
46 *
47 * @implements PDMICHARCONNECTOR
48 */
49typedef struct DRVHOSTSERIAL
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 PPDMICHARPORT pDrvCharPort;
55 /** Our char interface. */
56 PDMICHARCONNECTOR ICharConnector;
57 /** I/O thread. */
58 PPDMTHREAD pIoThrd;
59 /** The serial port handle. */
60 RTSERIALPORT hSerialPort;
61 /** the device path */
62 char *pszDevicePath;
63
64
65 /** Internal send FIFO queue */
66 uint8_t volatile u8SendByte;
67 bool volatile fSending;
68 uint8_t Alignment[2];
69
70 /** The read queue. */
71 uint8_t abReadBuf[256];
72 /** Read buffer currently used. */
73 size_t cbReadBufUsed;
74 /** Current offset into the read buffer. */
75 uint32_t offReadBuf;
76
77 /** Read/write statistics */
78 STAMCOUNTER StatBytesRead;
79 STAMCOUNTER StatBytesWritten;
80} DRVHOSTSERIAL, *PDRVHOSTSERIAL;
81
82
83
84/* -=-=-=-=- IBase -=-=-=-=- */
85
86/**
87 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
88 */
89static DECLCALLBACK(void *) drvHostSerialQueryInterface(PPDMIBASE pInterface, const char *pszIID)
90{
91 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
92 PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
93
94 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
95 PDMIBASE_RETURN_INTERFACE(pszIID, PDMICHARCONNECTOR, &pThis->ICharConnector);
96 return NULL;
97}
98
99
100/* -=-=-=-=- ICharConnector -=-=-=-=- */
101
102/** @interface_method_impl{PDMICHARCONNECTOR,pfnWrite} */
103static DECLCALLBACK(int) drvHostSerialWrite(PPDMICHARCONNECTOR pInterface, const void *pvBuf, size_t cbWrite)
104{
105 PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ICharConnector);
106 const uint8_t *pbBuffer = (const uint8_t *)pvBuf;
107
108 LogFlow(("%s: pvBuf=%#p cbWrite=%d\n", __FUNCTION__, pvBuf, cbWrite));
109
110 for (uint32_t i = 0; i < cbWrite; i++)
111 {
112 if (ASMAtomicXchgBool(&pThis->fSending, true))
113 return VERR_BUFFER_OVERFLOW;
114
115 pThis->u8SendByte = pbBuffer[i];
116 RTSerialPortEvtPollInterrupt(pThis->hSerialPort);
117 STAM_COUNTER_INC(&pThis->StatBytesWritten);
118 }
119 return VINF_SUCCESS;
120}
121
122
123static DECLCALLBACK(int) drvHostSerialSetParameters(PPDMICHARCONNECTOR pInterface, unsigned Bps, char chParity, unsigned cDataBits, unsigned cStopBits)
124{
125 PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ICharConnector);
126 RTSERIALPORTCFG Cfg;
127
128 Cfg.uBaudRate = Bps;
129
130 switch (chParity)
131 {
132 case 'E':
133 Cfg.enmParity = RTSERIALPORTPARITY_EVEN;
134 break;
135 case 'O':
136 Cfg.enmParity = RTSERIALPORTPARITY_ODD;
137 break;
138 case 'N':
139 Cfg.enmParity = RTSERIALPORTPARITY_NONE;
140 break;
141 default:
142 AssertMsgFailed(("Unsupported parity setting %c\n", chParity)); /* Should not happen. */
143 Cfg.enmParity = RTSERIALPORTPARITY_NONE;
144 }
145
146 switch (cDataBits)
147 {
148 case 5:
149 Cfg.enmDataBitCount = RTSERIALPORTDATABITS_5BITS;
150 break;
151 case 6:
152 Cfg.enmDataBitCount = RTSERIALPORTDATABITS_6BITS;
153 break;
154 case 7:
155 Cfg.enmDataBitCount = RTSERIALPORTDATABITS_7BITS;
156 break;
157 case 8:
158 Cfg.enmDataBitCount = RTSERIALPORTDATABITS_8BITS;
159 break;
160 default:
161 AssertMsgFailed(("Unsupported data bit count %u\n", cDataBits)); /* Should not happen. */
162 Cfg.enmDataBitCount = RTSERIALPORTDATABITS_8BITS;
163 }
164
165 if (cStopBits == 2)
166 Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_TWO;
167 else
168 Cfg.enmStopBitCount = RTSERIALPORTSTOPBITS_ONE;
169
170 int rc = RTSerialPortCfgSet(pThis->hSerialPort, &Cfg, NULL);
171 if (RT_FAILURE(rc))
172 LogRelMax(10, ("HostSerial#%u: Failed to change settings to %u:%u%c%u (rc=%Rrc)\n",
173 pThis->pDrvIns->iInstance, Bps, cDataBits, chParity, cStopBits, rc));
174 return rc;
175}
176
177/* -=-=-=-=- receive thread -=-=-=-=- */
178
179/**
180 * I/O thread loop.
181 *
182 * @returns VINF_SUCCESS.
183 * @param pDrvIns PDM driver instance data.
184 * @param pThread The PDM thread data.
185 */
186static DECLCALLBACK(int) drvHostSerialIoThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
187{
188 PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
189
190 if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
191 return VINF_SUCCESS;
192
193 while (pThread->enmState == PDMTHREADSTATE_RUNNING)
194 {
195 if (pThis->offReadBuf < pThis->cbReadBufUsed)
196 {
197 /* Try to send data to the guest. */
198 size_t cbProcessed = pThis->cbReadBufUsed - pThis->offReadBuf;
199 int rc = pThis->pDrvCharPort->pfnNotifyRead(pThis->pDrvCharPort, &pThis->abReadBuf[pThis->offReadBuf], &cbProcessed);
200 if (RT_SUCCESS(rc))
201 {
202 Assert(cbProcessed); Assert(cbProcessed <= pThis->cbReadBufUsed);
203 pThis->offReadBuf += cbProcessed;
204 STAM_COUNTER_ADD(&pThis->StatBytesRead, cbProcessed);
205 }
206 else if (rc != VERR_TIMEOUT)
207 LogRelMax(10, ("HostSerial#%d: NotifyRead failed with %Rrc, expect errorneous device behavior.\n",
208 pDrvIns->iInstance, rc));
209 }
210
211 uint32_t fEvtFlags = RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED | RTSERIALPORT_EVT_F_BREAK_DETECTED;
212
213 /* Wait until there is room again if there is anyting to send. */
214 if (pThis->fSending)
215 fEvtFlags |= RTSERIALPORT_EVT_F_DATA_TX;
216
217 /* Try to receive more if there is still room. */
218 if (pThis->cbReadBufUsed < sizeof(pThis->abReadBuf))
219 fEvtFlags |= RTSERIALPORT_EVT_F_DATA_RX;
220
221 uint32_t fEvtsRecv = 0;
222 int rc = RTSerialPortEvtPoll(pThis->hSerialPort, fEvtFlags, &fEvtsRecv,
223 pThis->offReadBuf < pThis->cbReadBufUsed ? 100 : RT_INDEFINITE_WAIT);
224 if (RT_SUCCESS(rc))
225 {
226 if (fEvtsRecv & RTSERIALPORT_EVT_F_DATA_TX)
227 {
228 Assert(pThis->fSending);
229 size_t cbWritten = 0;
230 uint8_t bSend = pThis->u8SendByte;
231 rc = RTSerialPortWriteNB(pThis->hSerialPort, &bSend, 1, &cbWritten);
232 if (RT_SUCCESS(rc))
233 {
234 Assert(cbWritten == 1);
235 ASMAtomicXchgBool(&pThis->fSending, false);
236 }
237 else
238 LogRelMax(10, ("HostSerial#%d: Sending data failed even though the serial port is marked as writeable (rc=%Rrc)\n",
239 pThis->pDrvIns->iInstance, rc));
240 }
241
242 if (fEvtsRecv & RTSERIALPORT_EVT_F_DATA_RX)
243 {
244 /* Move all remaining data in the buffer to the front to make up as much room as possible. */
245 if (pThis->offReadBuf)
246 {
247 memmove(&pThis->abReadBuf[0], &pThis->abReadBuf[pThis->offReadBuf], pThis->cbReadBufUsed - pThis->offReadBuf);
248 pThis->cbReadBufUsed -= pThis->offReadBuf;
249 pThis->offReadBuf = 0;
250 }
251 size_t cbToRead = sizeof(pThis->abReadBuf) - pThis->cbReadBufUsed;
252 size_t cbRead = 0;
253 rc = RTSerialPortReadNB(pThis->hSerialPort, &pThis->abReadBuf[pThis->cbReadBufUsed], cbToRead, &cbRead);
254 if (RT_SUCCESS(rc))
255 pThis->cbReadBufUsed += cbRead;
256 else
257 LogRelMax(10, ("HostSerial#%d: Reading data failed even though the serial port is marked as readable (rc=%Rrc)\n",
258 pThis->pDrvIns->iInstance, rc));
259 }
260
261 if (fEvtsRecv & RTSERIALPORT_EVT_F_BREAK_DETECTED)
262 pThis->pDrvCharPort->pfnNotifyBreak(pThis->pDrvCharPort);
263
264 if (fEvtsRecv & RTSERIALPORT_EVT_F_STATUS_LINE_CHANGED)
265 {
266 /* The status lines have changed. Notify the device. */
267 uint32_t fStsLines = 0;
268 rc = RTSerialPortQueryStatusLines(pThis->hSerialPort, &fStsLines);
269 if (RT_SUCCESS(rc))
270 {
271 uint32_t fPdmStsLines = 0;
272
273 if (fStsLines & RTSERIALPORT_STS_LINE_DCD)
274 fPdmStsLines |= PDMICHARPORT_STATUS_LINES_DCD;
275 if (fStsLines & RTSERIALPORT_STS_LINE_RI)
276 fPdmStsLines |= PDMICHARPORT_STATUS_LINES_RI;
277 if (fStsLines & RTSERIALPORT_STS_LINE_DSR)
278 fPdmStsLines |= PDMICHARPORT_STATUS_LINES_DSR;
279 if (fStsLines & RTSERIALPORT_STS_LINE_CTS)
280 fPdmStsLines |= PDMICHARPORT_STATUS_LINES_CTS;
281
282 rc = pThis->pDrvCharPort->pfnNotifyStatusLinesChanged(pThis->pDrvCharPort, fPdmStsLines);
283 if (RT_FAILURE(rc))
284 {
285 /* Notifying device failed, continue but log it */
286 LogRelMax(10, ("HostSerial#%d: Notifying device about changed status lines failed with error %Rrc; continuing.\n",
287 pDrvIns->iInstance, rc));
288 }
289 }
290 else
291 LogRelMax(10, ("HostSerial#%d: Getting status lines state failed with error %Rrc; continuing.\n", pDrvIns->iInstance, rc));
292 }
293
294 if (fEvtsRecv & RTSERIALPORT_EVT_F_STATUS_LINE_MONITOR_FAILED)
295 LogRel(("HostSerial#%d: Status line monitoring failed at a lower level and is disabled\n", pDrvIns->iInstance));
296 }
297 else if (rc == VERR_TIMEOUT || rc == VERR_INTERRUPTED)
298 {
299 /* Getting interrupted or running into a timeout are no error conditions. */
300 rc = VINF_SUCCESS;
301 }
302 }
303
304 return VINF_SUCCESS;
305}
306
307
308/**
309 * Unblock the send thread so it can respond to a state change.
310 *
311 * @returns a VBox status code.
312 * @param pDrvIns The driver instance.
313 * @param pThread The send thread.
314 */
315static DECLCALLBACK(int) drvHostSerialWakeupIoThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
316{
317 RT_NOREF(pThread);
318 PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
319
320 return RTSerialPortEvtPollInterrupt(pThis->hSerialPort);;
321}
322
323
324/**
325 * Set the modem lines.
326 *
327 * @returns VBox status code
328 * @param pInterface Pointer to the interface structure.
329 * @param fRts Set to true if this control line should be made active.
330 * @param fDtr Set to true if this control line should be made active.
331 */
332static DECLCALLBACK(int) drvHostSerialSetModemLines(PPDMICHARCONNECTOR pInterface, bool fRts, bool fDtr)
333{
334 PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ICharConnector);
335
336 uint32_t fClear = 0;
337 uint32_t fSet = 0;
338
339 if (fRts)
340 fSet |= RTSERIALPORT_CHG_STS_LINES_F_RTS;
341 else
342 fClear |= RTSERIALPORT_CHG_STS_LINES_F_RTS;
343
344 if (fDtr)
345 fSet |= RTSERIALPORT_CHG_STS_LINES_F_DTR;
346 else
347 fClear |= RTSERIALPORT_CHG_STS_LINES_F_DTR;
348
349 return RTSerialPortChgStatusLines(pThis->hSerialPort, fClear, fSet);
350}
351
352
353/**
354 * Sets the TD line into break condition.
355 *
356 * @returns VBox status code.
357 * @param pInterface Pointer to the interface structure containing the called function pointer.
358 * @param fBreak Set to true to let the device send a break false to put into normal operation.
359 * @thread Any thread.
360 */
361static DECLCALLBACK(int) drvHostSerialSetBreak(PPDMICHARCONNECTOR pInterface, bool fBreak)
362{
363 PDRVHOSTSERIAL pThis = RT_FROM_MEMBER(pInterface, DRVHOSTSERIAL, ICharConnector);
364
365 return RTSerialPortChgBreakCondition(pThis->hSerialPort, fBreak);
366}
367
368
369/* -=-=-=-=- driver interface -=-=-=-=- */
370
371/**
372 * Destruct a char driver instance.
373 *
374 * Most VM resources are freed by the VM. This callback is provided so that
375 * any non-VM resources can be freed correctly.
376 *
377 * @param pDrvIns The driver instance data.
378 */
379static DECLCALLBACK(void) drvHostSerialDestruct(PPDMDRVINS pDrvIns)
380{
381 PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
382 LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
383 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
384
385 if (pThis->hSerialPort != NIL_RTSERIALPORT)
386 {
387 RTSerialPortClose(pThis->hSerialPort);
388 pThis->hSerialPort = NIL_RTSERIALPORT;
389 }
390
391 if (pThis->pszDevicePath)
392 {
393 MMR3HeapFree(pThis->pszDevicePath);
394 pThis->pszDevicePath = NULL;
395 }
396}
397
398
399/**
400 * Construct a char driver instance.
401 *
402 * @copydoc FNPDMDRVCONSTRUCT
403 */
404static DECLCALLBACK(int) drvHostSerialConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
405{
406 RT_NOREF1(fFlags);
407 PDRVHOSTSERIAL pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTSERIAL);
408 LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
409 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
410
411 /*
412 * Init basic data members and interfaces.
413 */
414 pThis->hSerialPort = NIL_RTSERIALPORT;
415 pThis->offReadBuf = 0;
416 pThis->cbReadBufUsed = 0;
417 /* IBase. */
418 pDrvIns->IBase.pfnQueryInterface = drvHostSerialQueryInterface;
419 /* ICharConnector. */
420 pThis->ICharConnector.pfnWrite = drvHostSerialWrite;
421 pThis->ICharConnector.pfnSetParameters = drvHostSerialSetParameters;
422 pThis->ICharConnector.pfnSetModemLines = drvHostSerialSetModemLines;
423 pThis->ICharConnector.pfnSetBreak = drvHostSerialSetBreak;
424
425 /*
426 * Query configuration.
427 */
428 /* Device */
429 int rc = CFGMR3QueryStringAlloc(pCfg, "DevicePath", &pThis->pszDevicePath);
430 if (RT_FAILURE(rc))
431 {
432 AssertMsgFailed(("Configuration error: query for \"DevicePath\" string returned %Rra.\n", rc));
433 return rc;
434 }
435
436 /*
437 * Open the device
438 */
439 uint32_t fOpenFlags = RTSERIALPORT_OPEN_F_READ
440 | RTSERIALPORT_OPEN_F_WRITE
441 | RTSERIALPORT_OPEN_F_SUPPORT_STATUS_LINE_MONITORING
442 | RTSERIALPORT_OPEN_F_DETECT_BREAK_CONDITION;
443 rc = RTSerialPortOpen(&pThis->hSerialPort, pThis->pszDevicePath, fOpenFlags);
444 if (RT_FAILURE(rc))
445 {
446 AssertMsgFailed(("Could not open host device %s, rc=%Rrc\n", pThis->pszDevicePath, rc));
447 switch (rc)
448 {
449 case VERR_ACCESS_DENIED:
450 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
451#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) || defined(RT_OS_SOLARIS) || defined(RT_OS_FREEBSD)
452 N_("Cannot open host device '%s' for read/write access. Check the permissions "
453 "of that device ('/bin/ls -l %s'): Most probably you need to be member "
454 "of the device group. Make sure that you logout/login after changing "
455 "the group settings of the current user"),
456#else
457 N_("Cannot open host device '%s' for read/write access. Check the permissions "
458 "of that device"),
459#endif
460 pThis->pszDevicePath, pThis->pszDevicePath);
461 default:
462 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
463 N_("Failed to open host device '%s'"),
464 pThis->pszDevicePath);
465 }
466 }
467
468 /*
469 * Get the ICharPort interface of the above driver/device.
470 */
471 pThis->pDrvCharPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMICHARPORT);
472 if (!pThis->pDrvCharPort)
473 return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE, RT_SRC_POS, N_("HostSerial#%d has no char port interface above"), pDrvIns->iInstance);
474
475 /*
476 * Create the I/O thread.
477 */
478 rc = PDMDrvHlpThreadCreate(pDrvIns, &pThis->pIoThrd, pThis, drvHostSerialIoThread, drvHostSerialWakeupIoThread, 0, RTTHREADTYPE_IO, "SerIo");
479 if (RT_FAILURE(rc))
480 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("HostSerial#%d cannot create I/O thread"), pDrvIns->iInstance);
481
482 /*
483 * Register release statistics.
484 */
485 PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES, "Nr of bytes written", "/Devices/HostSerial%d/Written", pDrvIns->iInstance);
486 PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES, "Nr of bytes read", "/Devices/HostSerial%d/Read", pDrvIns->iInstance);
487
488 return VINF_SUCCESS;
489}
490
491/**
492 * Char driver registration record.
493 */
494const PDMDRVREG g_DrvHostSerial =
495{
496 /* u32Version */
497 PDM_DRVREG_VERSION,
498 /* szName */
499 "Host Serial",
500 /* szRCMod */
501 "",
502 /* szR0Mod */
503 "",
504 /* pszDescription */
505 "Host serial driver.",
506 /* fFlags */
507 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
508 /* fClass. */
509 PDM_DRVREG_CLASS_CHAR,
510 /* cMaxInstances */
511 ~0U,
512 /* cbInstance */
513 sizeof(DRVHOSTSERIAL),
514 /* pfnConstruct */
515 drvHostSerialConstruct,
516 /* pfnDestruct */
517 drvHostSerialDestruct,
518 /* pfnRelocate */
519 NULL,
520 /* pfnIOCtl */
521 NULL,
522 /* pfnPowerOn */
523 NULL,
524 /* pfnReset */
525 NULL,
526 /* pfnSuspend */
527 NULL,
528 /* pfnResume */
529 NULL,
530 /* pfnAttach */
531 NULL,
532 /* pfnDetach */
533 NULL,
534 /* pfnPowerOff */
535 NULL,
536 /* pfnSoftReset */
537 NULL,
538 /* u32EndVersion */
539 PDM_DRVREG_VERSION
540};
541
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