VirtualBox

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

最後變更 在這個檔案從72073是 72073,由 vboxsync 提交於 7 年 前

Devices/Serial: New version for the character driver while working on the conversion to the new interface, some updates to the already newly implemented host serial driver

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

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette