VirtualBox

source: vbox/trunk/src/VBox/Devices/Storage/DrvSCSI.cpp@ 43640

Last change on this file since 43640 was 43640, checked in by vboxsync, 12 years ago

VSCSI: Added basic media change support.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 37.5 KB
Line 
1/* $Id: DrvSCSI.cpp 43640 2012-10-15 12:39:52Z vboxsync $ */
2/** @file
3 * VBox storage drivers: Generic SCSI command parser and execution 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* Header Files *
20*******************************************************************************/
21//#define DEBUG
22#define LOG_GROUP LOG_GROUP_DRV_SCSI
23#include <VBox/vmm/pdmdrv.h>
24#include <VBox/vmm/pdmifs.h>
25#include <VBox/vmm/pdmthread.h>
26#include <VBox/vscsi.h>
27#include <VBox/scsi.h>
28#include <iprt/asm.h>
29#include <iprt/assert.h>
30#include <iprt/mem.h>
31#include <iprt/req.h>
32#include <iprt/semaphore.h>
33#include <iprt/string.h>
34#include <iprt/uuid.h>
35
36#include "VBoxDD.h"
37
38/** The maximum number of release log entries per device. */
39#define MAX_LOG_REL_ERRORS 1024
40
41/**
42 * SCSI driver instance data.
43 *
44 * @implements PDMISCSICONNECTOR
45 * @implements PDMIBLOCKASYNCPORT
46 * @implements PDMIMOUNTNOTIFY
47 */
48typedef struct DRVSCSI
49{
50 /** Pointer driver instance. */
51 PPDMDRVINS pDrvIns;
52
53 /** Pointer to the attached driver's base interface. */
54 PPDMIBASE pDrvBase;
55 /** Pointer to the attached driver's block interface. */
56 PPDMIBLOCK pDrvBlock;
57 /** Pointer to the attached driver's async block interface. */
58 PPDMIBLOCKASYNC pDrvBlockAsync;
59 /** Pointer to the attached driver's block bios interface. */
60 PPDMIBLOCKBIOS pDrvBlockBios;
61 /** Pointer to the attached driver's mount interface. */
62 PPDMIMOUNT pDrvMount;
63 /** Pointer to the SCSI port interface of the device above. */
64 PPDMISCSIPORT pDevScsiPort;
65 /** pointer to the Led port interface of the dveice above. */
66 PPDMILEDPORTS pLedPort;
67 /** The scsi connector interface .*/
68 PDMISCSICONNECTOR ISCSIConnector;
69 /** The block port interface. */
70 PDMIBLOCKPORT IPort;
71 /** The optional block async port interface. */
72 PDMIBLOCKASYNCPORT IPortAsync;
73 /** The mount notify interface. */
74 PDMIMOUNTNOTIFY IMountNotify;
75 /** Fallback status LED state for this drive.
76 * This is used in case the device doesn't has a LED interface. */
77 PDMLED Led;
78 /** Pointer to the status LED for this drive. */
79 PPDMLED pLed;
80
81 /** VSCSI device handle. */
82 VSCSIDEVICE hVScsiDevice;
83 /** VSCSI LUN handle. */
84 VSCSILUN hVScsiLun;
85 /** I/O callbacks. */
86 VSCSILUNIOCALLBACKS VScsiIoCallbacks;
87
88 /** The dedicated I/O thread for the non async approach. */
89 PPDMTHREAD pAsyncIOThread;
90 /** Queue for passing the requests to the thread. */
91 RTREQQUEUE hQueueRequests;
92 /** Request that we've left pending on wakeup or reset. */
93 PRTREQ pPendingDummyReq;
94 /** Indicates whether PDMDrvHlpAsyncNotificationCompleted should be called by
95 * any of the dummy functions. */
96 bool volatile fDummySignal;
97 /** Release statistics: number of bytes written. */
98 STAMCOUNTER StatBytesWritten;
99 /** Release statistics: number of bytes read. */
100 STAMCOUNTER StatBytesRead;
101 /** Release statistics: Current I/O depth. */
102 volatile uint32_t StatIoDepth;
103 /** Errors printed in the release log. */
104 unsigned cErrors;
105 /** Mark the drive as having a non-rotational medium (i.e. as a SSD). */
106 bool fNonRotational;
107 /** Medium is readonly */
108 bool fReadonly;
109} DRVSCSI, *PDRVSCSI;
110
111/** Converts a pointer to DRVSCSI::ISCSIConnector to a PDRVSCSI. */
112#define PDMISCSICONNECTOR_2_DRVSCSI(pInterface) ( (PDRVSCSI)((uintptr_t)pInterface - RT_OFFSETOF(DRVSCSI, ISCSIConnector)) )
113/** Converts a pointer to DRVSCSI::IPortAsync to a PDRVSCSI. */
114#define PDMIBLOCKASYNCPORT_2_DRVSCSI(pInterface) ( (PDRVSCSI)((uintptr_t)pInterface - RT_OFFSETOF(DRVSCSI, IPortAsync)) )
115/** Converts a pointer to DRVSCSI::IMountNotify to PDRVSCSI. */
116#define PDMIMOUNTNOTIFY_2_DRVSCSI(pInterface) ( (PDRVSCSI)((uintptr_t)pInterface - RT_OFFSETOF(DRVSCSI, IMountNotify)) )
117/** Converts a pointer to DRVSCSI::IPort to a PDRVSCSI. */
118#define PDMIBLOCKPORT_2_DRVSCSI(pInterface) ( (PDRVSCSI)((uintptr_t)pInterface - RT_OFFSETOF(DRVSCSI, IPort)) )
119
120static bool drvscsiIsRedoPossible(int rc)
121{
122 if ( rc == VERR_DISK_FULL
123 || rc == VERR_FILE_TOO_BIG
124 || rc == VERR_BROKEN_PIPE
125 || rc == VERR_NET_CONNECTION_REFUSED)
126 return true;
127
128 return false;
129}
130
131static int drvscsiProcessRequestOne(PDRVSCSI pThis, VSCSIIOREQ hVScsiIoReq)
132{
133 int rc = VINF_SUCCESS;
134 VSCSIIOREQTXDIR enmTxDir;
135
136 enmTxDir = VSCSIIoReqTxDirGet(hVScsiIoReq);
137
138 switch (enmTxDir)
139 {
140 case VSCSIIOREQTXDIR_FLUSH:
141 {
142 rc = pThis->pDrvBlock->pfnFlush(pThis->pDrvBlock);
143 if ( RT_FAILURE(rc)
144 && pThis->cErrors++ < MAX_LOG_REL_ERRORS)
145 LogRel(("SCSI#%u: Flush returned rc=%Rrc\n",
146 pThis->pDrvIns->iInstance, rc));
147 break;
148 }
149 case VSCSIIOREQTXDIR_READ:
150 case VSCSIIOREQTXDIR_WRITE:
151 {
152 uint64_t uOffset = 0;
153 size_t cbTransfer = 0;
154 size_t cbSeg = 0;
155 PCRTSGSEG paSeg = NULL;
156 unsigned cSeg = 0;
157
158 rc = VSCSIIoReqParamsGet(hVScsiIoReq, &uOffset, &cbTransfer, &cSeg, &cbSeg,
159 &paSeg);
160 AssertRC(rc);
161
162 while (cbTransfer && cSeg)
163 {
164 size_t cbProcess = (cbTransfer < paSeg->cbSeg) ? cbTransfer : paSeg->cbSeg;
165
166 Log(("%s: uOffset=%llu cbProcess=%u\n", __FUNCTION__, uOffset, cbProcess));
167
168 if (enmTxDir == VSCSIIOREQTXDIR_READ)
169 {
170 pThis->pLed->Asserted.s.fReading = pThis->pLed->Actual.s.fReading = 1;
171 rc = pThis->pDrvBlock->pfnRead(pThis->pDrvBlock, uOffset,
172 paSeg->pvSeg, cbProcess);
173 pThis->pLed->Actual.s.fReading = 0;
174 if (RT_FAILURE(rc))
175 break;
176 STAM_REL_COUNTER_ADD(&pThis->StatBytesRead, cbProcess);
177 }
178 else
179 {
180 pThis->pLed->Asserted.s.fWriting = pThis->pLed->Actual.s.fWriting = 1;
181 rc = pThis->pDrvBlock->pfnWrite(pThis->pDrvBlock, uOffset,
182 paSeg->pvSeg, cbProcess);
183 pThis->pLed->Actual.s.fWriting = 0;
184 if (RT_FAILURE(rc))
185 break;
186 STAM_REL_COUNTER_ADD(&pThis->StatBytesWritten, cbProcess);
187 }
188
189 /* Go to the next entry. */
190 uOffset += cbProcess;
191 cbTransfer -= cbProcess;
192 paSeg++;
193 cSeg--;
194 }
195
196 if ( RT_FAILURE(rc)
197 && pThis->cErrors++ < MAX_LOG_REL_ERRORS)
198 LogRel(("SCSI#%u: %s at offset %llu (%u bytes left) returned rc=%Rrc\n",
199 pThis->pDrvIns->iInstance,
200 enmTxDir == VSCSIIOREQTXDIR_READ
201 ? "Read"
202 : "Write",
203 uOffset,
204 cbTransfer, rc));
205
206 break;
207 }
208 case VSCSIIOREQTXDIR_UNMAP:
209 {
210 PCRTRANGE paRanges;
211 unsigned cRanges;
212
213 rc = VSCSIIoReqUnmapParamsGet(hVScsiIoReq, &paRanges, &cRanges);
214 AssertRC(rc);
215
216 pThis->pLed->Asserted.s.fWriting = pThis->pLed->Actual.s.fWriting = 1;
217 rc = pThis->pDrvBlock->pfnDiscard(pThis->pDrvBlock, paRanges, cRanges);
218 pThis->pLed->Actual.s.fWriting = 0;
219
220 if ( RT_FAILURE(rc)
221 && pThis->cErrors++ < MAX_LOG_REL_ERRORS)
222 LogRel(("SCSI#%u: Unmap returned rc=%Rrc\n",
223 pThis->pDrvIns->iInstance, rc));
224
225 break;
226 }
227 default:
228 AssertMsgFailed(("Invalid transfer direction %d\n", enmTxDir));
229 }
230
231 if (RT_SUCCESS(rc))
232 VSCSIIoReqCompleted(hVScsiIoReq, rc, false /* fRedoPossible */);
233 else
234 VSCSIIoReqCompleted(hVScsiIoReq, rc, drvscsiIsRedoPossible(rc));
235
236 return VINF_SUCCESS;
237}
238
239static DECLCALLBACK(int) drvscsiGetSize(VSCSILUN hVScsiLun, void *pvScsiLunUser, uint64_t *pcbSize)
240{
241 PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
242
243 *pcbSize = pThis->pDrvBlock->pfnGetSize(pThis->pDrvBlock);
244
245 return VINF_SUCCESS;
246}
247
248static int drvscsiTransferCompleteNotify(PPDMIBLOCKASYNCPORT pInterface, void *pvUser, int rc)
249{
250 PDRVSCSI pThis = PDMIBLOCKASYNCPORT_2_DRVSCSI(pInterface);
251 VSCSIIOREQ hVScsiIoReq = (VSCSIIOREQ)pvUser;
252 VSCSIIOREQTXDIR enmTxDir = VSCSIIoReqTxDirGet(hVScsiIoReq);
253
254 LogFlowFunc(("Request hVScsiIoReq=%#p completed\n", hVScsiIoReq));
255
256 if (enmTxDir == VSCSIIOREQTXDIR_READ)
257 pThis->pLed->Actual.s.fReading = 0;
258 else if ( enmTxDir == VSCSIIOREQTXDIR_WRITE
259 || enmTxDir == VSCSIIOREQTXDIR_UNMAP)
260 pThis->pLed->Actual.s.fWriting = 0;
261 else
262 AssertMsg(enmTxDir == VSCSIIOREQTXDIR_FLUSH, ("Invalid transfer direction %u\n", enmTxDir));
263
264 if (RT_SUCCESS(rc))
265 VSCSIIoReqCompleted(hVScsiIoReq, rc, false /* fRedoPossible */);
266 else
267 {
268 pThis->cErrors++;
269 if (pThis->cErrors < MAX_LOG_REL_ERRORS)
270 {
271 if (enmTxDir == VSCSIIOREQTXDIR_FLUSH)
272 LogRel(("SCSI#%u: Flush returned rc=%Rrc\n",
273 pThis->pDrvIns->iInstance, rc));
274 else if (enmTxDir == VSCSIIOREQTXDIR_UNMAP)
275 LogRel(("SCSI#%u: Unmap returned rc=%Rrc\n",
276 pThis->pDrvIns->iInstance, rc));
277 else
278 {
279 uint64_t uOffset = 0;
280 size_t cbTransfer = 0;
281 size_t cbSeg = 0;
282 PCRTSGSEG paSeg = NULL;
283 unsigned cSeg = 0;
284
285 VSCSIIoReqParamsGet(hVScsiIoReq, &uOffset, &cbTransfer,
286 &cSeg, &cbSeg, &paSeg);
287
288 LogRel(("SCSI#%u: %s at offset %llu (%u bytes left) returned rc=%Rrc\n",
289 pThis->pDrvIns->iInstance,
290 enmTxDir == VSCSIIOREQTXDIR_READ
291 ? "Read"
292 : "Write",
293 uOffset,
294 cbTransfer, rc));
295 }
296 }
297
298 VSCSIIoReqCompleted(hVScsiIoReq, rc, drvscsiIsRedoPossible(rc));
299 }
300
301 return VINF_SUCCESS;
302}
303
304static DECLCALLBACK(int) drvscsiReqTransferEnqueue(VSCSILUN hVScsiLun,
305 void *pvScsiLunUser,
306 VSCSIIOREQ hVScsiIoReq)
307{
308 int rc = VINF_SUCCESS;
309 PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
310
311 if (pThis->pDrvBlockAsync)
312 {
313 /* async I/O path. */
314 VSCSIIOREQTXDIR enmTxDir;
315
316 LogFlowFunc(("Enqueuing hVScsiIoReq=%#p\n", hVScsiIoReq));
317
318 enmTxDir = VSCSIIoReqTxDirGet(hVScsiIoReq);
319
320 switch (enmTxDir)
321 {
322 case VSCSIIOREQTXDIR_FLUSH:
323 {
324 rc = pThis->pDrvBlockAsync->pfnStartFlush(pThis->pDrvBlockAsync, hVScsiIoReq);
325 if ( RT_FAILURE(rc)
326 && rc != VERR_VD_ASYNC_IO_IN_PROGRESS
327 && pThis->cErrors++ < MAX_LOG_REL_ERRORS)
328 LogRel(("SCSI#%u: Flush returned rc=%Rrc\n",
329 pThis->pDrvIns->iInstance, rc));
330 break;
331 }
332 case VSCSIIOREQTXDIR_UNMAP:
333 {
334 PCRTRANGE paRanges;
335 unsigned cRanges;
336
337 rc = VSCSIIoReqUnmapParamsGet(hVScsiIoReq, &paRanges, &cRanges);
338 AssertRC(rc);
339
340 pThis->pLed->Asserted.s.fWriting = pThis->pLed->Actual.s.fWriting = 1;
341 rc = pThis->pDrvBlockAsync->pfnStartDiscard(pThis->pDrvBlockAsync, paRanges, cRanges, hVScsiIoReq);
342 if ( RT_FAILURE(rc)
343 && rc != VERR_VD_ASYNC_IO_IN_PROGRESS
344 && pThis->cErrors++ < MAX_LOG_REL_ERRORS)
345 LogRel(("SCSI#%u: Discard returned rc=%Rrc\n",
346 pThis->pDrvIns->iInstance, rc));
347 break;
348 }
349 case VSCSIIOREQTXDIR_READ:
350 case VSCSIIOREQTXDIR_WRITE:
351 {
352 uint64_t uOffset = 0;
353 size_t cbTransfer = 0;
354 size_t cbSeg = 0;
355 PCRTSGSEG paSeg = NULL;
356 unsigned cSeg = 0;
357
358 rc = VSCSIIoReqParamsGet(hVScsiIoReq, &uOffset, &cbTransfer,
359 &cSeg, &cbSeg, &paSeg);
360 AssertRC(rc);
361
362 if (enmTxDir == VSCSIIOREQTXDIR_READ)
363 {
364 pThis->pLed->Asserted.s.fReading = pThis->pLed->Actual.s.fReading = 1;
365 rc = pThis->pDrvBlockAsync->pfnStartRead(pThis->pDrvBlockAsync, uOffset,
366 paSeg, cSeg, cbTransfer,
367 hVScsiIoReq);
368 STAM_REL_COUNTER_ADD(&pThis->StatBytesRead, cbTransfer);
369 }
370 else
371 {
372 pThis->pLed->Asserted.s.fWriting = pThis->pLed->Actual.s.fWriting = 1;
373 rc = pThis->pDrvBlockAsync->pfnStartWrite(pThis->pDrvBlockAsync, uOffset,
374 paSeg, cSeg, cbTransfer,
375 hVScsiIoReq);
376 STAM_REL_COUNTER_ADD(&pThis->StatBytesWritten, cbTransfer);
377 }
378
379 if ( RT_FAILURE(rc)
380 && rc != VERR_VD_ASYNC_IO_IN_PROGRESS
381 && pThis->cErrors++ < MAX_LOG_REL_ERRORS)
382 LogRel(("SCSI#%u: %s at offset %llu (%u bytes left) returned rc=%Rrc\n",
383 pThis->pDrvIns->iInstance,
384 enmTxDir == VSCSIIOREQTXDIR_READ
385 ? "Read"
386 : "Write",
387 uOffset,
388 cbTransfer, rc));
389 break;
390 }
391 default:
392 AssertMsgFailed(("Invalid transfer direction %u\n", enmTxDir));
393 }
394
395 if (rc == VINF_VD_ASYNC_IO_FINISHED)
396 {
397 if (enmTxDir == VSCSIIOREQTXDIR_READ)
398 pThis->pLed->Actual.s.fReading = 0;
399 else if (enmTxDir == VSCSIIOREQTXDIR_WRITE)
400 pThis->pLed->Actual.s.fWriting = 0;
401 else
402 AssertMsg(enmTxDir == VSCSIIOREQTXDIR_FLUSH, ("Invalid transfer direction %u\n", enmTxDir));
403
404 VSCSIIoReqCompleted(hVScsiIoReq, VINF_SUCCESS, false);
405 rc = VINF_SUCCESS;
406 }
407 else if (rc == VERR_VD_ASYNC_IO_IN_PROGRESS)
408 rc = VINF_SUCCESS;
409 else if (RT_FAILURE(rc))
410 {
411 if (enmTxDir == VSCSIIOREQTXDIR_READ)
412 pThis->pLed->Actual.s.fReading = 0;
413 else if (enmTxDir == VSCSIIOREQTXDIR_WRITE)
414 pThis->pLed->Actual.s.fWriting = 0;
415 else
416 AssertMsg(enmTxDir == VSCSIIOREQTXDIR_FLUSH, ("Invalid transfer direction %u\n", enmTxDir));
417
418 VSCSIIoReqCompleted(hVScsiIoReq, rc, drvscsiIsRedoPossible(rc));
419 rc = VINF_SUCCESS;
420 }
421 else
422 AssertMsgFailed(("Invalid return code rc=%Rrc\n", rc));
423 }
424 else
425 {
426 /* I/O thread. */
427 rc = RTReqQueueCallEx(pThis->hQueueRequests, NULL, 0, RTREQFLAGS_NO_WAIT,
428 (PFNRT)drvscsiProcessRequestOne, 2, pThis, hVScsiIoReq);
429 }
430
431 return rc;
432}
433
434static DECLCALLBACK(int) drvscsiGetFeatureFlags(VSCSILUN hVScsiLun,
435 void *pvScsiLunUser,
436 uint64_t *pfFeatures)
437{
438 int rc = VINF_SUCCESS;
439 PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser;
440
441 *pfFeatures = 0;
442
443 if ( pThis->pDrvBlock->pfnDiscard
444 || ( pThis->pDrvBlockAsync
445 && pThis->pDrvBlockAsync->pfnStartDiscard))
446 *pfFeatures |= VSCSI_LUN_FEATURE_UNMAP;
447
448 if (pThis->fNonRotational)
449 *pfFeatures |= VSCSI_LUN_FEATURE_NON_ROTATIONAL;
450
451 if (pThis->fReadonly)
452 *pfFeatures |= VSCSI_LUN_FEATURE_READONLY;
453
454 return VINF_SUCCESS;
455}
456
457static void drvscsiVScsiReqCompleted(VSCSIDEVICE hVScsiDevice, void *pVScsiDeviceUser,
458 void *pVScsiReqUser, int rcScsiCode, bool fRedoPossible,
459 int rcReq)
460{
461 PDRVSCSI pThis = (PDRVSCSI)pVScsiDeviceUser;
462
463 ASMAtomicDecU32(&pThis->StatIoDepth);
464
465 pThis->pDevScsiPort->pfnSCSIRequestCompleted(pThis->pDevScsiPort, (PPDMSCSIREQUEST)pVScsiReqUser,
466 rcScsiCode, fRedoPossible, rcReq);
467
468 if (RT_UNLIKELY(pThis->fDummySignal) && !pThis->StatIoDepth)
469 PDMDrvHlpAsyncNotificationCompleted(pThis->pDrvIns);
470}
471
472/**
473 * Dummy request function used by drvscsiReset to wait for all pending requests
474 * to complete prior to the device reset.
475 *
476 * @param pThis Pointer to the instance data.
477 * @returns VINF_SUCCESS.
478 */
479static int drvscsiAsyncIOLoopSyncCallback(PDRVSCSI pThis)
480{
481 if (pThis->fDummySignal)
482 PDMDrvHlpAsyncNotificationCompleted(pThis->pDrvIns);
483 return VINF_SUCCESS;
484}
485
486/**
487 * Request function to wakeup the thread.
488 *
489 * @param pThis Pointer to the instance data.
490 * @returns VWRN_STATE_CHANGED.
491 */
492static int drvscsiAsyncIOLoopWakeupFunc(PDRVSCSI pThis)
493{
494 if (pThis->fDummySignal)
495 PDMDrvHlpAsyncNotificationCompleted(pThis->pDrvIns);
496 return VWRN_STATE_CHANGED;
497}
498
499/**
500 * The thread function which processes the requests asynchronously.
501 *
502 * @returns VBox status code.
503 * @param pDrvIns Pointer to the driver instance data.
504 * @param pThread Pointer to the thread instance data.
505 */
506static int drvscsiAsyncIOLoop(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
507{
508 int rc = VINF_SUCCESS;
509 PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
510
511 LogFlowFunc(("Entering async IO loop.\n"));
512
513 if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
514 return VINF_SUCCESS;
515
516 while (pThread->enmState == PDMTHREADSTATE_RUNNING)
517 {
518 rc = RTReqQueueProcess(pThis->hQueueRequests, RT_INDEFINITE_WAIT);
519 AssertMsg(rc == VWRN_STATE_CHANGED, ("Left RTReqProcess and error code is not VWRN_STATE_CHANGED rc=%Rrc\n", rc));
520 }
521
522 return VINF_SUCCESS;
523}
524
525/**
526 * Deals with any pending dummy request
527 *
528 * @returns true if no pending dummy request, false if still pending.
529 * @param pThis The instance data.
530 * @param cMillies The number of milliseconds to wait for any
531 * pending request to finish.
532 */
533static bool drvscsiAsyncIOLoopNoPendingDummy(PDRVSCSI pThis, uint32_t cMillies)
534{
535 if (!pThis->pPendingDummyReq)
536 return true;
537 int rc = RTReqWait(pThis->pPendingDummyReq, cMillies);
538 if (RT_FAILURE(rc))
539 return false;
540 RTReqRelease(pThis->pPendingDummyReq);
541 pThis->pPendingDummyReq = NULL;
542 return true;
543}
544
545static int drvscsiAsyncIOLoopWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
546{
547 PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
548 PRTREQ pReq;
549 int rc;
550
551 AssertMsgReturn(pThis->hQueueRequests != NIL_RTREQQUEUE, ("hQueueRequests is NULL\n"), VERR_INVALID_STATE);
552
553 if (!drvscsiAsyncIOLoopNoPendingDummy(pThis, 10000 /* 10 sec */))
554 {
555 LogRel(("drvscsiAsyncIOLoopWakeup#%u: previous dummy request is still pending\n", pDrvIns->iInstance));
556 return VERR_TIMEOUT;
557 }
558
559 rc = RTReqQueueCall(pThis->hQueueRequests, &pReq, 10000 /* 10 sec. */, (PFNRT)drvscsiAsyncIOLoopWakeupFunc, 1, pThis);
560 if (RT_SUCCESS(rc))
561 RTReqRelease(pReq);
562 else
563 {
564 pThis->pPendingDummyReq = pReq;
565 LogRel(("drvscsiAsyncIOLoopWakeup#%u: %Rrc pReq=%p\n", pDrvIns->iInstance, rc, pReq));
566 }
567
568 return rc;
569}
570
571/* -=-=-=-=- ISCSIConnector -=-=-=-=- */
572
573#ifdef DEBUG
574/**
575 * Dumps a SCSI request structure for debugging purposes.
576 *
577 * @returns nothing.
578 * @param pRequest Pointer to the request to dump.
579 */
580static void drvscsiDumpScsiRequest(PPDMSCSIREQUEST pRequest)
581{
582 Log(("Dump for pRequest=%#p Command: %s\n", pRequest, SCSICmdText(pRequest->pbCDB[0])));
583 Log(("cbCDB=%u\n", pRequest->cbCDB));
584 for (uint32_t i = 0; i < pRequest->cbCDB; i++)
585 Log(("pbCDB[%u]=%#x\n", i, pRequest->pbCDB[i]));
586 Log(("cbScatterGather=%u\n", pRequest->cbScatterGather));
587 Log(("cScatterGatherEntries=%u\n", pRequest->cScatterGatherEntries));
588 /* Print all scatter gather entries. */
589 for (uint32_t i = 0; i < pRequest->cScatterGatherEntries; i++)
590 {
591 Log(("ScatterGatherEntry[%u].cbSeg=%u\n", i, pRequest->paScatterGatherHead[i].cbSeg));
592 Log(("ScatterGatherEntry[%u].pvSeg=%#p\n", i, pRequest->paScatterGatherHead[i].pvSeg));
593 }
594 Log(("pvUser=%#p\n", pRequest->pvUser));
595}
596#endif
597
598/** @copydoc PDMISCSICONNECTOR::pfnSCSIRequestSend. */
599static DECLCALLBACK(int) drvscsiRequestSend(PPDMISCSICONNECTOR pInterface, PPDMSCSIREQUEST pSCSIRequest)
600{
601 int rc;
602 PDRVSCSI pThis = PDMISCSICONNECTOR_2_DRVSCSI(pInterface);
603 VSCSIREQ hVScsiReq;
604
605#ifdef DEBUG
606 drvscsiDumpScsiRequest(pSCSIRequest);
607#endif
608
609 rc = VSCSIDeviceReqCreate(pThis->hVScsiDevice, &hVScsiReq,
610 pSCSIRequest->uLogicalUnit,
611 pSCSIRequest->pbCDB,
612 pSCSIRequest->cbCDB,
613 pSCSIRequest->cbScatterGather,
614 pSCSIRequest->cScatterGatherEntries,
615 pSCSIRequest->paScatterGatherHead,
616 pSCSIRequest->pbSenseBuffer,
617 pSCSIRequest->cbSenseBuffer,
618 pSCSIRequest);
619 if (RT_FAILURE(rc))
620 return rc;
621
622 ASMAtomicIncU32(&pThis->StatIoDepth);
623 rc = VSCSIDeviceReqEnqueue(pThis->hVScsiDevice, hVScsiReq);
624
625 return rc;
626}
627
628/* -=-=-=-=- IBase -=-=-=-=- */
629
630/**
631 * @interface_method_impl{PDMIBASE,pfnQueryInterface}
632 */
633static DECLCALLBACK(void *) drvscsiQueryInterface(PPDMIBASE pInterface, const char *pszIID)
634{
635 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
636 PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
637
638 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUNT, pThis->pDrvMount);
639 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
640 PDMIBASE_RETURN_INTERFACE(pszIID, PDMISCSICONNECTOR, &pThis->ISCSIConnector);
641 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBLOCKPORT, &pThis->IPort);
642 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIMOUNTNOTIFY, &pThis->IMountNotify);
643 PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBLOCKASYNCPORT, &pThis->IPortAsync);
644 return NULL;
645}
646
647static DECLCALLBACK(int) drvscsiQueryDeviceLocation(PPDMIBLOCKPORT pInterface, const char **ppcszController,
648 uint32_t *piInstance, uint32_t *piLUN)
649{
650 PDRVSCSI pThis = PDMIBLOCKPORT_2_DRVSCSI(pInterface);
651
652 return pThis->pDevScsiPort->pfnQueryDeviceLocation(pThis->pDevScsiPort, ppcszController,
653 piInstance, piLUN);
654}
655
656/**
657 * Called when media is mounted.
658 *
659 * @param pInterface Pointer to the interface structure containing the called function pointer.
660 */
661static DECLCALLBACK(void) drvscsiMountNotify(PPDMIMOUNTNOTIFY pInterface)
662{
663 PDRVSCSI pThis = PDMIMOUNTNOTIFY_2_DRVSCSI(pInterface);
664 LogFlowFunc(("mounting LUN#%p\n", pThis->hVScsiLun));
665
666 /* Ignore the call if we're called while being attached. */
667 if (!pThis->pDrvBlock)
668 return;
669
670 /* Let the LUN know that a medium was mounted. */
671 VSCSILunMountNotify(pThis->hVScsiLun);
672}
673
674/**
675 * Called when media is unmounted
676 *
677 * @param pInterface Pointer to the interface structure containing the called function pointer.
678 */
679static DECLCALLBACK(void) drvscsiUnmountNotify(PPDMIMOUNTNOTIFY pInterface)
680{
681 PDRVSCSI pThis = PDMIMOUNTNOTIFY_2_DRVSCSI(pInterface);
682 LogFlowFunc(("unmounting LUN#%p\n", pThis->hVScsiLun));
683
684 /* Let the LUN know that the medium was unmounted. */
685 VSCSILunUnmountNotify(pThis->hVScsiLun);
686}
687
688/**
689 * Worker for drvscsiReset, drvscsiSuspend and drvscsiPowerOff.
690 *
691 * @param pDrvIns The driver instance.
692 * @param pfnAsyncNotify The async callback.
693 */
694static void drvscsiR3ResetOrSuspendOrPowerOff(PPDMDRVINS pDrvIns, PFNPDMDRVASYNCNOTIFY pfnAsyncNotify)
695{
696 PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
697
698 if (!pThis->pDrvBlockAsync)
699 {
700 if (pThis->hQueueRequests != NIL_RTREQQUEUE)
701 return;
702
703 ASMAtomicWriteBool(&pThis->fDummySignal, true);
704 if (drvscsiAsyncIOLoopNoPendingDummy(pThis, 0 /*ms*/))
705 {
706 if (!RTReqQueueIsBusy(pThis->hQueueRequests))
707 {
708 ASMAtomicWriteBool(&pThis->fDummySignal, false);
709 return;
710 }
711
712 PRTREQ pReq;
713 int rc = RTReqQueueCall(pThis->hQueueRequests, &pReq, 0 /*ms*/, (PFNRT)drvscsiAsyncIOLoopSyncCallback, 1, pThis);
714 if (RT_SUCCESS(rc))
715 {
716 ASMAtomicWriteBool(&pThis->fDummySignal, false);
717 RTReqRelease(pReq);
718 return;
719 }
720
721 pThis->pPendingDummyReq = pReq;
722 }
723 }
724 else
725 {
726 if (pThis->StatIoDepth > 0)
727 {
728 ASMAtomicWriteBool(&pThis->fDummySignal, true);
729 }
730 return;
731 }
732
733 PDMDrvHlpSetAsyncNotification(pDrvIns, pfnAsyncNotify);
734}
735
736/**
737 * Callback employed by drvscsiSuspend and drvscsiPowerOff.
738 *
739 * @returns true if we've quiesced, false if we're still working.
740 * @param pDrvIns The driver instance.
741 */
742static DECLCALLBACK(bool) drvscsiIsAsyncSuspendOrPowerOffDone(PPDMDRVINS pDrvIns)
743{
744 PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
745
746 if (pThis->pDrvBlockAsync)
747 {
748 if (pThis->StatIoDepth > 0)
749 return false;
750 else
751 return true;
752 }
753 else
754 {
755 if (!drvscsiAsyncIOLoopNoPendingDummy(pThis, 0 /*ms*/))
756 return false;
757 ASMAtomicWriteBool(&pThis->fDummySignal, false);
758 PDMR3ThreadSuspend(pThis->pAsyncIOThread);
759 return true;
760 }
761}
762
763/**
764 * @copydoc FNPDMDRVPOWEROFF
765 */
766static DECLCALLBACK(void) drvscsiPowerOff(PPDMDRVINS pDrvIns)
767{
768 drvscsiR3ResetOrSuspendOrPowerOff(pDrvIns, drvscsiIsAsyncSuspendOrPowerOffDone);
769}
770
771/**
772 * @copydoc FNPDMDRVSUSPEND
773 */
774static DECLCALLBACK(void) drvscsiSuspend(PPDMDRVINS pDrvIns)
775{
776 drvscsiR3ResetOrSuspendOrPowerOff(pDrvIns, drvscsiIsAsyncSuspendOrPowerOffDone);
777}
778
779/**
780 * Callback employed by drvscsiReset.
781 *
782 * @returns true if we've quiesced, false if we're still working.
783 * @param pDrvIns The driver instance.
784 */
785static DECLCALLBACK(bool) drvscsiIsAsyncResetDone(PPDMDRVINS pDrvIns)
786{
787 PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
788
789 if (pThis->pDrvBlockAsync)
790 {
791 if (pThis->StatIoDepth > 0)
792 return false;
793 else
794 return true;
795 }
796 else
797 {
798 if (!drvscsiAsyncIOLoopNoPendingDummy(pThis, 0 /*ms*/))
799 return false;
800 ASMAtomicWriteBool(&pThis->fDummySignal, false);
801 return true;
802 }
803}
804
805/**
806 * @copydoc FNPDMDRVRESET
807 */
808static DECLCALLBACK(void) drvscsiReset(PPDMDRVINS pDrvIns)
809{
810 drvscsiR3ResetOrSuspendOrPowerOff(pDrvIns, drvscsiIsAsyncResetDone);
811}
812
813/**
814 * Destruct a driver instance.
815 *
816 * Most VM resources are freed by the VM. This callback is provided so that any non-VM
817 * resources can be freed correctly.
818 *
819 * @param pDrvIns The driver instance data.
820 */
821static DECLCALLBACK(void) drvscsiDestruct(PPDMDRVINS pDrvIns)
822{
823 PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
824 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
825
826 if (pThis->hQueueRequests != NIL_RTREQQUEUE)
827 {
828 if (!drvscsiAsyncIOLoopNoPendingDummy(pThis, 100 /*ms*/))
829 LogRel(("drvscsiDestruct#%u: previous dummy request is still pending\n", pDrvIns->iInstance));
830
831 int rc = RTReqQueueDestroy(pThis->hQueueRequests);
832 AssertMsgRC(rc, ("Failed to destroy queue rc=%Rrc\n", rc));
833 pThis->hQueueRequests = NIL_RTREQQUEUE;
834 }
835
836 /* Free the VSCSI device and LUN handle. */
837 VSCSILUN hVScsiLun;
838 int rc = VSCSIDeviceLunDetach(pThis->hVScsiDevice, 0, &hVScsiLun);
839 AssertRC(rc);
840
841 Assert(hVScsiLun == pThis->hVScsiLun);
842 rc = VSCSILunDestroy(hVScsiLun);
843 AssertRC(rc);
844 rc = VSCSIDeviceDestroy(pThis->hVScsiDevice);
845 AssertRC(rc);
846}
847
848/**
849 * Construct a block driver instance.
850 *
851 * @copydoc FNPDMDRVCONSTRUCT
852 */
853static DECLCALLBACK(int) drvscsiConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
854{
855 int rc = VINF_SUCCESS;
856 PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
857 LogFlowFunc(("pDrvIns=%#p pCfg=%#p\n", pDrvIns, pCfg));
858LogRelFunc(("pDrvIns=%#p pCfg=%#p\n", pDrvIns, pCfg));
859 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
860
861 /*
862 * Initialize the instance data.
863 */
864 pThis->pDrvIns = pDrvIns;
865 pThis->ISCSIConnector.pfnSCSIRequestSend = drvscsiRequestSend;
866
867 pDrvIns->IBase.pfnQueryInterface = drvscsiQueryInterface;
868
869 pThis->IMountNotify.pfnMountNotify = drvscsiMountNotify;
870 pThis->IMountNotify.pfnUnmountNotify = drvscsiUnmountNotify;
871 pThis->IPort.pfnQueryDeviceLocation = drvscsiQueryDeviceLocation;
872 pThis->IPortAsync.pfnTransferCompleteNotify = drvscsiTransferCompleteNotify;
873 pThis->hQueueRequests = NIL_RTREQQUEUE;
874
875 /* Query the SCSI port interface above. */
876 pThis->pDevScsiPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMISCSIPORT);
877 AssertMsgReturn(pThis->pDevScsiPort, ("Missing SCSI port interface above\n"), VERR_PDM_MISSING_INTERFACE);
878
879 /* Query the optional LED interface above. */
880 pThis->pLedPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMILEDPORTS);
881 if (pThis->pLedPort != NULL)
882 {
883 /* Get The Led. */
884 rc = pThis->pLedPort->pfnQueryStatusLed(pThis->pLedPort, 0, &pThis->pLed);
885 if (RT_FAILURE(rc))
886 pThis->pLed = &pThis->Led;
887 }
888 else
889 pThis->pLed = &pThis->Led;
890
891 /*
892 * Validate and read configuration.
893 */
894 if (!CFGMR3AreValuesValid(pCfg, "NonRotationalMedium\0Readonly\0"))
895 return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
896 N_("SCSI configuration error: unknown option specified"));
897
898 rc = CFGMR3QueryBoolDef(pCfg, "NonRotationalMedium", &pThis->fNonRotational, false);
899 if (RT_FAILURE(rc))
900 return PDMDRV_SET_ERROR(pDrvIns, rc,
901 N_("SCSI configuration error: failed to read \"NonRotationalMedium\" as boolean"));
902
903 rc = CFGMR3QueryBoolDef(pCfg, "Readonly", &pThis->fReadonly, false);
904 if (RT_FAILURE(rc))
905 return PDMDRV_SET_ERROR(pDrvIns, rc,
906 N_("SCSI configuration error: failed to read \"Readonly\" as boolean"));
907
908 /*
909 * Try attach driver below and query it's block interface.
910 */
911 rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pThis->pDrvBase);
912 AssertMsgReturn(RT_SUCCESS(rc), ("Attaching driver below failed rc=%Rrc\n", rc), rc);
913
914 /*
915 * Query the block and blockbios interfaces.
916 */
917 pThis->pDrvBlock = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIBLOCK);
918 if (!pThis->pDrvBlock)
919 {
920 AssertMsgFailed(("Configuration error: No block interface!\n"));
921 return VERR_PDM_MISSING_INTERFACE;
922 }
923 pThis->pDrvBlockBios = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIBLOCKBIOS);
924 if (!pThis->pDrvBlockBios)
925 {
926 AssertMsgFailed(("Configuration error: No block BIOS interface!\n"));
927 return VERR_PDM_MISSING_INTERFACE;
928 }
929
930 pThis->pDrvMount = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIMOUNT);
931
932 /* Try to get the optional async block interface. */
933 pThis->pDrvBlockAsync = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIBLOCKASYNC);
934
935 PDMBLOCKTYPE enmType = pThis->pDrvBlock->pfnGetType(pThis->pDrvBlock);
936 VSCSILUNTYPE enmLunType;
937 switch (enmType)
938 {
939 case PDMBLOCKTYPE_HARD_DISK:
940 enmLunType = VSCSILUNTYPE_SBC;
941 break;
942 case PDMBLOCKTYPE_CDROM:
943 case PDMBLOCKTYPE_DVD:
944 enmLunType = VSCSILUNTYPE_MMC;
945 break;
946 default:
947 return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_UNSUPPORTED_BLOCK_TYPE, RT_SRC_POS,
948 N_("Only hard disks and CD/DVD-ROMs are currently supported as SCSI devices (enmType=%d)"),
949 enmType);
950 }
951 if ( ( enmType == PDMBLOCKTYPE_DVD
952 || enmType == PDMBLOCKTYPE_CDROM)
953 && !pThis->pDrvMount)
954 {
955 AssertMsgFailed(("Internal error: cdrom without a mountable interface\n"));
956 return VERR_INTERNAL_ERROR;
957 }
958
959 /* Create VSCSI device and LUN. */
960 pThis->VScsiIoCallbacks.pfnVScsiLunMediumGetSize = drvscsiGetSize;
961 pThis->VScsiIoCallbacks.pfnVScsiLunReqTransferEnqueue = drvscsiReqTransferEnqueue;
962 pThis->VScsiIoCallbacks.pfnVScsiLunGetFeatureFlags = drvscsiGetFeatureFlags;
963
964 rc = VSCSIDeviceCreate(&pThis->hVScsiDevice, drvscsiVScsiReqCompleted, pThis);
965 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create VSCSI device rc=%Rrc\n"), rc);
966 rc = VSCSILunCreate(&pThis->hVScsiLun, enmLunType, &pThis->VScsiIoCallbacks,
967 pThis);
968 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create VSCSI LUN rc=%Rrc\n"), rc);
969 rc = VSCSIDeviceLunAttach(pThis->hVScsiDevice, pThis->hVScsiLun, 0);
970 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to attached the LUN to the SCSI device\n"), rc);
971
972 //@todo: This is a very hacky way of telling the LUN whether a medium was mounted.
973 // The mount/unmount interface doesn't work in a very sensible manner!
974 if (pThis->pDrvMount)
975 {
976 if (pThis->pDrvBlock->pfnGetSize(pThis->pDrvBlock))
977 {
978 rc = VINF_SUCCESS; VSCSILunMountNotify(pThis->hVScsiLun);
979 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to notify the LUN of media being mounted\n"), rc);
980 }
981 else
982 {
983 rc = VINF_SUCCESS; VSCSILunUnmountNotify(pThis->hVScsiLun);
984 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to notify the LUN of media being unmounted\n"), rc);
985 }
986 }
987
988 /* Register statistics counter. */
989 /** @todo aeichner: Find a way to put the instance number of the attached
990 * controller device when we support more than one controller of the same type.
991 * At the moment we have the 0 hardcoded. */
992 PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
993 "Amount of data read.", "/Devices/SCSI0/%d/ReadBytes", pDrvIns->iInstance);
994 PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
995 "Amount of data written.", "/Devices/SCSI0/%d/WrittenBytes", pDrvIns->iInstance);
996
997 pThis->StatIoDepth = 0;
998
999 PDMDrvHlpSTAMRegisterF(pDrvIns, (void *)&pThis->StatIoDepth, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT,
1000 "Number of active tasks.", "/Devices/SCSI0/%d/IoDepth", pDrvIns->iInstance);
1001
1002 if (!pThis->pDrvBlockAsync)
1003 {
1004 /* Create request queue. */
1005 rc = RTReqQueueCreate(&pThis->hQueueRequests);
1006 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create request queue rc=%Rrc\n"), rc);
1007 /* Create I/O thread. */
1008 rc = PDMDrvHlpThreadCreate(pDrvIns, &pThis->pAsyncIOThread, pThis, drvscsiAsyncIOLoop,
1009 drvscsiAsyncIOLoopWakeup, 0, RTTHREADTYPE_IO, "SCSI async IO");
1010 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create async I/O thread rc=%Rrc\n"), rc);
1011
1012 LogRel(("SCSI#%d: using normal I/O\n", pDrvIns->iInstance));
1013 }
1014 else
1015 LogRel(("SCSI#%d: using async I/O\n", pDrvIns->iInstance));
1016
1017 if ( pThis->pDrvBlock->pfnDiscard
1018 || ( pThis->pDrvBlockAsync
1019 && pThis->pDrvBlockAsync->pfnStartDiscard))
1020 LogRel(("SCSI#%d: Enabled UNMAP support\n"));
1021
1022 return VINF_SUCCESS;
1023}
1024
1025/**
1026 * SCSI driver registration record.
1027 */
1028const PDMDRVREG g_DrvSCSI =
1029{
1030 /* u32Version */
1031 PDM_DRVREG_VERSION,
1032 /* szName */
1033 "SCSI",
1034 /* szRCMod */
1035 "",
1036 /* szR0Mod */
1037 "",
1038 /* pszDescription */
1039 "Generic SCSI driver.",
1040 /* fFlags */
1041 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1042 /* fClass. */
1043 PDM_DRVREG_CLASS_SCSI,
1044 /* cMaxInstances */
1045 ~0U,
1046 /* cbInstance */
1047 sizeof(DRVSCSI),
1048 /* pfnConstruct */
1049 drvscsiConstruct,
1050 /* pfnDestruct */
1051 drvscsiDestruct,
1052 /* pfnRelocate */
1053 NULL,
1054 /* pfnIOCtl */
1055 NULL,
1056 /* pfnPowerOn */
1057 NULL,
1058 /* pfnReset */
1059 drvscsiReset,
1060 /* pfnSuspend */
1061 drvscsiSuspend,
1062 /* pfnResume */
1063 NULL,
1064 /* pfnAttach */
1065 NULL,
1066 /* pfnDetach */
1067 NULL,
1068 /* pfnPowerOff */
1069 drvscsiPowerOff,
1070 /* pfnSoftReset */
1071 NULL,
1072 /* u32EndVersion */
1073 PDM_DRVREG_VERSION
1074};
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