1 | /* $Id: DrvHostDVD.cpp 64320 2016-10-19 14:55:59Z 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 | /**
|
---|
39 | * Host DVD driver instance data.
|
---|
40 | */
|
---|
41 | typedef struct DRVHOSTDVD
|
---|
42 | {
|
---|
43 | /** Base drivr data. */
|
---|
44 | DRVHOSTBASE Core;
|
---|
45 | /** The current tracklist of the loaded medium if passthrough is used. */
|
---|
46 | PTRACKLIST pTrackList;
|
---|
47 | } DRVHOSTDVD;
|
---|
48 | /** Pointer to the host DVD driver instance data. */
|
---|
49 | typedef DRVHOSTDVD *PDRVHOSTDVD;
|
---|
50 |
|
---|
51 | /*********************************************************************************************************************************
|
---|
52 | * Internal Functions *
|
---|
53 | *********************************************************************************************************************************/
|
---|
54 |
|
---|
55 |
|
---|
56 | static PDMMEDIATXDIR drvHostDvdGetTxDirFromCmd(uint8_t bCdb)
|
---|
57 | {
|
---|
58 | PDMMEDIATXDIR enmTxDir = PDMMEDIATXDIR_NONE;
|
---|
59 |
|
---|
60 | switch (bCdb)
|
---|
61 | {
|
---|
62 | case SCSI_BLANK:
|
---|
63 | case SCSI_CLOSE_TRACK_SESSION:
|
---|
64 | case SCSI_LOAD_UNLOAD_MEDIUM:
|
---|
65 | case SCSI_PAUSE_RESUME:
|
---|
66 | case SCSI_PLAY_AUDIO_10:
|
---|
67 | case SCSI_PLAY_AUDIO_12:
|
---|
68 | case SCSI_PLAY_AUDIO_MSF:
|
---|
69 | case SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL:
|
---|
70 | case SCSI_RESERVE_TRACK:
|
---|
71 | case SCSI_SCAN:
|
---|
72 | case SCSI_SEEK_10:
|
---|
73 | case SCSI_REPAIR_TRACK:
|
---|
74 | case SCSI_SET_CD_SPEED:
|
---|
75 | case SCSI_SET_READ_AHEAD:
|
---|
76 | case SCSI_START_STOP_UNIT:
|
---|
77 | case SCSI_STOP_PLAY_SCAN:
|
---|
78 | case SCSI_SYNCHRONIZE_CACHE:
|
---|
79 | case SCSI_TEST_UNIT_READY:
|
---|
80 | case SCSI_VERIFY_10:
|
---|
81 | enmTxDir = PDMMEDIATXDIR_NONE;
|
---|
82 | break;
|
---|
83 | case SCSI_SET_STREAMING:
|
---|
84 | case SCSI_ERASE_10:
|
---|
85 | case SCSI_FORMAT_UNIT:
|
---|
86 | case SCSI_MODE_SELECT_10:
|
---|
87 | case SCSI_SEND_CUE_SHEET:
|
---|
88 | case SCSI_SEND_DVD_STRUCTURE:
|
---|
89 | case SCSI_SEND_EVENT:
|
---|
90 | case SCSI_SEND_KEY:
|
---|
91 | case SCSI_SEND_OPC_INFORMATION:
|
---|
92 | case SCSI_WRITE_10:
|
---|
93 | case SCSI_WRITE_AND_VERIFY_10:
|
---|
94 | case SCSI_WRITE_12:
|
---|
95 | enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
|
---|
96 | break;
|
---|
97 | case SCSI_GET_CONFIGURATION:
|
---|
98 | case SCSI_GET_EVENT_STATUS_NOTIFICATION:
|
---|
99 | case SCSI_GET_PERFORMANCE:
|
---|
100 | case SCSI_INQUIRY:
|
---|
101 | case SCSI_MECHANISM_STATUS:
|
---|
102 | case SCSI_MODE_SENSE_10:
|
---|
103 | case SCSI_READ_10:
|
---|
104 | case SCSI_READ_12:
|
---|
105 | case SCSI_READ_BUFFER:
|
---|
106 | case SCSI_READ_BUFFER_CAPACITY:
|
---|
107 | case SCSI_READ_CAPACITY:
|
---|
108 | case SCSI_READ_CD:
|
---|
109 | case SCSI_READ_CD_MSF:
|
---|
110 | case SCSI_READ_DISC_INFORMATION:
|
---|
111 | case SCSI_READ_DVD_STRUCTURE:
|
---|
112 | case SCSI_READ_FORMAT_CAPACITIES:
|
---|
113 | case SCSI_READ_SUBCHANNEL:
|
---|
114 | case SCSI_READ_TOC_PMA_ATIP:
|
---|
115 | case SCSI_READ_TRACK_INFORMATION:
|
---|
116 | case SCSI_REPORT_KEY:
|
---|
117 | case SCSI_REQUEST_SENSE:
|
---|
118 | case SCSI_REPORT_LUNS: /* Not part of MMC-3, but used by Windows. */
|
---|
119 | enmTxDir = PDMMEDIATXDIR_FROM_DEVICE;
|
---|
120 | break;
|
---|
121 |
|
---|
122 | case SCSI_WRITE_BUFFER:
|
---|
123 | #if 0
|
---|
124 | switch (pbPacket[1] & 0x1f)
|
---|
125 | {
|
---|
126 | case 0x04: /* download microcode */
|
---|
127 | case 0x05: /* download microcode and save */
|
---|
128 | case 0x06: /* download microcode with offsets */
|
---|
129 | case 0x07: /* download microcode with offsets and save */
|
---|
130 | case 0x0e: /* download microcode with offsets and defer activation */
|
---|
131 | case 0x0f: /* activate deferred microcode */
|
---|
132 | LogRel(("PIIX3 ATA: LUN#%d: CD-ROM passthrough command attempted to update firmware, blocked\n", s->iLUN));
|
---|
133 | atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_INV_FIELD_IN_CMD_PACKET);
|
---|
134 | break;
|
---|
135 | default:
|
---|
136 | enmTxDir = PDMMEDIATXDIR_TO_DEVICE;
|
---|
137 | break;
|
---|
138 | }
|
---|
139 | #endif
|
---|
140 | break;
|
---|
141 | case SCSI_REZERO_UNIT:
|
---|
142 | /* Obsolete command used by cdrecord. What else would one expect?
|
---|
143 | * This command is not sent to the drive, it is handled internally,
|
---|
144 | * as the Linux kernel doesn't like it (message "scsi: unknown
|
---|
145 | * opcode 0x01" in syslog) and replies with a sense code of 0,
|
---|
146 | * which sends cdrecord to an endless loop. */
|
---|
147 | //atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
|
---|
148 | break;
|
---|
149 | default:
|
---|
150 | LogRel(("HostDVD: passthrough unimplemented for command %#x\n", bCdb));
|
---|
151 | //atapiR3CmdErrorSimple(s, SCSI_SENSE_ILLEGAL_REQUEST, SCSI_ASC_ILLEGAL_OPCODE);
|
---|
152 | break;
|
---|
153 | }
|
---|
154 |
|
---|
155 | return enmTxDir;
|
---|
156 | }
|
---|
157 |
|
---|
158 | /**
|
---|
159 | * Locks or unlocks the drive.
|
---|
160 | *
|
---|
161 | * @returns VBox status code.
|
---|
162 | * @param pThis The instance data.
|
---|
163 | * @param fLock True if the request is to lock the drive, false if to unlock.
|
---|
164 | */
|
---|
165 | static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock)
|
---|
166 | {
|
---|
167 | int rc = drvHostBaseDoLockOs(pThis, fLock);
|
---|
168 |
|
---|
169 | LogFlow(("drvHostDvdDoLock(, fLock=%RTbool): returns %Rrc\n", fLock, rc));
|
---|
170 | return rc;
|
---|
171 | }
|
---|
172 |
|
---|
173 |
|
---|
174 | /** @interface_method_impl{PDMIMEDIA,pfnSendCmd} */
|
---|
175 | static DECLCALLBACK(int) drvHostDvdSendCmd(PPDMIMEDIA pInterface, const uint8_t *pbCmd,
|
---|
176 | PDMMEDIATXDIR enmTxDir, void *pvBuf, uint32_t *pcbBuf,
|
---|
177 | uint8_t *pabSense, size_t cbSense, uint32_t cTimeoutMillies)
|
---|
178 | {
|
---|
179 | PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMedia);
|
---|
180 | int rc;
|
---|
181 | LogFlow(("%s: cmd[0]=%#04x txdir=%d pcbBuf=%d timeout=%d\n", __FUNCTION__, pbCmd[0], enmTxDir, *pcbBuf, cTimeoutMillies));
|
---|
182 |
|
---|
183 | RTCritSectEnter(&pThis->CritSect);
|
---|
184 | /*
|
---|
185 | * Pass the request on to the internal scsi command interface.
|
---|
186 | * The command seems to be 12 bytes long, the docs a bit copy&pasty on the command length point...
|
---|
187 | */
|
---|
188 | if (enmTxDir == PDMMEDIATXDIR_FROM_DEVICE)
|
---|
189 | memset(pvBuf, '\0', *pcbBuf); /* we got read size, but zero it anyway. */
|
---|
190 | rc = drvHostBaseScsiCmdOs(pThis, pbCmd, 12, enmTxDir, pvBuf, pcbBuf, pabSense, cbSense, cTimeoutMillies);
|
---|
191 | if (rc == VERR_UNRESOLVED_ERROR)
|
---|
192 | /* sense information set */
|
---|
193 | rc = VERR_DEV_IO_ERROR;
|
---|
194 |
|
---|
195 | if (pbCmd[0] == SCSI_GET_EVENT_STATUS_NOTIFICATION)
|
---|
196 | {
|
---|
197 | uint8_t *pbBuf = (uint8_t*)pvBuf;
|
---|
198 | Log2(("Event Status Notification class=%#02x supported classes=%#02x\n", pbBuf[2], pbBuf[3]));
|
---|
199 | if (RT_BE2H_U16(*(uint16_t*)pbBuf) >= 6)
|
---|
200 | Log2((" event %#02x %#02x %#02x %#02x\n", pbBuf[4], pbBuf[5], pbBuf[6], pbBuf[7]));
|
---|
201 | }
|
---|
202 | RTCritSectLeave(&pThis->CritSect);
|
---|
203 |
|
---|
204 | LogFlow(("%s: rc=%Rrc\n", __FUNCTION__, rc));
|
---|
205 | return rc;
|
---|
206 | }
|
---|
207 |
|
---|
208 |
|
---|
209 | /** @interface_method_impl{PDMIMEDIAEX,pfnIoReqSendScsiCmd} */
|
---|
210 | static DECLCALLBACK(int) drvHostDvdIoReqSendScsiCmd(PPDMIMEDIAEX pInterface, PDMMEDIAEXIOREQ hIoReq, uint32_t uLun,
|
---|
211 | const uint8_t *pbCdb, size_t cbCdb, PDMMEDIAEXIOREQSCSITXDIR enmTxDir,
|
---|
212 | size_t cbBuf, uint8_t *pabSense, size_t cbSense, uint8_t *pu8ScsiSts,
|
---|
213 | uint32_t cTimeoutMillies)
|
---|
214 | {
|
---|
215 | RT_NOREF2(uLun, cTimeoutMillies);
|
---|
216 |
|
---|
217 | PDRVHOSTBASE pThis = RT_FROM_MEMBER(pInterface, DRVHOSTBASE, IMediaEx);
|
---|
218 | PDRVHOSTBASEREQ pReq = (PDRVHOSTBASEREQ)hIoReq;
|
---|
219 | int rc = VINF_SUCCESS;
|
---|
220 | void *pvBuf = NULL;
|
---|
221 |
|
---|
222 | LogFlow(("%s: pbCdb[0]=%#04x enmTxDir=%d cbBuf=%zu timeout=%u\n", __FUNCTION__, pbCdb[0], enmTxDir, cbBuf, cTimeoutMillies));
|
---|
223 |
|
---|
224 | RTCritSectEnter(&pThis->CritSect);
|
---|
225 | if (cbBuf)
|
---|
226 | {
|
---|
227 | bool fWrite = (enmTxDir == PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN || enmTxDir == PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE);
|
---|
228 | rc = drvHostBaseBufferRetain(pThis, pReq, cbBuf, fWrite, &pvBuf);
|
---|
229 | }
|
---|
230 |
|
---|
231 | if (RT_SUCCESS(rc))
|
---|
232 | {
|
---|
233 | uint32_t cbBufTmp = (uint32_t)cbBuf;
|
---|
234 | PDMMEDIATXDIR enmMediaTxDir = PDMMEDIATXDIR_NONE;
|
---|
235 | /*
|
---|
236 | * Pass the request on to the internal scsi command interface.
|
---|
237 | * The command seems to be 12 bytes long, the docs a bit copy&pasty on the command length point...
|
---|
238 | */
|
---|
239 | if (enmTxDir == PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN || enmTxDir == PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE)
|
---|
240 | memset(pvBuf, '\0', cbBuf); /* we got read size, but zero it anyway. */
|
---|
241 |
|
---|
242 | if (cbBuf)
|
---|
243 | {
|
---|
244 | switch (enmTxDir)
|
---|
245 | {
|
---|
246 | case PDMMEDIAEXIOREQSCSITXDIR_FROM_DEVICE:
|
---|
247 | enmMediaTxDir = PDMMEDIATXDIR_FROM_DEVICE;
|
---|
248 | break;
|
---|
249 | case PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE:
|
---|
250 | enmMediaTxDir = PDMMEDIATXDIR_TO_DEVICE;
|
---|
251 | break;
|
---|
252 | case PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN:
|
---|
253 | enmMediaTxDir = drvHostDvdGetTxDirFromCmd(pbCdb[0]);
|
---|
254 | break;
|
---|
255 | case PDMMEDIAEXIOREQSCSITXDIR_NONE:
|
---|
256 | default:
|
---|
257 | enmMediaTxDir = PDMMEDIATXDIR_NONE;
|
---|
258 | break;
|
---|
259 | }
|
---|
260 | }
|
---|
261 |
|
---|
262 | rc = drvHostBaseScsiCmdOs(pThis, pbCdb, cbCdb, enmMediaTxDir, pvBuf, &cbBufTmp, pabSense, cbSense, cTimeoutMillies);
|
---|
263 | if (rc == VERR_UNRESOLVED_ERROR)
|
---|
264 | {
|
---|
265 | /* sense information set */
|
---|
266 | rc = VERR_DEV_IO_ERROR;
|
---|
267 | *pu8ScsiSts = SCSI_STATUS_CHECK_CONDITION;
|
---|
268 | }
|
---|
269 |
|
---|
270 | if (pbCdb[0] == SCSI_GET_EVENT_STATUS_NOTIFICATION)
|
---|
271 | {
|
---|
272 | uint8_t *pbBuf = (uint8_t*)pvBuf;
|
---|
273 | Log2(("Event Status Notification class=%#02x supported classes=%#02x\n", pbBuf[2], pbBuf[3]));
|
---|
274 | if (RT_BE2H_U16(*(uint16_t*)pbBuf) >= 6)
|
---|
275 | Log2((" event %#02x %#02x %#02x %#02x\n", pbBuf[4], pbBuf[5], pbBuf[6], pbBuf[7]));
|
---|
276 | }
|
---|
277 |
|
---|
278 | if (cbBuf)
|
---|
279 | {
|
---|
280 | bool fWrite = (enmTxDir == PDMMEDIAEXIOREQSCSITXDIR_UNKNOWN || enmTxDir == PDMMEDIAEXIOREQSCSITXDIR_TO_DEVICE);
|
---|
281 | rc = drvHostBaseBufferRelease(pThis, pReq, cbBuf, fWrite, pvBuf);
|
---|
282 | }
|
---|
283 | }
|
---|
284 | RTCritSectLeave(&pThis->CritSect);
|
---|
285 |
|
---|
286 | LogFlow(("%s: rc=%Rrc\n", __FUNCTION__, rc));
|
---|
287 | return rc;
|
---|
288 | }
|
---|
289 |
|
---|
290 |
|
---|
291 | /* -=-=-=-=- driver interface -=-=-=-=- */
|
---|
292 |
|
---|
293 |
|
---|
294 | /** @interface_method_impl{PDMDRVREG,pfnDestruct} */
|
---|
295 | static DECLCALLBACK(void) drvHostDvdDestruct(PPDMDRVINS pDrvIns)
|
---|
296 | {
|
---|
297 | PDRVHOSTDVD pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDVD);
|
---|
298 |
|
---|
299 | if (pThis->pTrackList)
|
---|
300 | {
|
---|
301 | ATAPIPassthroughTrackListDestroy(pThis->pTrackList);
|
---|
302 | pThis->pTrackList = NULL;
|
---|
303 | }
|
---|
304 |
|
---|
305 | DRVHostBaseDestruct(pDrvIns);
|
---|
306 | }
|
---|
307 |
|
---|
308 | /**
|
---|
309 | * Construct a host dvd drive driver instance.
|
---|
310 | *
|
---|
311 | * @copydoc FNPDMDRVCONSTRUCT
|
---|
312 | */
|
---|
313 | static DECLCALLBACK(int) drvHostDvdConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
|
---|
314 | {
|
---|
315 | RT_NOREF(fFlags);
|
---|
316 | PDRVHOSTDVD pThis = PDMINS_2_DATA(pDrvIns, PDRVHOSTDVD);
|
---|
317 | LogFlow(("drvHostDvdConstruct: iInstance=%d\n", pDrvIns->iInstance));
|
---|
318 |
|
---|
319 | bool fPassthrough;
|
---|
320 | int rc = CFGMR3QueryBool(pCfg, "Passthrough", &fPassthrough);
|
---|
321 | if (RT_SUCCESS(rc) && fPassthrough)
|
---|
322 | {
|
---|
323 | pThis->Core.IMedia.pfnSendCmd = drvHostDvdSendCmd;
|
---|
324 | pThis->Core.IMediaEx.pfnIoReqSendScsiCmd = drvHostDvdIoReqSendScsiCmd;
|
---|
325 | /* Passthrough requires opening the device in R/W mode. */
|
---|
326 | pThis->Core.fReadOnlyConfig = false;
|
---|
327 | }
|
---|
328 |
|
---|
329 | pThis->Core.pfnDoLock = drvHostDvdDoLock;
|
---|
330 |
|
---|
331 | /*
|
---|
332 | * Init instance data.
|
---|
333 | */
|
---|
334 | rc = DRVHostBaseInit(pDrvIns, pCfg, "Path\0Interval\0Locked\0BIOSVisible\0AttachFailError\0Passthrough\0",
|
---|
335 | PDMMEDIATYPE_DVD);
|
---|
336 | LogFlow(("drvHostDvdConstruct: returns %Rrc\n", rc));
|
---|
337 | return rc;
|
---|
338 | }
|
---|
339 |
|
---|
340 |
|
---|
341 | /**
|
---|
342 | * Block driver registration record.
|
---|
343 | */
|
---|
344 | const PDMDRVREG g_DrvHostDVD =
|
---|
345 | {
|
---|
346 | /* u32Version */
|
---|
347 | PDM_DRVREG_VERSION,
|
---|
348 | /* szName */
|
---|
349 | "HostDVD",
|
---|
350 | /* szRCMod */
|
---|
351 | "",
|
---|
352 | /* szR0Mod */
|
---|
353 | "",
|
---|
354 | /* pszDescription */
|
---|
355 | "Host DVD Block Driver.",
|
---|
356 | /* fFlags */
|
---|
357 | PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
|
---|
358 | /* fClass. */
|
---|
359 | PDM_DRVREG_CLASS_BLOCK,
|
---|
360 | /* cMaxInstances */
|
---|
361 | ~0U,
|
---|
362 | /* cbInstance */
|
---|
363 | sizeof(DRVHOSTDVD),
|
---|
364 | /* pfnConstruct */
|
---|
365 | drvHostDvdConstruct,
|
---|
366 | /* pfnDestruct */
|
---|
367 | drvHostDvdDestruct,
|
---|
368 | /* pfnRelocate */
|
---|
369 | NULL,
|
---|
370 | /* pfnIOCtl */
|
---|
371 | NULL,
|
---|
372 | /* pfnPowerOn */
|
---|
373 | NULL,
|
---|
374 | /* pfnReset */
|
---|
375 | NULL,
|
---|
376 | /* pfnSuspend */
|
---|
377 | NULL,
|
---|
378 | /* pfnResume */
|
---|
379 | NULL,
|
---|
380 | /* pfnAttach */
|
---|
381 | NULL,
|
---|
382 | /* pfnDetach */
|
---|
383 | NULL,
|
---|
384 | /* pfnPowerOff */
|
---|
385 | NULL,
|
---|
386 | /* pfnSoftReset */
|
---|
387 | NULL,
|
---|
388 | /* u32EndVersion */
|
---|
389 | PDM_DRVREG_VERSION
|
---|
390 | };
|
---|
391 |
|
---|