VirtualBox

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

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

DrvSCSI: Connect some of the dots for removable media change (not yet complete).

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 36.7 KB
Line 
1/* $Id: DrvSCSI.cpp 43517 2012-10-02 16:34:27Z 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 //@todo: Notify of media change? Update media size?
671}
672
673/**
674 * Called when media is unmounted
675 *
676 * @param pInterface Pointer to the interface structure containing the called function pointer.
677 */
678static DECLCALLBACK(void) drvscsiUnmountNotify(PPDMIMOUNTNOTIFY pInterface)
679{
680 PDRVSCSI pThis = PDMIMOUNTNOTIFY_2_DRVSCSI(pInterface);
681 LogFlowFunc(("unmounting LUN#%p\n", pThis->hVScsiLun));
682
683 //@todo: Notify of media change? Report that no media is present?
684}
685
686/**
687 * Worker for drvscsiReset, drvscsiSuspend and drvscsiPowerOff.
688 *
689 * @param pDrvIns The driver instance.
690 * @param pfnAsyncNotify The async callback.
691 */
692static void drvscsiR3ResetOrSuspendOrPowerOff(PPDMDRVINS pDrvIns, PFNPDMDRVASYNCNOTIFY pfnAsyncNotify)
693{
694 PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
695
696 if (!pThis->pDrvBlockAsync)
697 {
698 if (pThis->hQueueRequests != NIL_RTREQQUEUE)
699 return;
700
701 ASMAtomicWriteBool(&pThis->fDummySignal, true);
702 if (drvscsiAsyncIOLoopNoPendingDummy(pThis, 0 /*ms*/))
703 {
704 if (!RTReqQueueIsBusy(pThis->hQueueRequests))
705 {
706 ASMAtomicWriteBool(&pThis->fDummySignal, false);
707 return;
708 }
709
710 PRTREQ pReq;
711 int rc = RTReqQueueCall(pThis->hQueueRequests, &pReq, 0 /*ms*/, (PFNRT)drvscsiAsyncIOLoopSyncCallback, 1, pThis);
712 if (RT_SUCCESS(rc))
713 {
714 ASMAtomicWriteBool(&pThis->fDummySignal, false);
715 RTReqRelease(pReq);
716 return;
717 }
718
719 pThis->pPendingDummyReq = pReq;
720 }
721 }
722 else
723 {
724 if (pThis->StatIoDepth > 0)
725 {
726 ASMAtomicWriteBool(&pThis->fDummySignal, true);
727 }
728 return;
729 }
730
731 PDMDrvHlpSetAsyncNotification(pDrvIns, pfnAsyncNotify);
732}
733
734/**
735 * Callback employed by drvscsiSuspend and drvscsiPowerOff.
736 *
737 * @returns true if we've quiesced, false if we're still working.
738 * @param pDrvIns The driver instance.
739 */
740static DECLCALLBACK(bool) drvscsiIsAsyncSuspendOrPowerOffDone(PPDMDRVINS pDrvIns)
741{
742 PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
743
744 if (pThis->pDrvBlockAsync)
745 {
746 if (pThis->StatIoDepth > 0)
747 return false;
748 else
749 return true;
750 }
751 else
752 {
753 if (!drvscsiAsyncIOLoopNoPendingDummy(pThis, 0 /*ms*/))
754 return false;
755 ASMAtomicWriteBool(&pThis->fDummySignal, false);
756 PDMR3ThreadSuspend(pThis->pAsyncIOThread);
757 return true;
758 }
759}
760
761/**
762 * @copydoc FNPDMDRVPOWEROFF
763 */
764static DECLCALLBACK(void) drvscsiPowerOff(PPDMDRVINS pDrvIns)
765{
766 drvscsiR3ResetOrSuspendOrPowerOff(pDrvIns, drvscsiIsAsyncSuspendOrPowerOffDone);
767}
768
769/**
770 * @copydoc FNPDMDRVSUSPEND
771 */
772static DECLCALLBACK(void) drvscsiSuspend(PPDMDRVINS pDrvIns)
773{
774 drvscsiR3ResetOrSuspendOrPowerOff(pDrvIns, drvscsiIsAsyncSuspendOrPowerOffDone);
775}
776
777/**
778 * Callback employed by drvscsiReset.
779 *
780 * @returns true if we've quiesced, false if we're still working.
781 * @param pDrvIns The driver instance.
782 */
783static DECLCALLBACK(bool) drvscsiIsAsyncResetDone(PPDMDRVINS pDrvIns)
784{
785 PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
786
787 if (pThis->pDrvBlockAsync)
788 {
789 if (pThis->StatIoDepth > 0)
790 return false;
791 else
792 return true;
793 }
794 else
795 {
796 if (!drvscsiAsyncIOLoopNoPendingDummy(pThis, 0 /*ms*/))
797 return false;
798 ASMAtomicWriteBool(&pThis->fDummySignal, false);
799 return true;
800 }
801}
802
803/**
804 * @copydoc FNPDMDRVRESET
805 */
806static DECLCALLBACK(void) drvscsiReset(PPDMDRVINS pDrvIns)
807{
808 drvscsiR3ResetOrSuspendOrPowerOff(pDrvIns, drvscsiIsAsyncResetDone);
809}
810
811/**
812 * Destruct a driver instance.
813 *
814 * Most VM resources are freed by the VM. This callback is provided so that any non-VM
815 * resources can be freed correctly.
816 *
817 * @param pDrvIns The driver instance data.
818 */
819static DECLCALLBACK(void) drvscsiDestruct(PPDMDRVINS pDrvIns)
820{
821 PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
822 PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
823
824 if (pThis->hQueueRequests != NIL_RTREQQUEUE)
825 {
826 if (!drvscsiAsyncIOLoopNoPendingDummy(pThis, 100 /*ms*/))
827 LogRel(("drvscsiDestruct#%u: previous dummy request is still pending\n", pDrvIns->iInstance));
828
829 int rc = RTReqQueueDestroy(pThis->hQueueRequests);
830 AssertMsgRC(rc, ("Failed to destroy queue rc=%Rrc\n", rc));
831 pThis->hQueueRequests = NIL_RTREQQUEUE;
832 }
833
834 /* Free the VSCSI device and LUN handle. */
835 VSCSILUN hVScsiLun;
836 int rc = VSCSIDeviceLunDetach(pThis->hVScsiDevice, 0, &hVScsiLun);
837 AssertRC(rc);
838
839 Assert(hVScsiLun == pThis->hVScsiLun);
840 rc = VSCSILunDestroy(hVScsiLun);
841 AssertRC(rc);
842 rc = VSCSIDeviceDestroy(pThis->hVScsiDevice);
843 AssertRC(rc);
844}
845
846/**
847 * Construct a block driver instance.
848 *
849 * @copydoc FNPDMDRVCONSTRUCT
850 */
851static DECLCALLBACK(int) drvscsiConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
852{
853 int rc = VINF_SUCCESS;
854 PDRVSCSI pThis = PDMINS_2_DATA(pDrvIns, PDRVSCSI);
855 LogFlowFunc(("pDrvIns=%#p pCfg=%#p\n", pDrvIns, pCfg));
856 PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
857
858 /*
859 * Initialize the instance data.
860 */
861 pThis->pDrvIns = pDrvIns;
862 pThis->ISCSIConnector.pfnSCSIRequestSend = drvscsiRequestSend;
863
864 pDrvIns->IBase.pfnQueryInterface = drvscsiQueryInterface;
865
866 pThis->IMountNotify.pfnMountNotify = drvscsiMountNotify;
867 pThis->IMountNotify.pfnUnmountNotify = drvscsiUnmountNotify;
868 pThis->IPort.pfnQueryDeviceLocation = drvscsiQueryDeviceLocation;
869 pThis->IPortAsync.pfnTransferCompleteNotify = drvscsiTransferCompleteNotify;
870 pThis->hQueueRequests = NIL_RTREQQUEUE;
871
872 /* Query the SCSI port interface above. */
873 pThis->pDevScsiPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMISCSIPORT);
874 AssertMsgReturn(pThis->pDevScsiPort, ("Missing SCSI port interface above\n"), VERR_PDM_MISSING_INTERFACE);
875
876 /* Query the optional LED interface above. */
877 pThis->pLedPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, PDMILEDPORTS);
878 if (pThis->pLedPort != NULL)
879 {
880 /* Get The Led. */
881 rc = pThis->pLedPort->pfnQueryStatusLed(pThis->pLedPort, 0, &pThis->pLed);
882 if (RT_FAILURE(rc))
883 pThis->pLed = &pThis->Led;
884 }
885 else
886 pThis->pLed = &pThis->Led;
887
888 /*
889 * Validate and read configuration.
890 */
891 if (!CFGMR3AreValuesValid(pCfg, "NonRotationalMedium\0Readonly\0"))
892 return PDMDRV_SET_ERROR(pDrvIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
893 N_("SCSI configuration error: unknown option specified"));
894
895 rc = CFGMR3QueryBoolDef(pCfg, "NonRotationalMedium", &pThis->fNonRotational, false);
896 if (RT_FAILURE(rc))
897 return PDMDRV_SET_ERROR(pDrvIns, rc,
898 N_("SCSI configuration error: failed to read \"NonRotationalMedium\" as boolean"));
899
900 rc = CFGMR3QueryBoolDef(pCfg, "Readonly", &pThis->fReadonly, false);
901 if (RT_FAILURE(rc))
902 return PDMDRV_SET_ERROR(pDrvIns, rc,
903 N_("SCSI configuration error: failed to read \"Readonly\" as boolean"));
904
905 /*
906 * Try attach driver below and query it's block interface.
907 */
908 rc = PDMDrvHlpAttach(pDrvIns, fFlags, &pThis->pDrvBase);
909 AssertMsgReturn(RT_SUCCESS(rc), ("Attaching driver below failed rc=%Rrc\n", rc), rc);
910
911 /*
912 * Query the block and blockbios interfaces.
913 */
914 pThis->pDrvBlock = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIBLOCK);
915 if (!pThis->pDrvBlock)
916 {
917 AssertMsgFailed(("Configuration error: No block interface!\n"));
918 return VERR_PDM_MISSING_INTERFACE;
919 }
920 pThis->pDrvBlockBios = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIBLOCKBIOS);
921 if (!pThis->pDrvBlockBios)
922 {
923 AssertMsgFailed(("Configuration error: No block BIOS interface!\n"));
924 return VERR_PDM_MISSING_INTERFACE;
925 }
926
927 pThis->pDrvMount = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIMOUNT);
928
929 /* Try to get the optional async block interface. */
930 pThis->pDrvBlockAsync = PDMIBASE_QUERY_INTERFACE(pThis->pDrvBase, PDMIBLOCKASYNC);
931
932 PDMBLOCKTYPE enmType = pThis->pDrvBlock->pfnGetType(pThis->pDrvBlock);
933 VSCSILUNTYPE enmLunType;
934 switch (enmType)
935 {
936 case PDMBLOCKTYPE_HARD_DISK:
937 enmLunType = VSCSILUNTYPE_SBC;
938 break;
939 case PDMBLOCKTYPE_CDROM:
940 case PDMBLOCKTYPE_DVD:
941 enmLunType = VSCSILUNTYPE_MMC;
942 break;
943 default:
944 return PDMDrvHlpVMSetError(pDrvIns, VERR_PDM_UNSUPPORTED_BLOCK_TYPE, RT_SRC_POS,
945 N_("Only hard disks and CD/DVD-ROMs are currently supported as SCSI devices (enmType=%d)"),
946 enmType);
947 }
948 if ( ( enmType == PDMBLOCKTYPE_DVD
949 || enmType == PDMBLOCKTYPE_CDROM)
950 && !pThis->pDrvMount)
951 {
952 AssertMsgFailed(("Internal error: cdrom without a mountable interface\n"));
953 return VERR_INTERNAL_ERROR;
954 }
955
956 /* Create VSCSI device and LUN. */
957 pThis->VScsiIoCallbacks.pfnVScsiLunMediumGetSize = drvscsiGetSize;
958 pThis->VScsiIoCallbacks.pfnVScsiLunReqTransferEnqueue = drvscsiReqTransferEnqueue;
959 pThis->VScsiIoCallbacks.pfnVScsiLunGetFeatureFlags = drvscsiGetFeatureFlags;
960
961 rc = VSCSIDeviceCreate(&pThis->hVScsiDevice, drvscsiVScsiReqCompleted, pThis);
962 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create VSCSI device rc=%Rrc\n"), rc);
963 rc = VSCSILunCreate(&pThis->hVScsiLun, enmLunType, &pThis->VScsiIoCallbacks,
964 pThis);
965 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create VSCSI LUN rc=%Rrc\n"), rc);
966 rc = VSCSIDeviceLunAttach(pThis->hVScsiDevice, pThis->hVScsiLun, 0);
967 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to attached the LUN to the SCSI device\n"), rc);
968
969 /* Register statistics counter. */
970 /** @todo aeichner: Find a way to put the instance number of the attached
971 * controller device when we support more than one controller of the same type.
972 * At the moment we have the 0 hardcoded. */
973 PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesRead, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
974 "Amount of data read.", "/Devices/SCSI0/%d/ReadBytes", pDrvIns->iInstance);
975 PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatBytesWritten, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES,
976 "Amount of data written.", "/Devices/SCSI0/%d/WrittenBytes", pDrvIns->iInstance);
977
978 pThis->StatIoDepth = 0;
979
980 PDMDrvHlpSTAMRegisterF(pDrvIns, (void *)&pThis->StatIoDepth, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT,
981 "Number of active tasks.", "/Devices/SCSI0/%d/IoDepth", pDrvIns->iInstance);
982
983 if (!pThis->pDrvBlockAsync)
984 {
985 /* Create request queue. */
986 rc = RTReqQueueCreate(&pThis->hQueueRequests);
987 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create request queue rc=%Rrc\n"), rc);
988 /* Create I/O thread. */
989 rc = PDMDrvHlpThreadCreate(pDrvIns, &pThis->pAsyncIOThread, pThis, drvscsiAsyncIOLoop,
990 drvscsiAsyncIOLoopWakeup, 0, RTTHREADTYPE_IO, "SCSI async IO");
991 AssertMsgReturn(RT_SUCCESS(rc), ("Failed to create async I/O thread rc=%Rrc\n"), rc);
992
993 LogRel(("SCSI#%d: using normal I/O\n", pDrvIns->iInstance));
994 }
995 else
996 LogRel(("SCSI#%d: using async I/O\n", pDrvIns->iInstance));
997
998 if ( pThis->pDrvBlock->pfnDiscard
999 || ( pThis->pDrvBlockAsync
1000 && pThis->pDrvBlockAsync->pfnStartDiscard))
1001 LogRel(("SCSI#%d: Enabled UNMAP support\n"));
1002
1003 return VINF_SUCCESS;
1004}
1005
1006/**
1007 * SCSI driver registration record.
1008 */
1009const PDMDRVREG g_DrvSCSI =
1010{
1011 /* u32Version */
1012 PDM_DRVREG_VERSION,
1013 /* szName */
1014 "SCSI",
1015 /* szRCMod */
1016 "",
1017 /* szR0Mod */
1018 "",
1019 /* pszDescription */
1020 "Generic SCSI driver.",
1021 /* fFlags */
1022 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
1023 /* fClass. */
1024 PDM_DRVREG_CLASS_SCSI,
1025 /* cMaxInstances */
1026 ~0U,
1027 /* cbInstance */
1028 sizeof(DRVSCSI),
1029 /* pfnConstruct */
1030 drvscsiConstruct,
1031 /* pfnDestruct */
1032 drvscsiDestruct,
1033 /* pfnRelocate */
1034 NULL,
1035 /* pfnIOCtl */
1036 NULL,
1037 /* pfnPowerOn */
1038 NULL,
1039 /* pfnReset */
1040 drvscsiReset,
1041 /* pfnSuspend */
1042 drvscsiSuspend,
1043 /* pfnResume */
1044 NULL,
1045 /* pfnAttach */
1046 NULL,
1047 /* pfnDetach */
1048 NULL,
1049 /* pfnPowerOff */
1050 drvscsiPowerOff,
1051 /* pfnSoftReset */
1052 NULL,
1053 /* u32EndVersion */
1054 PDM_DRVREG_VERSION
1055};
1056
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