VirtualBox

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

Last change on this file since 1965 was 1965, checked in by vboxsync, 18 years ago

HostDVD support for Darwin.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.3 KB
Line 
1/** @file
2 *
3 * VBox storage devices:
4 * Host DVD block driver
5 */
6
7/*
8 * Copyright (C) 2006 InnoTek Systemberatung GmbH
9 *
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.
17 *
18 * If you received this file as part of a commercial VirtualBox
19 * distribution, then only the terms of your commercial VirtualBox
20 * license agreement apply instead of the previous paragraph.
21 */
22
23
24/*******************************************************************************
25* Header Files *
26*******************************************************************************/
27#define LOG_GROUP LOG_GROUP_DRV_HOST_DVD
28#ifdef __DARWIN__
29# include <mach/mach.h>
30# include <Carbon/Carbon.h>
31# include <IOKit/IOKitLib.h>
32# include <IOKit/IOCFPlugIn.h>
33# include <IOKit/scsi-commands/SCSITaskLib.h>
34# include <IOKit/scsi-commands/SCSICommandOperationCodes.h>
35# include <IOKit/storage/IOStorageDeviceCharacteristics.h>
36# include <mach/mach_error.h>
37# define USE_MEDIA_POLLING
38
39#elif defined(__L4ENV__)
40/* nothing (yet). */
41
42#elif defined __LINUX__
43# include <sys/ioctl.h>
44/* This is a hack to work around conflicts between these linux kernel headers
45 * and the GLIBC tcpip headers. They have different declarations of the 4
46 * standard byte order functions. */
47# define _LINUX_BYTEORDER_GENERIC_H
48/* This is another hack for not bothering with C++ unfriendly byteswap macros. */
49# define _LINUX_BYTEORDER_SWAB_H
50/* Those macros that are needed are defined in the header below */
51# include "swab.h"
52# include <linux/cdrom.h>
53# include <sys/fcntl.h>
54# include <errno.h>
55# define USE_MEDIA_POLLING
56
57#elif defined(__WIN__)
58# include <Windows.h>
59# include <winioctl.h>
60# include <ntddscsi.h>
61# undef USE_MEDIA_POLLING
62
63#else
64# error "Unsupported Platform."
65#endif
66
67#include <VBox/pdm.h>
68#include <VBox/cfgm.h>
69#include <VBox/err.h>
70
71#include <VBox/log.h>
72#include <iprt/assert.h>
73#include <iprt/file.h>
74#include <iprt/string.h>
75#include <iprt/thread.h>
76#include <iprt/critsect.h>
77#include <VBox/scsi.h>
78
79#include "Builtins.h"
80#include "DrvHostBase.h"
81
82
83
84
85/** @copydoc PDMIMOUNT::pfnUnmount */
86static DECLCALLBACK(int) drvHostDvdUnmount(PPDMIMOUNT pInterface)
87{
88 PDRVHOSTBASE pThis = PDMIMOUNT_2_DRVHOSTBASE(pInterface);
89 RTCritSectEnter(&pThis->CritSect);
90
91 /*
92 * Validate state.
93 */
94 int rc = VINF_SUCCESS;
95 if (!pThis->fLocked)
96 {
97 /*
98 * Eject the disc.
99 */
100#ifdef __DARWIN__
101 uint8_t abCmd[16] =
102 {
103 SCSI_START_STOP_UNIT, 0, 0, 0, 2 /*eject+stop*/, 0,
104 0,0,0,0,0,0,0,0,0,0
105 };
106 rc = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_NONE, NULL, NULL, NULL, 0, 0);
107
108#elif defined(__LINUX__)
109 rc = ioctl(pThis->FileDevice, CDROMEJECT, 0);
110 if (rc < 0)
111 {
112 if (errno == EBUSY)
113 rc = VERR_PDM_MEDIA_LOCKED;
114 else if (errno == ENOSYS)
115 rc = VERR_NOT_SUPPORTED;
116 else
117 rc = RTErrConvertFromErrno(errno);
118 }
119
120#elif defined(__WIN__)
121 RTFILE FileDevice = pThis->FileDevice;
122 if (FileDevice == NIL_RTFILE) /* obsolete crap */
123 rc = RTFileOpen(&FileDevice, pThis->pszDeviceOpen, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
124 if (VBOX_SUCCESS(rc))
125 {
126 /* do ioctl */
127 DWORD cbReturned;
128 if (DeviceIoControl((HANDLE)FileDevice, IOCTL_STORAGE_EJECT_MEDIA,
129 NULL, 0,
130 NULL, 0, &cbReturned,
131 NULL))
132 rc = VINF_SUCCESS;
133 else
134 rc = RTErrConvertFromWin32(GetLastError());
135
136 /* clean up handle */
137 if (FileDevice != pThis->FileDevice)
138 RTFileClose(FileDevice);
139 }
140 else
141 AssertMsgFailed(("Failed to open '%s' for ejecting this tray.\n", rc));
142
143
144#else
145 AssertMsgFailed(("Eject is not implemented!\n"));
146 rc = VINF_SUCCESS;
147#endif
148
149 /*
150 * Media is no longer present.
151 */
152 DRVHostBaseMediaNotPresent(pThis); /** @todo This isn't thread safe! */
153 }
154 else
155 {
156 Log(("drvHostDvdUnmount: Locked\n"));
157 rc = VERR_PDM_MEDIA_LOCKED;
158 }
159
160 RTCritSectLeave(&pThis->CritSect);
161 LogFlow(("drvHostDvdUnmount: returns %Vrc\n", rc));
162 return rc;
163}
164
165
166/**
167 * Locks or unlocks the drive.
168 *
169 * @returns VBox status code.
170 * @param pThis The instance data.
171 * @param fLock True if the request is to lock the drive, false if to unlock.
172 */
173static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock)
174{
175#ifdef __DARWIN__
176# if 0 /// @todo dig up the specification for this command and implement it. (not important on mac)
177 uint8_t abCmd[16] =
178 {
179 SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0, 0, 0, 0, 0,
180 0,0,0,0,0,0,0,0,0,0
181 };
182 int rc = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_NONE, NULL, NULL, NULL, 0, 0);
183# else
184 int rc = VINF_SUCCESS;
185# endif
186
187#elif defined(__LINUX__)
188 int rc = ioctl(pThis->FileDevice, CDROM_LOCKDOOR, (int)fLock);
189 if (rc < 0)
190 {
191 if (errno == EBUSY)
192 rc = VERR_ACCESS_DENIED;
193 else if (errno == EDRIVE_CANT_DO_THIS)
194 rc = VERR_NOT_SUPPORTED;
195 else
196 rc = RTErrConvertFromErrno(errno);
197 }
198
199#elif defined(__WIN__)
200
201 PREVENT_MEDIA_REMOVAL PreventMediaRemoval = {fLock};
202 DWORD cbReturned;
203 int rc;
204 if (DeviceIoControl((HANDLE)pThis->FileDevice, IOCTL_STORAGE_MEDIA_REMOVAL,
205 &PreventMediaRemoval, sizeof(PreventMediaRemoval),
206 NULL, 0, &cbReturned,
207 NULL))
208 rc = VINF_SUCCESS;
209 else
210 /** @todo figure out the return codes for already locked. */
211 rc = RTErrConvertFromWin32(GetLastError());
212
213#else
214 AssertMsgFailed(("Lock/Unlock is not implemented!\n"));
215 int rc = VINF_SUCCESS;
216
217#endif
218
219 LogFlow(("drvHostDvdDoLock(, fLock=%RTbool): returns %Vrc\n", fLock, rc));
220 return rc;
221}
222
223
224
225#ifdef __LINUX__
226/**
227 * Get the media size.
228 *
229 * @returns VBox status code.
230 * @param pThis The instance data.
231 * @param pcb Where to store the size.
232 */
233static int drvHostDvdGetMediaSize(PDRVHOSTBASE pThis, uint64_t *pcb)
234{
235 /*
236 * Query the media size.
237 */
238 /* Clear the media-changed-since-last-call-thingy just to be on the safe side. */
239 ioctl(pThis->FileDevice, CDROM_MEDIA_CHANGED, CDSL_CURRENT);
240 return RTFileSeek(pThis->FileDevice, 0, RTFILE_SEEK_END, pcb);
241
242}
243#endif /* __LINUX__ */
244
245
246#ifdef USE_MEDIA_POLLING
247/**
248 * Do media change polling.
249 */
250DECLCALLBACK(int) drvHostDvdPoll(PDRVHOSTBASE pThis)
251{
252 /*
253 * Poll for media change.
254 */
255#ifdef __DARWIN__
256 AssertReturn(pThis->ppScsiTaskDI, VERR_INTERNAL_ERROR);
257
258 /*
259 * Issue a TEST UNIT READY request.
260 */
261 bool fMediaChanged = false;
262 bool fMediaPresent = false;
263 uint8_t abCmd[16] = { SCSI_TEST_UNIT_READY, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
264 uint8_t abSense[32];
265 int rc2 = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_NONE, NULL, NULL, abSense, sizeof(abSense), 0);
266 if (VBOX_SUCCESS(rc2))
267 fMediaPresent = true;
268 else if ( rc2 == VERR_UNRESOLVED_ERROR
269 && abSense[2] == 6 /* unit attention */
270 && ( (abSense[12] == 0x29 && abSense[13] < 5 /* reset */)
271 || (abSense[12] == 0x2a && abSense[13] == 0 /* parameters changed */) //???
272 || (abSense[12] == 0x3f && abSense[13] == 0 /* target operating conditions have changed */) //???
273 || (abSense[12] == 0x3f && abSense[13] == 2 /* changed operating definition */) //???
274 || (abSense[12] == 0x3f && abSense[13] == 3 /* inquery parameters changed */)
275 || (abSense[12] == 0x3f && abSense[13] == 5 /* device identifier changed */)
276 )
277 )
278 {
279 fMediaPresent = false;
280 fMediaChanged = true;
281 /** @todo check this media chance stuff on Darwin. */
282 }
283
284#elif defined(__LINUX__)
285 bool fMediaPresent = ioctl(pThis->FileDevice, CDROM_DRIVE_STATUS, CDSL_CURRENT) == CDS_DISC_OK;
286
287#else
288# error "Unsupported platform."
289#endif
290
291 RTCritSectEnter(&pThis->CritSect);
292
293 int rc = VINF_SUCCESS;
294 if (pThis->fMediaPresent != fMediaPresent)
295 {
296 LogFlow(("drvHostDvdPoll: %d -> %d\n", pThis->fMediaPresent, fMediaPresent));
297 pThis->fMediaPresent = false;
298 if (fMediaPresent)
299 rc = DRVHostBaseMediaPresent(pThis);
300 else
301 DRVHostBaseMediaNotPresent(pThis);
302 }
303 else if (fMediaPresent)
304 {
305 /*
306 * Poll for media change.
307 */
308#ifdef __DARWIN__
309 /* taken care of above. */
310#elif defined(__LINUX__)
311 bool fMediaChanged = ioctl(pThis->FileDevice, CDROM_MEDIA_CHANGED, CDSL_CURRENT) == 1;
312#else
313# error "Unsupported platform."
314#endif
315 if (fMediaChanged)
316 {
317 LogFlow(("drvHostDVDMediaThread: Media changed!\n"));
318 DRVHostBaseMediaNotPresent(pThis);
319 rc = DRVHostBaseMediaPresent(pThis);
320 }
321 }
322
323 RTCritSectLeave(&pThis->CritSect);
324 return rc;
325}
326#endif /* USE_MEDIA_POLLING */
327
328
329/** @copydoc PDMIBLOCK::pfnSendCmd */
330static int drvHostDvdSendCmd(PPDMIBLOCK pInterface, const uint8_t *pbCmd, PDMBLOCKTXDIR enmTxDir, void *pvBuf, size_t *pcbBuf,
331 uint8_t *pbStat, uint32_t cTimeoutMillies)
332{
333 PDRVHOSTBASE pThis = PDMIBLOCK_2_DRVHOSTBASE(pInterface);
334 int rc;
335 LogFlow(("%s: cmd[0]=%#04x txdir=%d pcbBuf=%d timeout=%d\n", __FUNCTION__, pbCmd[0], enmTxDir, *pcbBuf, cTimeoutMillies));
336
337#ifdef __DARWIN__
338 /*
339 * Pass the request on to the internal scsi command interface.
340 * The command seems to be 12 bytes long, the docs a bit copy&pasty on the command length point...
341 */
342 if (enmTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
343 memset(pvBuf, '\0', *pcbBuf); /* we got read size, but zero it anyway. */
344 uint8_t abSense[32];
345 rc = DRVHostBaseScsiCmd(pThis, pbCmd, 12, PDMBLOCKTXDIR_FROM_DEVICE, pvBuf, pcbBuf, abSense, sizeof(abSense), cTimeoutMillies);
346 if (rc == VERR_UNRESOLVED_ERROR)
347 {
348 *pbStat = abSense[2] & 0x0f;
349 rc = VINF_SUCCESS;
350 }
351
352#elif defined(__L4ENV__)
353 /* Not really ported to L4 yet. */
354 rc = VERR_INTERNAL_ERROR;
355
356#elif defined(__LINUX__)
357 int direction;
358 struct cdrom_generic_command cgc;
359 request_sense sense;
360
361 switch (enmTxDir)
362 {
363 case PDMBLOCKTXDIR_NONE:
364 Assert(*pcbBuf == 0);
365 direction = CGC_DATA_NONE;
366 break;
367 case PDMBLOCKTXDIR_FROM_DEVICE:
368 Assert(*pcbBuf != 0);
369 /* Make sure that the buffer is clear for commands reading
370 * data. The actually received data may be shorter than what
371 * we expect, and due to the unreliable feedback about how much
372 * data the ioctl actually transferred, it's impossible to
373 * prevent that. Returning previous buffer contents may cause
374 * security problems inside the guest OS, if users can issue
375 * commands to the CDROM device. */
376 memset(pvBuf, '\0', *pcbBuf);
377 direction = CGC_DATA_READ;
378 break;
379 case PDMBLOCKTXDIR_TO_DEVICE:
380 Assert(*pcbBuf != 0);
381 direction = CGC_DATA_WRITE;
382 break;
383 default:
384 AssertMsgFailed(("enmTxDir invalid!\n"));
385 direction = CGC_DATA_NONE;
386 }
387 memset(&cgc, '\0', sizeof(cgc));
388 memcpy(cgc.cmd, pbCmd, CDROM_PACKET_SIZE);
389 cgc.buffer = (unsigned char *)pvBuf;
390 cgc.buflen = *pcbBuf;
391 cgc.stat = 0;
392 cgc.sense = &sense;
393 cgc.data_direction = direction;
394 cgc.quiet = false;
395 cgc.timeout = cTimeoutMillies;
396 rc = ioctl(pThis->FileDevice, CDROM_SEND_PACKET, &cgc);
397 if (rc < 0)
398 {
399 if (errno == EBUSY)
400 rc = VERR_PDM_MEDIA_LOCKED;
401 else if (errno == ENOSYS)
402 rc = VERR_NOT_SUPPORTED;
403 else
404 {
405 if (rc == VERR_ACCESS_DENIED && cgc.sense->sense_key == SCSI_SENSE_NONE)
406 cgc.sense->sense_key = SCSI_SENSE_ILLEGAL_REQUEST;
407 *pbStat = cgc.sense->sense_key;
408 rc = RTErrConvertFromErrno(errno);
409 Log2(("%s: error status %d, rc=%Vrc\n", __FUNCTION__, cgc.stat, rc));
410 }
411 }
412 Log2(("%s: after ioctl: cgc.buflen=%d txlen=%d\n", __FUNCTION__, cgc.buflen, *pcbBuf));
413 /* The value of cgc.buflen does not reliably reflect the actual amount
414 * of data transferred (for packet commands with little data transfer
415 * it's 0). So just assume that everything worked ok. */
416
417#elif defined(__WIN__)
418 int direction;
419 struct _REQ
420 {
421 SCSI_PASS_THROUGH_DIRECT spt;
422 uint8_t aSense[18];
423 } Req;
424 DWORD cbReturned = 0;
425
426 switch (enmTxDir)
427 {
428 case PDMBLOCKTXDIR_NONE:
429 direction = SCSI_IOCTL_DATA_UNSPECIFIED;
430 break;
431 case PDMBLOCKTXDIR_FROM_DEVICE:
432 Assert(*pcbBuf != 0);
433 /* Make sure that the buffer is clear for commands reading
434 * data. The actually received data may be shorter than what
435 * we expect, and due to the unreliable feedback about how much
436 * data the ioctl actually transferred, it's impossible to
437 * prevent that. Returning previous buffer contents may cause
438 * security problems inside the guest OS, if users can issue
439 * commands to the CDROM device. */
440 memset(pvBuf, '\0', *pcbBuf);
441 direction = SCSI_IOCTL_DATA_IN;
442 break;
443 case PDMBLOCKTXDIR_TO_DEVICE:
444 direction = SCSI_IOCTL_DATA_OUT;
445 break;
446 default:
447 AssertMsgFailed(("enmTxDir invalid!\n"));
448 direction = SCSI_IOCTL_DATA_UNSPECIFIED;
449 }
450 memset(&Req, '\0', sizeof(Req));
451 Req.spt.Length = sizeof(Req.spt);
452 Req.spt.CdbLength = 12;
453 memcpy(Req.spt.Cdb, pbCmd, Req.spt.CdbLength);
454 Req.spt.DataBuffer = pvBuf;
455 Req.spt.DataTransferLength = *pcbBuf;
456 Req.spt.DataIn = direction;
457 Req.spt.TimeOutValue = (cTimeoutMillies + 999) / 1000; /* Convert to seconds */
458 Req.spt.SenseInfoLength = sizeof(Req.aSense);
459 Req.spt.SenseInfoOffset = RT_OFFSETOF(struct _REQ, aSense);
460 if (DeviceIoControl((HANDLE)pThis->FileDevice, IOCTL_SCSI_PASS_THROUGH_DIRECT,
461 &Req, sizeof(Req), &Req, sizeof(Req), &cbReturned, NULL))
462 {
463 if (cbReturned > RT_OFFSETOF(struct _REQ, aSense))
464 *pbStat = Req.aSense[2] & 0x0f;
465 else
466 *pbStat = 0;
467 /* Windows shares the property of not properly reflecting the actually
468 * transferred data size. See above. Assume that everything worked ok. */
469 rc = VINF_SUCCESS;
470 }
471 else
472 rc = RTErrConvertFromWin32(GetLastError());
473 Log2(("%s: scsistatus=%d bytes returned=%d tlength=%d\n", __FUNCTION__, Req.spt.ScsiStatus, cbReturned, Req.spt.DataTransferLength));
474
475#else
476# error "Unsupported platform."
477#endif
478 LogFlow(("%s: rc=%Vrc\n", __FUNCTION__, rc));
479 return rc;
480}
481
482
483/* -=-=-=-=- driver interface -=-=-=-=- */
484
485
486/**
487 * Construct a host dvd drive driver instance.
488 *
489 * @returns VBox status.
490 * @param pDrvIns The driver instance data.
491 * If the registration structure is needed, pDrvIns->pDrvReg points to it.
492 * @param pCfgHandle Configuration node handle for the driver. Use this to obtain the configuration
493 * of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
494 * iInstance it's expected to be used a bit in this function.
495 */
496static DECLCALLBACK(int) drvHostDvdConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
497{
498 PDRVHOSTBASE pThis = PDMINS2DATA(pDrvIns, PDRVHOSTBASE);
499 LogFlow(("drvHostDvdConstruct: iInstance=%d\n", pDrvIns->iInstance));
500
501 /*
502 * Validate configuration.
503 */
504 if (!CFGMR3AreValuesValid(pCfgHandle, "Path\0Interval\0Locked\0BIOSVisible\0Passthrough\0"))
505 return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
506
507
508 /*
509 * Init instance data.
510 */
511 int rc = DRVHostBaseInitData(pDrvIns, pCfgHandle, PDMBLOCKTYPE_DVD);
512 if (VBOX_SUCCESS(rc))
513 {
514 /*
515 * Override stuff.
516 */
517
518#ifndef __L4ENV__ /* Passthrough is not supported on L4 yet */
519 bool fPassthrough;
520 rc = CFGMR3QueryBool(pCfgHandle, "Passthrough", &fPassthrough);
521 if (VBOX_SUCCESS(rc) && fPassthrough)
522 {
523 pThis->IBlock.pfnSendCmd = drvHostDvdSendCmd;
524 /* Passthrough requires opening the device in R/W mode. */
525 pThis->fReadOnlyConfig = false;
526 }
527#endif /* !__L4ENV__ */
528
529 pThis->IMount.pfnUnmount = drvHostDvdUnmount;
530 pThis->pfnDoLock = drvHostDvdDoLock;
531#ifdef USE_MEDIA_POLLING
532 if (!fPassthrough)
533 pThis->pfnPoll = drvHostDvdPoll;
534 else
535 pThis->pfnPoll = NULL;
536#endif
537#ifdef __LINUX__
538 pThis->pfnGetMediaSize = drvHostDvdGetMediaSize;
539#endif
540
541 /*
542 * 2nd init part.
543 */
544 rc = DRVHostBaseInitFinish(pThis);
545 if (VBOX_SUCCESS(rc))
546 {
547 LogFlow(("drvHostDvdConstruct: return %Vrc\n", rc));
548 return rc;
549 }
550 }
551 DRVHostBaseDestruct(pDrvIns);
552
553 LogFlow(("drvHostDvdConstruct: returns %Vrc\n", rc));
554 return rc;
555}
556
557
558/**
559 * Block driver registration record.
560 */
561const PDMDRVREG g_DrvHostDVD =
562{
563 /* u32Version */
564 PDM_DRVREG_VERSION,
565 /* szDriverName */
566 "HostDVD",
567 /* pszDescription */
568 "Host DVD Block Driver.",
569 /* fFlags */
570 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
571 /* fClass. */
572 PDM_DRVREG_CLASS_BLOCK,
573 /* cMaxInstances */
574 ~0,
575 /* cbInstance */
576 sizeof(DRVHOSTBASE),
577 /* pfnConstruct */
578 drvHostDvdConstruct,
579 /* pfnDestruct */
580 DRVHostBaseDestruct,
581 /* pfnIOCtl */
582 NULL,
583 /* pfnPowerOn */
584 NULL,
585 /* pfnReset */
586 NULL,
587 /* pfnSuspend */
588 NULL,
589 /* pfnResume */
590 NULL,
591 /* pfnDetach */
592 NULL
593};
594
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