VirtualBox

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

Last change on this file since 64410 was 64407, checked in by vboxsync, 8 years ago

Devices/Storage: Add callback to query the actual transfer size of an I/O request if PDMIMEDIAEX is used, useful to calculate the amount of data transferred for requests where the transfer size can be different from the guest buffer size (e.g. SCSI)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 31.3 KB
Line 
1/* $Id: DrvHostDVD.cpp 64407 2016-10-25 11:53:00Z vboxsync $ */
2/** @file
3 * DrvHostDVD - Host DVD block driver.
4 */
5
6/*
7 * Copyright (C) 2006-2016 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17
18
19/*********************************************************************************************************************************
20* Header Files *
21*********************************************************************************************************************************/
22#define LOG_GROUP LOG_GROUP_DRV_HOST_DVD
23#include <iprt/asm.h>
24#include <VBox/vmm/pdmdrv.h>
25#include <VBox/vmm/pdmstorageifs.h>
26#include <iprt/asm.h>
27#include <iprt/assert.h>
28#include <iprt/file.h>
29#include <iprt/string.h>
30#include <iprt/thread.h>
31#include <iprt/critsect.h>
32#include <VBox/scsi.h>
33
34#include "VBoxDD.h"
35#include "DrvHostBase.h"
36#include "ATAPIPassthrough.h"
37
38/** ATAPI sense info size. */
39#define ATAPI_SENSE_SIZE 64
40/** Size of an ATAPI packet. */
41#define ATAPI_PACKET_SIZE 12
42
43/**
44 * Host DVD driver instance data.
45 */
46typedef struct DRVHOSTDVD
47{
48 /** Base drivr data. */
49 DRVHOSTBASE Core;
50 /** The current tracklist of the loaded medium if passthrough is used. */
51 PTRACKLIST pTrackList;
52 /** ATAPI sense data. */
53 uint8_t abATAPISense[ATAPI_SENSE_SIZE];
54} DRVHOSTDVD;
55/** Pointer to the host DVD driver instance data. */
56typedef DRVHOSTDVD *PDRVHOSTDVD;
57
58/*********************************************************************************************************************************
59* Internal Functions *
60*********************************************************************************************************************************/
61
62DECLINLINE(void) drvHostDvdH2BE_U16(uint8_t *pbBuf, uint16_t val)
63{
64 pbBuf[0] = val >> 8;
65 pbBuf[1] = val;
66}
67
68
69DECLINLINE(void) drvHostDvdH2BE_U24(uint8_t *pbBuf, uint32_t val)
70{
71 pbBuf[0] = val >> 16;
72 pbBuf[1] = val >> 8;
73 pbBuf[2] = val;
74}
75
76
77DECLINLINE(void) drvHostDvdH2BE_U32(uint8_t *pbBuf, uint32_t val)
78{
79 pbBuf[0] = val >> 24;
80 pbBuf[1] = val >> 16;
81 pbBuf[2] = val >> 8;
82 pbBuf[3] = val;
83}
84
85
86DECLINLINE(uint16_t) drvHostDvdBE2H_U16(const uint8_t *pbBuf)
87{
88 return (pbBuf[0] << 8) | pbBuf[1];
89}
90
91
92DECLINLINE(uint32_t) drvHostDvdBE2H_U24(const uint8_t *pbBuf)
93{
94 return (pbBuf[0] << 16) | (pbBuf[1] << 8) | pbBuf[2];
95}
96
97
98DECLINLINE(uint32_t) drvHostDvdBE2H_U32(const uint8_t *pbBuf)
99{
100 return (pbBuf[0] << 24) | (pbBuf[1] << 16) | (pbBuf[2] << 8) | pbBuf[3];
101}
102
103
104DECLINLINE(void) drvHostDvdLBA2MSF(uint8_t *pbBuf, uint32_t iATAPILBA)
105{
106 iATAPILBA += 150;
107 pbBuf[0] = (iATAPILBA / 75) / 60;
108 pbBuf[1] = (iATAPILBA / 75) % 60;
109 pbBuf[2] = iATAPILBA % 75;
110}
111
112
113DECLINLINE(uint32_t) drvHostDvdMSF2LBA(const uint8_t *pbBuf)
114{
115 return (pbBuf[0] * 60 + pbBuf[1]) * 75 + pbBuf[2];
116}
117
118static void drvHostDvdCmdOK(PDRVHOSTDVD pThis)
119{
120 memset(pThis->abATAPISense, '\0', sizeof(pThis->abATAPISense));
121 pThis->abATAPISense[0] = 0x70;
122 pThis->abATAPISense[7] = 10;
123}
124
125static void drvHostDvdCmdError(PDRVHOSTDVD pThis, const uint8_t *pabATAPISense, size_t cbATAPISense)
126{
127 Log(("%s: sense=%#x (%s) asc=%#x ascq=%#x (%s)\n", __FUNCTION__, pabATAPISense[2] & 0x0f, SCSISenseText(pabATAPISense[2] & 0x0f),
128 pabATAPISense[12], pabATAPISense[13], SCSISenseExtText(pabATAPISense[12], pabATAPISense[13])));
129 memset(pThis->abATAPISense, '\0', sizeof(pThis->abATAPISense));
130 memcpy(pThis->abATAPISense, pabATAPISense, RT_MIN(cbATAPISense, sizeof(pThis->abATAPISense)));
131}
132
133/** @todo deprecated function - doesn't provide enough info. Replace by direct
134 * calls to drvHostDvdCmdError() with full data. */
135static void drvHostDvdCmdErrorSimple(PDRVHOSTDVD pThis, uint8_t uATAPISenseKey, uint8_t uATAPIASC)
136{
137 uint8_t abATAPISense[ATAPI_SENSE_SIZE];
138 memset(abATAPISense, '\0', sizeof(abATAPISense));
139 abATAPISense[0] = 0x70 | (1 << 7);
140 abATAPISense[2] = uATAPISenseKey & 0x0f;
141 abATAPISense[7] = 10;
142 abATAPISense[12] = uATAPIASC;
143 drvHostDvdCmdError(pThis, abATAPISense, sizeof(abATAPISense));
144}
145
146static void drvHostDvdSCSIPadStr(uint8_t *pbDst, const char *pbSrc, uint32_t cbSize)
147{
148 for (uint32_t i = 0; i < cbSize; i++)
149 {
150 if (*pbSrc)
151 pbDst[i] = *pbSrc++;
152 else
153 pbDst[i] = ' ';
154 }
155}
156
157
158static bool drvHostDvdParseCdb(PDRVHOSTDVD pThis, PDRVHOSTBASEREQ pReq,
159 const uint8_t *pbCdb, size_t cbCdb, size_t cbBuf,
160 PDMMEDIATXDIR *penmTxDir, size_t *pcbXfer,
161 size_t *pcbSector, uint8_t *pu8ScsiSts)
162{
163 uint32_t uLba = 0;
164 uint32_t cSectors = 0;
165 size_t cbSector = 0;
166 size_t cbXfer = 0;
167 bool fPassthrough = false;
168 PDMMEDIATXDIR enmTxDir = PDMMEDIATXDIR_NONE;
169
170 RT_NOREF(cbCdb);
171
172 switch (pbCdb[0])
173 {
174 /* First the commands we can pass through without further processing. */
175 case SCSI_BLANK:
176 case SCSI_CLOSE_TRACK_SESSION:
177 case SCSI_LOAD_UNLOAD_MEDIUM:
178 case SCSI_PAUSE_RESUME:
179 case SCSI_PLAY_AUDIO_10:
180 case SCSI_PLAY_AUDIO_12:
181 case SCSI_PLAY_AUDIO_MSF:
182 case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
183 case SCSI_REPAIR_TRACK:
184 case SCSI_RESERVE_TRACK:
185 case SCSI_SCAN:
186 case SCSI_SEEK_10:
187 case SCSI_SET_CD_SPEED:
188 case SCSI_SET_READ_AHEAD:
189 case SCSI_START_STOP_UNIT:
190 case SCSI_STOP_PLAY_SCAN:
191 case SCSI_SYNCHRONIZE_CACHE:
192 case SCSI_TEST_UNIT_READY:
193 case SCSI_VERIFY_10:
194 fPassthrough = true;
195 break;
196 case SCSI_ERASE_10:
197 uLba = drvHostDvdBE2H_U32(pbCdb + 2);
198 cbXfer = drvHostDvdBE2H_U16(pbCdb + 7);
199 enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
200 fPassthrough = true;
201 break;
202 case SCSI_FORMAT_UNIT:
203 cbXfer = cbBuf;
204 enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
205 fPassthrough = true;
206 break;
207 case SCSI_GET_CONFIGURATION:
208 cbXfer = drvHostDvdBE2H_U16(pbCdb + 7);
209 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
210 fPassthrough = true;
211 break;
212 case SCSI_GET_EVENT_STATUS_NOTIFICATION:
213 cbXfer = drvHostDvdBE2H_U16(pbCdb + 7);
214 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
215 fPassthrough = true;
216 break;
217 case SCSI_GET_PERFORMANCE:
218 cbXfer = cbBuf;
219 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
220 fPassthrough = true;
221 break;
222 case SCSI_INQUIRY:
223 cbXfer = drvHostDvdBE2H_U16(pbCdb + 3);
224 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
225 fPassthrough = true;
226 break;
227 case SCSI_MECHANISM_STATUS:
228 cbXfer = drvHostDvdBE2H_U16(pbCdb + 8);
229 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
230 fPassthrough = true;
231 break;
232 case SCSI_MODE_SELECT_10:
233 cbXfer = drvHostDvdBE2H_U16(pbCdb + 7);
234 enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
235 fPassthrough = true;
236 break;
237 case SCSI_MODE_SENSE_10:
238 cbXfer = drvHostDvdBE2H_U16(pbCdb + 7);
239 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
240 fPassthrough = true;
241 break;
242 case SCSI_READ_10:
243 uLba = drvHostDvdBE2H_U32(pbCdb + 2);
244 cSectors = drvHostDvdBE2H_U16(pbCdb + 7);
245 cbSector = 2048;
246 cbXfer = cSectors * cbSector;
247 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
248 fPassthrough = true;
249 break;
250 case SCSI_READ_12:
251 uLba = drvHostDvdBE2H_U32(pbCdb + 2);
252 cSectors = drvHostDvdBE2H_U32(pbCdb + 6);
253 cbSector = 2048;
254 cbXfer = cSectors * cbSector;
255 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
256 fPassthrough = true;
257 break;
258 case SCSI_READ_BUFFER:
259 cbXfer = drvHostDvdBE2H_U24(pbCdb + 6);
260 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
261 fPassthrough = true;
262 break;
263 case SCSI_READ_BUFFER_CAPACITY:
264 cbXfer = drvHostDvdBE2H_U16(pbCdb + 7);
265 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
266 fPassthrough = true;
267 break;
268 case SCSI_READ_CAPACITY:
269 cbXfer = 8;
270 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
271 fPassthrough = true;
272 break;
273 case SCSI_READ_CD:
274 case SCSI_READ_CD_MSF:
275 {
276 /* Get sector size based on the expected sector type field. */
277 switch ((pbCdb[1] >> 2) & 0x7)
278 {
279 case 0x0: /* All types. */
280 {
281 uint32_t iLbaStart;
282
283 if (pbCdb[0] == SCSI_READ_CD)
284 iLbaStart = drvHostDvdBE2H_U32(&pbCdb[2]);
285 else
286 iLbaStart = drvHostDvdMSF2LBA(&pbCdb[3]);
287
288 if (pThis->pTrackList)
289 cbSector = ATAPIPassthroughTrackListGetSectorSizeFromLba(pThis->pTrackList, iLbaStart);
290 else
291 cbSector = 2048; /* Might be incorrect if we couldn't determine the type. */
292 break;
293 }
294 case 0x1: /* CD-DA */
295 cbSector = 2352;
296 break;
297 case 0x2: /* Mode 1 */
298 cbSector = 2048;
299 break;
300 case 0x3: /* Mode 2 formless */
301 cbSector = 2336;
302 break;
303 case 0x4: /* Mode 2 form 1 */
304 cbSector = 2048;
305 break;
306 case 0x5: /* Mode 2 form 2 */
307 cbSector = 2324;
308 break;
309 default: /* Reserved */
310 AssertMsgFailed(("Unknown sector type\n"));
311 cbSector = 0; /** @todo we should probably fail the command here already. */
312 }
313
314 if (pbCdb[0] == SCSI_READ_CD)
315 cbXfer = drvHostDvdBE2H_U24(pbCdb + 6) * cbSector;
316 else /* SCSI_READ_MSF */
317 {
318 cSectors = drvHostDvdMSF2LBA(pbCdb + 6) - drvHostDvdMSF2LBA(pbCdb + 3);
319 if (cSectors > 32)
320 cSectors = 32; /* Limit transfer size to 64~74K. Safety first. In any case this can only harm software doing CDDA extraction. */
321 cbXfer = cSectors * cbSector;
322 }
323 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
324 fPassthrough = true;
325 break;
326 }
327 case SCSI_READ_DISC_INFORMATION:
328 cbXfer = drvHostDvdBE2H_U16(pbCdb + 7);
329 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
330 fPassthrough = true;
331 break;
332 case SCSI_READ_DVD_STRUCTURE:
333 cbXfer = drvHostDvdBE2H_U16(pbCdb + 8);
334 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
335 fPassthrough = true;
336 break;
337 case SCSI_READ_FORMAT_CAPACITIES:
338 cbXfer = drvHostDvdBE2H_U16(pbCdb + 7);
339 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
340 fPassthrough = true;
341 break;
342 case SCSI_READ_SUBCHANNEL:
343 cbXfer = drvHostDvdBE2H_U16(pbCdb + 7);
344 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
345 fPassthrough = true;
346 break;
347 case SCSI_READ_TOC_PMA_ATIP:
348 cbXfer = drvHostDvdBE2H_U16(pbCdb + 7);
349 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
350 fPassthrough = true;
351 break;
352 case SCSI_READ_TRACK_INFORMATION:
353 cbXfer = drvHostDvdBE2H_U16(pbCdb + 7);
354 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
355 fPassthrough = true;
356 break;
357 case SCSI_REPORT_KEY:
358 cbXfer = drvHostDvdBE2H_U16(pbCdb + 8);
359 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
360 fPassthrough = true;
361 break;
362 case SCSI_REQUEST_SENSE:
363 cbXfer = pbCdb[4];
364 if ((pThis->abATAPISense[2] & 0x0f) != SCSI_SENSE_NONE)
365 {
366 /* Copy sense data over. */
367 void *pvBuf = NULL;
368 int rc = drvHostBaseBufferRetain(&pThis->Core, pReq, cbBuf, false /*fWrite*/, &pvBuf);
369 if (RT_SUCCESS(rc))
370 {
371 memcpy(pvBuf, &pThis->abATAPISense[0], RT_MIN(sizeof(pThis->abATAPISense), cbBuf));
372 rc = drvHostBaseBufferRelease(&pThis->Core, pReq, cbBuf, false /* fWrite */, pvBuf);
373 AssertRC(rc);
374 drvHostDvdCmdOK(pThis);
375 *pu8ScsiSts = SCSI_STATUS_OK;
376 }
377 break;
378 }
379 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
380 fPassthrough = true;
381 break;
382 case SCSI_SEND_CUE_SHEET:
383 cbXfer = drvHostDvdBE2H_U24(pbCdb + 6);
384 enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
385 fPassthrough = true;
386 break;
387 case SCSI_SEND_DVD_STRUCTURE:
388 cbXfer = drvHostDvdBE2H_U16(pbCdb + 8);
389 enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
390 fPassthrough = true;
391 break;
392 case SCSI_SEND_EVENT:
393 cbXfer = drvHostDvdBE2H_U16(pbCdb + 8);
394 enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
395 fPassthrough = true;
396 break;
397 case SCSI_SEND_KEY:
398 cbXfer = drvHostDvdBE2H_U16(pbCdb + 8);
399 enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
400 fPassthrough = true;
401 break;
402 case SCSI_SEND_OPC_INFORMATION:
403 cbXfer = drvHostDvdBE2H_U16(pbCdb + 7);
404 enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
405 fPassthrough = true;
406 break;
407 case SCSI_SET_STREAMING:
408 cbXfer = drvHostDvdBE2H_U16(pbCdb + 9);
409 enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
410 fPassthrough = true;
411 break;
412 case SCSI_WRITE_10:
413 case SCSI_WRITE_AND_VERIFY_10:
414 uLba = drvHostDvdBE2H_U32(pbCdb + 2);
415 cSectors = drvHostDvdBE2H_U16(pbCdb + 7);
416 if (pThis->pTrackList)
417 cbSector = ATAPIPassthroughTrackListGetSectorSizeFromLba(pThis->pTrackList, uLba);
418 else
419 cbSector = 2048;
420 cbXfer = cSectors * cbSector;
421 enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
422 fPassthrough = true;
423 break;
424 case SCSI_WRITE_12:
425 uLba = drvHostDvdBE2H_U32(pbCdb + 2);
426 cSectors = drvHostDvdBE2H_U32(pbCdb + 6);
427 if (pThis->pTrackList)
428 cbSector = ATAPIPassthroughTrackListGetSectorSizeFromLba(pThis->pTrackList, uLba);
429 else
430 cbSector = 2048;
431 cbXfer = cSectors * cbSector;
432 enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
433 fPassthrough = true;
434 break;
435 case SCSI_WRITE_BUFFER:
436 switch (pbCdb[1] & 0x1f)
437 {
438 case 0x04: /* download microcode */
439 case 0x05: /* download microcode and save */
440 case 0x06: /* download microcode with offsets */
441 case 0x07: /* download microcode with offsets and save */
442 case 0x0e: /* download microcode with offsets and defer activation */
443 case 0x0f: /* activate deferred microcode */
444 LogRel(("HostDVD#%u: CD-ROM passthrough command attempted to update firmware, blocked\n", pThis->Core.pDrvIns->iInstance));
445 drvHostDvdCmdErrorSimple(pThis, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
446 *pu8ScsiSts = SCSI_STATUS_CHECK_CONDITION;
447 break;
448 default:
449 cbXfer = drvHostDvdBE2H_U16(pbCdb + 6);
450 enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
451 fPassthrough = true;
452 break;
453 }
454 break;
455 case SCSI_REPORT_LUNS: /* Not part of MMC-3, but used by Windows. */
456 cbXfer = drvHostDvdBE2H_U32(pbCdb + 6);
457 enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
458 fPassthrough = true;
459 break;
460 case SCSI_REZERO_UNIT:
461 /* Obsolete command used by cdrecord. What else would one expect?
462 * This command is not sent to the drive, it is handled internally,
463 * as the Linux kernel doesn't like it (message "scsi: unknown
464 * opcode 0x01" in syslog) and replies with a sense code of 0,
465 * which sends cdrecord to an endless loop. */
466 drvHostDvdCmdErrorSimple(pThis, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
467 *pu8ScsiSts = SCSI_STATUS_CHECK_CONDITION;
468 break;
469 default:
470 LogRel(("HostDVD#%u: Passthrough unimplemented for command %#x\n", pThis->Core.pDrvIns->iInstance, pbCdb[0]));
471 drvHostDvdCmdErrorSimple(pThis, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
472 *pu8ScsiSts = SCSI_STATUS_CHECK_CONDITION;
473 break;
474 }
475
476 if (fPassthrough)
477 {
478 *penmTxDir = enmTxDir;
479 *pcbXfer = cbXfer;
480 *pcbSector = cbSector;
481 }
482
483 return fPassthrough;
484}
485
486/**
487 * Locks or unlocks the drive.
488 *
489 * @returns VBox status code.
490 * @param pThis The instance data.
491 * @param fLock True if the request is to lock the drive, false if to unlock.
492 */
493static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock)
494{
495 int rc = drvHostBaseDoLockOs(pThis, fLock);
496
497 LogFlow(("drvHostDvdDoLock(, fLock=%RTbool): returns %Rrc\n", fLock, rc));
498 return rc;
499}
500
501
502/** @interface_method_impl{PDMIMEDIA,pfnSendCmd} */
503static DECLCALLBACK(int) drvHostDvdSendCmd(PPDMIMEDIA pInterface, const uint8_t *pbCmd,
504 PDMMEDIATXDIR enmTxDir, void *pvBuf, uint32_t *pcbBuf,
505 uint8_t *pabSense, size_t cbSense, uint32_t cTimeoutMillies)
506{
507 PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
508 int rc;
509 LogFlow(("%s: cmd[0]=%#04x txdir=%d pcbBuf=%d timeout=%d\n", __FUNCTION__, pbCmd[0], enmTxDir, *pcbBuf, cTimeoutMillies));
510
511 RTCritSectEnter(&pThis->CritSect);
512 /*
513 * Pass the request on to the internal scsi command interface.
514 * The command seems to be 12 bytes long, the docs a bit copy&pasty on the command length point...
515 */
516 if (enmTxDir == PDMMEDIATXDIR_FROM_DEVICE)
517 memset(pvBuf, '\0', *pcbBuf); /* we got read size, but zero it anyway. */
518 rc = drvHostBaseScsiCmdOs(pThis, pbCmd, 12, enmTxDir, pvBuf, pcbBuf, pabSense, cbSense, cTimeoutMillies);
519 if (rc == VERR_UNRESOLVED_ERROR)
520 /* sense information set */
521 rc = VERR_DEV_IO_ERROR;
522
523 if (pbCmd[0] == SCSI_GET_EVENT_STATUS_NOTIFICATION)
524 {
525 uint8_t *pbBuf = (uint8_t*)pvBuf;
526 Log2(("Event Status Notification class=%#02x supported classes=%#02x\n", pbBuf[2], pbBuf[3]));
527 if (RT_BE2H_U16(*(uint16_t*)pbBuf) >= 6)
528 Log2((" event %#02x %#02x %#02x %#02x\n", pbBuf[4], pbBuf[5], pbBuf[6], pbBuf[7]));
529 }
530 RTCritSectLeave(&pThis->CritSect);
531
532 LogFlow(("%s: rc=%Rrc\n", __FUNCTION__, rc));
533 return rc;
534}
535
536
537/** @interface_method_impl{PDMIMEDIAEX,pfnIoReqSendScsiCmd} */
538static DECLCALLBACK(int) drvHostDvdIoReqSendScsiCmd(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint32_t uLun,
539 const uint8_t *pbCdb, size_t cbCdb, PDMMEDIAEXIOREQSCSITXDIR enmTxDir,
540 size_t cbBuf, uint8_t *pabSense, size_t cbSense, uint8_t *pu8ScsiSts,
541 uint32_t cTimeoutMillies)
542{
543 RT_NOREF3(uLun, cTimeoutMillies, enmTxDir);
544
545 PDRVHOSTDVD pThis = RT_FROM_MEMBER(pInterface, DRVHOSTDVD, Core.IMediaEx);
546 PDRVHOSTBASEREQ pReq = (PDRVHOSTBASEREQ)hIoReq;
547 int rc = VINF_SUCCESS;
548
549 LogFlow(("%s: pbCdb[0]=%#04x{%s} enmTxDir=%d cbBuf=%zu timeout=%u\n",
550 __FUNCTION__, pbCdb[0], SCSICmdText(pbCdb[0]), enmTxDir, cbBuf, cTimeoutMillies));
551
552 RTCritSectEnter(&pThis->Core.CritSect);
553
554 /*
555 * Parse the command first to fend off any illegal or dangeroups commands we don't want the guest
556 * to execute on the host drive.
557 */
558 PDMMEDIATXDIR enmXferDir = PDMMEDIATXDIR_NONE;
559 size_t cbXfer = 0;
560 size_t cbSector = 0;
561 bool fPassthrough = drvHostDvdParseCdb(pThis, pReq, pbCdb, cbCdb, cbBuf,
562 &enmXferDir, &cbXfer, &cbSector, pu8ScsiSts);
563 if (fPassthrough)
564 {
565 void *pvBuf = NULL;
566 size_t cbXferCur = cbXfer;
567
568 pReq->cbReq = cbXfer;
569 pReq->cbResidual = cbXfer;
570
571 if (cbXfer)
572 rc = drvHostBaseBufferRetain(&pThis->Core, pReq, cbXfer, enmXferDir == PDMMEDIATXDIR_TO_DEVICE, &pvBuf);
573
574 if (cbXfer > SCSI_MAX_BUFFER_SIZE)
575 {
576 /* Linux accepts commands with up to 100KB of data, but expects
577 * us to handle commands with up to 128KB of data. The usual
578 * imbalance of powers. */
579 uint8_t aATAPICmd[ATAPI_PACKET_SIZE];
580 uint32_t iATAPILBA, cSectors;
581 uint8_t *pbBuf = (uint8_t *)pvBuf;
582
583 switch (pbCdb[0])
584 {
585 case SCSI_READ_10:
586 case SCSI_WRITE_10:
587 case SCSI_WRITE_AND_VERIFY_10:
588 iATAPILBA = drvHostDvdBE2H_U32(pbCdb + 2);
589 cSectors = drvHostDvdBE2H_U16(pbCdb + 7);
590 break;
591 case SCSI_READ_12:
592 case SCSI_WRITE_12:
593 iATAPILBA = drvHostDvdBE2H_U32(pbCdb + 2);
594 cSectors = drvHostDvdBE2H_U32(pbCdb + 6);
595 break;
596 case SCSI_READ_CD:
597 iATAPILBA = drvHostDvdBE2H_U32(pbCdb + 2);
598 cSectors = drvHostDvdBE2H_U24(pbCdb + 6);
599 break;
600 case SCSI_READ_CD_MSF:
601 iATAPILBA = drvHostDvdMSF2LBA(pbCdb + 3);
602 cSectors = drvHostDvdMSF2LBA(pbCdb + 6) - iATAPILBA;
603 break;
604 default:
605 AssertMsgFailed(("Don't know how to split command %#04x\n", pbCdb[0]));
606 LogRelMax(10, ("HostDVD#%u: CD-ROM passthrough split error\n", pThis->Core.pDrvIns->iInstance));
607 drvHostDvdCmdErrorSimple(pThis, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
608 *pu8ScsiSts = SCSI_STATUS_CHECK_CONDITION;
609 rc = drvHostBaseBufferRelease(&pThis->Core, pReq, cbBuf, enmXferDir == PDMMEDIATXDIR_TO_DEVICE, pvBuf);
610 RTCritSectLeave(&pThis->Core.CritSect);
611 return VINF_SUCCESS;
612 }
613 memcpy(aATAPICmd, pbCdb, RT_MIN(cbCdb, ATAPI_PACKET_SIZE));
614 uint32_t cReqSectors = 0;
615 for (uint32_t i = cSectors; i > 0; i -= cReqSectors)
616 {
617 if (i * cbSector > SCSI_MAX_BUFFER_SIZE)
618 cReqSectors = SCSI_MAX_BUFFER_SIZE / (uint32_t)cbSector;
619 else
620 cReqSectors = i;
621 uint32_t cbCurrTX = (uint32_t)cbSector * cReqSectors;
622 switch (pbCdb[0])
623 {
624 case SCSI_READ_10:
625 case SCSI_WRITE_10:
626 case SCSI_WRITE_AND_VERIFY_10:
627 drvHostDvdH2BE_U32(aATAPICmd + 2, iATAPILBA);
628 drvHostDvdH2BE_U16(aATAPICmd + 7, cReqSectors);
629 break;
630 case SCSI_READ_12:
631 case SCSI_WRITE_12:
632 drvHostDvdH2BE_U32(aATAPICmd + 2, iATAPILBA);
633 drvHostDvdH2BE_U32(aATAPICmd + 6, cReqSectors);
634 break;
635 case SCSI_READ_CD:
636 drvHostDvdH2BE_U32(aATAPICmd + 2, iATAPILBA);
637 drvHostDvdH2BE_U24(aATAPICmd + 6, cReqSectors);
638 break;
639 case SCSI_READ_CD_MSF:
640 drvHostDvdLBA2MSF(aATAPICmd + 3, iATAPILBA);
641 drvHostDvdLBA2MSF(aATAPICmd + 6, iATAPILBA + cReqSectors);
642 break;
643 }
644 rc = drvHostBaseScsiCmdOs(&pThis->Core, aATAPICmd, sizeof(aATAPICmd),
645 enmXferDir, pbBuf, &cbCurrTX,
646 &pThis->abATAPISense[0], sizeof(pThis->abATAPISense),
647 cTimeoutMillies /**< @todo timeout */);
648 if (rc != VINF_SUCCESS)
649 break;
650
651 pReq->cbResidual -= cbCurrTX;
652 iATAPILBA += cReqSectors;
653 pbBuf += cbSector * cReqSectors;
654 }
655 }
656 else
657 {
658 uint32_t cbXferTmp = (uint32_t)cbXferCur;
659 rc = drvHostBaseScsiCmdOs(&pThis->Core, pbCdb, cbCdb, enmXferDir, pvBuf, &cbXferTmp,
660 &pThis->abATAPISense[0], sizeof(pThis->abATAPISense), cTimeoutMillies);
661 if (RT_SUCCESS(rc))
662 pReq->cbResidual -= cbXferTmp;
663 }
664
665 if (RT_SUCCESS(rc))
666 {
667 /* Do post processing for certain commands. */
668 switch (pbCdb[0])
669 {
670 case SCSI_SEND_CUE_SHEET:
671 case SCSI_READ_TOC_PMA_ATIP:
672 {
673 if (!pThis->pTrackList)
674 rc = ATAPIPassthroughTrackListCreateEmpty(&pThis->pTrackList);
675
676 if (RT_SUCCESS(rc))
677 rc = ATAPIPassthroughTrackListUpdate(pThis->pTrackList, pbCdb, pvBuf);
678
679 if (RT_FAILURE(rc))
680 LogRelMax(10, ("HostDVD#%u: Error (%Rrc) while updating the tracklist during %s, burning the disc might fail\n",
681 pThis->Core.pDrvIns->iInstance, rc,
682 pbCdb[0] == SCSI_SEND_CUE_SHEET ? "SEND CUE SHEET" : "READ TOC/PMA/ATIP"));
683 break;
684 }
685 case SCSI_SYNCHRONIZE_CACHE:
686 {
687 if (pThis->pTrackList)
688 ATAPIPassthroughTrackListClear(pThis->pTrackList);
689 break;
690 }
691 }
692
693 if (enmXferDir == PDMMEDIATXDIR_FROM_DEVICE)
694 {
695 Assert(cbXferCur <= cbXfer);
696
697 if (pbCdb[0] == SCSI_INQUIRY)
698 {
699 /* Make sure that the real drive cannot be identified.
700 * Motivation: changing the VM configuration should be as
701 * invisible as possible to the guest. */
702 if (cbXferCur >= 8 + 8)
703 drvHostDvdSCSIPadStr((uint8_t *)pvBuf + 8, "VBOX", 8);
704 if (cbXferCur >= 16 + 16)
705 drvHostDvdSCSIPadStr((uint8_t *)pvBuf + 16, "CD-ROM", 16);
706 if (cbXferCur >= 32 + 4)
707 drvHostDvdSCSIPadStr((uint8_t *)pvBuf + 32, "1.0", 4);
708 }
709
710 if (cbXferCur)
711 Log3(("ATAPI PT data read (%d): %.*Rhxs\n", cbXferCur, cbXferCur, (uint8_t *)pvBuf));
712 }
713
714 drvHostDvdCmdOK(pThis);
715 *pu8ScsiSts = SCSI_STATUS_OK;
716 }
717 else
718 {
719 do
720 {
721 /* don't log superfluous errors */
722 if ( rc == VERR_DEV_IO_ERROR
723 && ( pbCdb[0] == SCSI_TEST_UNIT_READY
724 || pbCdb[0] == SCSI_READ_CAPACITY
725 || pbCdb[0] == SCSI_READ_DVD_STRUCTURE
726 || pbCdb[0] == SCSI_READ_TOC_PMA_ATIP))
727 break;
728 LogRelMax(10, ("HostDVD#%u: CD-ROM passthrough cmd=%#04x sense=%d ASC=%#02x ASCQ=%#02x %Rrc\n",
729 pThis->Core.pDrvIns->iInstance, pbCdb[0], pThis->abATAPISense[2] & 0x0f,
730 pThis->abATAPISense[12], pThis->abATAPISense[13], rc));
731 } while (0);
732 drvHostDvdCmdError(pThis, &pThis->abATAPISense[0], sizeof(pThis->abATAPISense));
733 *pu8ScsiSts = SCSI_STATUS_CHECK_CONDITION;
734 rc = VINF_SUCCESS;
735 }
736
737 if (cbXfer)
738 rc = drvHostBaseBufferRelease(&pThis->Core, pReq, cbXfer, enmXferDir == PDMMEDIATXDIR_TO_DEVICE, pvBuf);
739 }
740
741 /*
742 * We handled the command, check the status code and copy over the sense data if
743 * it is CHECK CONDITION.
744 */
745 if ( *pu8ScsiSts == SCSI_STATUS_CHECK_CONDITION
746 && VALID_PTR(pabSense)
747 && cbSense > 0)
748 memcpy(pabSense, &pThis->abATAPISense[0], RT_MIN(cbSense, sizeof(pThis->abATAPISense)));
749
750 RTCritSectLeave(&pThis->Core.CritSect);
751
752 LogFlow(("%s: rc=%Rrc\n", __FUNCTION__, rc));
753 return rc;
754}
755
756
757/* -=-=-=-=- driver interface -=-=-=-=- */
758
759
760/** @interface_method_impl{PDMDRVREG,pfnDestruct} */
761static DECLCALLBACK(void) drvHostDvdDestruct(PPDMDRVINS pDrvIns)
762{
763 PDRVHOSTDVD pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDVD);
764
765 if (pThis->pTrackList)
766 {
767 ATAPIPassthroughTrackListDestroy(pThis->pTrackList);
768 pThis->pTrackList = NULL;
769 }
770
771 DRVHostBaseDestruct(pDrvIns);
772}
773
774/**
775 * Construct a host dvd drive driver instance.
776 *
777 * @copydoc FNPDMDRVCONSTRUCT
778 */
779static DECLCALLBACK(int) drvHostDvdConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
780{
781 RT_NOREF(fFlags);
782 PDRVHOSTDVD pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDVD);
783 LogFlow(("drvHostDvdConstruct: iInstance=%d\n", pDrvIns->iInstance));
784
785 bool fPassthrough;
786 int rc = CFGMR3QueryBool(pCfg, "Passthrough", &fPassthrough);
787 if (RT_SUCCESS(rc) && fPassthrough)
788 {
789 pThis->Core.IMedia.pfnSendCmd = drvHostDvdSendCmd;
790 pThis->Core.IMediaEx.pfnIoReqSendScsiCmd = drvHostDvdIoReqSendScsiCmd;
791 /* Passthrough requires opening the device in R/W mode. */
792 pThis->Core.fReadOnlyConfig = false;
793 }
794
795 pThis->Core.pfnDoLock = drvHostDvdDoLock;
796
797 /*
798 * Init instance data.
799 */
800 rc = DRVHostBaseInit(pDrvIns, pCfg, "Path\0Interval\0Locked\0BIOSVisible\0AttachFailError\0Passthrough\0",
801 PDMMEDIATYPE_DVD);
802 LogFlow(("drvHostDvdConstruct: returns %Rrc\n", rc));
803 return rc;
804}
805
806
807/**
808 * Block driver registration record.
809 */
810const PDMDRVREG g_DrvHostDVD =
811{
812 /* u32Version */
813 PDM_DRVREG_VERSION,
814 /* szName */
815 "HostDVD",
816 /* szRCMod */
817 "",
818 /* szR0Mod */
819 "",
820 /* pszDescription */
821 "Host DVD Block Driver.",
822 /* fFlags */
823 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
824 /* fClass. */
825 PDM_DRVREG_CLASS_BLOCK,
826 /* cMaxInstances */
827 ~0U,
828 /* cbInstance */
829 sizeof(DRVHOSTDVD),
830 /* pfnConstruct */
831 drvHostDvdConstruct,
832 /* pfnDestruct */
833 drvHostDvdDestruct,
834 /* pfnRelocate */
835 NULL,
836 /* pfnIOCtl */
837 NULL,
838 /* pfnPowerOn */
839 NULL,
840 /* pfnReset */
841 NULL,
842 /* pfnSuspend */
843 NULL,
844 /* pfnResume */
845 NULL,
846 /* pfnAttach */
847 NULL,
848 /* pfnDetach */
849 NULL,
850 /* pfnPowerOff */
851 NULL,
852 /* pfnSoftReset */
853 NULL,
854 /* u32EndVersion */
855 PDM_DRVREG_VERSION
856};
857
Note: See TracBrowser for help on using the repository browser.

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