3
* VBox storage devices:
4
* Host DVD block driver
8
* Copyright (C) 2006-2007 innotek GmbH
10
* This file is part of VirtualBox Open Source Edition (OSE), as
11
* available from http://www.virtualbox.org. This file is free software;
12
* you can redistribute it and/or modify it under the terms of the GNU
13
* General Public License as published by the Free Software Foundation,
14
* in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
15
* distribution. VirtualBox OSE is distributed in the hope that it will
16
* be useful, but WITHOUT ANY WARRANTY of any kind.
20
/*******************************************************************************
22
*******************************************************************************/
23
#define LOG_GROUP LOG_GROUP_DRV_HOST_DVD
25
# include <mach/mach.h>
26
# include <Carbon/Carbon.h>
27
# include <IOKit/IOKitLib.h>
28
# include <IOKit/IOCFPlugIn.h>
29
# include <IOKit/scsi-commands/SCSITaskLib.h>
30
# include <IOKit/scsi-commands/SCSICommandOperationCodes.h>
31
# include <IOKit/storage/IOStorageDeviceCharacteristics.h>
32
# include <mach/mach_error.h>
33
# define USE_MEDIA_POLLING
35
#elif defined(RT_OS_L4)
38
#elif defined RT_OS_LINUX
39
# include <sys/ioctl.h>
40
/* This is a hack to work around conflicts between these linux kernel headers
41
* and the GLIBC tcpip headers. They have different declarations of the 4
42
* standard byte order functions. */
43
# define _LINUX_BYTEORDER_GENERIC_H
44
/* This is another hack for not bothering with C++ unfriendly byteswap macros. */
45
# define _LINUX_BYTEORDER_SWAB_H
46
/* Those macros that are needed are defined in the header below */
48
# include <linux/cdrom.h>
49
# include <sys/fcntl.h>
51
# define USE_MEDIA_POLLING
53
#elif defined(RT_OS_WINDOWS)
55
# include <winioctl.h>
56
# include <ntddscsi.h>
57
# undef USE_MEDIA_POLLING
60
# error "Unsupported Platform."
63
#include <VBox/pdmdrv.h>
64
#include <iprt/assert.h>
65
#include <iprt/file.h>
66
#include <iprt/string.h>
67
#include <iprt/thread.h>
68
#include <iprt/critsect.h>
69
#include <VBox/scsi.h>
72
#include "DrvHostBase.h"
75
/* Forward declarations. */
77
static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock);
80
/** @copydoc PDMIMOUNT::pfnUnmount */
81
static DECLCALLBACK(int) drvHostDvdUnmount(PPDMIMOUNT pInterface, bool fForce)
83
PDRVHOSTBASE pThis = PDMIMOUNT_2_DRVHOSTBASE(pInterface);
84
RTCritSectEnter(&pThis->CritSect);
89
int rc = VINF_SUCCESS;
90
if (!pThis->fLocked || fForce)
92
/* Unlock drive if necessary. */
94
drvHostDvdDoLock(pThis, false);
102
SCSI_START_STOP_UNIT, 0, 0, 0, 2 /*eject+stop*/, 0,
105
rc = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_NONE, NULL, NULL, NULL, 0, 0);
107
#elif defined(RT_OS_LINUX)
108
rc = ioctl(pThis->FileDevice, CDROMEJECT, 0);
112
rc = VERR_PDM_MEDIA_LOCKED;
113
else if (errno == ENOSYS)
114
rc = VERR_NOT_SUPPORTED;
116
rc = RTErrConvertFromErrno(errno);
119
#elif defined(RT_OS_WINDOWS)
120
RTFILE FileDevice = pThis->FileDevice;
121
if (FileDevice == NIL_RTFILE) /* obsolete crap */
122
rc = RTFileOpen(&FileDevice, pThis->pszDeviceOpen, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
123
if (VBOX_SUCCESS(rc))
127
if (DeviceIoControl((HANDLE)FileDevice, IOCTL_STORAGE_EJECT_MEDIA,
129
NULL, 0, &cbReturned,
133
rc = RTErrConvertFromWin32(GetLastError());
135
/* clean up handle */
136
if (FileDevice != pThis->FileDevice)
137
RTFileClose(FileDevice);
140
AssertMsgFailed(("Failed to open '%s' for ejecting this tray.\n", rc));
144
AssertMsgFailed(("Eject is not implemented!\n"));
149
* Media is no longer present.
151
DRVHostBaseMediaNotPresent(pThis); /** @todo This isn't thread safe! */
155
Log(("drvHostDvdUnmount: Locked\n"));
156
rc = VERR_PDM_MEDIA_LOCKED;
159
RTCritSectLeave(&pThis->CritSect);
160
LogFlow(("drvHostDvdUnmount: returns %Vrc\n", rc));
166
* Locks or unlocks the drive.
168
* @returns VBox status code.
169
* @param pThis The instance data.
170
* @param fLock True if the request is to lock the drive, false if to unlock.
172
static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock)
177
SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0, 0, 0, fLock, 0,
180
int rc = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_NONE, NULL, NULL, NULL, 0, 0);
182
#elif defined(RT_OS_LINUX)
183
int rc = ioctl(pThis->FileDevice, CDROM_LOCKDOOR, (int)fLock);
187
rc = VERR_ACCESS_DENIED;
188
else if (errno == EDRIVE_CANT_DO_THIS)
189
rc = VERR_NOT_SUPPORTED;
191
rc = RTErrConvertFromErrno(errno);
194
#elif defined(RT_OS_WINDOWS)
196
PREVENT_MEDIA_REMOVAL PreventMediaRemoval = {fLock};
199
if (DeviceIoControl((HANDLE)pThis->FileDevice, IOCTL_STORAGE_MEDIA_REMOVAL,
200
&PreventMediaRemoval, sizeof(PreventMediaRemoval),
201
NULL, 0, &cbReturned,
205
/** @todo figure out the return codes for already locked. */
206
rc = RTErrConvertFromWin32(GetLastError());
209
AssertMsgFailed(("Lock/Unlock is not implemented!\n"));
210
int rc = VINF_SUCCESS;
214
LogFlow(("drvHostDvdDoLock(, fLock=%RTbool): returns %Vrc\n", fLock, rc));
222
* Get the media size.
224
* @returns VBox status code.
225
* @param pThis The instance data.
226
* @param pcb Where to store the size.
228
static int drvHostDvdGetMediaSize(PDRVHOSTBASE pThis, uint64_t *pcb)
231
* Query the media size.
233
/* Clear the media-changed-since-last-call-thingy just to be on the safe side. */
234
ioctl(pThis->FileDevice, CDROM_MEDIA_CHANGED, CDSL_CURRENT);
235
return RTFileSeek(pThis->FileDevice, 0, RTFILE_SEEK_END, pcb);
238
#endif /* RT_OS_LINUX */
241
#ifdef USE_MEDIA_POLLING
243
* Do media change polling.
245
DECLCALLBACK(int) drvHostDvdPoll(PDRVHOSTBASE pThis)
248
* Poll for media change.
251
AssertReturn(pThis->ppScsiTaskDI, VERR_INTERNAL_ERROR);
254
* Issue a TEST UNIT READY request.
256
bool fMediaChanged = false;
257
bool fMediaPresent = false;
258
uint8_t abCmd[16] = { SCSI_TEST_UNIT_READY, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
260
int rc2 = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_NONE, NULL, NULL, abSense, sizeof(abSense), 0);
261
if (VBOX_SUCCESS(rc2))
262
fMediaPresent = true;
263
else if ( rc2 == VERR_UNRESOLVED_ERROR
264
&& abSense[2] == 6 /* unit attention */
265
&& ( (abSense[12] == 0x29 && abSense[13] < 5 /* reset */)
266
|| (abSense[12] == 0x2a && abSense[13] == 0 /* parameters changed */) //???
267
|| (abSense[12] == 0x3f && abSense[13] == 0 /* target operating conditions have changed */) //???
268
|| (abSense[12] == 0x3f && abSense[13] == 2 /* changed operating definition */) //???
269
|| (abSense[12] == 0x3f && abSense[13] == 3 /* inquery parameters changed */)
270
|| (abSense[12] == 0x3f && abSense[13] == 5 /* device identifier changed */)
274
fMediaPresent = false;
275
fMediaChanged = true;
276
/** @todo check this media chance stuff on Darwin. */
279
#elif defined(RT_OS_LINUX)
280
bool fMediaPresent = ioctl(pThis->FileDevice, CDROM_DRIVE_STATUS, CDSL_CURRENT) == CDS_DISC_OK;
283
# error "Unsupported platform."
286
RTCritSectEnter(&pThis->CritSect);
288
int rc = VINF_SUCCESS;
289
if (pThis->fMediaPresent != fMediaPresent)
291
LogFlow(("drvHostDvdPoll: %d -> %d\n", pThis->fMediaPresent, fMediaPresent));
292
pThis->fMediaPresent = false;
294
rc = DRVHostBaseMediaPresent(pThis);
296
DRVHostBaseMediaNotPresent(pThis);
298
else if (fMediaPresent)
301
* Poll for media change.
304
/* taken care of above. */
305
#elif defined(RT_OS_LINUX)
306
bool fMediaChanged = ioctl(pThis->FileDevice, CDROM_MEDIA_CHANGED, CDSL_CURRENT) == 1;
308
# error "Unsupported platform."
312
LogFlow(("drvHostDVDMediaThread: Media changed!\n"));
313
DRVHostBaseMediaNotPresent(pThis);
314
rc = DRVHostBaseMediaPresent(pThis);
318
RTCritSectLeave(&pThis->CritSect);
321
#endif /* USE_MEDIA_POLLING */
324
/** @copydoc PDMIBLOCK::pfnSendCmd */
325
static int drvHostDvdSendCmd(PPDMIBLOCK pInterface, const uint8_t *pbCmd, PDMBLOCKTXDIR enmTxDir, void *pvBuf, size_t *pcbBuf,
326
uint8_t *pbStat, uint32_t cTimeoutMillies)
328
PDRVHOSTBASE pThis = PDMIBLOCK_2_DRVHOSTBASE(pInterface);
330
LogFlow(("%s: cmd[0]=%#04x txdir=%d pcbBuf=%d timeout=%d\n", __FUNCTION__, pbCmd[0], enmTxDir, *pcbBuf, cTimeoutMillies));
334
* Pass the request on to the internal scsi command interface.
335
* The command seems to be 12 bytes long, the docs a bit copy&pasty on the command length point...
337
if (enmTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
338
memset(pvBuf, '\0', *pcbBuf); /* we got read size, but zero it anyway. */
340
rc = DRVHostBaseScsiCmd(pThis, pbCmd, 12, PDMBLOCKTXDIR_FROM_DEVICE, pvBuf, pcbBuf, abSense, sizeof(abSense), cTimeoutMillies);
341
if (rc == VERR_UNRESOLVED_ERROR)
343
*pbStat = abSense[2] & 0x0f;
347
#elif defined(RT_OS_L4)
348
/* Not really ported to L4 yet. */
349
rc = VERR_INTERNAL_ERROR;
351
#elif defined(RT_OS_LINUX)
353
struct cdrom_generic_command cgc;
358
case PDMBLOCKTXDIR_NONE:
359
Assert(*pcbBuf == 0);
360
direction = CGC_DATA_NONE;
362
case PDMBLOCKTXDIR_FROM_DEVICE:
363
Assert(*pcbBuf != 0);
364
/* Make sure that the buffer is clear for commands reading
365
* data. The actually received data may be shorter than what
366
* we expect, and due to the unreliable feedback about how much
367
* data the ioctl actually transferred, it's impossible to
368
* prevent that. Returning previous buffer contents may cause
369
* security problems inside the guest OS, if users can issue
370
* commands to the CDROM device. */
371
memset(pvBuf, '\0', *pcbBuf);
372
direction = CGC_DATA_READ;
374
case PDMBLOCKTXDIR_TO_DEVICE:
375
Assert(*pcbBuf != 0);
376
direction = CGC_DATA_WRITE;
379
AssertMsgFailed(("enmTxDir invalid!\n"));
380
direction = CGC_DATA_NONE;
382
memset(&cgc, '\0', sizeof(cgc));
383
memcpy(cgc.cmd, pbCmd, CDROM_PACKET_SIZE);
384
cgc.buffer = (unsigned char *)pvBuf;
385
cgc.buflen = *pcbBuf;
388
cgc.data_direction = direction;
390
cgc.timeout = cTimeoutMillies;
391
rc = ioctl(pThis->FileDevice, CDROM_SEND_PACKET, &cgc);
395
rc = VERR_PDM_MEDIA_LOCKED;
396
else if (errno == ENOSYS)
397
rc = VERR_NOT_SUPPORTED;
400
if (rc == VERR_ACCESS_DENIED && cgc.sense->sense_key == SCSI_SENSE_NONE)
401
cgc.sense->sense_key = SCSI_SENSE_ILLEGAL_REQUEST;
402
*pbStat = cgc.sense->sense_key;
403
rc = RTErrConvertFromErrno(errno);
404
Log2(("%s: error status %d, rc=%Vrc\n", __FUNCTION__, cgc.stat, rc));
407
Log2(("%s: after ioctl: cgc.buflen=%d txlen=%d\n", __FUNCTION__, cgc.buflen, *pcbBuf));
408
/* The value of cgc.buflen does not reliably reflect the actual amount
409
* of data transferred (for packet commands with little data transfer
410
* it's 0). So just assume that everything worked ok. */
412
#elif defined(RT_OS_WINDOWS)
416
SCSI_PASS_THROUGH_DIRECT spt;
419
DWORD cbReturned = 0;
423
case PDMBLOCKTXDIR_NONE:
424
direction = SCSI_IOCTL_DATA_UNSPECIFIED;
426
case PDMBLOCKTXDIR_FROM_DEVICE:
427
Assert(*pcbBuf != 0);
428
/* Make sure that the buffer is clear for commands reading
429
* data. The actually received data may be shorter than what
430
* we expect, and due to the unreliable feedback about how much
431
* data the ioctl actually transferred, it's impossible to
432
* prevent that. Returning previous buffer contents may cause
433
* security problems inside the guest OS, if users can issue
434
* commands to the CDROM device. */
435
memset(pvBuf, '\0', *pcbBuf);
436
direction = SCSI_IOCTL_DATA_IN;
438
case PDMBLOCKTXDIR_TO_DEVICE:
439
direction = SCSI_IOCTL_DATA_OUT;
442
AssertMsgFailed(("enmTxDir invalid!\n"));
443
direction = SCSI_IOCTL_DATA_UNSPECIFIED;
445
memset(&Req, '\0', sizeof(Req));
446
Req.spt.Length = sizeof(Req.spt);
447
Req.spt.CdbLength = 12;
448
memcpy(Req.spt.Cdb, pbCmd, Req.spt.CdbLength);
449
Req.spt.DataBuffer = pvBuf;
450
Req.spt.DataTransferLength = *pcbBuf;
451
Req.spt.DataIn = direction;
452
Req.spt.TimeOutValue = (cTimeoutMillies + 999) / 1000; /* Convert to seconds */
453
Req.spt.SenseInfoLength = sizeof(Req.aSense);
454
Req.spt.SenseInfoOffset = RT_OFFSETOF(struct _REQ, aSense);
455
if (DeviceIoControl((HANDLE)pThis->FileDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT,
456
&Req, sizeof(Req), &Req, sizeof(Req), &cbReturned, NULL))
458
if (cbReturned > RT_OFFSETOF(struct _REQ, aSense))
459
*pbStat = Req.aSense[2] & 0x0f;
462
/* Windows shares the property of not properly reflecting the actually
463
* transferred data size. See above. Assume that everything worked ok. */
467
rc = RTErrConvertFromWin32(GetLastError());
468
Log2(("%s: scsistatus=%d bytes returned=%d tlength=%d\n", __FUNCTION__, Req.spt.ScsiStatus, cbReturned, Req.spt.DataTransferLength));
471
# error "Unsupported platform."
473
LogFlow(("%s: rc=%Vrc\n", __FUNCTION__, rc));
478
/* -=-=-=-=- driver interface -=-=-=-=- */
482
* Construct a host dvd drive driver instance.
484
* @returns VBox status.
485
* @param pDrvIns The driver instance data.
486
* If the registration structure is needed, pDrvIns->pDrvReg points to it.
487
* @param pCfgHandle Configuration node handle for the driver. Use this to obtain the configuration
488
* of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
489
* iInstance it's expected to be used a bit in this function.
491
static DECLCALLBACK(int) drvHostDvdConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
493
PDRVHOSTBASE pThis = PDMINS2DATA(pDrvIns, PDRVHOSTBASE);
494
LogFlow(("drvHostDvdConstruct: iInstance=%d\n", pDrvIns->iInstance));
497
* Validate configuration.
499
if (!CFGMR3AreValuesValid(pCfgHandle, "Path\0Interval\0Locked\0BIOSVisible\0AttachFailError\0Passthrough\0"))
500
return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
504
* Init instance data.
506
int rc = DRVHostBaseInitData(pDrvIns, pCfgHandle, PDMBLOCKTYPE_DVD);
507
if (VBOX_SUCCESS(rc))
513
#ifndef RT_OS_L4 /* Passthrough is not supported on L4 yet */
515
rc = CFGMR3QueryBool(pCfgHandle, "Passthrough", &fPassthrough);
516
if (VBOX_SUCCESS(rc) && fPassthrough)
518
pThis->IBlock.pfnSendCmd = drvHostDvdSendCmd;
519
/* Passthrough requires opening the device in R/W mode. */
520
pThis->fReadOnlyConfig = false;
522
#endif /* !RT_OS_L4 */
524
pThis->IMount.pfnUnmount = drvHostDvdUnmount;
525
pThis->pfnDoLock = drvHostDvdDoLock;
526
#ifdef USE_MEDIA_POLLING
528
pThis->pfnPoll = drvHostDvdPoll;
530
pThis->pfnPoll = NULL;
533
pThis->pfnGetMediaSize = drvHostDvdGetMediaSize;
539
rc = DRVHostBaseInitFinish(pThis);
542
if (VBOX_FAILURE(rc))
544
if (!pThis->fAttachFailError)
546
/* Suppressing the attach failure error must not affect the normal
547
* DRVHostBaseDestruct, so reset this flag below before leaving. */
548
pThis->fKeepInstance = true;
551
DRVHostBaseDestruct(pDrvIns);
552
pThis->fKeepInstance = false;
555
LogFlow(("drvHostDvdConstruct: returns %Vrc\n", rc));
561
* Block driver registration record.
563
const PDMDRVREG g_DrvHostDVD =
570
"Host DVD Block Driver.",
572
PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
574
PDM_DRVREG_CLASS_BLOCK,