VirtualBox

source: vbox/trunk/src/VBox/Devices/Storage/DrvBlock.cpp@ 40257

Last change on this file since 40257 was 38878, checked in by vboxsync, 13 years ago

PDM,Devices,Drivers: Add async discard API and make us of it

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 33.7 KB
Line 
1/* $Id: DrvBlock.cpp 38878 2011-09-27 09:07:07Z vboxsync $ */
2/** @file
3 * VBox storage devices: Generic block driver
4 */
5
6/*
7 * Copyright (C) 2006-2010 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_BLOCK
23#include <VBox/vmm/pdmdrv.h>
24#include <iprt/assert.h>
25#include <iprt/string.h>
26#include <iprt/uuid.h>
27
28#include "VBoxDD.h"
29
30
31/** @def VBOX_PERIODIC_FLUSH
32 * Enable support for periodically flushing the VDI to disk. This may prove
33 * useful for those nasty problems with the ultra-slow host filesystems.
34 * If this is enabled, it can be configured via the CFGM key
35 * "VBoxInternal/Devices/piix3ide/0/LUN#<x>/Config/FlushInterval". <x>
36 * must be replaced with the correct LUN number of the disk that should
37 * do the periodic flushes. The value of the key is the number of bytes
38 * written between flushes. A value of 0 (the default) denotes no flushes. */
39#define VBOX_PERIODIC_FLUSH
40
41/** @def VBOX_IGNORE_FLUSH
42 * Enable support for ignoring VDI flush requests. This can be useful for
43 * filesystems that show bad guest IDE write performance (especially with
44 * Windows guests). NOTE that this does not disable the flushes caused by
45 * the periodic flush cache feature above.
46 * If this feature is enabled, it can be configured via the CFGM key
47 * "VBoxInternal/Devices/piix3ide/0/LUN#<x>/Config/IgnoreFlush". <x>
48 * must be replaced with the correct LUN number of the disk that should
49 * ignore flush requests. The value of the key is a boolean. The default
50 * is to ignore flushes, i.e. true. */
51#define VBOX_IGNORE_FLUSH
52
53
54/*******************************************************************************
55* Structures and Typedefs *
56*******************************************************************************/
57/**
58 * Block driver instance data.
59 *
60 * @implements PDMIBLOCK
61 * @implements PDMIBLOCKBIOS
62 * @implements PDMIMOUNT
63 * @implements PDMIMEDIAASYNCPORT
64 * @implements PDMIBLOCKASYNC
65 */
66typedef struct DRVBLOCK
67{
68 /** Pointer driver instance. */
69 PPDMDRVINS pDrvIns;
70 /** Drive type. */
71 PDMBLOCKTYPE enmType;
72 /** Locked indicator. */
73 bool fLocked;
74 /** Mountable indicator. */
75 bool fMountable;
76 /** Visible to the BIOS. */
77 bool fBiosVisible;
78#ifdef VBOX_PERIODIC_FLUSH
79 /** HACK: Configuration value for number of bytes written after which to flush. */
80 uint32_t cbFlushInterval;
81 /** HACK: Current count for the number of bytes written since the last flush. */
82 uint32_t cbDataWritten;
83#endif /* VBOX_PERIODIC_FLUSH */
84#ifdef VBOX_IGNORE_FLUSH
85 /** HACK: Disable flushes for this drive. */
86 bool fIgnoreFlush;
87 /** Disable async flushes for this drive. */
88 bool fIgnoreFlushAsync;
89#endif /* VBOX_IGNORE_FLUSH */
90 /** Pointer to the media driver below us.
91 * This is NULL if the media is not mounted. */
92 PPDMIMEDIA pDrvMedia;
93 /** Pointer to the block port interface above us. */
94 PPDMIBLOCKPORT pDrvBlockPort;
95 /** Pointer to the mount notify interface above us. */
96 PPDMIMOUNTNOTIFY pDrvMountNotify;
97 /** Our block interface. */
98 PDMIBLOCK IBlock;
99 /** Our block interface. */
100 PDMIBLOCKBIOS IBlockBios;
101 /** Our mountable interface. */
102 PDMIMOUNT IMount;
103 /** Our media port interface. */
104 PDMIMEDIAPORT IMediaPort;
105
106 /** Pointer to the async media driver below us.
107 * This is NULL if the media is not mounted. */
108 PPDMIMEDIAASYNC pDrvMediaAsync;
109 /** Our media async port. */
110 PDMIMEDIAASYNCPORT IMediaAsyncPort;
111 /** Pointer to the async block port interface above us. */
112 PPDMIBLOCKASYNCPORT pDrvBlockAsyncPort;
113 /** Our async block interface. */
114 PDMIBLOCKASYNC IBlockAsync;
115
116 /** Uuid of the drive. */
117 RTUUID Uuid;
118
119 /** BIOS PCHS Geometry. */
120 PDMMEDIAGEOMETRY PCHSGeometry;
121 /** BIOS LCHS Geometry. */
122 PDMMEDIAGEOMETRY LCHSGeometry;
123} DRVBLOCK, *PDRVBLOCK;
124
125
126/* -=-=-=-=- IBlock -=-=-=-=- */
127
128/** Makes a PDRVBLOCK out of a PPDMIBLOCK. */
129#define PDMIBLOCK_2_DRVBLOCK(pInterface) ( (PDRVBLOCK)((uintptr_t)pInterface - RT_OFFSETOF(DRVBLOCK, IBlock)) )
130
131/** @copydoc PDMIBLOCK::pfnRead */
132static DECLCALLBACK(int) drvblockRead(PPDMIBLOCK pInterface, uint64_t off, void *pvBuf, size_t cbRead)
133{
134 PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
135
136 /*
137 * Check the state.
138 */
139 if (!pThis->pDrvMedia)
140 {
141 AssertMsgFailed(("Invalid state! Not mounted!\n"));
142 return VERR_PDM_MEDIA_NOT_MOUNTED;
143 }
144
145 int rc = pThis->pDrvMedia->pfnRead(pThis->pDrvMedia, off, pvBuf, cbRead);
146 return rc;
147}
148
149
150/** @copydoc PDMIBLOCK::pfnWrite */
151static DECLCALLBACK(int) drvblockWrite(PPDMIBLOCK pInterface, uint64_t off, const void *pvBuf, size_t cbWrite)
152{
153 PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
154
155 /*
156 * Check the state.
157 */
158 if (!pThis->pDrvMedia)
159 {
160 AssertMsgFailed(("Invalid state! Not mounted!\n"));
161 return VERR_PDM_MEDIA_NOT_MOUNTED;
162 }
163
164 /* Set an FTM checkpoint as this operation changes the state permanently. */
165 PDMDrvHlpFTSetCheckpoint(pThis->pDrvIns, FTMCHECKPOINTTYPE_STORAGE);
166
167 int rc = pThis->pDrvMedia->pfnWrite(pThis->pDrvMedia, off, pvBuf, cbWrite);
168#ifdef VBOX_PERIODIC_FLUSH
169 if (pThis->cbFlushInterval)
170 {
171 pThis->cbDataWritten += (uint32_t)cbWrite;
172 if (pThis->cbDataWritten > pThis->cbFlushInterval)
173 {
174 pThis->cbDataWritten = 0;
175 pThis->pDrvMedia->pfnFlush(pThis->pDrvMedia);
176 }
177 }
178#endif /* VBOX_PERIODIC_FLUSH */
179
180 return rc;
181}
182
183
184/** @copydoc PDMIBLOCK::pfnFlush */
185static DECLCALLBACK(int) drvblockFlush(PPDMIBLOCK pInterface)
186{
187 PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
188
189 /*
190 * Check the state.
191 */
192 if (!pThis->pDrvMedia)
193 {
194 AssertMsgFailed(("Invalid state! Not mounted!\n"));
195 return VERR_PDM_MEDIA_NOT_MOUNTED;
196 }
197
198#ifdef VBOX_IGNORE_FLUSH
199 if (pThis->fIgnoreFlush)
200 return VINF_SUCCESS;
201#endif /* VBOX_IGNORE_FLUSH */
202
203 int rc = pThis->pDrvMedia->pfnFlush(pThis->pDrvMedia);
204 if (rc == VERR_NOT_IMPLEMENTED)
205 rc = VINF_SUCCESS;
206 return rc;
207}
208
209
210/** @copydoc PDMIBLOCK::pfnMerge */
211static DECLCALLBACK(int) drvblockMerge(PPDMIBLOCK pInterface,
212 PFNSIMPLEPROGRESS pfnProgress,
213 void *pvUser)
214{
215 PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
216
217 /*
218 * Check the state.
219 */
220 if (!pThis->pDrvMedia)
221 {
222 AssertMsgFailed(("Invalid state! Not mounted!\n"));
223 return VERR_PDM_MEDIA_NOT_MOUNTED;
224 }
225
226 if (!pThis->pDrvMedia->pfnMerge)
227 return VERR_NOT_SUPPORTED;
228
229 int rc = pThis->pDrvMedia->pfnMerge(pThis->pDrvMedia, pfnProgress, pvUser);
230 return rc;
231}
232
233
234/** @copydoc PDMIBLOCK::pfnIsReadOnly */
235static DECLCALLBACK(bool) drvblockIsReadOnly(PPDMIBLOCK pInterface)
236{
237 PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
238
239 /*
240 * Check the state.
241 */
242 if (!pThis->pDrvMedia)
243 return false;
244
245 bool fRc = pThis->pDrvMedia->pfnIsReadOnly(pThis->pDrvMedia);
246 return fRc;
247}
248
249
250/** @copydoc PDMIBLOCK::pfnGetSize */
251static DECLCALLBACK(uint64_t) drvblockGetSize(PPDMIBLOCK pInterface)
252{
253 PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
254
255 /*
256 * Check the state.
257 */
258 if (!pThis->pDrvMedia)
259 return 0;
260
261 uint64_t cb = pThis->pDrvMedia->pfnGetSize(pThis->pDrvMedia);
262 LogFlow(("drvblockGetSize: returns %llu\n", cb));
263 return cb;
264}
265
266
267/** @copydoc PDMIBLOCK::pfnGetType */
268static DECLCALLBACK(PDMBLOCKTYPE) drvblockGetType(PPDMIBLOCK pInterface)
269{
270 PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
271 LogFlow(("drvblockGetType: returns %d\n", pThis->enmType));
272 return pThis->enmType;
273}
274
275
276/** @copydoc PDMIBLOCK::pfnGetUuid */
277static DECLCALLBACK(int) drvblockGetUuid(PPDMIBLOCK pInterface, PRTUUID pUuid)
278{
279 PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
280
281 /*
282 * Copy the uuid.
283 */
284 *pUuid = pThis->Uuid;
285 return VINF_SUCCESS;
286}
287
288static DECLCALLBACK(int) drvblockDiscard(PPDMIBLOCK pInterface, PCRTRANGE paRanges, unsigned cRanges)
289{
290 PDRVBLOCK pThis = PDMIBLOCK_2_DRVBLOCK(pInterface);
291
292 return pThis->pDrvMedia->pfnDiscard(pThis->pDrvMedia, paRanges, cRanges);
293}
294
295/* -=-=-=-=- IBlockAsync -=-=-=-=- */
296
297/** Makes a PDRVBLOCK out of a PPDMIBLOCKASYNC. */
298#define PDMIBLOCKASYNC_2_DRVBLOCK(pInterface) ( (PDRVBLOCK)((uintptr_t)pInterface - RT_OFFSETOF(DRVBLOCK, IBlockAsync)) )
299
300/** @copydoc PDMIBLOCKASYNC::pfnStartRead */
301static DECLCALLBACK(int) drvblockAsyncReadStart(PPDMIBLOCKASYNC pInterface, uint64_t off, PCRTSGSEG pSeg, unsigned cSeg, size_t cbRead, void *pvUser)
302{
303 PDRVBLOCK pThis = PDMIBLOCKASYNC_2_DRVBLOCK(pInterface);
304
305 /*
306 * Check the state.
307 */
308 if (!pThis->pDrvMediaAsync)
309 {
310 AssertMsgFailed(("Invalid state! Not mounted!\n"));
311 return VERR_PDM_MEDIA_NOT_MOUNTED;
312 }
313
314 int rc = pThis->pDrvMediaAsync->pfnStartRead(pThis->pDrvMediaAsync, off, pSeg, cSeg, cbRead, pvUser);
315 return rc;
316}
317
318
319/** @copydoc PDMIBLOCKASYNC::pfnStartWrite */
320static DECLCALLBACK(int) drvblockAsyncWriteStart(PPDMIBLOCKASYNC pInterface, uint64_t off, PCRTSGSEG pSeg, unsigned cSeg, size_t cbWrite, void *pvUser)
321{
322 PDRVBLOCK pThis = PDMIBLOCKASYNC_2_DRVBLOCK(pInterface);
323
324 /*
325 * Check the state.
326 */
327 if (!pThis->pDrvMediaAsync)
328 {
329 AssertMsgFailed(("Invalid state! Not mounted!\n"));
330 return VERR_PDM_MEDIA_NOT_MOUNTED;
331 }
332
333 int rc = pThis->pDrvMediaAsync->pfnStartWrite(pThis->pDrvMediaAsync, off, pSeg, cSeg, cbWrite, pvUser);
334
335 return rc;
336}
337
338
339/** @copydoc PDMIBLOCKASYNC::pfnStartFlush */
340static DECLCALLBACK(int) drvblockAsyncFlushStart(PPDMIBLOCKASYNC pInterface, void *pvUser)
341{
342 PDRVBLOCK pThis = PDMIBLOCKASYNC_2_DRVBLOCK(pInterface);
343
344 /*
345 * Check the state.
346 */
347 if (!pThis->pDrvMediaAsync)
348 {
349 AssertMsgFailed(("Invalid state! Not mounted!\n"));
350 return VERR_PDM_MEDIA_NOT_MOUNTED;
351 }
352
353#ifdef VBOX_IGNORE_FLUSH
354 if (pThis->fIgnoreFlushAsync)
355 return VINF_VD_ASYNC_IO_FINISHED;
356#endif /* VBOX_IGNORE_FLUSH */
357
358 int rc = pThis->pDrvMediaAsync->pfnStartFlush(pThis->pDrvMediaAsync, pvUser);
359
360 return rc;
361}
362
363
364/** @copydoc PDMIBLOCKASYNC::pfnStartDiscard */
365static DECLCALLBACK(int) drvblockStartDiscard(PPDMIBLOCKASYNC pInterface, PCRTRANGE paRanges, unsigned cRanges, void *pvUser)
366{
367 PDRVBLOCK pThis = PDMIBLOCKASYNC_2_DRVBLOCK(pInterface);
368
369 /*
370 * Check the state.
371 */
372 if (!pThis->pDrvMediaAsync)
373 {
374 AssertMsgFailed(("Invalid state! Not mounted!\n"));
375 return VERR_PDM_MEDIA_NOT_MOUNTED;
376 }
377
378 return pThis->pDrvMediaAsync->pfnStartDiscard(pThis->pDrvMediaAsync, paRanges, cRanges, pvUser);
379}
380
381/* -=-=-=-=- IMediaAsyncPort -=-=-=-=- */
382
383/** Makes a PDRVBLOCKASYNC out of a PPDMIMEDIAASYNCPORT. */
384#define PDMIMEDIAASYNCPORT_2_DRVBLOCK(pInterface) ( (PDRVBLOCK((uintptr_t)pInterface - RT_OFFSETOF(DRVBLOCK, IMediaAsyncPort))) )
385
386static DECLCALLBACK(int) drvblockAsyncTransferCompleteNotify(PPDMIMEDIAASYNCPORT pInterface, void *pvUser, int rcReq)
387{
388 PDRVBLOCK pThis = PDMIMEDIAASYNCPORT_2_DRVBLOCK(pInterface);
389
390 return pThis->pDrvBlockAsyncPort->pfnTransferCompleteNotify(pThis->pDrvBlockAsyncPort, pvUser, rcReq);
391}
392
393/* -=-=-=-=- IBlockBios -=-=-=-=- */
394
395/** Makes a PDRVBLOCK out of a PPDMIBLOCKBIOS. */
396#define PDMIBLOCKBIOS_2_DRVBLOCK(pInterface) ( (PDRVBLOCK((uintptr_t)pInterface - RT_OFFSETOF(DRVBLOCK, IBlockBios))) )
397
398
399/** @copydoc PDMIBLOCKBIOS::pfnGetPCHSGeometry */
400static DECLCALLBACK(int) drvblockGetPCHSGeometry(PPDMIBLOCKBIOS pInterface, PPDMMEDIAGEOMETRY pPCHSGeometry)
401{
402 PDRVBLOCK pThis = PDMIBLOCKBIOS_2_DRVBLOCK(pInterface);
403
404 /*
405 * Check the state.
406 */
407 if (!pThis->pDrvMedia)
408 return VERR_PDM_MEDIA_NOT_MOUNTED;
409
410 /*
411 * Use configured/cached values if present.
412 */
413 if ( pThis->PCHSGeometry.cCylinders > 0
414 && pThis->PCHSGeometry.cHeads > 0
415 && pThis->PCHSGeometry.cSectors > 0)
416 {
417 *pPCHSGeometry = pThis->PCHSGeometry;
418 LogFlow(("%s: returns VINF_SUCCESS {%d,%d,%d}\n", __FUNCTION__, pThis->PCHSGeometry.cCylinders, pThis->PCHSGeometry.cHeads, pThis->PCHSGeometry.cSectors));
419 return VINF_SUCCESS;
420 }
421
422 /*
423 * Call media.
424 */
425 int rc = pThis->pDrvMedia->pfnBiosGetPCHSGeometry(pThis->pDrvMedia, &pThis->PCHSGeometry);
426
427 if (RT_SUCCESS(rc))
428 {
429 *pPCHSGeometry = pThis->PCHSGeometry;
430 LogFlow(("%s: returns %Rrc {%d,%d,%d}\n", __FUNCTION__, rc, pThis->PCHSGeometry.cCylinders, pThis->PCHSGeometry.cHeads, pThis->PCHSGeometry.cSectors));
431 }
432 else if (rc == VERR_NOT_IMPLEMENTED)
433 {
434 rc = VERR_PDM_GEOMETRY_NOT_SET;
435 LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
436 }
437 return rc;
438}
439
440
441/** @copydoc PDMIBLOCKBIOS::pfnSetPCHSGeometry */
442static DECLCALLBACK(int) drvblockSetPCHSGeometry(PPDMIBLOCKBIOS pInterface, PCPDMMEDIAGEOMETRY pPCHSGeometry)
443{
444 LogFlow(("%s: cCylinders=%d cHeads=%d cSectors=%d\n", __FUNCTION__, pPCHSGeometry->cCylinders, pPCHSGeometry->cHeads, pPCHSGeometry->cSectors));
445 PDRVBLOCK pThis = PDMIBLOCKBIOS_2_DRVBLOCK(pInterface);
446
447 /*
448 * Check the state.
449 */
450 if (!pThis->pDrvMedia)
451 {
452 AssertMsgFailed(("Invalid state! Not mounted!\n"));
453 return VERR_PDM_MEDIA_NOT_MOUNTED;
454 }
455
456 /*
457 * Call media. Ignore the not implemented return code.
458 */
459 int rc = pThis->pDrvMedia->pfnBiosSetPCHSGeometry(pThis->pDrvMedia, pPCHSGeometry);
460
461 if ( RT_SUCCESS(rc)
462 || rc == VERR_NOT_IMPLEMENTED)
463 {
464 pThis->PCHSGeometry = *pPCHSGeometry;
465 rc = VINF_SUCCESS;
466 }
467 return rc;
468}
469
470
471/** @copydoc PDMIBLOCKBIOS::pfnGetLCHSGeometry */
472static DECLCALLBACK(int) drvblockGetLCHSGeometry(PPDMIBLOCKBIOS pInterface, PPDMMEDIAGEOMETRY pLCHSGeometry)
473{
474 PDRVBLOCK pThis = PDMIBLOCKBIOS_2_DRVBLOCK(pInterface);
475
476 /*
477 * Check the state.
478 */
479 if (!pThis->pDrvMedia)
480 return VERR_PDM_MEDIA_NOT_MOUNTED;
481
482 /*
483 * Use configured/cached values if present.
484 */
485 if ( pThis->LCHSGeometry.cCylinders > 0
486 && pThis->LCHSGeometry.cHeads > 0
487 && pThis->LCHSGeometry.cSectors > 0)
488 {
489 *pLCHSGeometry = pThis->LCHSGeometry;
490 LogFlow(("%s: returns VINF_SUCCESS {%d,%d,%d}\n", __FUNCTION__, pThis->LCHSGeometry.cCylinders, pThis->LCHSGeometry.cHeads, pThis->LCHSGeometry.cSectors));
491 return VINF_SUCCESS;
492 }
493
494 /*
495 * Call media.
496 */
497 int rc = pThis->pDrvMedia->pfnBiosGetLCHSGeometry(pThis->pDrvMedia, &pThis->LCHSGeometry);
498
499 if (RT_SUCCESS(rc))
500 {
501 *pLCHSGeometry = pThis->LCHSGeometry;
502 LogFlow(("%s: returns %Rrc {%d,%d,%d}\n", __FUNCTION__, rc, pThis->LCHSGeometry.cCylinders, pThis->LCHSGeometry.cHeads, pThis->LCHSGeometry.cSectors));
503 }
504 else if (rc == VERR_NOT_IMPLEMENTED)
505 {
506 rc = VERR_PDM_GEOMETRY_NOT_SET;
507 LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc));
508 }
509 return rc;
510}
511
512
513/** @copydoc PDMIBLOCKBIOS::pfnSetLCHSGeometry */
514static DECLCALLBACK(int) drvblockSetLCHSGeometry(PPDMIBLOCKBIOS pInterface, PCPDMMEDIAGEOMETRY pLCHSGeometry)
515{
516 LogFlow(("%s: cCylinders=%d cHeads=%d cSectors=%d\n", __FUNCTION__, pLCHSGeometry->cCylinders, pLCHSGeometry->cHeads, pLCHSGeometry->cSectors));
517 PDRVBLOCK pThis = PDMIBLOCKBIOS_2_DRVBLOCK(pInterface);
518
519 /*
520 * Check the state.
521 */
522 if (!pThis->pDrvMedia)
523 {
524 AssertMsgFailed(("Invalid state! Not mounted!\n"));
525 return VERR_PDM_MEDIA_NOT_MOUNTED;
526 }
527
528 /*
529 * Call media. Ignore the not implemented return code.
530 */
531 int rc = pThis->pDrvMedia->pfnBiosSetLCHSGeometry(pThis->pDrvMedia, pLCHSGeometry);
532
533 if ( RT_SUCCESS(rc)
534 || rc == VERR_NOT_IMPLEMENTED)
535 {
536 pThis->LCHSGeometry = *pLCHSGeometry;
537 rc = VINF_SUCCESS;
538 }
539 return rc;
540}
541
542
543/** @copydoc PDMIBLOCKBIOS::pfnIsVisible */
544static DECLCALLBACK(bool) drvblockIsVisible(PPDMIBLOCKBIOS pInterface)
545{
546 PDRVBLOCK pThis = PDMIBLOCKBIOS_2_DRVBLOCK(pInterface);
547 LogFlow(("drvblockIsVisible: returns %d\n", pThis->fBiosVisible));
548 return pThis->fBiosVisible;
549}
550
551
552/** @copydoc PDMIBLOCKBIOS::pfnGetType */
553static DECLCALLBACK(PDMBLOCKTYPE) drvblockBiosGetType(PPDMIBLOCKBIOS pInterface)
554{
555 PDRVBLOCK pThis = PDMIBLOCKBIOS_2_DRVBLOCK(pInterface);
556 LogFlow(("drvblockBiosGetType: returns %d\n", pThis->enmType));
557 return pThis->enmType;
558}
559
560
561
562/* -=-=-=-=- IMount -=-=-=-=- */
563
564/** Makes a PDRVBLOCK out of a PPDMIMOUNT. */
565#define PDMIMOUNT_2_DRVBLOCK(pInterface) ( (PDRVBLOCK)((uintptr_t)pInterface - RT_OFFSETOF(DRVBLOCK, IMount)) )
566
567
568/** @copydoc PDMIMOUNT::pfnMount */
569static DECLCALLBACK(int) drvblockMount(PPDMIMOUNT pInterface, const char *pszFilename, const char *pszCoreDriver)
570{
571 LogFlow(("drvblockMount: pszFilename=%p:{%s} pszCoreDriver=%p:{%s}\n", pszFilename, pszFilename, pszCoreDriver, pszCoreDriver));
572 PDRVBLOCK pThis = PDMIMOUNT_2_DRVBLOCK(pInterface);
573
574 /*
575 * Validate state.
576 */
577 if (pThis->pDrvMedia)
578 {
579 AssertMsgFailed(("Already mounted\n"));
580 return VERR_PDM_MEDIA_MOUNTED;
581 }
582
583 /*
584 * Prepare configuration.
585 */
586 if (pszFilename)
587 {
588 int rc = PDMDrvHlpMountPrepare(pThis->pDrvIns, pszFilename, pszCoreDriver);
589 if (RT_FAILURE(rc))
590 {
591 Log(("drvblockMount: Prepare failed for \"%s\" rc=%Rrc\n", pszFilename, rc));
592 return rc;
593 }
594 }
595
596 /*
597 * Attach the media driver and query it's interface.
598 */
599 uint32_t fTachFlags = 0; /** @todo figure attachment flags for mount. */
600 PPDMIBASE pBase;
601 int rc = PDMDrvHlpAttach(pThis->pDrvIns, fTachFlags, &pBase);
602 if (RT_FAILURE(rc))
603 {
604 Log(("drvblockMount: Attach failed rc=%Rrc\n", rc));
605 return rc;
606 }
607
608 pThis->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMEDIA);
609 if (pThis->pDrvMedia)
610 {
611 /** @todo r=klaus missing async handling, this is just a band aid to
612 * avoid using stale information */
613 pThis->pDrvMediaAsync = NULL;
614
615 /*
616 * Initialize state.
617 */
618 pThis->fLocked = false;
619 pThis->PCHSGeometry.cCylinders = 0;
620 pThis->PCHSGeometry.cHeads = 0;
621 pThis->PCHSGeometry.cSectors = 0;
622 pThis->LCHSGeometry.cCylinders = 0;
623 pThis->LCHSGeometry.cHeads = 0;
624 pThis->LCHSGeometry.cSectors = 0;
625#ifdef VBOX_PERIODIC_FLUSH
626 pThis->cbDataWritten = 0;
627#endif /* VBOX_PERIODIC_FLUSH */
628
629 /*
630 * Notify driver/device above us.
631 */
632 if (pThis->pDrvMountNotify)
633 pThis->pDrvMountNotify->pfnMountNotify(pThis->pDrvMountNotify);
634 Log(("drvblockMount: Success\n"));
635 return VINF_SUCCESS;
636 }
637 else
638 rc = VERR_PDM_MISSING_INTERFACE_BELOW;
639
640 /*
641 * Failed, detatch the media driver.
642 */
643 AssertMsgFailed(("No media interface!\n"));
644 int rc2 = PDMDrvHlpDetach(pThis->pDrvIns, fTachFlags);
645 AssertRC(rc2);
646 pThis->pDrvMedia = NULL;
647 return rc;
648}
649
650
651/** @copydoc PDMIMOUNT::pfnUnmount */
652static DECLCALLBACK(int) drvblockUnmount(PPDMIMOUNT pInterface, bool fForce, bool fEject)
653{
654 PDRVBLOCK pThis = PDMIMOUNT_2_DRVBLOCK(pInterface);
655
656 /*
657 * Validate state.
658 */
659 if (!pThis->pDrvMedia)
660 {
661 Log(("drvblockUmount: Not mounted\n"));
662 return VERR_PDM_MEDIA_NOT_MOUNTED;
663 }
664 if (pThis->fLocked && !fForce)
665 {
666 Log(("drvblockUmount: Locked\n"));
667 return VERR_PDM_MEDIA_LOCKED;
668 }
669
670 /* Media is no longer locked even if it was previously. */
671 pThis->fLocked = false;
672
673 /*
674 * Detach the media driver and query it's interface.
675 */
676 int rc = PDMDrvHlpDetach(pThis->pDrvIns, 0 /*fFlags*/);
677 if (RT_FAILURE(rc))
678 {
679 Log(("drvblockUnmount: Detach failed rc=%Rrc\n", rc));
680 return rc;
681 }
682 Assert(!pThis->pDrvMedia);
683
684 /*
685 * Notify driver/device above us.
686 */
687 if (pThis->pDrvMountNotify)
688 pThis->pDrvMountNotify->pfnUnmountNotify(pThis->pDrvMountNotify);
689 Log(("drvblockUnmount: success\n"));
690 return VINF_SUCCESS;
691}
692
693
694/** @copydoc PDMIMOUNT::pfnIsMounted */
695static DECLCALLBACK(bool) drvblockIsMounted(PPDMIMOUNT pInterface)
696{
697 PDRVBLOCK pThis = PDMIMOUNT_2_DRVBLOCK(pInterface);
698 return pThis->pDrvMedia != NULL;
699}
700
701/** @copydoc PDMIMOUNT::pfnLock */
702static DECLCALLBACK(int) drvblockLock(PPDMIMOUNT pInterface)
703{
704 PDRVBLOCK pThis = PDMIMOUNT_2_DRVBLOCK(pInterface);
705 Log(("drvblockLock: %d -> %d\n", pThis->fLocked, true));
706 pThis->fLocked = true;
707 return VINF_SUCCESS;
708}
709
710/** @copydoc PDMIMOUNT::pfnUnlock */
711static DECLCALLBACK(int) drvblockUnlock(PPDMIMOUNT pInterface)
712{
713 PDRVBLOCK pThis = PDMIMOUNT_2_DRVBLOCK(pInterface);
714 Log(("drvblockUnlock: %d -> %d\n", pThis->fLocked, false));
715 pThis->fLocked = false;
716 return VINF_SUCCESS;
717}
718
719/** @copydoc PDMIMOUNT::pfnIsLocked */
720static DECLCALLBACK(bool) drvblockIsLocked(PPDMIMOUNT pInterface)
721{
722 PDRVBLOCK pThis = PDMIMOUNT_2_DRVBLOCK(pInterface);
723 return pThis->fLocked;
724}
725
726
727
728/* -=-=-=-=- IMediaPort -=-=-=-=- */
729
730/** Makes a PDRVBLOCK out of a PPDMIMEDIAPORT. */
731#define PDMIMEDIAPORT_2_DRVBLOCK(pInterface) ( (PDRVBLOCK((uintptr_t)pInterface - RT_OFFSETOF(DRVBLOCK, IMediaPort))) )
732
733/**
734 * @interface_method_impl{PDMIMEDIAPORT,pfnQueryDeviceLocation}
735 */
736static DECLCALLBACK(int) drvblockQueryDeviceLocation(PPDMIMEDIAPORT pInterface, const char **ppcszController,
737 uint32_t *piInstance, uint32_t *piLUN)
738{
739 PDRVBLOCK pThis = PDMIMEDIAPORT_2_DRVBLOCK(pInterface);
740
741 return pThis->pDrvBlockPort->pfnQueryDeviceLocation(pThis->pDrvBlockPort, ppcszController,
742 piInstance, piLUN);
743}
744
745/* -=-=-=-=- IBase -=-=-=-=- */
746
747/**
748 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
749 */
750static DECLCALLBACK(void *) drvblockQueryInterface(PPDMIBASE pInterface, const char *pszIID)
751{
752 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
753 PDRVBLOCK pThis = PDMINS_2_DATA(pDrvIns, PDRVBLOCK);
754
755 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
756 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBLOCK, &pThis->IBlock);
757 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBLOCKBIOS, pThis->fBiosVisible ? &pThis->IBlockBios : NULL);
758 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUNT, pThis->fMountable ? &pThis->IMount : NULL);
759 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBLOCKASYNC, pThis->pDrvMediaAsync ? &pThis->IBlockAsync : NULL);
760 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAASYNCPORT, pThis->pDrvBlockAsyncPort ? &pThis->IMediaAsyncPort : NULL);
761 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMEDIAPORT, &pThis->IMediaPort);
762 return NULL;
763}
764
765
766/* -=-=-=-=- driver interface -=-=-=-=- */
767
768/** @copydoc FNPDMDRVDETACH. */
769static DECLCALLBACK(void) drvblockDetach(PPDMDRVINS pDrvIns, uint32_t fFlags)
770{
771 PDRVBLOCK pThis = PDMINS_2_DATA(pDrvIns, PDRVBLOCK);
772 pThis->pDrvMedia = NULL;
773 pThis->pDrvMediaAsync = NULL;
774 NOREF(fFlags);
775}
776
777/**
778 * Reset notification.
779 *
780 * @returns VBox status.
781 * @param pDevIns The driver instance data.
782 */
783static DECLCALLBACK(void) drvblockReset(PPDMDRVINS pDrvIns)
784{
785 PDRVBLOCK pThis = PDMINS_2_DATA(pDrvIns, PDRVBLOCK);
786
787 pThis->fLocked = false;
788}
789
790/**
791 * Construct a block driver instance.
792 *
793 * @copydoc FNPDMDRVCONSTRUCT
794 */
795static DECLCALLBACK(int) drvblockConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
796{
797 PDRVBLOCK pThis = PDMINS_2_DATA(pDrvIns, PDRVBLOCK);
798 LogFlow(("drvblockConstruct: iInstance=%d\n", pDrvIns->iInstance));
799 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
800
801 /*
802 * Validate configuration.
803 */
804#if defined(VBOX_PERIODIC_FLUSH) || defined(VBOX_IGNORE_FLUSH)
805 if (!CFGMR3AreValuesValid(pCfg, "Type\0Locked\0BIOSVisible\0AttachFailError\0Cylinders\0Heads\0Sectors\0Mountable\0FlushInterval\0IgnoreFlush\0IgnoreFlushAsync\0"))
806#else /* !(VBOX_PERIODIC_FLUSH || VBOX_IGNORE_FLUSH) */
807 if (!CFGMR3AreValuesValid(pCfg, "Type\0Locked\0BIOSVisible\0AttachFailError\0Cylinders\0Heads\0Sectors\0Mountable\0"))
808#endif /* !(VBOX_PERIODIC_FLUSH || VBOX_IGNORE_FLUSH) */
809 return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
810
811 /*
812 * Initialize most of the data members.
813 */
814 pThis->pDrvIns = pDrvIns;
815
816 /* IBase. */
817 pDrvIns->IBase.pfnQueryInterface = drvblockQueryInterface;
818
819 /* IBlock. */
820 pThis->IBlock.pfnRead = drvblockRead;
821 pThis->IBlock.pfnWrite = drvblockWrite;
822 pThis->IBlock.pfnFlush = drvblockFlush;
823 pThis->IBlock.pfnMerge = drvblockMerge;
824 pThis->IBlock.pfnIsReadOnly = drvblockIsReadOnly;
825 pThis->IBlock.pfnGetSize = drvblockGetSize;
826 pThis->IBlock.pfnGetType = drvblockGetType;
827 pThis->IBlock.pfnGetUuid = drvblockGetUuid;
828
829 /* IBlockBios. */
830 pThis->IBlockBios.pfnGetPCHSGeometry = drvblockGetPCHSGeometry;
831 pThis->IBlockBios.pfnSetPCHSGeometry = drvblockSetPCHSGeometry;
832 pThis->IBlockBios.pfnGetLCHSGeometry = drvblockGetLCHSGeometry;
833 pThis->IBlockBios.pfnSetLCHSGeometry = drvblockSetLCHSGeometry;
834 pThis->IBlockBios.pfnIsVisible = drvblockIsVisible;
835 pThis->IBlockBios.pfnGetType = drvblockBiosGetType;
836
837 /* IMount. */
838 pThis->IMount.pfnMount = drvblockMount;
839 pThis->IMount.pfnUnmount = drvblockUnmount;
840 pThis->IMount.pfnIsMounted = drvblockIsMounted;
841 pThis->IMount.pfnLock = drvblockLock;
842 pThis->IMount.pfnUnlock = drvblockUnlock;
843 pThis->IMount.pfnIsLocked = drvblockIsLocked;
844
845 /* IBlockAsync. */
846 pThis->IBlockAsync.pfnStartRead = drvblockAsyncReadStart;
847 pThis->IBlockAsync.pfnStartWrite = drvblockAsyncWriteStart;
848 pThis->IBlockAsync.pfnStartFlush = drvblockAsyncFlushStart;
849
850 /* IMediaAsyncPort. */
851 pThis->IMediaAsyncPort.pfnTransferCompleteNotify = drvblockAsyncTransferCompleteNotify;
852
853 /* IMediaPort */
854 pThis->IMediaPort.pfnQueryDeviceLocation = drvblockQueryDeviceLocation;
855
856 /*
857 * Get the IBlockPort & IMountNotify interfaces of the above driver/device.
858 */
859 pThis->pDrvBlockPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIBLOCKPORT);
860 if (!pThis->pDrvBlockPort)
861 return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_MISSING_INTERFACE_ABOVE,
862 N_("No block port interface above"));
863
864 /* Try to get the optional async block port interface above. */
865 pThis->pDrvBlockAsyncPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIBLOCKASYNCPORT);
866 pThis->pDrvMountNotify = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMIMOUNTNOTIFY);
867
868 /*
869 * Query configuration.
870 */
871 /* type */
872 char *psz;
873 int rc = CFGMR3QueryStringAlloc(pCfg, "Type", &psz);
874 if (RT_FAILURE(rc))
875 return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_BLOCK_NO_TYPE, N_("Failed to obtain the type"));
876 if (!strcmp(psz, "HardDisk"))
877 pThis->enmType = PDMBLOCKTYPE_HARD_DISK;
878 else if (!strcmp(psz, "DVD"))
879 pThis->enmType = PDMBLOCKTYPE_DVD;
880 else if (!strcmp(psz, "CDROM"))
881 pThis->enmType = PDMBLOCKTYPE_CDROM;
882 else if (!strcmp(psz, "Floppy 2.88"))
883 pThis->enmType = PDMBLOCKTYPE_FLOPPY_2_88;
884 else if (!strcmp(psz, "Floppy 1.44"))
885 pThis->enmType = PDMBLOCKTYPE_FLOPPY_1_44;
886 else if (!strcmp(psz, "Floppy 1.20"))
887 pThis->enmType = PDMBLOCKTYPE_FLOPPY_1_20;
888 else if (!strcmp(psz, "Floppy 720"))
889 pThis->enmType = PDMBLOCKTYPE_FLOPPY_720;
890 else if (!strcmp(psz, "Floppy 360"))
891 pThis->enmType = PDMBLOCKTYPE_FLOPPY_360;
892 else
893 {
894 PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_BLOCK_UNKNOWN_TYPE, RT_SRC_POS,
895 N_("Unknown type \"%s\""), psz);
896 MMR3HeapFree(psz);
897 return VERR_PDM_BLOCK_UNKNOWN_TYPE;
898 }
899 Log2(("drvblockConstruct: enmType=%d\n", pThis->enmType));
900 MMR3HeapFree(psz); psz = NULL;
901
902 /* Mountable */
903 rc = CFGMR3QueryBoolDef(pCfg, "Mountable", &pThis->fMountable, false);
904 if (RT_FAILURE(rc))
905 return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Mountable\" from the config"));
906
907 /* Locked */
908 rc = CFGMR3QueryBoolDef(pCfg, "Locked", &pThis->fLocked, false);
909 if (RT_FAILURE(rc))
910 return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Locked\" from the config"));
911
912 /* BIOS visible */
913 rc = CFGMR3QueryBoolDef(pCfg, "BIOSVisible", &pThis->fBiosVisible, true);
914 if (RT_FAILURE(rc))
915 return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"BIOSVisible\" from the config"));
916
917 /** @todo AttachFailError is currently completely ignored. */
918
919 /* Cylinders */
920 rc = CFGMR3QueryU32Def(pCfg, "Cylinders", &pThis->LCHSGeometry.cCylinders, 0);
921 if (RT_FAILURE(rc))
922 return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Cylinders\" from the config"));
923
924 /* Heads */
925 rc = CFGMR3QueryU32Def(pCfg, "Heads", &pThis->LCHSGeometry.cHeads, 0);
926 if (RT_FAILURE(rc))
927 return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Heads\" from the config"));
928
929 /* Sectors */
930 rc = CFGMR3QueryU32Def(pCfg, "Sectors", &pThis->LCHSGeometry.cSectors, 0);
931 if (RT_FAILURE(rc))
932 return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Sectors\" from the config"));
933
934 /* Uuid */
935 rc = CFGMR3QueryStringAlloc(pCfg, "Uuid", &psz);
936 if (rc == VERR_CFGM_VALUE_NOT_FOUND)
937 RTUuidClear(&pThis->Uuid);
938 else if (RT_SUCCESS(rc))
939 {
940 rc = RTUuidFromStr(&pThis->Uuid, psz);
941 if (RT_FAILURE(rc))
942 {
943 PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, "%s",
944 N_("Uuid from string failed on \"%s\""), psz);
945 MMR3HeapFree(psz);
946 return rc;
947 }
948 MMR3HeapFree(psz); psz = NULL;
949 }
950 else
951 return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"Uuid\" from the config"));
952
953#ifdef VBOX_PERIODIC_FLUSH
954 rc = CFGMR3QueryU32Def(pCfg, "FlushInterval", &pThis->cbFlushInterval, 0);
955 if (RT_FAILURE(rc))
956 return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"FlushInterval\" from the config"));
957#endif /* VBOX_PERIODIC_FLUSH */
958
959#ifdef VBOX_IGNORE_FLUSH
960 rc = CFGMR3QueryBoolDef(pCfg, "IgnoreFlush", &pThis->fIgnoreFlush, true);
961 if (RT_FAILURE(rc))
962 return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"IgnoreFlush\" from the config"));
963
964 if (pThis->fIgnoreFlush)
965 LogRel(("DrvBlock: Flushes will be ignored\n"));
966 else
967 LogRel(("DrvBlock: Flushes will be passed to the disk\n"));
968
969 rc = CFGMR3QueryBoolDef(pCfg, "IgnoreFlushAsync", &pThis->fIgnoreFlushAsync, false);
970 if (RT_FAILURE(rc))
971 return PDMDRV_SET_ERROR(pDrvIns, rc, N_("Failed to query \"IgnoreFlushAsync\" from the config"));
972
973 if (pThis->fIgnoreFlushAsync)
974 LogRel(("DrvBlock: Async flushes will be ignored\n"));
975 else
976 LogRel(("DrvBlock: Async flushes will be passed to the disk\n"));
977#endif /* VBOX_IGNORE_FLUSH */
978
979 /*
980 * Try attach driver below and query it's media interface.
981 */
982 PPDMIBASE pBase;
983 rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pBase);
984 if ( rc == VERR_PDM_NO_ATTACHED_DRIVER
985 && pThis->enmType != PDMBLOCKTYPE_HARD_DISK)
986 return VINF_SUCCESS;
987 if (RT_FAILURE(rc))
988 return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
989 N_("Failed to attach driver below us! %Rrf"), rc);
990
991 pThis->pDrvMedia = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMEDIA);
992 if (!pThis->pDrvMedia)
993 return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_MISSING_INTERFACE_BELOW,
994 N_("No media or async media interface below"));
995
996 /* Try to get the optional async interface. */
997 pThis->pDrvMediaAsync = PDMIBASE_QUERY_INTERFACE(pBase, PDMIMEDIAASYNC);
998
999 if (pThis->pDrvMedia->pfnDiscard)
1000 pThis->IBlock.pfnDiscard = drvblockDiscard;
1001
1002 if ( pThis->pDrvMediaAsync
1003 && pThis->pDrvMediaAsync->pfnStartDiscard)
1004 pThis->IBlockAsync.pfnStartDiscard = drvblockStartDiscard;
1005
1006 if (RTUuidIsNull(&pThis->Uuid))
1007 {
1008 if (pThis->enmType == PDMBLOCKTYPE_HARD_DISK)
1009 pThis->pDrvMedia->pfnGetUuid(pThis->pDrvMedia, &pThis->Uuid);
1010 }
1011
1012 return VINF_SUCCESS;
1013}
1014
1015
1016/**
1017 * Block driver registration record.
1018 */
1019const PDMDRVREG g_DrvBlock =
1020{
1021 /* u32Version */
1022 PDM_DRVREG_VERSION,
1023 /* szName */
1024 "Block",
1025 /* szRCMod */
1026 "",
1027 /* szR0Mod */
1028 "",
1029 /* pszDescription */
1030 "Generic block driver.",
1031 /* fFlags */
1032 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1033 /* fClass. */
1034 PDM_DRVREG_CLASS_BLOCK,
1035 /* cMaxInstances */
1036 ~0,
1037 /* cbInstance */
1038 sizeof(DRVBLOCK),
1039 /* pfnConstruct */
1040 drvblockConstruct,
1041 /* pfnDestruct */
1042 NULL,
1043 /* pfnRelocate */
1044 NULL,
1045 /* pfnIOCtl */
1046 NULL,
1047 /* pfnPowerOn */
1048 NULL,
1049 /* pfnReset */
1050 drvblockReset,
1051 /* pfnSuspend */
1052 NULL,
1053 /* pfnResume */
1054 NULL,
1055 /* pfnAttach */
1056 NULL,
1057 /* pfnDetach */
1058 drvblockDetach,
1059 /* pfnPowerOff */
1060 NULL,
1061 /* pfnSoftReset */
1062 NULL,
1063 /* u32EndVersion */
1064 PDM_DRVREG_VERSION
1065};
1066
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