VirtualBox

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

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

Biggest check-in ever. New source code headers for all (C) innotek files.

  • 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# define USE_MEDIA_POLLING
52
53#elif defined(RT_OS_WINDOWS)
54# include <Windows.h>
55# include <winioctl.h>
56# include <ntddscsi.h>
57# undef USE_MEDIA_POLLING
58
59#else
60# error "Unsupported Platform."
61#endif
62
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>
70
71#include "Builtins.h"
72#include "DrvHostBase.h"
73
74
75/* Forward declarations. */
76
77static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock);
78
79
80/** @copydoc PDMIMOUNT::pfnUnmount */
81static DECLCALLBACK(int) drvHostDvdUnmount(PPDMIMOUNT pInterface, bool fForce)
82{
83 PDRVHOSTBASE pThis = PDMIMOUNT_2_DRVHOSTBASE(pInterface);
84 RTCritSectEnter(&pThis->CritSect);
85
86 /*
87 * Validate state.
88 */
89 int rc = VINF_SUCCESS;
90 if (!pThis->fLocked || fForce)
91 {
92 /* Unlock drive if necessary. */
93 if (pThis->fLocked)
94 drvHostDvdDoLock(pThis, false);
95
96 /*
97 * Eject the disc.
98 */
99#ifdef RT_OS_DARWIN
100 uint8_t abCmd[16] =
101 {
102 SCSI_START_STOP_UNIT, 0, 0, 0, 2 /*eject+stop*/, 0,
103 0,0,0,0,0,0,0,0,0,0
104 };
105 rc = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_NONE, NULL, NULL, NULL, 0, 0);
106
107#elif defined(RT_OS_LINUX)
108 rc = ioctl(pThis->FileDevice, CDROMEJECT, 0);
109 if (rc < 0)
110 {
111 if (errno == EBUSY)
112 rc = VERR_PDM_MEDIA_LOCKED;
113 else if (errno == ENOSYS)
114 rc = VERR_NOT_SUPPORTED;
115 else
116 rc = RTErrConvertFromErrno(errno);
117 }
118
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))
124 {
125 /* do ioctl */
126 DWORD cbReturned;
127 if (DeviceIoControl((HANDLE)FileDevice, IOCTL_STORAGE_EJECT_MEDIA,
128 NULL, 0,
129 NULL, 0, &cbReturned,
130 NULL))
131 rc = VINF_SUCCESS;
132 else
133 rc = RTErrConvertFromWin32(GetLastError());
134
135 /* clean up handle */
136 if (FileDevice != pThis->FileDevice)
137 RTFileClose(FileDevice);
138 }
139 else
140 AssertMsgFailed(("Failed to open '%s' for ejecting this tray.\n", rc));
141
142
143#else
144 AssertMsgFailed(("Eject is not implemented!\n"));
145 rc = VINF_SUCCESS;
146#endif
147
148 /*
149 * Media is no longer present.
150 */
151 DRVHostBaseMediaNotPresent(pThis); /** @todo This isn't thread safe! */
152 }
153 else
154 {
155 Log(("drvHostDvdUnmount: Locked\n"));
156 rc = VERR_PDM_MEDIA_LOCKED;
157 }
158
159 RTCritSectLeave(&pThis->CritSect);
160 LogFlow(("drvHostDvdUnmount: returns %Vrc\n", rc));
161 return rc;
162}
163
164
165/**
166 * Locks or unlocks the drive.
167 *
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.
171 */
172static DECLCALLBACK(int) drvHostDvdDoLock(PDRVHOSTBASE pThis, bool fLock)
173{
174#ifdef RT_OS_DARWIN
175 uint8_t abCmd[16] =
176 {
177 SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0, 0, 0, fLock, 0,
178 0,0,0,0,0,0,0,0,0,0
179 };
180 int rc = DRVHostBaseScsiCmd(pThis, abCmd, 6, PDMBLOCKTXDIR_NONE, NULL, NULL, NULL, 0, 0);
181
182#elif defined(RT_OS_LINUX)
183 int rc = ioctl(pThis->FileDevice, CDROM_LOCKDOOR, (int)fLock);
184 if (rc < 0)
185 {
186 if (errno == EBUSY)
187 rc = VERR_ACCESS_DENIED;
188 else if (errno == EDRIVE_CANT_DO_THIS)
189 rc = VERR_NOT_SUPPORTED;
190 else
191 rc = RTErrConvertFromErrno(errno);
192 }
193
194#elif defined(RT_OS_WINDOWS)
195
196 PREVENT_MEDIA_REMOVAL PreventMediaRemoval = {fLock};
197 DWORD cbReturned;
198 int rc;
199 if (DeviceIoControl((HANDLE)pThis->FileDevice, IOCTL_STORAGE_MEDIA_REMOVAL,
200 &PreventMediaRemoval, sizeof(PreventMediaRemoval),
201 NULL, 0, &cbReturned,
202 NULL))
203 rc = VINF_SUCCESS;
204 else
205 /** @todo figure out the return codes for already locked. */
206 rc = RTErrConvertFromWin32(GetLastError());
207
208#else
209 AssertMsgFailed(("Lock/Unlock is not implemented!\n"));
210 int rc = VINF_SUCCESS;
211
212#endif
213
214 LogFlow(("drvHostDvdDoLock(, fLock=%RTbool): returns %Vrc\n", fLock, rc));
215 return rc;
216}
217
218
219
220#ifdef RT_OS_LINUX
221/**
222 * Get the media size.
223 *
224 * @returns VBox status code.
225 * @param pThis The instance data.
226 * @param pcb Where to store the size.
227 */
228static int drvHostDvdGetMediaSize(PDRVHOSTBASE pThis, uint64_t *pcb)
229{
230 /*
231 * Query the media size.
232 */
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);
236
237}
238#endif /* RT_OS_LINUX */
239
240
241#ifdef USE_MEDIA_POLLING
242/**
243 * Do media change polling.
244 */
245DECLCALLBACK(int) drvHostDvdPoll(PDRVHOSTBASE pThis)
246{
247 /*
248 * Poll for media change.
249 */
250#ifdef RT_OS_DARWIN
251 AssertReturn(pThis->ppScsiTaskDI, VERR_INTERNAL_ERROR);
252
253 /*
254 * Issue a TEST UNIT READY request.
255 */
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 };
259 uint8_t abSense[32];
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 */)
271 )
272 )
273 {
274 fMediaPresent = false;
275 fMediaChanged = true;
276 /** @todo check this media chance stuff on Darwin. */
277 }
278
279#elif defined(RT_OS_LINUX)
280 bool fMediaPresent = ioctl(pThis->FileDevice, CDROM_DRIVE_STATUS, CDSL_CURRENT) == CDS_DISC_OK;
281
282#else
283# error "Unsupported platform."
284#endif
285
286 RTCritSectEnter(&pThis->CritSect);
287
288 int rc = VINF_SUCCESS;
289 if (pThis->fMediaPresent != fMediaPresent)
290 {
291 LogFlow(("drvHostDvdPoll: %d -> %d\n", pThis->fMediaPresent, fMediaPresent));
292 pThis->fMediaPresent = false;
293 if (fMediaPresent)
294 rc = DRVHostBaseMediaPresent(pThis);
295 else
296 DRVHostBaseMediaNotPresent(pThis);
297 }
298 else if (fMediaPresent)
299 {
300 /*
301 * Poll for media change.
302 */
303#ifdef RT_OS_DARWIN
304 /* taken care of above. */
305#elif defined(RT_OS_LINUX)
306 bool fMediaChanged = ioctl(pThis->FileDevice, CDROM_MEDIA_CHANGED, CDSL_CURRENT) == 1;
307#else
308# error "Unsupported platform."
309#endif
310 if (fMediaChanged)
311 {
312 LogFlow(("drvHostDVDMediaThread: Media changed!\n"));
313 DRVHostBaseMediaNotPresent(pThis);
314 rc = DRVHostBaseMediaPresent(pThis);
315 }
316 }
317
318 RTCritSectLeave(&pThis->CritSect);
319 return rc;
320}
321#endif /* USE_MEDIA_POLLING */
322
323
324/** @copydoc PDMIBLOCK::pfnSendCmd */
325static int drvHostDvdSendCmd(PPDMIBLOCK pInterface, const uint8_t *pbCmd, PDMBLOCKTXDIR enmTxDir, void *pvBuf, size_t *pcbBuf,
326 uint8_t *pbStat, uint32_t cTimeoutMillies)
327{
328 PDRVHOSTBASE pThis = PDMIBLOCK_2_DRVHOSTBASE(pInterface);
329 int rc;
330 LogFlow(("%s: cmd[0]=%#04x txdir=%d pcbBuf=%d timeout=%d\n", __FUNCTION__, pbCmd[0], enmTxDir, *pcbBuf, cTimeoutMillies));
331
332#ifdef RT_OS_DARWIN
333 /*
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...
336 */
337 if (enmTxDir == PDMBLOCKTXDIR_FROM_DEVICE)
338 memset(pvBuf, '\0', *pcbBuf); /* we got read size, but zero it anyway. */
339 uint8_t abSense[32];
340 rc = DRVHostBaseScsiCmd(pThis, pbCmd, 12, PDMBLOCKTXDIR_FROM_DEVICE, pvBuf, pcbBuf, abSense, sizeof(abSense), cTimeoutMillies);
341 if (rc == VERR_UNRESOLVED_ERROR)
342 {
343 *pbStat = abSense[2] & 0x0f;
344 rc = VINF_SUCCESS;
345 }
346
347#elif defined(RT_OS_L4)
348 /* Not really ported to L4 yet. */
349 rc = VERR_INTERNAL_ERROR;
350
351#elif defined(RT_OS_LINUX)
352 int direction;
353 struct cdrom_generic_command cgc;
354 request_sense sense;
355
356 switch (enmTxDir)
357 {
358 case PDMBLOCKTXDIR_NONE:
359 Assert(*pcbBuf == 0);
360 direction = CGC_DATA_NONE;
361 break;
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;
373 break;
374 case PDMBLOCKTXDIR_TO_DEVICE:
375 Assert(*pcbBuf != 0);
376 direction = CGC_DATA_WRITE;
377 break;
378 default:
379 AssertMsgFailed(("enmTxDir invalid!\n"));
380 direction = CGC_DATA_NONE;
381 }
382 memset(&cgc, '\0', sizeof(cgc));
383 memcpy(cgc.cmd, pbCmd, CDROM_PACKET_SIZE);
384 cgc.buffer = (unsigned char *)pvBuf;
385 cgc.buflen = *pcbBuf;
386 cgc.stat = 0;
387 cgc.sense = &sense;
388 cgc.data_direction = direction;
389 cgc.quiet = false;
390 cgc.timeout = cTimeoutMillies;
391 rc = ioctl(pThis->FileDevice, CDROM_SEND_PACKET, &cgc);
392 if (rc < 0)
393 {
394 if (errno == EBUSY)
395 rc = VERR_PDM_MEDIA_LOCKED;
396 else if (errno == ENOSYS)
397 rc = VERR_NOT_SUPPORTED;
398 else
399 {
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));
405 }
406 }
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. */
411
412#elif defined(RT_OS_WINDOWS)
413 int direction;
414 struct _REQ
415 {
416 SCSI_PASS_THROUGH_DIRECT spt;
417 uint8_t aSense[18];
418 } Req;
419 DWORD cbReturned = 0;
420
421 switch (enmTxDir)
422 {
423 case PDMBLOCKTXDIR_NONE:
424 direction = SCSI_IOCTL_DATA_UNSPECIFIED;
425 break;
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;
437 break;
438 case PDMBLOCKTXDIR_TO_DEVICE:
439 direction = SCSI_IOCTL_DATA_OUT;
440 break;
441 default:
442 AssertMsgFailed(("enmTxDir invalid!\n"));
443 direction = SCSI_IOCTL_DATA_UNSPECIFIED;
444 }
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))
457 {
458 if (cbReturned > RT_OFFSETOF(struct _REQ, aSense))
459 *pbStat = Req.aSense[2] & 0x0f;
460 else
461 *pbStat = 0;
462 /* Windows shares the property of not properly reflecting the actually
463 * transferred data size. See above. Assume that everything worked ok. */
464 rc = VINF_SUCCESS;
465 }
466 else
467 rc = RTErrConvertFromWin32(GetLastError());
468 Log2(("%s: scsistatus=%d bytes returned=%d tlength=%d\n", __FUNCTION__, Req.spt.ScsiStatus, cbReturned, Req.spt.DataTransferLength));
469
470#else
471# error "Unsupported platform."
472#endif
473 LogFlow(("%s: rc=%Vrc\n", __FUNCTION__, rc));
474 return rc;
475}
476
477
478/* -=-=-=-=- driver interface -=-=-=-=- */
479
480
481/**
482 * Construct a host dvd drive driver instance.
483 *
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.
490 */
491static DECLCALLBACK(int) drvHostDvdConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
492{
493 PDRVHOSTBASE pThis = PDMINS2DATA(pDrvIns, PDRVHOSTBASE);
494 LogFlow(("drvHostDvdConstruct: iInstance=%d\n", pDrvIns->iInstance));
495
496 /*
497 * Validate configuration.
498 */
499 if (!CFGMR3AreValuesValid(pCfgHandle, "Path\0Interval\0Locked\0BIOSVisible\0AttachFailError\0Passthrough\0"))
500 return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
501
502
503 /*
504 * Init instance data.
505 */
506 int rc = DRVHostBaseInitData(pDrvIns, pCfgHandle, PDMBLOCKTYPE_DVD);
507 if (VBOX_SUCCESS(rc))
508 {
509 /*
510 * Override stuff.
511 */
512
513#ifndef RT_OS_L4 /* Passthrough is not supported on L4 yet */
514 bool fPassthrough;
515 rc = CFGMR3QueryBool(pCfgHandle, "Passthrough", &fPassthrough);
516 if (VBOX_SUCCESS(rc) && fPassthrough)
517 {
518 pThis->IBlock.pfnSendCmd = drvHostDvdSendCmd;
519 /* Passthrough requires opening the device in R/W mode. */
520 pThis->fReadOnlyConfig = false;
521 }
522#endif /* !RT_OS_L4 */
523
524 pThis->IMount.pfnUnmount = drvHostDvdUnmount;
525 pThis->pfnDoLock = drvHostDvdDoLock;
526#ifdef USE_MEDIA_POLLING
527 if (!fPassthrough)
528 pThis->pfnPoll = drvHostDvdPoll;
529 else
530 pThis->pfnPoll = NULL;
531#endif
532#ifdef RT_OS_LINUX
533 pThis->pfnGetMediaSize = drvHostDvdGetMediaSize;
534#endif
535
536 /*
537 * 2nd init part.
538 */
539 rc = DRVHostBaseInitFinish(pThis);
540 }
541
542 if (VBOX_FAILURE(rc))
543 {
544 if (!pThis->fAttachFailError)
545 {
546 /* Suppressing the attach failure error must not affect the normal
547 * DRVHostBaseDestruct, so reset this flag below before leaving. */
548 pThis->fKeepInstance = true;
549 rc = VINF_SUCCESS;
550 }
551 DRVHostBaseDestruct(pDrvIns);
552 pThis->fKeepInstance = false;
553 }
554
555 LogFlow(("drvHostDvdConstruct: returns %Vrc\n", rc));
556 return rc;
557}
558
559
560/**
561 * Block driver registration record.
562 */
563const PDMDRVREG g_DrvHostDVD =
564{
565 /* u32Version */
566 PDM_DRVREG_VERSION,
567 /* szDriverName */
568 "HostDVD",
569 /* pszDescription */
570 "Host DVD Block Driver.",
571 /* fFlags */
572 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
573 /* fClass. */
574 PDM_DRVREG_CLASS_BLOCK,
575 /* cMaxInstances */
576 ~0,
577 /* cbInstance */
578 sizeof(DRVHOSTBASE),
579 /* pfnConstruct */
580 drvHostDvdConstruct,
581 /* pfnDestruct */
582 DRVHostBaseDestruct,
583 /* pfnIOCtl */
584 NULL,
585 /* pfnPowerOn */
586 NULL,
587 /* pfnReset */
588 NULL,
589 /* pfnSuspend */
590 NULL,
591 /* pfnResume */
592 NULL,
593 /* pfnDetach */
594 NULL
595};
596
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