VirtualBox

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

Last change on this file since 5085 was 5085, checked in by vboxsync, 17 years ago

fedora8 build fix

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