VirtualBox

source: vbox/trunk/src/VBox/Devices/Storage/DrvHostDVD.cpp@ 78125

Last change on this file since 78125 was 76553, checked in by vboxsync, 6 years ago

scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 20.1 KB
Line 
1/* $Id: DrvHostDVD.cpp 76553 2019-01-01 01:45:53Z vboxsync $ */
2/** @file
3 * DrvHostDVD - Host DVD block driver.
4 */
5
6/*
7 * Copyright (C) 2006-2019 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* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_DRV_HOST_DVD
23#include <iprt/asm.h>
24#include <VBox/vmm/pdmdrv.h>
25#include <VBox/vmm/pdmstorageifs.h>
26#include <iprt/asm.h>
27#include <iprt/assert.h>
28#include <iprt/file.h>
29#include <iprt/string.h>
30#include <iprt/thread.h>
31#include <iprt/critsect.h>
32#include <VBox/scsi.h>
33#include <VBox/scsiinline.h>
34
35#include "VBoxDD.h"
36#include "DrvHostBase.h"
37#include "ATAPIPassthrough.h"
38
39/** ATAPI sense info size. */
40#define ATAPI_SENSE_SIZE 64
41/** Size of an ATAPI packet. */
42#define ATAPI_PACKET_SIZE 12
43
44/**
45 * Host DVD driver instance data.
46 */
47typedef struct DRVHOSTDVD
48{
49 /** Base drivr data. */
50 DRVHOSTBASE Core;
51 /** The current tracklist of the loaded medium if passthrough is used. */
52 PTRACKLIST pTrackList;
53 /** ATAPI sense data. */
54 uint8_t abATAPISense[ATAPI_SENSE_SIZE];
55 /** Flag whether to overwrite the inquiry data with our emulated settings. */
56 bool fInquiryOverwrite;
57} DRVHOSTDVD;
58/** Pointer to the host DVD driver instance data. */
59typedef DRVHOSTDVD *PDRVHOSTDVD;
60
61
62/*********************************************************************************************************************************
63* Internal Functions *
64*********************************************************************************************************************************/
65
66
67static uint8_t drvHostDvdCmdOK(PDRVHOSTDVD pThis)
68{
69 memset(pThis->abATAPISense, '\0', sizeof(pThis->abATAPISense));
70 pThis->abATAPISense[0] = 0x70;
71 pThis->abATAPISense[7] = 10;
72 return SCSI_STATUS_OK;
73}
74
75static uint8_t drvHostDvdCmdError(PDRVHOSTDVD pThis, const uint8_t *pabATAPISense, size_t cbATAPISense)
76{
77 Log(("%s: sense=%#x (%s) asc=%#x ascq=%#x (%s)\n", __FUNCTION__, pabATAPISense[2] & 0x0f, SCSISenseText(pabATAPISense[2] & 0x0f),
78 pabATAPISense[12], pabATAPISense[13], SCSISenseExtText(pabATAPISense[12], pabATAPISense[13])));
79 memset(pThis->abATAPISense, '\0', sizeof(pThis->abATAPISense));
80 memcpy(pThis->abATAPISense, pabATAPISense, RT_MIN(cbATAPISense, sizeof(pThis->abATAPISense)));
81 return SCSI_STATUS_CHECK_CONDITION;
82}
83
84/** @todo deprecated function - doesn't provide enough info. Replace by direct
85 * calls to drvHostDvdCmdError() with full data. */
86static uint8_t drvHostDvdCmdErrorSimple(PDRVHOSTDVD pThis, uint8_t uATAPISenseKey, uint8_t uATAPIASC)
87{
88 uint8_t abATAPISense[ATAPI_SENSE_SIZE];
89 memset(abATAPISense, '\0', sizeof(abATAPISense));
90 abATAPISense[0] = 0x70 | (1 << 7);
91 abATAPISense[2] = uATAPISenseKey & 0x0f;
92 abATAPISense[7] = 10;
93 abATAPISense[12] = uATAPIASC;
94 return drvHostDvdCmdError(pThis, abATAPISense, sizeof(abATAPISense));
95}
96
97
98/**
99 * Parse the CDB and check whether it can be passed through safely.
100 *
101 * @returns Flag whether to passthrough to the device is considered safe.
102 * @param pThis The host DVD driver instance.
103 * @param pReq The request.
104 * @param pbCdb The CDB to parse.
105 * @param cbCdb Size of the CDB in bytes.
106 * @param cbBuf Size of the guest buffer.
107 * @param penmTxDir Where to store the transfer direction (guest to host or vice versa).
108 * @param pcbXfer Where to store the transfer size encoded in the CDB.
109 * @param pcbSector Where to store the sector size used for the transfer.
110 * @param pu8ScsiSts Where to store the SCSI status code.
111 */
112static bool drvHostDvdParseCdb(PDRVHOSTDVD pThis, PDRVHOSTBASEREQ pReq,
113 const uint8_t *pbCdb, size_t cbCdb, size_t cbBuf,
114 PDMMEDIATXDIR *penmTxDir, size_t *pcbXfer,
115 size_t *pcbSector, uint8_t *pu8ScsiSts)
116{
117 bool fPassthrough = false;
118
119 if ( pbCdb[0] == SCSI_REQUEST_SENSE
120 && (pThis->abATAPISense[2] & 0x0f) != SCSI_SENSE_NONE)
121 {
122 /* Handle the command here and copy sense data over. */
123 void *pvBuf = NULL;
124 int rc = drvHostBaseBufferRetain(&pThis->Core, pReq, cbBuf, false /*fWrite*/, &pvBuf);
125 if (RT_SUCCESS(rc))
126 {
127 memcpy(pvBuf, &pThis->abATAPISense[0], RT_MIN(sizeof(pThis->abATAPISense), cbBuf));
128 rc = drvHostBaseBufferRelease(&pThis->Core, pReq, cbBuf, false /* fWrite */, pvBuf);
129 AssertRC(rc);
130 drvHostDvdCmdOK(pThis);
131 *pu8ScsiSts = SCSI_STATUS_OK;
132 }
133 }
134 else
135 fPassthrough = ATAPIPassthroughParseCdb(pbCdb, cbCdb, cbBuf, pThis->pTrackList,
136 &pThis->abATAPISense[0], sizeof(pThis->abATAPISense),
137 penmTxDir, pcbXfer, pcbSector, pu8ScsiSts);
138
139 return fPassthrough;
140}
141
142
143/**
144 * Locks or unlocks the drive.
145 *
146 * @returns VBox status code.
147 * @param pThis The instance data.
148 * @param fLock True if the request is to lock the drive, false if to unlock.
149 */
150static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock)
151{
152 int rc = drvHostBaseDoLockOs(pThis, fLock);
153
154 LogFlow(("drvHostDvdDoLock(, fLock=%RTbool): returns %Rrc\n", fLock, rc));
155 return rc;
156}
157
158
159/** @interface_method_impl{PDMIMEDIA,pfnSendCmd} */
160static DECLCALLBACK(int) drvHostDvdSendCmd(PPDMIMEDIA pInterface, const uint8_t *pbCdb, size_t cbCdb,
161 PDMMEDIATXDIR enmTxDir, void *pvBuf, uint32_t *pcbBuf,
162 uint8_t *pabSense, size_t cbSense, uint32_t cTimeoutMillies)
163{
164 PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
165 int rc;
166 LogFlow(("%s: cmd[0]=%#04x txdir=%d pcbBuf=%d timeout=%d\n", __FUNCTION__, pbCdb[0], enmTxDir, *pcbBuf, cTimeoutMillies));
167
168 RTCritSectEnter(&pThis->CritSect);
169 /* Pass the request on to the internal scsi command interface. */
170 if (enmTxDir == PDMMEDIATXDIR_FROM_DEVICE)
171 memset(pvBuf, '\0', *pcbBuf); /* we got read size, but zero it anyway. */
172 rc = drvHostBaseScsiCmdOs(pThis, pbCdb, cbCdb, enmTxDir, pvBuf, pcbBuf, pabSense, cbSense, cTimeoutMillies);
173 if (rc == VERR_UNRESOLVED_ERROR)
174 /* sense information set */
175 rc = VERR_DEV_IO_ERROR;
176
177 if (pbCdb[0] == SCSI_GET_EVENT_STATUS_NOTIFICATION)
178 {
179 uint8_t *pbBuf = (uint8_t*)pvBuf;
180 Log2(("Event Status Notification class=%#02x supported classes=%#02x\n", pbBuf[2], pbBuf[3]));
181 if (RT_BE2H_U16(*(uint16_t*)pbBuf) >= 6)
182 Log2((" event %#02x %#02x %#02x %#02x\n", pbBuf[4], pbBuf[5], pbBuf[6], pbBuf[7]));
183 }
184 RTCritSectLeave(&pThis->CritSect);
185
186 LogFlow(("%s: rc=%Rrc\n", __FUNCTION__, rc));
187 return rc;
188}
189
190
191/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqSendScsiCmd} */
192static DECLCALLBACK(int) drvHostDvdIoReqSendScsiCmd(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint32_t uLun,
193 const uint8_t *pbCdb, size_t cbCdb, PDMMEDIAEXIOREQSCSITXDIR enmTxDir,
194 size_t cbBuf, uint8_t *pabSense, size_t cbSense, uint8_t *pu8ScsiSts,
195 uint32_t cTimeoutMillies)
196{
197 RT_NOREF3(uLun, cTimeoutMillies, enmTxDir);
198
199 PDRVHOSTDVD pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDVD, Core.IMediaEx);
200 PDRVHOSTBASEREQ pReq = (PDRVHOSTBASEREQ)hIoReq;
201 int rc = VINF_SUCCESS;
202
203 LogFlow(("%s: pbCdb[0]=%#04x{%s} enmTxDir=%d cbBuf=%zu timeout=%u\n",
204 __FUNCTION__, pbCdb[0], SCSICmdText(pbCdb[0]), enmTxDir, cbBuf, cTimeoutMillies));
205
206 RTCritSectEnter(&pThis->Core.CritSect);
207
208 /*
209 * Parse the command first to fend off any illegal or dangerous commands we don't want the guest
210 * to execute on the host drive.
211 */
212 PDMMEDIATXDIR enmXferDir = PDMMEDIATXDIR_NONE;
213 size_t cbXfer = 0;
214 size_t cbSector = 0;
215 size_t cbScsiCmdBufLimit = drvHostBaseScsiCmdGetBufLimitOs(&pThis->Core);
216 bool fPassthrough = drvHostDvdParseCdb(pThis, pReq, pbCdb, cbCdb, cbBuf,
217 &enmXferDir, &cbXfer, &cbSector, pu8ScsiSts);
218 if (fPassthrough)
219 {
220 void *pvBuf = NULL;
221 size_t cbXferCur = cbXfer;
222
223 pReq->cbReq = cbXfer;
224 pReq->cbResidual = cbXfer;
225
226 if (cbXfer)
227 rc = drvHostBaseBufferRetain(&pThis->Core, pReq, cbXfer, enmXferDir == PDMMEDIATXDIR_TO_DEVICE, &pvBuf);
228
229 if (cbXfer > cbScsiCmdBufLimit)
230 {
231 /* Linux accepts commands with up to 100KB of data, but expects
232 * us to handle commands with up to 128KB of data. The usual
233 * imbalance of powers. */
234 uint8_t aATAPICmd[ATAPI_PACKET_SIZE];
235 uint32_t iATAPILBA, cSectors;
236 uint8_t *pbBuf = (uint8_t *)pvBuf;
237
238 switch (pbCdb[0])
239 {
240 case SCSI_READ_10:
241 case SCSI_WRITE_10:
242 case SCSI_WRITE_AND_VERIFY_10:
243 iATAPILBA = scsiBE2H_U32(pbCdb + 2);
244 cSectors = scsiBE2H_U16(pbCdb + 7);
245 break;
246 case SCSI_READ_12:
247 case SCSI_WRITE_12:
248 iATAPILBA = scsiBE2H_U32(pbCdb + 2);
249 cSectors = scsiBE2H_U32(pbCdb + 6);
250 break;
251 case SCSI_READ_CD:
252 iATAPILBA = scsiBE2H_U32(pbCdb + 2);
253 cSectors = scsiBE2H_U24(pbCdb + 6);
254 break;
255 case SCSI_READ_CD_MSF:
256 iATAPILBA = scsiMSF2LBA(pbCdb + 3);
257 cSectors = scsiMSF2LBA(pbCdb + 6) - iATAPILBA;
258 break;
259 default:
260 AssertMsgFailed(("Don't know how to split command %#04x\n", pbCdb[0]));
261 LogRelMax(10, ("HostDVD#%u: CD-ROM passthrough split error\n", pThis->Core.pDrvIns->iInstance));
262 *pu8ScsiSts = drvHostDvdCmdErrorSimple(pThis, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
263 rc = drvHostBaseBufferRelease(&pThis->Core, pReq, cbBuf, enmXferDir == PDMMEDIATXDIR_TO_DEVICE, pvBuf);
264 RTCritSectLeave(&pThis->Core.CritSect);
265 return VINF_SUCCESS;
266 }
267 memcpy(aATAPICmd, pbCdb, RT_MIN(cbCdb, ATAPI_PACKET_SIZE));
268 uint32_t cReqSectors = 0;
269 for (uint32_t i = cSectors; i > 0; i -= cReqSectors)
270 {
271 if (i * cbSector > cbScsiCmdBufLimit)
272 cReqSectors = (uint32_t)(cbScsiCmdBufLimit / cbSector);
273 else
274 cReqSectors = i;
275 uint32_t cbCurrTX = (uint32_t)cbSector * cReqSectors;
276 switch (pbCdb[0])
277 {
278 case SCSI_READ_10:
279 case SCSI_WRITE_10:
280 case SCSI_WRITE_AND_VERIFY_10:
281 scsiH2BE_U32(aATAPICmd + 2, iATAPILBA);
282 scsiH2BE_U16(aATAPICmd + 7, cReqSectors);
283 break;
284 case SCSI_READ_12:
285 case SCSI_WRITE_12:
286 scsiH2BE_U32(aATAPICmd + 2, iATAPILBA);
287 scsiH2BE_U32(aATAPICmd + 6, cReqSectors);
288 break;
289 case SCSI_READ_CD:
290 scsiH2BE_U32(aATAPICmd + 2, iATAPILBA);
291 scsiH2BE_U24(aATAPICmd + 6, cReqSectors);
292 break;
293 case SCSI_READ_CD_MSF:
294 scsiLBA2MSF(aATAPICmd + 3, iATAPILBA);
295 scsiLBA2MSF(aATAPICmd + 6, iATAPILBA + cReqSectors);
296 break;
297 }
298 rc = drvHostBaseScsiCmdOs(&pThis->Core, aATAPICmd, sizeof(aATAPICmd),
299 enmXferDir, pbBuf, &cbCurrTX,
300 &pThis->abATAPISense[0], sizeof(pThis->abATAPISense),
301 cTimeoutMillies /**< @todo timeout */);
302 if (rc != VINF_SUCCESS)
303 break;
304
305 pReq->cbResidual -= cbCurrTX;
306 iATAPILBA += cReqSectors;
307 pbBuf += cbSector * cReqSectors;
308 }
309 }
310 else
311 {
312 uint32_t cbXferTmp = (uint32_t)cbXferCur;
313 rc = drvHostBaseScsiCmdOs(&pThis->Core, pbCdb, cbCdb, enmXferDir, pvBuf, &cbXferTmp,
314 &pThis->abATAPISense[0], sizeof(pThis->abATAPISense), cTimeoutMillies);
315 if (RT_SUCCESS(rc))
316 pReq->cbResidual -= cbXferTmp;
317 }
318
319 if (RT_SUCCESS(rc))
320 {
321 /* Do post processing for certain commands. */
322 switch (pbCdb[0])
323 {
324 case SCSI_SEND_CUE_SHEET:
325 case SCSI_READ_TOC_PMA_ATIP:
326 {
327 if (!pThis->pTrackList)
328 rc = ATAPIPassthroughTrackListCreateEmpty(&pThis->pTrackList);
329
330 if (RT_SUCCESS(rc))
331 rc = ATAPIPassthroughTrackListUpdate(pThis->pTrackList, pbCdb, pvBuf);
332
333 if (RT_FAILURE(rc))
334 LogRelMax(10, ("HostDVD#%u: Error (%Rrc) while updating the tracklist during %s, burning the disc might fail\n",
335 pThis->Core.pDrvIns->iInstance, rc,
336 pbCdb[0] == SCSI_SEND_CUE_SHEET ? "SEND CUE SHEET" : "READ TOC/PMA/ATIP"));
337 break;
338 }
339 case SCSI_SYNCHRONIZE_CACHE:
340 {
341 if (pThis->pTrackList)
342 ATAPIPassthroughTrackListClear(pThis->pTrackList);
343 break;
344 }
345 }
346
347 if (enmXferDir == PDMMEDIATXDIR_FROM_DEVICE)
348 {
349 Assert(cbXferCur <= cbXfer);
350
351 if ( pbCdb[0] == SCSI_INQUIRY
352 && pThis->fInquiryOverwrite)
353 {
354 const char *pszInqVendorId = "VBOX";
355 const char *pszInqProductId = "CD-ROM";
356 const char *pszInqRevision = "1.0";
357
358 if (pThis->Core.pDrvMediaPort->pfnQueryScsiInqStrings)
359 {
360 rc = pThis->Core.pDrvMediaPort->pfnQueryScsiInqStrings(pThis->Core.pDrvMediaPort, &pszInqVendorId,
361 &pszInqProductId, &pszInqRevision);
362 AssertRC(rc);
363 }
364 /* Make sure that the real drive cannot be identified.
365 * Motivation: changing the VM configuration should be as
366 * invisible as possible to the guest. */
367 if (cbXferCur >= 8 + 8)
368 scsiPadStr((uint8_t *)pvBuf + 8, pszInqVendorId, 8);
369 if (cbXferCur >= 16 + 16)
370 scsiPadStr((uint8_t *)pvBuf + 16, pszInqProductId, 16);
371 if (cbXferCur >= 32 + 4)
372 scsiPadStr((uint8_t *)pvBuf + 32, pszInqRevision, 4);
373 }
374
375 if (cbXferCur)
376 Log3(("ATAPI PT data read (%d): %.*Rhxs\n", cbXferCur, cbXferCur, (uint8_t *)pvBuf));
377 }
378
379 *pu8ScsiSts = drvHostDvdCmdOK(pThis);
380 }
381 else
382 {
383 do
384 {
385 /* don't log superfluous errors */
386 if ( rc == VERR_DEV_IO_ERROR
387 && ( pbCdb[0] == SCSI_TEST_UNIT_READY
388 || pbCdb[0] == SCSI_READ_CAPACITY
389 || pbCdb[0] == SCSI_READ_DVD_STRUCTURE
390 || pbCdb[0] == SCSI_READ_TOC_PMA_ATIP))
391 break;
392 LogRelMax(10, ("HostDVD#%u: CD-ROM passthrough cmd=%#04x sense=%d ASC=%#02x ASCQ=%#02x %Rrc\n",
393 pThis->Core.pDrvIns->iInstance, pbCdb[0], pThis->abATAPISense[2] & 0x0f,
394 pThis->abATAPISense[12], pThis->abATAPISense[13], rc));
395 } while (0);
396 *pu8ScsiSts = SCSI_STATUS_CHECK_CONDITION;
397 rc = VINF_SUCCESS;
398 }
399
400 if (cbXfer)
401 rc = drvHostBaseBufferRelease(&pThis->Core, pReq, cbXfer, enmXferDir == PDMMEDIATXDIR_TO_DEVICE, pvBuf);
402 }
403
404 /*
405 * We handled the command, check the status code and copy over the sense data if
406 * it is CHECK CONDITION.
407 */
408 if ( *pu8ScsiSts == SCSI_STATUS_CHECK_CONDITION
409 && VALID_PTR(pabSense)
410 && cbSense > 0)
411 memcpy(pabSense, &pThis->abATAPISense[0], RT_MIN(cbSense, sizeof(pThis->abATAPISense)));
412
413 RTCritSectLeave(&pThis->Core.CritSect);
414
415 LogFlow(("%s: rc=%Rrc\n", __FUNCTION__, rc));
416 return rc;
417}
418
419
420/* -=-=-=-=- driver interface -=-=-=-=- */
421
422
423/** @interface_method_impl{PDMDRVREG,pfnDestruct} */
424static DECLCALLBACK(void) drvHostDvdDestruct(PPDMDRVINS pDrvIns)
425{
426 PDRVHOSTDVD pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDVD);
427
428 if (pThis->pTrackList)
429 {
430 ATAPIPassthroughTrackListDestroy(pThis->pTrackList);
431 pThis->pTrackList = NULL;
432 }
433
434 DRVHostBaseDestruct(pDrvIns);
435}
436
437/**
438 * Construct a host dvd drive driver instance.
439 *
440 * @copydoc FNPDMDRVCONSTRUCT
441 */
442static DECLCALLBACK(int) drvHostDvdConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
443{
444 RT_NOREF(fFlags);
445 PDRVHOSTDVD pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDVD);
446 LogFlow(("drvHostDvdConstruct: iInstance=%d\n", pDrvIns->iInstance));
447
448 int rc = CFGMR3QueryBoolDef(pCfg, "InquiryOverwrite", &pThis->fInquiryOverwrite, true);
449 if (RT_FAILURE(rc))
450 return PDMDRV_SET_ERROR(pDrvIns, rc,
451 N_("HostDVD configuration error: failed to read \"InquiryOverwrite\" as boolean"));
452
453 bool fPassthrough;
454 rc = CFGMR3QueryBool(pCfg, "Passthrough", &fPassthrough);
455 if (RT_SUCCESS(rc) && fPassthrough)
456 {
457 pThis->Core.IMedia.pfnSendCmd = drvHostDvdSendCmd;
458 pThis->Core.IMediaEx.pfnIoReqSendScsiCmd = drvHostDvdIoReqSendScsiCmd;
459 /* Passthrough requires opening the device in R/W mode. */
460 pThis->Core.fReadOnlyConfig = false;
461 }
462
463 pThis->Core.pfnDoLock = drvHostDvdDoLock;
464
465 /*
466 * Init instance data.
467 */
468 rc = DRVHostBaseInit(pDrvIns, pCfg, "Path\0Interval\0Locked\0BIOSVisible\0AttachFailError\0Passthrough\0InquiryOverwrite\0",
469 PDMMEDIATYPE_DVD);
470 LogFlow(("drvHostDvdConstruct: returns %Rrc\n", rc));
471 return rc;
472}
473
474
475/**
476 * Block driver registration record.
477 */
478const PDMDRVREG g_DrvHostDVD =
479{
480 /* u32Version */
481 PDM_DRVREG_VERSION,
482 /* szName */
483 "HostDVD",
484 /* szRCMod */
485 "",
486 /* szR0Mod */
487 "",
488 /* pszDescription */
489 "Host DVD Block Driver.",
490 /* fFlags */
491 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
492 /* fClass. */
493 PDM_DRVREG_CLASS_BLOCK,
494 /* cMaxInstances */
495 ~0U,
496 /* cbInstance */
497 sizeof(DRVHOSTDVD),
498 /* pfnConstruct */
499 drvHostDvdConstruct,
500 /* pfnDestruct */
501 drvHostDvdDestruct,
502 /* pfnRelocate */
503 NULL,
504 /* pfnIOCtl */
505 NULL,
506 /* pfnPowerOn */
507 NULL,
508 /* pfnReset */
509 NULL,
510 /* pfnSuspend */
511 NULL,
512 /* pfnResume */
513 NULL,
514 /* pfnAttach */
515 NULL,
516 /* pfnDetach */
517 NULL,
518 /* pfnPowerOff */
519 NULL,
520 /* pfnSoftReset */
521 NULL,
522 /* u32EndVersion */
523 PDM_DRVREG_VERSION
524};
525
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