VirtualBox

source: vbox/trunk/src/VBox/Devices/Serial/DrvTCP.cpp@ 68699

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

pdmifs.h,Serial: Reworked stream interface. The old design with the two read/write threads had a race where the read thread could access already destroyed VMM structures during destruction if data was read. This was solved by adding a poll callback which waits for data to arrive and which can be interrupt to make the thread respond to VM state changes and suspend before destruction starts. This required reworking all the drivers using it. DrvTCP was reworked to make use of the RTTcp*, RTSocket* and RTPoll* API in that process to get rid of platform dependent code there (which wasn't all available when the driver was createt).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.2 KB
Line 
1/* $Id: DrvTCP.cpp 68699 2017-09-07 15:12:54Z vboxsync $ */
2/** @file
3 * TCP socket driver implementing the IStream interface.
4 */
5
6/*
7 * Copyright (C) 2006-2017 Oracle Corporation.
8 *
9 * This file was contributed by Alexey Eromenko (derived from DrvNamedPipe)
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* Header Files *
23*********************************************************************************************************************************/
24#define LOG_GROUP LOG_GROUP_DRV_TCP
25#include <VBox/vmm/pdmdrv.h>
26#include <iprt/assert.h>
27#include <iprt/file.h>
28#include <iprt/stream.h>
29#include <iprt/alloc.h>
30#include <iprt/pipe.h>
31#include <iprt/poll.h>
32#include <iprt/string.h>
33#include <iprt/semaphore.h>
34#include <iprt/socket.h>
35#include <iprt/tcp.h>
36#include <iprt/uuid.h>
37#include <stdlib.h>
38
39#include "VBoxDD.h"
40
41/*********************************************************************************************************************************
42* Defined Constants And Macros *
43*********************************************************************************************************************************/
44
45#define DRVTCP_POLLSET_ID_SOCKET 0
46#define DRVTCP_POLLSET_ID_WAKEUP 1
47
48#define DRVTCP_WAKEUP_REASON_EXTERNAL 0
49#define DRVTCP_WAKEUP_REASON_NEW_CONNECTION 1
50
51/*********************************************************************************************************************************
52* Structures and Typedefs *
53*********************************************************************************************************************************/
54/**
55 * TCP driver instance data.
56 *
57 * @implements PDMISTREAM
58 */
59typedef struct DRVTCP
60{
61 /** The stream interface. */
62 PDMISTREAM IStream;
63 /** Pointer to the driver instance. */
64 PPDMDRVINS pDrvIns;
65 /** Pointer to the TCP server address:port or port only. (Freed by MM) */
66 char *pszLocation;
67 /** Flag whether VirtualBox represents the server or client side. */
68 bool fIsServer;
69
70 /** Handle of the TCP server for incoming connections. */
71 PRTTCPSERVER hTcpServ;
72 /** Socket handle of the TCP socket connection. */
73 RTSOCKET hTcpSock;
74
75 /** Poll set used to wait for I/O events. */
76 RTPOLLSET hPollSet;
77 /** Reading end of the wakeup pipe. */
78 RTPIPE hPipeWakeR;
79 /** Writing end of the wakeup pipe. */
80 RTPIPE hPipeWakeW;
81 /** Flag whether the socket is in the pollset. */
82 bool fTcpSockInPollSet;
83
84 /** Thread for listening for new connections. */
85 RTTHREAD ListenThread;
86 /** Flag to signal listening thread to shut down. */
87 bool volatile fShutdown;
88} DRVTCP, *PDRVTCP;
89
90
91/*********************************************************************************************************************************
92* Internal Functions *
93*********************************************************************************************************************************/
94
95
96/**
97 * Kicks any possibly polling thread to get informed about changes.
98 *
99 * @returns VBOx status code.
100 * @param pThis The TCP driver instance.
101 * @param bReason The reason code to handle.
102 */
103static int drvTcpPollerKick(PDRVTCP pThis, uint8_t bReason)
104{
105 size_t cbWritten = 0;
106 return RTPipeWrite(pThis->hPipeWakeW, &bReason, 1, &cbWritten);
107}
108
109
110/** @interface_method_impl{PDMISTREAM,pfnPoll} */
111static DECLCALLBACK(int) drvTcpPoll(PPDMISTREAM pInterface, uint32_t fEvts, uint32_t *pfEvts, RTMSINTERVAL cMillies)
112{
113 int rc = VINF_SUCCESS;
114 PDRVTCP pThis = RT_FROM_MEMBER(pInterface, DRVTCP, IStream);
115
116 if (pThis->hTcpSock != NIL_RTSOCKET)
117 {
118 if (!pThis->fTcpSockInPollSet)
119 {
120 rc = RTPollSetAddSocket(pThis->hPollSet, pThis->hTcpSock,
121 fEvts, DRVTCP_POLLSET_ID_SOCKET);
122 if (RT_SUCCESS(rc))
123 pThis->fTcpSockInPollSet = true;
124 }
125 else
126 {
127 /* Always include error event. */
128 fEvts |= RTPOLL_EVT_ERROR;
129 rc = RTPollSetEventsChange(pThis->hPollSet, DRVTCP_POLLSET_ID_SOCKET, fEvts);
130 AssertRC(rc);
131 }
132 }
133
134 if (RT_SUCCESS(rc))
135 {
136 while (RT_SUCCESS(rc))
137 {
138 uint32_t fEvtsRecv = 0;
139 uint32_t idHnd = 0;
140
141 rc = RTPoll(pThis->hPollSet, cMillies, &fEvtsRecv, &idHnd);
142 if (RT_SUCCESS(rc))
143 {
144 if (idHnd == DRVTCP_POLLSET_ID_WAKEUP)
145 {
146 /* We got woken up, drain the pipe and return. */
147 uint8_t bReason;
148 size_t cbRead = 0;
149 rc = RTPipeRead(pThis->hPipeWakeR, &bReason, 1, &cbRead);
150 AssertRC(rc);
151
152 if (bReason == DRVTCP_WAKEUP_REASON_EXTERNAL)
153 rc = VERR_INTERRUPTED;
154 else if (bReason == DRVTCP_WAKEUP_REASON_NEW_CONNECTION)
155 {
156 Assert(!pThis->fTcpSockInPollSet);
157 rc = RTPollSetAddSocket(pThis->hPollSet, pThis->hTcpSock,
158 fEvts, DRVTCP_POLLSET_ID_SOCKET);
159 if (RT_SUCCESS(rc))
160 pThis->fTcpSockInPollSet = true;
161 }
162 else
163 AssertMsgFailed(("Unknown wakeup reason in pipe %u\n", bReason));
164 }
165 else
166 {
167 Assert(idHnd == DRVTCP_POLLSET_ID_SOCKET);
168
169 /* On error we close the socket here. */
170 if (fEvtsRecv & RTPOLL_EVT_ERROR)
171 {
172 rc = RTPollSetRemove(pThis->hPollSet, DRVTCP_POLLSET_ID_SOCKET);
173 AssertRC(rc);
174
175 if (pThis->fIsServer)
176 RTTcpServerDisconnectClient2(pThis->hTcpSock);
177 else
178 RTSocketClose(pThis->hTcpSock);
179 pThis->hTcpSock = NIL_RTSOCKET;
180 pThis->fTcpSockInPollSet = false;
181 /* Continue with polling. */
182 }
183 else
184 {
185 *pfEvts = fEvtsRecv;
186 break;
187 }
188 }
189 }
190 }
191 }
192
193 return rc;
194}
195
196
197/** @interface_method_impl{PDMISTREAM,pfnPollInterrupt} */
198static DECLCALLBACK(int) drvTcpPollInterrupt(PPDMISTREAM pInterface)
199{
200 PDRVTCP pThis = RT_FROM_MEMBER(pInterface, DRVTCP, IStream);
201 return drvTcpPollerKick(pThis, DRVTCP_WAKEUP_REASON_EXTERNAL);
202}
203
204
205/** @interface_method_impl{PDMISTREAM,pfnRead} */
206static DECLCALLBACK(int) drvTcpRead(PPDMISTREAM pInterface, void *pvBuf, size_t *pcbRead)
207{
208 int rc = VINF_SUCCESS;
209 PDRVTCP pThis = RT_FROM_MEMBER(pInterface, DRVTCP, IStream);
210 LogFlow(("%s: pvBuf=%p *pcbRead=%#x (%s)\n", __FUNCTION__, pvBuf, *pcbRead, pThis->pszLocation));
211
212 Assert(pvBuf);
213
214 if (pThis->hTcpSock != NIL_RTSOCKET)
215 {
216 size_t cbRead;
217 size_t cbBuf = *pcbRead;
218 rc = RTSocketReadNB(pThis->hTcpSock, pvBuf, cbBuf, &cbRead);
219 if (RT_SUCCESS(rc))
220 {
221 if (!cbRead && rc != VINF_TRY_AGAIN)
222 {
223 rc = RTPollSetRemove(pThis->hPollSet, DRVTCP_POLLSET_ID_SOCKET);
224 AssertRC(rc);
225
226 if (pThis->fIsServer)
227 RTTcpServerDisconnectClient2(pThis->hTcpSock);
228 else
229 RTSocketClose(pThis->hTcpSock);
230 pThis->hTcpSock = NIL_RTSOCKET;
231 pThis->fTcpSockInPollSet = false;
232 rc = VINF_SUCCESS;
233 }
234 *pcbRead = cbRead;
235 }
236 }
237 else
238 {
239 RTThreadSleep(100);
240 *pcbRead = 0;
241 }
242
243 LogFlow(("%s: *pcbRead=%zu returns %Rrc\n", __FUNCTION__, *pcbRead, rc));
244 return rc;
245}
246
247
248/** @interface_method_impl{PDMISTREAM,pfnWrite} */
249static DECLCALLBACK(int) drvTcpWrite(PPDMISTREAM pInterface, const void *pvBuf, size_t *pcbWrite)
250{
251 int rc = VINF_SUCCESS;
252 PDRVTCP pThis = RT_FROM_MEMBER(pInterface, DRVTCP, IStream);
253 LogFlow(("%s: pvBuf=%p *pcbWrite=%#x (%s)\n", __FUNCTION__, pvBuf, *pcbWrite, pThis->pszLocation));
254
255 Assert(pvBuf);
256 if (pThis->hTcpSock != NIL_RTSOCKET)
257 {
258 size_t cbBuf = *pcbWrite;
259 rc = RTSocketWriteNB(pThis->hTcpSock, pvBuf, cbBuf, pcbWrite);
260 }
261 else
262 *pcbWrite = 0;
263
264 LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
265 return rc;
266}
267
268
269/**
270 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
271 */
272static DECLCALLBACK(void *) drvTCPQueryInterface(PPDMIBASE pInterface, const char *pszIID)
273{
274 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
275 PDRVTCP pThis = PDMINS_2_DATA(pDrvIns, PDRVTCP);
276 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
277 PDMIBASE_RETURN_INTERFACE(pszIID, PDMISTREAM, &pThis->IStream);
278 return NULL;
279}
280
281
282/* -=-=-=-=- listen thread -=-=-=-=- */
283
284/**
285 * Receive thread loop.
286 *
287 * @returns VINF_SUCCESS
288 * @param hThreadSelf Thread handle to this thread.
289 * @param pvUser User argument.
290 */
291static DECLCALLBACK(int) drvTCPListenLoop(RTTHREAD hThreadSelf, void *pvUser)
292{
293 RT_NOREF(hThreadSelf);
294 PDRVTCP pThis = (PDRVTCP)pvUser;
295
296 while (RT_LIKELY(!pThis->fShutdown))
297 {
298 RTSOCKET hTcpSockNew = NIL_RTSOCKET;
299 int rc = RTTcpServerListen2(pThis->hTcpServ, &hTcpSockNew);
300 if (RT_SUCCESS(rc))
301 {
302 if (pThis->hTcpSock != NIL_RTSOCKET)
303 {
304 LogRel(("DrvTCP%d: only single connection supported\n", pThis->pDrvIns->iInstance));
305 RTTcpServerDisconnectClient2(hTcpSockNew);
306 }
307 else
308 {
309 pThis->hTcpSock = hTcpSockNew;
310 /* Inform the poller about the new socket. */
311 drvTcpPollerKick(pThis, DRVTCP_WAKEUP_REASON_NEW_CONNECTION);
312 }
313 }
314 }
315
316 return VINF_SUCCESS;
317}
318
319/* -=-=-=-=- PDMDRVREG -=-=-=-=- */
320
321/**
322 * Common worker for drvTCPPowerOff and drvTCPDestructor.
323 *
324 * @param pThis The instance data.
325 */
326static void drvTCPShutdownListener(PDRVTCP pThis)
327{
328 /*
329 * Signal shutdown of the listener thread.
330 */
331 pThis->fShutdown = true;
332 if ( pThis->fIsServer
333 && pThis->hTcpServ != NULL)
334 {
335 int rc = RTTcpServerShutdown(pThis->hTcpServ);
336 AssertRC(rc);
337 pThis->hTcpServ = NULL;
338 }
339}
340
341
342/**
343 * Power off a TCP socket stream driver instance.
344 *
345 * This does most of the destruction work, to avoid ordering dependencies.
346 *
347 * @param pDrvIns The driver instance data.
348 */
349static DECLCALLBACK(void) drvTCPPowerOff(PPDMDRVINS pDrvIns)
350{
351 PDRVTCP pThis = PDMINS_2_DATA(pDrvIns, PDRVTCP);
352 LogFlow(("%s: %s\n", __FUNCTION__, pThis->pszLocation));
353
354 drvTCPShutdownListener(pThis);
355}
356
357
358/**
359 * Destruct a TCP socket stream driver instance.
360 *
361 * Most VM resources are freed by the VM. This callback is provided so that
362 * any non-VM resources can be freed correctly.
363 *
364 * @param pDrvIns The driver instance data.
365 */
366static DECLCALLBACK(void) drvTCPDestruct(PPDMDRVINS pDrvIns)
367{
368 PDRVTCP pThis = PDMINS_2_DATA(pDrvIns, PDRVTCP);
369 LogFlow(("%s: %s\n", __FUNCTION__, pThis->pszLocation));
370 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
371
372 drvTCPShutdownListener(pThis);
373
374 /*
375 * While the thread exits, clean up as much as we can.
376 */
377 if (pThis->hTcpSock != NIL_RTSOCKET)
378 {
379 int rc = RTPollSetRemove(pThis->hPollSet, DRVTCP_POLLSET_ID_SOCKET);
380 AssertRC(rc);
381
382 rc = RTSocketShutdown(pThis->hTcpSock, true /* fRead */, true /* fWrite */);
383 AssertRC(rc);
384
385 rc = RTSocketClose(pThis->hTcpSock);
386 AssertRC(rc); RT_NOREF(rc);
387
388 pThis->hTcpSock = NIL_RTSOCKET;
389 }
390
391 if (pThis->hPipeWakeR != NIL_RTPIPE)
392 {
393 int rc = RTPipeClose(pThis->hPipeWakeR);
394 AssertRC(rc);
395
396 pThis->hPipeWakeR = NIL_RTPIPE;
397 }
398
399 if (pThis->hPipeWakeW != NIL_RTPIPE)
400 {
401 int rc = RTPipeClose(pThis->hPipeWakeW);
402 AssertRC(rc);
403
404 pThis->hPipeWakeW = NIL_RTPIPE;
405 }
406
407 if (pThis->hPollSet != NIL_RTPOLLSET)
408 {
409 int rc = RTPollSetDestroy(pThis->hPollSet);
410 AssertRC(rc);
411
412 pThis->hPollSet = NIL_RTPOLLSET;
413 }
414
415 MMR3HeapFree(pThis->pszLocation);
416 pThis->pszLocation = NULL;
417
418 /*
419 * Wait for the thread.
420 */
421 if (pThis->ListenThread != NIL_RTTHREAD)
422 {
423 int rc = RTThreadWait(pThis->ListenThread, 30000, NULL);
424 if (RT_SUCCESS(rc))
425 pThis->ListenThread = NIL_RTTHREAD;
426 else
427 LogRel(("DrvTCP%d: listen thread did not terminate (%Rrc)\n", pDrvIns->iInstance, rc));
428 }
429}
430
431
432/**
433 * Construct a TCP socket stream driver instance.
434 *
435 * @copydoc FNPDMDRVCONSTRUCT
436 */
437static DECLCALLBACK(int) drvTCPConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
438{
439 RT_NOREF(fFlags);
440 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
441 PDRVTCP pThis = PDMINS_2_DATA(pDrvIns, PDRVTCP);
442
443 /*
444 * Init the static parts.
445 */
446 pThis->pDrvIns = pDrvIns;
447 pThis->pszLocation = NULL;
448 pThis->fIsServer = false;
449
450 pThis->hTcpServ = NULL;
451 pThis->hTcpSock = NIL_RTSOCKET;
452
453 pThis->hPollSet = NIL_RTPOLLSET;
454 pThis->hPipeWakeR = NIL_RTPIPE;
455 pThis->hPipeWakeW = NIL_RTPIPE;
456 pThis->fTcpSockInPollSet = false;
457
458 pThis->ListenThread = NIL_RTTHREAD;
459 pThis->fShutdown = false;
460 /* IBase */
461 pDrvIns->IBase.pfnQueryInterface = drvTCPQueryInterface;
462 /* IStream */
463 pThis->IStream.pfnPoll = drvTcpPoll;
464 pThis->IStream.pfnPollInterrupt = drvTcpPollInterrupt;
465 pThis->IStream.pfnRead = drvTcpRead;
466 pThis->IStream.pfnWrite = drvTcpWrite;
467
468 /*
469 * Validate and read the configuration.
470 */
471 PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "Location|IsServer", "");
472
473 int rc = CFGMR3QueryStringAlloc(pCfg, "Location", &pThis->pszLocation);
474 if (RT_FAILURE(rc))
475 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
476 N_("Configuration error: querying \"Location\" resulted in %Rrc"), rc);
477 rc = CFGMR3QueryBool(pCfg, "IsServer", &pThis->fIsServer);
478 if (RT_FAILURE(rc))
479 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
480 N_("Configuration error: querying \"IsServer\" resulted in %Rrc"), rc);
481
482 rc = RTPipeCreate(&pThis->hPipeWakeR, &pThis->hPipeWakeW, 0 /* fFlags */);
483 if (RT_FAILURE(rc))
484 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
485 N_("DrvTCP#%d: Failed to create wake pipe"), pDrvIns->iInstance);
486
487 rc = RTPollSetCreate(&pThis->hPollSet);
488 if (RT_FAILURE(rc))
489 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
490 N_("DrvTCP#%d: Failed to create poll set"), pDrvIns->iInstance);
491
492 rc = RTPollSetAddPipe(pThis->hPollSet, pThis->hPipeWakeR,
493 RTPOLL_EVT_READ | RTPOLL_EVT_ERROR,
494 DRVTCP_POLLSET_ID_WAKEUP);
495 if (RT_FAILURE(rc))
496 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
497 N_("DrvTCP#%d failed to add wakeup pipe for %s to poll set"),
498 pDrvIns->iInstance, pThis->pszLocation);
499
500 /*
501 * Create/Open the socket.
502 */
503 if (pThis->fIsServer)
504 {
505 uint32_t uPort = 0;
506 rc = RTStrToUInt32Ex(pThis->pszLocation, NULL, 10, &uPort);
507 if (RT_FAILURE(rc))
508 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
509 N_("DrvTCP#%d: The port part of the location is not a numerical value"),
510 pDrvIns->iInstance);
511
512 /** @todo: Allow binding to distinct interfaces. */
513 rc = RTTcpServerCreateEx(NULL, uPort, &pThis->hTcpServ);
514 if (RT_FAILURE(rc))
515 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
516 N_("DrvTCP#%d failed to create server socket"), pDrvIns->iInstance);
517
518 rc = RTThreadCreate(&pThis->ListenThread, drvTCPListenLoop, (void *)pThis, 0,
519 RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "DrvTCPStream");
520 if (RT_FAILURE(rc))
521 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
522 N_("DrvTCP#%d failed to create listening thread"), pDrvIns->iInstance);
523 }
524 else
525 {
526 char *pszPort = strchr(pThis->pszLocation, ':');
527 if (!pszPort)
528 return PDMDrvHlpVMSetError(pDrvIns, VERR_NOT_FOUND, RT_SRC_POS,
529 N_("DrvTCP#%d: The location misses the port to connect to"),
530 pDrvIns->iInstance);
531
532 *pszPort = '\0'; /* Overwrite temporarily to avoid copying the hostname into a temporary buffer. */
533 uint32_t uPort = 0;
534 rc = RTStrToUInt32Ex(pszPort + 1, NULL, 10, &uPort);
535 if (RT_FAILURE(rc))
536 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
537 N_("DrvTCP#%d: The port part of the location is not a numerical value"),
538 pDrvIns->iInstance);
539
540 rc = RTTcpClientConnect(pThis->pszLocation, uPort, &pThis->hTcpSock);
541 *pszPort = ':'; /* Restore delimiter before checking the status. */
542 if (RT_FAILURE(rc))
543 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
544 N_("DrvTCP#%d failed to connect to socket %s"),
545 pDrvIns->iInstance, pThis->pszLocation);
546
547 rc = RTPollSetAddSocket(pThis->hPollSet, pThis->hTcpSock,
548 RTPOLL_EVT_READ | RTPOLL_EVT_WRITE | RTPOLL_EVT_ERROR,
549 DRVTCP_POLLSET_ID_SOCKET);
550 if (RT_FAILURE(rc))
551 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
552 N_("DrvTCP#%d failed to add socket for %s to poll set"),
553 pDrvIns->iInstance, pThis->pszLocation);
554
555 pThis->fTcpSockInPollSet = true;
556 }
557
558 LogRel(("DrvTCP: %s, %s\n", pThis->pszLocation, pThis->fIsServer ? "server" : "client"));
559 return VINF_SUCCESS;
560}
561
562
563/**
564 * TCP stream driver registration record.
565 */
566const PDMDRVREG g_DrvTCP =
567{
568 /* u32Version */
569 PDM_DRVREG_VERSION,
570 /* szName */
571 "TCP",
572 /* szRCMod */
573 "",
574 /* szR0Mod */
575 "",
576 /* pszDescription */
577 "TCP serial stream driver.",
578 /* fFlags */
579 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
580 /* fClass. */
581 PDM_DRVREG_CLASS_STREAM,
582 /* cMaxInstances */
583 ~0U,
584 /* cbInstance */
585 sizeof(DRVTCP),
586 /* pfnConstruct */
587 drvTCPConstruct,
588 /* pfnDestruct */
589 drvTCPDestruct,
590 /* pfnRelocate */
591 NULL,
592 /* pfnIOCtl */
593 NULL,
594 /* pfnPowerOn */
595 NULL,
596 /* pfnReset */
597 NULL,
598 /* pfnSuspend */
599 NULL,
600 /* pfnResume */
601 NULL,
602 /* pfnAttach */
603 NULL,
604 /* pfnDetach */
605 NULL,
606 /* pfnPowerOff */
607 drvTCPPowerOff,
608 /* pfnSoftReset */
609 NULL,
610 /* u32EndVersion */
611 PDM_DRVREG_VERSION
612};
613
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