VirtualBox

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

Last change on this file since 7102 was 6271, checked in by vboxsync, 17 years ago

serial: fixed error handling; be a little bit more verbose on errors

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 34.9 KB
Line 
1/** $Id: DrvHostSerial.cpp 6271 2008-01-08 08:45:43Z vboxsync $ */
2/** @file
3 * VBox stream I/O devices: Host serial driver
4 *
5 * Contributed by: Alexander Eichner
6 */
7
8/*
9 * Copyright (C) 2006-2007 innotek GmbH
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 */
19
20
21
22/*******************************************************************************
23* Header Files *
24*******************************************************************************/
25#define LOG_GROUP LOG_GROUP_DRV_HOST_SERIAL
26#include <VBox/pdm.h>
27#include <VBox/err.h>
28
29#include <VBox/log.h>
30#include <iprt/asm.h>
31#include <iprt/assert.h>
32#include <iprt/stream.h>
33#include <iprt/semaphore.h>
34#include <iprt/file.h>
35#include <iprt/alloc.h>
36
37#ifdef RT_OS_LINUX
38# include <errno.h>
39# include <termios.h>
40# include <sys/types.h>
41# include <fcntl.h>
42# include <string.h>
43# include <unistd.h>
44# include <sys/poll.h>
45# include <sys/ioctl.h>
46
47/*
48 * TIOCM_LOOP is not defined in the above header files for some reason but in asm/termios.h.
49 * But inclusion of this file however leads to compilation errors because of redefinition of some
50 * structs. Thatswhy it is defined here until a better solution is found.
51 */
52#ifndef TIOCM_LOOP
53# define TIOCM_LOOP 0x8000
54#endif
55
56#elif defined(RT_OS_WINDOWS)
57# include <windows.h>
58#endif
59
60#include "Builtins.h"
61
62
63/** Size of the send fifo queue (in bytes) */
64#define CHAR_MAX_SEND_QUEUE 0x80
65#define CHAR_MAX_SEND_QUEUE_MASK 0x7f
66
67/*******************************************************************************
68* Structures and Typedefs *
69*******************************************************************************/
70
71/**
72 * Char driver instance data.
73 */
74typedef struct DRVHOSTSERIAL
75{
76 /** Pointer to the driver instance structure. */
77 PPDMDRVINS pDrvIns;
78 /** Pointer to the char port interface of the driver/device above us. */
79 PPDMICHARPORT pDrvCharPort;
80 /** Our char interface. */
81 PDMICHAR IChar;
82 /** Receive thread. */
83 PPDMTHREAD pRecvThread;
84 /** Send thread. */
85 PPDMTHREAD pSendThread;
86 /** Status lines monitor thread. */
87 PPDMTHREAD pMonitorThread;
88 /** Send event semephore */
89 RTSEMEVENT SendSem;
90
91 /** the device path */
92 char *pszDevicePath;
93
94#ifdef RT_OS_LINUX
95 /** the device handle */
96 RTFILE DeviceFile;
97 /** The read end of the control pipe */
98 RTFILE WakeupPipeR;
99 /** The write end of the control pipe */
100 RTFILE WakeupPipeW;
101#elif defined(RT_OS_WINDOWS)
102 /** the device handle */
103 HANDLE hDeviceFile;
104 /** The event semaphore for waking up the receive thread */
105 HANDLE hHaltEventSem;
106 /** The event semaphore for overlapped receiving */
107 HANDLE hEventRecv;
108 /** For overlapped receiving */
109 OVERLAPPED overlappedRecv;
110 /** The event semaphore for overlapped sending */
111 HANDLE hEventSend;
112 /** For overlapped sending */
113 OVERLAPPED overlappedSend;
114#endif
115
116 /** Internal send FIFO queue */
117 uint8_t aSendQueue[CHAR_MAX_SEND_QUEUE];
118 uint32_t iSendQueueHead;
119 uint32_t iSendQueueTail;
120
121 /** Read/write statistics */
122 STAMCOUNTER StatBytesRead;
123 STAMCOUNTER StatBytesWritten;
124} DRVHOSTSERIAL, *PDRVHOSTSERIAL;
125
126
127/** Converts a pointer to DRVCHAR::IChar to a PDRVHOSTSERIAL. */
128#define PDMICHAR_2_DRVHOSTSERIAL(pInterface) ( (PDRVHOSTSERIAL)((uintptr_t)pInterface - RT_OFFSETOF(DRVHOSTSERIAL, IChar)) )
129
130
131/* -=-=-=-=- IBase -=-=-=-=- */
132
133/**
134 * Queries an interface to the driver.
135 *
136 * @returns Pointer to interface.
137 * @returns NULL if the interface was not supported by the driver.
138 * @param pInterface Pointer to this interface structure.
139 * @param enmInterface The requested interface identification.
140 */
141static DECLCALLBACK(void *) drvHostSerialQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
142{
143 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
144 PDRVHOSTSERIAL pData = PDMINS2DATA(pDrvIns, PDRVHOSTSERIAL);
145 switch (enmInterface)
146 {
147 case PDMINTERFACE_BASE:
148 return &pDrvIns->IBase;
149 case PDMINTERFACE_CHAR:
150 return &pData->IChar;
151 default:
152 return NULL;
153 }
154}
155
156
157/* -=-=-=-=- IChar -=-=-=-=- */
158
159/** @copydoc PDMICHAR::pfnWrite */
160static DECLCALLBACK(int) drvHostSerialWrite(PPDMICHAR pInterface, const void *pvBuf, size_t cbWrite)
161{
162 PDRVHOSTSERIAL pData = PDMICHAR_2_DRVHOSTSERIAL(pInterface);
163 const uint8_t *pbBuffer = (const uint8_t *)pvBuf;
164
165 LogFlow(("%s: pvBuf=%#p cbWrite=%d\n", __FUNCTION__, pvBuf, cbWrite));
166
167 for (uint32_t i=0;i<cbWrite;i++)
168 {
169 uint32_t idx = pData->iSendQueueHead;
170
171 pData->aSendQueue[idx] = pbBuffer[i];
172 idx = (idx + 1) & CHAR_MAX_SEND_QUEUE_MASK;
173
174 STAM_COUNTER_INC(&pData->StatBytesWritten);
175 ASMAtomicXchgU32(&pData->iSendQueueHead, idx);
176 }
177 RTSemEventSignal(pData->SendSem);
178 return VINF_SUCCESS;
179}
180
181static DECLCALLBACK(int) drvHostSerialSetParameters(PPDMICHAR pInterface, unsigned Bps, char chParity, unsigned cDataBits, unsigned cStopBits)
182{
183 PDRVHOSTSERIAL pData = PDMICHAR_2_DRVHOSTSERIAL(pInterface);
184#ifdef RT_OS_LINUX
185 struct termios *termiosSetup;
186 int baud_rate;
187#elif defined(RT_OS_WINDOWS)
188 LPDCB comSetup;
189#endif
190
191 LogFlow(("%s: Bps=%u chParity=%c cDataBits=%u cStopBits=%u\n", __FUNCTION__, Bps, chParity, cDataBits, cStopBits));
192
193#ifdef RT_OS_LINUX
194 termiosSetup = (struct termios *)RTMemTmpAllocZ(sizeof(struct termios));
195
196 /* Enable receiver */
197 termiosSetup->c_cflag |= (CLOCAL | CREAD);
198
199 switch (Bps) {
200 case 50:
201 baud_rate = B50;
202 break;
203 case 75:
204 baud_rate = B75;
205 break;
206 case 110:
207 baud_rate = B110;
208 break;
209 case 134:
210 baud_rate = B134;
211 break;
212 case 150:
213 baud_rate = B150;
214 break;
215 case 200:
216 baud_rate = B200;
217 break;
218 case 300:
219 baud_rate = B300;
220 break;
221 case 600:
222 baud_rate = B600;
223 break;
224 case 1200:
225 baud_rate = B1200;
226 break;
227 case 1800:
228 baud_rate = B1800;
229 break;
230 case 2400:
231 baud_rate = B2400;
232 break;
233 case 4800:
234 baud_rate = B4800;
235 break;
236 case 9600:
237 baud_rate = B9600;
238 break;
239 case 19200:
240 baud_rate = B19200;
241 break;
242 case 38400:
243 baud_rate = B38400;
244 break;
245 case 57600:
246 baud_rate = B57600;
247 break;
248 case 115200:
249 baud_rate = B115200;
250 break;
251 default:
252 baud_rate = B9600;
253 }
254
255 cfsetispeed(termiosSetup, baud_rate);
256 cfsetospeed(termiosSetup, baud_rate);
257
258 switch (chParity) {
259 case 'E':
260 termiosSetup->c_cflag |= PARENB;
261 break;
262 case 'O':
263 termiosSetup->c_cflag |= (PARENB | PARODD);
264 break;
265 case 'N':
266 break;
267 default:
268 break;
269 }
270
271 switch (cDataBits) {
272 case 5:
273 termiosSetup->c_cflag |= CS5;
274 break;
275 case 6:
276 termiosSetup->c_cflag |= CS6;
277 break;
278 case 7:
279 termiosSetup->c_cflag |= CS7;
280 break;
281 case 8:
282 termiosSetup->c_cflag |= CS8;
283 break;
284 default:
285 break;
286 }
287
288 switch (cStopBits) {
289 case 2:
290 termiosSetup->c_cflag |= CSTOPB;
291 default:
292 break;
293 }
294
295 /* set serial port to raw input */
296 termiosSetup->c_lflag = ~(ICANON | ECHO | ECHOE | ISIG);
297
298 tcsetattr(pData->DeviceFile, TCSANOW, termiosSetup);
299 RTMemTmpFree(termiosSetup);
300#elif defined(RT_OS_WINDOWS)
301 comSetup = (LPDCB)RTMemTmpAllocZ(sizeof(DCB));
302
303 comSetup->DCBlength = sizeof(DCB);
304
305 switch (Bps) {
306 case 110:
307 comSetup->BaudRate = CBR_110;
308 break;
309 case 300:
310 comSetup->BaudRate = CBR_300;
311 break;
312 case 600:
313 comSetup->BaudRate = CBR_600;
314 break;
315 case 1200:
316 comSetup->BaudRate = CBR_1200;
317 break;
318 case 2400:
319 comSetup->BaudRate = CBR_2400;
320 break;
321 case 4800:
322 comSetup->BaudRate = CBR_4800;
323 break;
324 case 9600:
325 comSetup->BaudRate = CBR_9600;
326 break;
327 case 14400:
328 comSetup->BaudRate = CBR_14400;
329 break;
330 case 19200:
331 comSetup->BaudRate = CBR_19200;
332 break;
333 case 38400:
334 comSetup->BaudRate = CBR_38400;
335 break;
336 case 57600:
337 comSetup->BaudRate = CBR_57600;
338 break;
339 case 115200:
340 comSetup->BaudRate = CBR_115200;
341 break;
342 default:
343 comSetup->BaudRate = CBR_9600;
344 }
345
346 comSetup->fBinary = TRUE;
347 comSetup->fOutxCtsFlow = FALSE;
348 comSetup->fOutxDsrFlow = FALSE;
349 comSetup->fDtrControl = DTR_CONTROL_DISABLE;
350 comSetup->fDsrSensitivity = FALSE;
351 comSetup->fTXContinueOnXoff = TRUE;
352 comSetup->fOutX = FALSE;
353 comSetup->fInX = FALSE;
354 comSetup->fErrorChar = FALSE;
355 comSetup->fNull = FALSE;
356 comSetup->fRtsControl = RTS_CONTROL_DISABLE;
357 comSetup->fAbortOnError = FALSE;
358 comSetup->wReserved = 0;
359 comSetup->XonLim = 5;
360 comSetup->XoffLim = 5;
361 comSetup->ByteSize = cDataBits;
362
363 switch (chParity) {
364 case 'E':
365 comSetup->Parity = EVENPARITY;
366 break;
367 case 'O':
368 comSetup->Parity = ODDPARITY;
369 break;
370 case 'N':
371 comSetup->Parity = NOPARITY;
372 break;
373 default:
374 break;
375 }
376
377 switch (cStopBits) {
378 case 1:
379 comSetup->StopBits = ONESTOPBIT;
380 break;
381 case 2:
382 comSetup->StopBits = TWOSTOPBITS;
383 break;
384 default:
385 break;
386 }
387
388 comSetup->XonChar = 0;
389 comSetup->XoffChar = 0;
390 comSetup->ErrorChar = 0;
391 comSetup->EofChar = 0;
392 comSetup->EvtChar = 0;
393
394 SetCommState(pData->hDeviceFile, comSetup);
395 RTMemTmpFree(comSetup);
396#endif /* RT_OS_WINDOWS */
397
398 return VINF_SUCCESS;
399}
400
401/* -=-=-=-=- receive thread -=-=-=-=- */
402
403/**
404 * Send thread loop.
405 *
406 * @returns VINF_SUCCESS.
407 * @param ThreadSelf Thread handle to this thread.
408 * @param pvUser User argument.
409 */
410static DECLCALLBACK(int) drvHostSerialSendThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
411{
412 PDRVHOSTSERIAL pData = PDMINS2DATA(pDrvIns, PDRVHOSTSERIAL);
413
414 if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
415 return VINF_SUCCESS;
416
417#ifdef RT_OS_WINDOWS
418 HANDLE haWait[2];
419 haWait[0] = pData->hEventSend;
420 haWait[1] = pData->hHaltEventSem;
421#endif
422
423 while (pThread->enmState == PDMTHREADSTATE_RUNNING)
424 {
425 int rc = RTSemEventWait(pData->SendSem, RT_INDEFINITE_WAIT);
426 if (VBOX_FAILURE(rc))
427 break;
428
429 /*
430 * Write the character to the host device.
431 */
432 while ( pThread->enmState == PDMTHREADSTATE_RUNNING
433 && pData->iSendQueueTail != pData->iSendQueueHead)
434 {
435 unsigned cbProcessed = 1;
436
437#if defined(RT_OS_LINUX)
438
439 rc = RTFileWrite(pData->DeviceFile, &pData->aSendQueue[pData->iSendQueueTail], cbProcessed, NULL);
440
441#elif defined(RT_OS_WINDOWS)
442
443 DWORD cbBytesWritten;
444 memset(&pData->overlappedSend, 0, sizeof(pData->overlappedSend));
445 pData->overlappedSend.hEvent = pData->hEventSend;
446
447 if (!WriteFile(pData->hDeviceFile, &pData->aSendQueue[pData->iSendQueueTail], cbProcessed, &cbBytesWritten, &pData->overlappedSend))
448 {
449 DWORD dwRet = GetLastError();
450 if (dwRet == ERROR_IO_PENDING)
451 {
452 /*
453 * write blocked, wait ...
454 */
455 dwRet = WaitForMultipleObjects(2, haWait, FALSE, INFINITE);
456 if (dwRet != WAIT_OBJECT_0)
457 break;
458 }
459 else
460 rc = RTErrConvertFromWin32(dwRet);
461 }
462
463#endif
464
465 if (VBOX_SUCCESS(rc))
466 {
467 Assert(cbProcessed);
468 pData->iSendQueueTail++;
469 pData->iSendQueueTail &= CHAR_MAX_SEND_QUEUE_MASK;
470 }
471 else if (VBOX_FAILURE(rc))
472 {
473 LogRel(("HostSerial#%d: Serial Write failed with %Vrc; terminating send thread\n", pDrvIns->iInstance, rc));
474 return VINF_SUCCESS;
475 }
476 }
477 }
478
479 return VINF_SUCCESS;
480}
481
482/**
483 * Unblock the send thread so it can respond to a state change.
484 *
485 * @returns a VBox status code.
486 * @param pDrvIns The driver instance.
487 * @param pThread The send thread.
488 */
489static DECLCALLBACK(int) drvHostSerialWakeupSendThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
490{
491 PDRVHOSTSERIAL pData = PDMINS2DATA(pDrvIns, PDRVHOSTSERIAL);
492 int rc;
493
494 rc = RTSemEventSignal(pData->SendSem);
495 if (RT_FAILURE(rc))
496 return rc;
497
498#ifdef RT_OS_WINDOWS
499 if (!SetEvent(pData->hHaltEventSem))
500 return RTErrConvertFromWin32(GetLastError());
501#endif
502
503 return VINF_SUCCESS;
504}
505
506/* -=-=-=-=- receive thread -=-=-=-=- */
507
508/**
509 * Receive thread loop.
510 *
511 * This thread pushes data from the host serial device up the driver
512 * chain toward the serial device.
513 *
514 * @returns VINF_SUCCESS.
515 * @param ThreadSelf Thread handle to this thread.
516 * @param pvUser User argument.
517 */
518static DECLCALLBACK(int) drvHostSerialRecvThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
519{
520 PDRVHOSTSERIAL pData = PDMINS2DATA(pDrvIns, PDRVHOSTSERIAL);
521 uint8_t abBuffer[256];
522 uint8_t *pbBuffer = NULL;
523 size_t cbRemaining = 0; /* start by reading host data */
524 int rc = VINF_SUCCESS;
525
526 if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
527 return VINF_SUCCESS;
528
529#ifdef RT_OS_WINDOWS
530 HANDLE haWait[2];
531 haWait[0] = pData->hEventRecv;
532 haWait[1] = pData->hHaltEventSem;
533#endif
534
535 while (pThread->enmState == PDMTHREADSTATE_RUNNING)
536 {
537 if (!cbRemaining)
538 {
539 /* Get a block of data from the host serial device. */
540
541#if defined(RT_OS_LINUX)
542
543 size_t cbRead;
544 struct pollfd aFDs[2];
545 aFDs[0].fd = pData->DeviceFile;
546 aFDs[0].events = POLLIN;
547 aFDs[0].revents = 0;
548 aFDs[1].fd = pData->WakeupPipeR;
549 aFDs[1].events = POLLIN | POLLERR | POLLHUP;
550 aFDs[1].revents = 0;
551 rc = poll(aFDs, ELEMENTS(aFDs), -1);
552 if (rc < 0)
553 {
554 /* poll failed for whatever reason */
555 break;
556 }
557 /* this might have changed in the meantime */
558 if (pThread->enmState != PDMTHREADSTATE_RUNNING)
559 break;
560 if (rc > 0 && aFDs[1].revents)
561 {
562 if (aFDs[1].revents & (POLLHUP | POLLERR | POLLNVAL))
563 break;
564 /* notification to terminate -- drain the pipe */
565 char ch;
566 size_t cbRead;
567 RTFileRead(pData->WakeupPipeR, &ch, 1, &cbRead);
568 continue;
569 }
570 rc = RTFileRead(pData->DeviceFile, abBuffer, sizeof(abBuffer), &cbRead);
571 if (VBOX_FAILURE(rc))
572 {
573 LogRel(("HostSerial#%d: Read failed with %Vrc, terminating the worker thread.\n", pDrvIns->iInstance, rc));
574 break;
575 }
576 cbRemaining = cbRead;
577
578#elif defined(RT_OS_WINDOWS)
579
580 DWORD dwEventMask = 0;
581 DWORD dwNumberOfBytesTransferred;
582
583 memset(&pData->overlappedRecv, 0, sizeof(pData->overlappedRecv));
584 pData->overlappedRecv.hEvent = pData->hEventRecv;
585
586 if (!WaitCommEvent(pData->hDeviceFile, &dwEventMask, &pData->overlappedRecv))
587 {
588 DWORD dwRet = GetLastError();
589 if (dwRet == ERROR_IO_PENDING)
590 {
591 dwRet = WaitForMultipleObjects(2, haWait, FALSE, INFINITE);
592 if (dwRet != WAIT_OBJECT_0)
593 {
594 /* notification to terminate */
595 break;
596 }
597 }
598 else
599 {
600 LogRel(("HostSerial#%d: Wait failed with error %Vrc; terminating the worker thread.\n", pDrvIns->iInstance, RTErrConvertFromWin32(dwRet)));
601 break;
602 }
603 }
604 /* this might have changed in the meantime */
605 if (pThread->enmState != PDMTHREADSTATE_RUNNING)
606 break;
607
608 /* Check the event */
609 if (dwEventMask & EV_RXCHAR)
610 {
611 if (!ReadFile(pData->hDeviceFile, abBuffer, sizeof(abBuffer), &dwNumberOfBytesTransferred, &pData->overlappedRecv))
612 {
613 LogRel(("HostSerial#%d: Read failed with error %Vrc; terminating the worker thread.\n", pDrvIns->iInstance, RTErrConvertFromWin32(GetLastError())));
614 break;
615 }
616 cbRemaining = dwNumberOfBytesTransferred;
617 }
618 else
619 {
620 /* The status lines have changed. Notify the device. */
621 DWORD dwNewStatusLinesState = 0;
622 uint32_t uNewStatusLinesState = 0;
623
624 /* Get the new state */
625 if (GetCommModemStatus(pData->hDeviceFile, &dwNewStatusLinesState))
626 {
627 if (dwNewStatusLinesState & MS_RLSD_ON)
628 uNewStatusLinesState |= PDM_ICHAR_STATUS_LINES_DCD;
629 if (dwNewStatusLinesState & MS_RING_ON)
630 uNewStatusLinesState |= PDM_ICHAR_STATUS_LINES_RI;
631 if (dwNewStatusLinesState & MS_DSR_ON)
632 uNewStatusLinesState |= PDM_ICHAR_STATUS_LINES_DSR;
633 if (dwNewStatusLinesState & MS_CTS_ON)
634 uNewStatusLinesState |= PDM_ICHAR_STATUS_LINES_CTS;
635 rc = pData->pDrvCharPort->pfnNotifyStatusLinesChanged(pData->pDrvCharPort, uNewStatusLinesState);
636 if (VBOX_FAILURE(rc))
637 {
638 /* Notifying device failed, continue but log it */
639 LogRel(("HostSerial#%d: Notifying device failed with error %Vrc; continuing.\n", pDrvIns->iInstance, rc));
640 }
641 }
642 else
643 {
644 /* Getting new state failed, continue but log it */
645 LogRel(("HostSerial#%d: Getting status lines state failed with error %Vrc; continuing.\n", pDrvIns->iInstance, RTErrConvertFromWin32(GetLastError())));
646 }
647 }
648#endif
649
650 Log(("Read %d bytes.\n", cbRemaining));
651 pbBuffer = abBuffer;
652 }
653 else
654 {
655 /* Send data to the guest. */
656 size_t cbProcessed = cbRemaining;
657 rc = pData->pDrvCharPort->pfnNotifyRead(pData->pDrvCharPort, pbBuffer, &cbProcessed);
658 if (VBOX_SUCCESS(rc))
659 {
660 Assert(cbProcessed); Assert(cbProcessed <= cbRemaining);
661 pbBuffer += cbProcessed;
662 cbRemaining -= cbProcessed;
663 STAM_COUNTER_ADD(&pData->StatBytesRead, cbProcessed);
664 }
665 else if (rc == VERR_TIMEOUT)
666 {
667 /* Normal case, just means that the guest didn't accept a new
668 * character before the timeout elapsed. Just retry. */
669 rc = VINF_SUCCESS;
670 }
671 else
672 {
673 LogRel(("HostSerial#%d: NotifyRead failed with %Vrc, terminating the worker thread.\n", pDrvIns->iInstance, rc));
674 break;
675 }
676 }
677 }
678
679 return VINF_SUCCESS;
680}
681
682/**
683 * Unblock the send thread so it can respond to a state change.
684 *
685 * @returns a VBox status code.
686 * @param pDrvIns The driver instance.
687 * @param pThread The send thread.
688 */
689static DECLCALLBACK(int) drvHostSerialWakeupRecvThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
690{
691 PDRVHOSTSERIAL pData = PDMINS2DATA(pDrvIns, PDRVHOSTSERIAL);
692#ifdef RT_OS_LINUX
693 return RTFileWrite(pData->WakeupPipeW, "", 1, NULL);
694#elif defined(RT_OS_WINDOWS)
695 if (!SetEvent(pData->hHaltEventSem))
696 return RTErrConvertFromWin32(GetLastError());
697 return VINF_SUCCESS;
698#else
699# error adapt me!
700#endif
701}
702
703#if defined(RT_OS_LINUX)
704/* -=-=-=-=- Monitor thread -=-=-=-=- */
705
706/**
707 * Monitor thread loop.
708 *
709 * This thread monitors the status lines and notifies the device
710 * if they change.
711 *
712 * @returns VINF_SUCCESS.
713 * @param ThreadSelf Thread handle to this thread.
714 * @param pvUser User argument.
715 */
716static DECLCALLBACK(int) drvHostSerialMonitorThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
717{
718 PDRVHOSTSERIAL pData = PDMINS2DATA(pDrvIns, PDRVHOSTSERIAL);
719 int rc = VINF_SUCCESS;
720 unsigned uStatusLinesToCheck = 0;
721
722 uStatusLinesToCheck = TIOCM_CAR | TIOCM_RNG | TIOCM_LE | TIOCM_CTS;
723
724 if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
725 return VINF_SUCCESS;
726
727 while (pThread->enmState == PDMTHREADSTATE_RUNNING)
728 {
729 uint32_t newStatusLine = 0;
730 unsigned int statusLines;
731
732 /*
733 * Wait for status line change.
734 */
735 rc = ioctl(pData->DeviceFile, TIOCMIWAIT, &uStatusLinesToCheck);
736 if (pThread->enmState != PDMTHREADSTATE_RUNNING)
737 break;
738 if (rc < 0)
739 {
740ioctl_error:
741 PDMDrvHlpVMSetRuntimeError(pDrvIns, false, "DrvHostSerialFail",
742 N_("Ioctl failed for serial host device '%s' (%Vrc). The device will not work properly"),
743 pData->pszDevicePath, RTErrConvertFromErrno(errno));
744 break;
745 }
746
747 rc = ioctl(pData->DeviceFile, TIOCMGET, &statusLines);
748 if (rc < 0)
749 goto ioctl_error;
750
751 if (statusLines & TIOCM_CAR)
752 newStatusLine |= PDM_ICHAR_STATUS_LINES_DCD;
753 if (statusLines & TIOCM_RNG)
754 newStatusLine |= PDM_ICHAR_STATUS_LINES_RI;
755 if (statusLines & TIOCM_LE)
756 newStatusLine |= PDM_ICHAR_STATUS_LINES_DSR;
757 if (statusLines & TIOCM_CTS)
758 newStatusLine |= PDM_ICHAR_STATUS_LINES_CTS;
759 rc = pData->pDrvCharPort->pfnNotifyStatusLinesChanged(pData->pDrvCharPort, newStatusLine);
760 }
761
762 return VINF_SUCCESS;
763}
764
765/**
766 * Unblock the monitor thread so it can respond to a state change.
767 * We need to execute this code exactly once during initialization.
768 * But we don't want to block --- therefore this dedicated thread.
769 *
770 * @returns a VBox status code.
771 * @param pDrvIns The driver instance.
772 * @param pThread The send thread.
773 */
774static DECLCALLBACK(int) drvHostSerialWakeupMonitorThread(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
775{
776 PDRVHOSTSERIAL pData = PDMINS2DATA(pDrvIns, PDRVHOSTSERIAL);
777 int rc;
778
779 /*
780 * Linux is a bit difficult as the thread is sleeping in an ioctl call.
781 * So there is no way to have a wakeup pipe.
782 * Thatswhy we set the serial device into loopback mode and change one of the
783 * modem control bits.
784 * This should make the ioctl call return.
785 */
786 rc = ioctl(pData->DeviceFile, TIOCMBIS, TIOCM_LOOP);
787 if (rc < 0)
788 goto ioctl_error;
789
790 rc = ioctl(pData->DeviceFile, TIOCMBIS, TIOCM_RTS);
791 if (rc < 0)
792 goto ioctl_error;
793
794 /*
795 * Set serial device into normal state.
796 */
797 rc = ioctl(pData->DeviceFile, TIOCMBIC, TIOCM_LOOP);
798 if (rc >= 0)
799 return VINF_SUCCESS;
800
801ioctl_error:
802 PDMDrvHlpVMSetRuntimeError(pDrvIns, false, "DrvHostSerialFail",
803 N_("Ioctl failed for serial host device '%s' (%Vrc). The device will not work properly"),
804 pData->pszDevicePath, RTErrConvertFromErrno(errno));
805 return VINF_SUCCESS;
806}
807#endif /* RT_OS_LINUX */
808
809/**
810 * Set the modem lines.
811 *
812 * @returns VBox status code
813 * @param pInterface Pointer to the interface structure.
814 * @param RequestToSend Set to true if this control line should be made active.
815 * @param DataTerminalReady Set to true if this control line should be made active.
816 */
817static DECLCALLBACK(int) drvHostSerialSetModemLines(PPDMICHAR pInterface, bool RequestToSend, bool DataTerminalReady)
818{
819 PDRVHOSTSERIAL pData = PDMICHAR_2_DRVHOSTSERIAL(pInterface);
820
821#ifdef RT_OS_LINUX
822 int modemStateSet = 0;
823 int modemStateClear = 0;
824
825 if (RequestToSend)
826 modemStateSet |= TIOCM_RTS;
827 else
828 modemStateClear |= TIOCM_RTS;
829
830 if (DataTerminalReady)
831 modemStateSet |= TIOCM_DTR;
832 else
833 modemStateClear |= TIOCM_DTR;
834
835 if (modemStateSet)
836 ioctl(pData->DeviceFile, TIOCMBIS, &modemStateSet);
837
838 if (modemStateClear)
839 ioctl(pData->DeviceFile, TIOCMBIC, &modemStateClear);
840#elif defined(RT_OS_WINDOWS)
841 if (RequestToSend)
842 EscapeCommFunction(pData->hDeviceFile, SETRTS);
843 else
844 EscapeCommFunction(pData->hDeviceFile, CLRRTS);
845
846 if (DataTerminalReady)
847 EscapeCommFunction(pData->hDeviceFile, SETDTR);
848 else
849 EscapeCommFunction(pData->hDeviceFile, CLRDTR);
850#endif
851
852 return VINF_SUCCESS;
853}
854
855/* -=-=-=-=- driver interface -=-=-=-=- */
856
857/**
858 * Construct a char driver instance.
859 *
860 * @returns VBox status.
861 * @param pDrvIns The driver instance data.
862 * If the registration structure is needed,
863 * pDrvIns->pDrvReg points to it.
864 * @param pCfgHandle Configuration node handle for the driver. Use this to
865 * obtain the configuration of the driver instance. It's
866 * also found in pDrvIns->pCfgHandle as it's expected to
867 * be used frequently in this function.
868 */
869static DECLCALLBACK(int) drvHostSerialConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
870{
871 PDRVHOSTSERIAL pData = PDMINS2DATA(pDrvIns, PDRVHOSTSERIAL);
872 LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
873
874 /*
875 * Init basic data members and interfaces.
876 */
877#ifdef RT_OS_LINUX
878 pData->DeviceFile = NIL_RTFILE;
879 pData->WakeupPipeR = NIL_RTFILE;
880 pData->WakeupPipeW = NIL_RTFILE;
881#endif
882 /* IBase. */
883 pDrvIns->IBase.pfnQueryInterface = drvHostSerialQueryInterface;
884 /* IChar. */
885 pData->IChar.pfnWrite = drvHostSerialWrite;
886 pData->IChar.pfnSetParameters = drvHostSerialSetParameters;
887 pData->IChar.pfnSetModemLines = drvHostSerialSetModemLines;
888
889 /*
890 * Query configuration.
891 */
892 /* Device */
893 int rc = CFGMR3QueryStringAlloc(pCfgHandle, "DevicePath", &pData->pszDevicePath);
894 if (VBOX_FAILURE(rc))
895 {
896 AssertMsgFailed(("Configuration error: query for \"DevicePath\" string returned %Vra.\n", rc));
897 return rc;
898 }
899
900 /*
901 * Open the device
902 */
903#ifdef RT_OS_WINDOWS
904
905 pData->hHaltEventSem = CreateEvent(NULL, FALSE, FALSE, NULL);
906 AssertReturn(pData->hHaltEventSem != NULL, VERR_NO_MEMORY);
907
908 pData->hEventRecv = CreateEvent(NULL, FALSE, FALSE, NULL);
909 AssertReturn(pData->hEventRecv != NULL, VERR_NO_MEMORY);
910
911 pData->hEventSend = CreateEvent(NULL, FALSE, FALSE, NULL);
912 AssertReturn(pData->hEventSend != NULL, VERR_NO_MEMORY);
913
914 HANDLE hFile = CreateFile(pData->pszDevicePath,
915 GENERIC_READ | GENERIC_WRITE,
916 0, // must be opened with exclusive access
917 NULL, // no SECURITY_ATTRIBUTES structure
918 OPEN_EXISTING, // must use OPEN_EXISTING
919 FILE_FLAG_OVERLAPPED, // overlapped I/O
920 NULL); // no template file
921 if (hFile == INVALID_HANDLE_VALUE)
922 rc = RTErrConvertFromWin32(GetLastError());
923 else
924 {
925 pData->hDeviceFile = hFile;
926 /* for overlapped read */
927 if (!SetCommMask(hFile, EV_RXCHAR | EV_CTS | EV_DSR | EV_RING | EV_RLSD))
928 {
929 LogRel(("HostSerial#%d: SetCommMask failed with error %d.\n", pDrvIns->iInstance, GetLastError()));
930 return VERR_FILE_IO_ERROR;
931 }
932 rc = VINF_SUCCESS;
933 }
934
935#else
936
937 rc = RTFileOpen(&pData->DeviceFile, pData->pszDevicePath, RTFILE_O_OPEN | RTFILE_O_READWRITE);
938
939#endif
940
941 if (VBOX_FAILURE(rc))
942 {
943 AssertMsgFailed(("Could not open host device %s, rc=%Vrc\n", pData->pszDevicePath, rc));
944 switch (rc)
945 {
946 case VERR_ACCESS_DENIED:
947 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
948#ifdef RT_OS_LINUX
949 N_("Cannot open host device '%s' for read/write access. Check the permissions "
950 "of that device ('/bin/ls -l %s'): Most probably you need to be member "
951 "of the device group. Make sure that you logout/login after changing "
952 "the group settings of the current user"),
953#else
954 N_("Cannot open host device '%s' for read/write access. Check the permissions "
955 "of that device"),
956#endif
957 pData->pszDevicePath, pData->pszDevicePath);
958 default:
959 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
960 N_("Failed to open host device '%s'"),
961 pData->pszDevicePath);
962 }
963 }
964
965 /* Set to non blocking I/O */
966#ifdef RT_OS_LINUX
967
968 fcntl(pData->DeviceFile, F_SETFL, O_NONBLOCK);
969 int aFDs[2];
970 if (pipe(aFDs) != 0)
971 {
972 int rc = RTErrConvertFromErrno(errno);
973 AssertRC(rc);
974 return rc;
975 }
976 pData->WakeupPipeR = aFDs[0];
977 pData->WakeupPipeW = aFDs[1];
978
979#elif defined(RT_OS_WINDOWS)
980
981 /* Set the COMMTIMEOUTS to get non blocking I/O */
982 COMMTIMEOUTS comTimeout;
983
984 comTimeout.ReadIntervalTimeout = MAXDWORD;
985 comTimeout.ReadTotalTimeoutMultiplier = 0;
986 comTimeout.ReadTotalTimeoutConstant = 0;
987 comTimeout.WriteTotalTimeoutMultiplier = 0;
988 comTimeout.WriteTotalTimeoutConstant = 0;
989
990 SetCommTimeouts(pData->hDeviceFile, &comTimeout);
991
992#endif
993
994 /*
995 * Get the ICharPort interface of the above driver/device.
996 */
997 pData->pDrvCharPort = (PPDMICHARPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_CHAR_PORT);
998 if (!pData->pDrvCharPort)
999 return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE, RT_SRC_POS, N_("HostSerial#%d has no char port interface above"), pDrvIns->iInstance);
1000
1001 rc = PDMDrvHlpPDMThreadCreate(pDrvIns, &pData->pRecvThread, pData, drvHostSerialRecvThread, drvHostSerialWakeupRecvThread, 0, RTTHREADTYPE_IO, "Serial Receive");
1002 if (VBOX_FAILURE(rc))
1003 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("HostSerial#%d cannot create receive thread"), pDrvIns->iInstance);
1004
1005 rc = RTSemEventCreate(&pData->SendSem);
1006 AssertRC(rc);
1007
1008 rc = PDMDrvHlpPDMThreadCreate(pDrvIns, &pData->pSendThread, pData, drvHostSerialSendThread, drvHostSerialWakeupSendThread, 0, RTTHREADTYPE_IO, "Serial Send");
1009 if (VBOX_FAILURE(rc))
1010 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("HostSerial#%d cannot create send thread"), pDrvIns->iInstance);
1011
1012#if defined(RT_OS_LINUX)
1013 /* Linux needs a separate thread which monitors the status lines. */
1014 rc = PDMDrvHlpPDMThreadCreate(pDrvIns, &pData->pMonitorThread, pData, drvHostSerialMonitorThread, drvHostSerialWakeupMonitorThread, 0, RTTHREADTYPE_IO, "Serial Monitor");
1015 if (VBOX_FAILURE(rc))
1016 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, N_("HostSerial#%d cannot create monitor thread"), pDrvIns->iInstance);
1017#endif
1018
1019 PDMDrvHlpSTAMRegisterF(pDrvIns, &pData->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES, "Nr of bytes written", "/Devices/HostSerial%d/Written", pDrvIns->iInstance);
1020 PDMDrvHlpSTAMRegisterF(pDrvIns, &pData->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES, "Nr of bytes read", "/Devices/HostSerial%d/Read", pDrvIns->iInstance);
1021
1022 return VINF_SUCCESS;
1023}
1024
1025
1026/**
1027 * Destruct a char driver instance.
1028 *
1029 * Most VM resources are freed by the VM. This callback is provided so that
1030 * any non-VM resources can be freed correctly.
1031 *
1032 * @param pDrvIns The driver instance data.
1033 */
1034static DECLCALLBACK(void) drvHostSerialDestruct(PPDMDRVINS pDrvIns)
1035{
1036 PDRVHOSTSERIAL pData = PDMINS2DATA(pDrvIns, PDRVHOSTSERIAL);
1037
1038 LogFlow(("%s: iInstance=%d\n", __FUNCTION__, pDrvIns->iInstance));
1039
1040 /* Empty the send queue */
1041 pData->iSendQueueTail = pData->iSendQueueHead = 0;
1042
1043 RTSemEventDestroy(pData->SendSem);
1044 pData->SendSem = NIL_RTSEMEVENT;
1045
1046#if defined(RT_OS_LINUX)
1047
1048 if (pData->WakeupPipeW != NIL_RTFILE)
1049 {
1050 int rc = RTFileClose(pData->WakeupPipeW);
1051 AssertRC(rc);
1052 pData->WakeupPipeW = NIL_RTFILE;
1053 }
1054 if (pData->WakeupPipeR != NIL_RTFILE)
1055 {
1056 int rc = RTFileClose(pData->WakeupPipeR);
1057 AssertRC(rc);
1058 pData->WakeupPipeR = NIL_RTFILE;
1059 }
1060 if (pData->DeviceFile != NIL_RTFILE)
1061 {
1062 int rc = RTFileClose(pData->DeviceFile);
1063 AssertRC(rc);
1064 pData->DeviceFile = NIL_RTFILE;
1065 }
1066
1067#elif defined(RT_OS_WINDOWS)
1068
1069 CloseHandle(pData->hEventRecv);
1070 CloseHandle(pData->hEventSend);
1071 CancelIo(pData->hDeviceFile);
1072 CloseHandle(pData->hDeviceFile);
1073
1074#endif
1075}
1076
1077/**
1078 * Char driver registration record.
1079 */
1080const PDMDRVREG g_DrvHostSerial =
1081{
1082 /* u32Version */
1083 PDM_DRVREG_VERSION,
1084 /* szDriverName */
1085 "Host Serial",
1086 /* pszDescription */
1087 "Host serial driver.",
1088 /* fFlags */
1089 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1090 /* fClass. */
1091 PDM_DRVREG_CLASS_CHAR,
1092 /* cMaxInstances */
1093 ~0,
1094 /* cbInstance */
1095 sizeof(DRVHOSTSERIAL),
1096 /* pfnConstruct */
1097 drvHostSerialConstruct,
1098 /* pfnDestruct */
1099 drvHostSerialDestruct,
1100 /* pfnIOCtl */
1101 NULL,
1102 /* pfnPowerOn */
1103 NULL,
1104 /* pfnReset */
1105 NULL,
1106 /* pfnSuspend */
1107 NULL,
1108 /* pfnResume */
1109 NULL,
1110 /* pfnDetach */
1111 NULL,
1112 /** pfnPowerOff */
1113 NULL
1114};
1115
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