VirtualBox

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

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

Implement virtual paperclip (forced unmounting of media, currently
unused) and non-fatal error when media is not available on powerup.

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