VirtualBox

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

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

Use pdmdrv.h and pdmdev.h where appropirate.

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