VirtualBox

source: vbox/trunk/src/VBox/Devices/Storage/DrvHostBase-darwin.cpp@ 72919

Last change on this file since 72919 was 69500, checked in by vboxsync, 7 years ago

*: scm --update-copyright-year

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 29.2 KB
Line 
1/* $Id: DrvHostBase-darwin.cpp 69500 2017-10-28 15:14:05Z vboxsync $ */
2/** @file
3 * DrvHostBase - Host base drive access driver, OS X specifics.
4 */
5
6/*
7 * Copyright (C) 2006-2017 Oracle Corporation
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License (GPL) as published by the Free Software
13 * Foundation, in version 2 as it comes in the "COPYING" file of the
14 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
15 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
16 */
17#define LOG_GROUP LOG_GROUP_DRV_HOST_BASE
18#include <mach/mach.h>
19#include <Carbon/Carbon.h>
20#include <IOKit/IOKitLib.h>
21#include <IOKit/storage/IOStorageDeviceCharacteristics.h>
22#include <IOKit/scsi/SCSITaskLib.h>
23#include <IOKit/scsi/SCSICommandOperationCodes.h>
24#include <IOKit/IOBSD.h>
25#include <DiskArbitration/DiskArbitration.h>
26#include <mach/mach_error.h>
27#include <VBox/scsi.h>
28
29/** Maximum buffer size we support, check whether darwin has some real upper limit. */
30#define DARWIN_SCSI_MAX_BUFFER_SIZE (100 * _1K)
31
32/**
33 * Host backend specific data.
34 */
35typedef struct DRVHOSTBASEOS
36{
37 /** The master port. */
38 mach_port_t MasterPort;
39 /** The MMC-2 Device Interface. (This is only used to get the scsi task interface.) */
40 MMCDeviceInterface **ppMMCDI;
41 /** The SCSI Task Device Interface. */
42 SCSITaskDeviceInterface **ppScsiTaskDI;
43 /** The block size. Set when querying the media size. */
44 uint32_t cbBlock;
45 /** The disk arbitration session reference. NULL if we didn't have to claim & unmount the device. */
46 DASessionRef pDASession;
47 /** The disk arbitration disk reference. NULL if we didn't have to claim & unmount the device. */
48 DADiskRef pDADisk;
49 /** The number of errors that could go into the release log. (flood gate) */
50 uint32_t cLogRelErrors;
51} DRVHOSTBASEOS;
52/** Pointer to the host backend specific data. */
53typedef DRVHOSTBASEOS *PDRVHOSBASEOS;
54AssertCompile(sizeof(DRVHOSTBASEOS) <= 64);
55
56#define DRVHOSTBASE_OS_INT_DECLARED
57#include "DrvHostBase.h"
58
59/** The runloop input source name for the disk arbitration events. */
60# define MY_RUN_LOOP_MODE CFSTR("drvHostBaseDA") /** @todo r=bird: Check if this will cause trouble in the same way that the one in the USB code did. */
61
62/**
63 * Gets the BSD Name (/dev/disc[0-9]+) for the service.
64 *
65 * This is done by recursing down the I/O registry until we hit upon an entry
66 * with a BSD Name. Usually we find it two levels down. (Further down under
67 * the IOCDPartitionScheme, the volume (slices) BSD Name is found. We don't
68 * seem to have to go this far fortunately.)
69 *
70 * @return VINF_SUCCESS if found, VERR_FILE_NOT_FOUND otherwise.
71 * @param Entry The current I/O registry entry reference.
72 * @param pszName Where to store the name. 128 bytes.
73 * @param cRecursions Number of recursions. This is used as an precaution
74 * just to limit the depth and avoid blowing the stack
75 * should we hit a bug or something.
76 */
77static int drvHostBaseGetBSDName(io_registry_entry_t Entry, char *pszName, unsigned cRecursions)
78{
79 int rc = VERR_FILE_NOT_FOUND;
80 io_iterator_t Children = 0;
81 kern_return_t krc = IORegistryEntryGetChildIterator(Entry, kIOServicePlane, &Children);
82 if (krc == KERN_SUCCESS)
83 {
84 io_object_t Child;
85 while ( rc == VERR_FILE_NOT_FOUND
86 && (Child = IOIteratorNext(Children)) != 0)
87 {
88 CFStringRef BSDNameStrRef = (CFStringRef)IORegistryEntryCreateCFProperty(Child, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
89 if (BSDNameStrRef)
90 {
91 if (CFStringGetCString(BSDNameStrRef, pszName, 128, kCFStringEncodingUTF8))
92 rc = VINF_SUCCESS;
93 else
94 AssertFailed();
95 CFRelease(BSDNameStrRef);
96 }
97 if (rc == VERR_FILE_NOT_FOUND && cRecursions < 10)
98 rc = drvHostBaseGetBSDName(Child, pszName, cRecursions + 1);
99 IOObjectRelease(Child);
100 }
101 IOObjectRelease(Children);
102 }
103 return rc;
104}
105
106
107/**
108 * Callback notifying us that the async DADiskClaim()/DADiskUnmount call has completed.
109 *
110 * @param DiskRef The disk that was attempted claimed / unmounted.
111 * @param DissenterRef NULL on success, contains details on failure.
112 * @param pvContext Pointer to the return code variable.
113 */
114static void drvHostBaseDADoneCallback(DADiskRef DiskRef, DADissenterRef DissenterRef, void *pvContext)
115{
116 RT_NOREF(DiskRef);
117 int *prc = (int *)pvContext;
118 if (!DissenterRef)
119 *prc = 0;
120 else
121 *prc = DADissenterGetStatus(DissenterRef) ? DADissenterGetStatus(DissenterRef) : -1;
122 CFRunLoopStop(CFRunLoopGetCurrent());
123}
124
125
126/**
127 * Obtain exclusive access to the DVD device, umount it if necessary.
128 *
129 * @return VBox status code.
130 * @param pThis The driver instance.
131 * @param DVDService The DVD service object.
132 */
133static int drvHostBaseObtainExclusiveAccess(PDRVHOSTBASE pThis, io_object_t DVDService)
134{
135 PPDMDRVINS pDrvIns = pThis->pDrvIns; NOREF(pDrvIns);
136
137 for (unsigned iTry = 0;; iTry++)
138 {
139 IOReturn irc = (*pThis->Os.ppScsiTaskDI)->ObtainExclusiveAccess(pThis->Os.ppScsiTaskDI);
140 if (irc == kIOReturnSuccess)
141 {
142 /*
143 * This is a bit weird, but if we unmounted the DVD drive we also need to
144 * unlock it afterwards or the guest won't be able to eject it later on.
145 */
146 if (pThis->Os.pDADisk)
147 {
148 uint8_t abCmd[16] =
149 {
150 SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0, 0, 0, false, 0,
151 0,0,0,0,0,0,0,0,0,0
152 };
153 drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, NULL, 0, 0);
154 }
155 return VINF_SUCCESS;
156 }
157 if (irc == kIOReturnExclusiveAccess)
158 return VERR_SHARING_VIOLATION; /* already used exclusivly. */
159 if (irc != kIOReturnBusy)
160 return VERR_GENERAL_FAILURE; /* not mounted */
161
162 /*
163 * Attempt to the unmount all volumes of the device.
164 * It seems we can can do this all in one go without having to enumerate the
165 * volumes (sessions) and deal with them one by one. This is very fortuitous
166 * as the disk arbitration API is a bit cumbersome to deal with.
167 */
168 if (iTry > 2)
169 return VERR_DRIVE_LOCKED;
170 char szName[128];
171 int rc = drvHostBaseGetBSDName(DVDService, &szName[0], 0);
172 if (RT_SUCCESS(rc))
173 {
174 pThis->Os.pDASession = DASessionCreate(kCFAllocatorDefault);
175 if (pThis->Os.pDASession)
176 {
177 DASessionScheduleWithRunLoop(pThis->Os.pDASession, CFRunLoopGetCurrent(), MY_RUN_LOOP_MODE);
178 pThis->Os.pDADisk = DADiskCreateFromBSDName(kCFAllocatorDefault, pThis->Os.pDASession, szName);
179 if (pThis->Os.pDADisk)
180 {
181 /*
182 * Try claim the device.
183 */
184 Log(("%s-%d: calling DADiskClaim on '%s'.\n", pDrvIns->pReg->szName, pDrvIns->iInstance, szName));
185 int rcDA = -2;
186 DADiskClaim(pThis->Os.pDADisk, kDADiskClaimOptionDefault, NULL, NULL, drvHostBaseDADoneCallback, &rcDA);
187 SInt32 rc32 = CFRunLoopRunInMode(MY_RUN_LOOP_MODE, 120.0, FALSE);
188 AssertMsg(rc32 == kCFRunLoopRunStopped, ("rc32=%RI32 (%RX32)\n", rc32, rc32));
189 if ( rc32 == kCFRunLoopRunStopped
190 && !rcDA)
191 {
192 /*
193 * Try unmount the device.
194 */
195 Log(("%s-%d: calling DADiskUnmount on '%s'.\n", pDrvIns->pReg->szName, pDrvIns->iInstance, szName));
196 rcDA = -2;
197 DADiskUnmount(pThis->Os.pDADisk, kDADiskUnmountOptionWhole, drvHostBaseDADoneCallback, &rcDA);
198 rc32 = CFRunLoopRunInMode(MY_RUN_LOOP_MODE, 120.0, FALSE);
199 AssertMsg(rc32 == kCFRunLoopRunStopped, ("rc32=%RI32 (%RX32)\n", rc32, rc32));
200 if ( rc32 == kCFRunLoopRunStopped
201 && !rcDA)
202 {
203 iTry = 99;
204 DASessionUnscheduleFromRunLoop(pThis->Os.pDASession, CFRunLoopGetCurrent(), MY_RUN_LOOP_MODE);
205 Log(("%s-%d: unmount succeed - retrying.\n", pDrvIns->pReg->szName, pDrvIns->iInstance));
206 continue;
207 }
208 Log(("%s-%d: umount => rc32=%d & rcDA=%#x\n", pDrvIns->pReg->szName, pDrvIns->iInstance, rc32, rcDA));
209
210 /* failed - cleanup */
211 DADiskUnclaim(pThis->Os.pDADisk);
212 }
213 else
214 Log(("%s-%d: claim => rc32=%d & rcDA=%#x\n", pDrvIns->pReg->szName, pDrvIns->iInstance, rc32, rcDA));
215
216 CFRelease(pThis->Os.pDADisk);
217 pThis->Os.pDADisk = NULL;
218 }
219 else
220 Log(("%s-%d: failed to open disk '%s'!\n", pDrvIns->pReg->szName, pDrvIns->iInstance, szName));
221
222 DASessionUnscheduleFromRunLoop(pThis->Os.pDASession, CFRunLoopGetCurrent(), MY_RUN_LOOP_MODE);
223 CFRelease(pThis->Os.pDASession);
224 pThis->Os.pDASession = NULL;
225 }
226 else
227 Log(("%s-%d: failed to create DA session!\n", pDrvIns->pReg->szName, pDrvIns->iInstance));
228 }
229 RTThreadSleep(10);
230 }
231}
232
233DECLHIDDEN(int) drvHostBaseScsiCmdOs(PDRVHOSTBASE pThis, const uint8_t *pbCmd, size_t cbCmd, PDMMEDIATXDIR enmTxDir,
234 void *pvBuf, uint32_t *pcbBuf, uint8_t *pbSense, size_t cbSense, uint32_t cTimeoutMillies)
235{
236 /*
237 * Minimal input validation.
238 */
239 Assert(enmTxDir == PDMMEDIATXDIR_NONE || enmTxDir == PDMMEDIATXDIR_FROM_DEVICE || enmTxDir == PDMMEDIATXDIR_TO_DEVICE);
240 Assert(!pvBuf || pcbBuf);
241 Assert(pvBuf || enmTxDir == PDMMEDIATXDIR_NONE);
242 Assert(pbSense || !cbSense);
243 AssertPtr(pbCmd);
244 Assert(cbCmd <= 16 && cbCmd >= 1);
245 const uint32_t cbBuf = pcbBuf ? *pcbBuf : 0;
246 if (pcbBuf)
247 *pcbBuf = 0;
248
249 Assert(pThis->Os.ppScsiTaskDI);
250
251 int rc = VERR_GENERAL_FAILURE;
252 SCSITaskInterface **ppScsiTaskI = (*pThis->Os.ppScsiTaskDI)->CreateSCSITask(pThis->Os.ppScsiTaskDI);
253 if (!ppScsiTaskI)
254 return VERR_NO_MEMORY;
255 do
256 {
257 /* Setup the scsi command. */
258 SCSICommandDescriptorBlock cdb = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
259 memcpy(&cdb[0], pbCmd, cbCmd);
260 IOReturn irc = (*ppScsiTaskI)->SetCommandDescriptorBlock(ppScsiTaskI, cdb, cbCmd);
261 AssertBreak(irc == kIOReturnSuccess);
262
263 /* Setup the buffer. */
264 if (enmTxDir == PDMMEDIATXDIR_NONE)
265 irc = (*ppScsiTaskI)->SetScatterGatherEntries(ppScsiTaskI, NULL, 0, 0, kSCSIDataTransfer_NoDataTransfer);
266 else
267 {
268 IOVirtualRange Range = { (IOVirtualAddress)pvBuf, cbBuf };
269 irc = (*ppScsiTaskI)->SetScatterGatherEntries(ppScsiTaskI, &Range, 1, cbBuf,
270 enmTxDir == PDMMEDIATXDIR_FROM_DEVICE
271 ? kSCSIDataTransfer_FromTargetToInitiator
272 : kSCSIDataTransfer_FromInitiatorToTarget);
273 }
274 AssertBreak(irc == kIOReturnSuccess);
275
276 /* Set the timeout. */
277 irc = (*ppScsiTaskI)->SetTimeoutDuration(ppScsiTaskI, cTimeoutMillies ? cTimeoutMillies : 30000 /*ms*/);
278 AssertBreak(irc == kIOReturnSuccess);
279
280 /* Execute the command and get the response. */
281 SCSI_Sense_Data SenseData = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
282 SCSIServiceResponse ServiceResponse = kSCSIServiceResponse_Request_In_Process;
283 SCSITaskStatus TaskStatus = kSCSITaskStatus_GOOD;
284 UInt64 cbReturned = 0;
285 irc = (*ppScsiTaskI)->ExecuteTaskSync(ppScsiTaskI, &SenseData, &TaskStatus, &cbReturned);
286 AssertBreak(irc == kIOReturnSuccess);
287 if (pcbBuf)
288 *pcbBuf = (int32_t)cbReturned;
289
290 irc = (*ppScsiTaskI)->GetSCSIServiceResponse(ppScsiTaskI, &ServiceResponse);
291 AssertBreak(irc == kIOReturnSuccess);
292 AssertBreak(ServiceResponse == kSCSIServiceResponse_TASK_COMPLETE);
293
294 if (TaskStatus == kSCSITaskStatus_GOOD)
295 rc = VINF_SUCCESS;
296 else if ( TaskStatus == kSCSITaskStatus_CHECK_CONDITION
297 && pbSense)
298 {
299 memset(pbSense, 0, cbSense); /* lazy */
300 memcpy(pbSense, &SenseData, RT_MIN(sizeof(SenseData), cbSense));
301 rc = VERR_UNRESOLVED_ERROR;
302 }
303 /** @todo convert sense codes when caller doesn't wish to do this himself. */
304 /*else if ( TaskStatus == kSCSITaskStatus_CHECK_CONDITION
305 && SenseData.ADDITIONAL_SENSE_CODE == 0x3A)
306 rc = VERR_MEDIA_NOT_PRESENT; */
307 else
308 {
309 rc = enmTxDir == PDMMEDIATXDIR_NONE
310 ? VERR_DEV_IO_ERROR
311 : enmTxDir == PDMMEDIATXDIR_FROM_DEVICE
312 ? VERR_READ_ERROR
313 : VERR_WRITE_ERROR;
314 if (pThis->Os.cLogRelErrors++ < 10)
315 LogRel(("DVD scsi error: cmd={%.*Rhxs} TaskStatus=%#x key=%#x ASC=%#x ASCQ=%#x (%Rrc)\n",
316 cbCmd, pbCmd, TaskStatus, SenseData.SENSE_KEY, SenseData.ADDITIONAL_SENSE_CODE,
317 SenseData.ADDITIONAL_SENSE_CODE_QUALIFIER, rc));
318 }
319 } while (0);
320
321 (*ppScsiTaskI)->Release(ppScsiTaskI);
322
323 return rc;
324}
325
326
327DECLHIDDEN(size_t) drvHostBaseScsiCmdGetBufLimitOs(PDRVHOSTBASE pThis)
328{
329 RT_NOREF(pThis);
330
331 return DARWIN_SCSI_MAX_BUFFER_SIZE;
332}
333
334
335DECLHIDDEN(int) drvHostBaseGetMediaSizeOs(PDRVHOSTBASE pThis, uint64_t *pcb)
336{
337 /*
338 * Try a READ_CAPACITY command...
339 */
340 struct
341 {
342 uint32_t cBlocks;
343 uint32_t cbBlock;
344 } Buf = {0, 0};
345 uint32_t cbBuf = sizeof(Buf);
346 uint8_t abCmd[16] =
347 {
348 SCSI_READ_CAPACITY, 0, 0, 0, 0, 0, 0,
349 0,0,0,0,0,0,0,0,0
350 };
351 int rc = drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_FROM_DEVICE, &Buf, &cbBuf, NULL, 0, 0);
352 if (RT_SUCCESS(rc))
353 {
354 Assert(cbBuf == sizeof(Buf));
355 Buf.cBlocks = RT_BE2H_U32(Buf.cBlocks);
356 Buf.cbBlock = RT_BE2H_U32(Buf.cbBlock);
357 //if (Buf.cbBlock > 2048) /* everyone else is doing this... check if it needed/right.*/
358 // Buf.cbBlock = 2048;
359 pThis->Os.cbBlock = Buf.cbBlock;
360
361 *pcb = (uint64_t)Buf.cBlocks * Buf.cbBlock;
362 }
363 return rc;
364}
365
366
367DECLHIDDEN(int) drvHostBaseReadOs(PDRVHOSTBASE pThis, uint64_t off, void *pvBuf, size_t cbRead)
368{
369 int rc = VINF_SUCCESS;
370
371 if ( pThis->Os.ppScsiTaskDI
372 && pThis->Os.cbBlock)
373 {
374 /*
375 * Issue a READ(12) request.
376 */
377 do
378 {
379 const uint32_t LBA = off / pThis->Os.cbBlock;
380 AssertReturn(!(off % pThis->Os.cbBlock), VERR_INVALID_PARAMETER);
381 uint32_t cbRead32 = cbRead > SCSI_MAX_BUFFER_SIZE
382 ? SCSI_MAX_BUFFER_SIZE
383 : (uint32_t)cbRead;
384 const uint32_t cBlocks = cbRead32 / pThis->Os.cbBlock;
385 AssertReturn(!(cbRead % pThis->Os.cbBlock), VERR_INVALID_PARAMETER);
386 uint8_t abCmd[16] =
387 {
388 SCSI_READ_12, 0,
389 RT_BYTE4(LBA), RT_BYTE3(LBA), RT_BYTE2(LBA), RT_BYTE1(LBA),
390 RT_BYTE4(cBlocks), RT_BYTE3(cBlocks), RT_BYTE2(cBlocks), RT_BYTE1(cBlocks),
391 0, 0, 0, 0, 0
392 };
393 rc = drvHostBaseScsiCmdOs(pThis, abCmd, 12, PDMMEDIATXDIR_FROM_DEVICE, pvBuf, &cbRead32, NULL, 0, 0);
394
395 off += cbRead32;
396 cbRead -= cbRead32;
397 pvBuf = (uint8_t *)pvBuf + cbRead32;
398 } while ((cbRead > 0) && RT_SUCCESS(rc));
399 }
400 else
401 rc = VERR_MEDIA_NOT_PRESENT;
402
403 return rc;
404}
405
406
407DECLHIDDEN(int) drvHostBaseWriteOs(PDRVHOSTBASE pThis, uint64_t off, const void *pvBuf, size_t cbWrite)
408{
409 RT_NOREF4(pThis, off, pvBuf, cbWrite);
410 return VERR_WRITE_PROTECT;
411}
412
413
414DECLHIDDEN(int) drvHostBaseFlushOs(PDRVHOSTBASE pThis)
415{
416 RT_NOREF1(pThis);
417 return VINF_SUCCESS;
418}
419
420
421DECLHIDDEN(int) drvHostBaseDoLockOs(PDRVHOSTBASE pThis, bool fLock)
422{
423 uint8_t abCmd[16] =
424 {
425 SCSI_PREVENT_ALLOW_MEDIUM_REMOVAL, 0, 0, 0, fLock, 0,
426 0,0,0,0,0,0,0,0,0,0
427 };
428 return drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, NULL, 0, 0);
429}
430
431
432DECLHIDDEN(int) drvHostBaseEjectOs(PDRVHOSTBASE pThis)
433{
434 uint8_t abCmd[16] =
435 {
436 SCSI_START_STOP_UNIT, 0, 0, 0, 2 /*eject+stop*/, 0,
437 0,0,0,0,0,0,0,0,0,0
438 };
439 return drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, NULL, 0, 0);
440}
441
442
443DECLHIDDEN(int) drvHostBaseQueryMediaStatusOs(PDRVHOSTBASE pThis, bool *pfMediaChanged, bool *pfMediaPresent)
444{
445 AssertReturn(pThis->Os.ppScsiTaskDI, VERR_INTERNAL_ERROR);
446
447 /*
448 * Issue a TEST UNIT READY request.
449 */
450 *pfMediaChanged = false;
451 *pfMediaPresent = false;
452 uint8_t abCmd[16] = { SCSI_TEST_UNIT_READY, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };
453 uint8_t abSense[32];
454 int rc = drvHostBaseScsiCmdOs(pThis, abCmd, 6, PDMMEDIATXDIR_NONE, NULL, NULL, abSense, sizeof(abSense), 0);
455 if (RT_SUCCESS(rc))
456 *pfMediaPresent = true;
457 else if ( rc == VERR_UNRESOLVED_ERROR
458 && abSense[2] == 6 /* unit attention */
459 && ( (abSense[12] == 0x29 && abSense[13] < 5 /* reset */)
460 || (abSense[12] == 0x2a && abSense[13] == 0 /* parameters changed */) //???
461 || (abSense[12] == 0x3f && abSense[13] == 0 /* target operating conditions have changed */) //???
462 || (abSense[12] == 0x3f && abSense[13] == 2 /* changed operating definition */) //???
463 || (abSense[12] == 0x3f && abSense[13] == 3 /* inquiry parameters changed */)
464 || (abSense[12] == 0x3f && abSense[13] == 5 /* device identifier changed */)
465 )
466 )
467 {
468 *pfMediaPresent = false;
469 *pfMediaChanged = true;
470 rc = VINF_SUCCESS;
471 /** @todo check this media change stuff on Darwin. */
472 }
473
474 return rc;
475}
476
477
478DECLHIDDEN(void) drvHostBaseInitOs(PDRVHOSTBASE pThis)
479{
480 pThis->Os.MasterPort = IO_OBJECT_NULL;
481 pThis->Os.ppMMCDI = NULL;
482 pThis->Os.ppScsiTaskDI = NULL;
483 pThis->Os.cbBlock = 0;
484 pThis->Os.pDADisk = NULL;
485 pThis->Os.pDASession = NULL;
486}
487
488
489DECLHIDDEN(int) drvHostBaseOpenOs(PDRVHOSTBASE pThis, bool fReadOnly)
490{
491 RT_NOREF(fReadOnly);
492
493 /* Darwin is kind of special... */
494 Assert(!pThis->Os.cbBlock);
495 Assert(pThis->Os.MasterPort == IO_OBJECT_NULL);
496 Assert(!pThis->Os.ppMMCDI);
497 Assert(!pThis->Os.ppScsiTaskDI);
498
499 /*
500 * Open the master port on the first invocation.
501 */
502 kern_return_t krc = IOMasterPort(MACH_PORT_NULL, &pThis->Os.MasterPort);
503 AssertReturn(krc == KERN_SUCCESS, VERR_GENERAL_FAILURE);
504
505 /*
506 * Create a matching dictionary for searching for CD, DVD and BlueRay services in the IOKit.
507 *
508 * The idea is to find all the devices which are of class IOCDBlockStorageDevice.
509 * CD devices are represented by IOCDBlockStorageDevice class itself, while DVD and BlueRay ones
510 * have it as a parent class.
511 */
512 CFMutableDictionaryRef RefMatchingDict = IOServiceMatching("IOCDBlockStorageDevice");
513 AssertReturn(RefMatchingDict, VERR_NOT_FOUND);
514
515 /*
516 * do the search and get a collection of keyboards.
517 */
518 io_iterator_t DVDServices = IO_OBJECT_NULL;
519 IOReturn irc = IOServiceGetMatchingServices(pThis->Os.MasterPort, RefMatchingDict, &DVDServices);
520 AssertMsgReturn(irc == kIOReturnSuccess, ("irc=%d\n", irc), VERR_NOT_FOUND);
521 RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */
522
523 /*
524 * Enumerate the matching drives (services).
525 * (This enumeration must be identical to the one performed in Main/src-server/darwin/iokit.cpp.)
526 */
527 int rc = VERR_FILE_NOT_FOUND;
528 unsigned i = 0;
529 io_object_t DVDService;
530 while ((DVDService = IOIteratorNext(DVDServices)) != 0)
531 {
532 /*
533 * Get the properties we use to identify the DVD drive.
534 *
535 * While there is a (weird 12 byte) GUID, it isn't persistent
536 * across boots. So, we have to use a combination of the
537 * vendor name and product name properties with an optional
538 * sequence number for identification.
539 */
540 CFMutableDictionaryRef PropsRef = 0;
541 krc = IORegistryEntryCreateCFProperties(DVDService, &PropsRef, kCFAllocatorDefault, kNilOptions);
542 if (krc == KERN_SUCCESS)
543 {
544 /* Get the Device Characteristics dictionary. */
545 CFDictionaryRef DevCharRef = (CFDictionaryRef)CFDictionaryGetValue(PropsRef, CFSTR(kIOPropertyDeviceCharacteristicsKey));
546 if (DevCharRef)
547 {
548 /* The vendor name. */
549 char szVendor[128];
550 char *pszVendor = &szVendor[0];
551 CFTypeRef ValueRef = CFDictionaryGetValue(DevCharRef, CFSTR(kIOPropertyVendorNameKey));
552 if ( ValueRef
553 && CFGetTypeID(ValueRef) == CFStringGetTypeID()
554 && CFStringGetCString((CFStringRef)ValueRef, szVendor, sizeof(szVendor), kCFStringEncodingUTF8))
555 pszVendor = RTStrStrip(szVendor);
556 else
557 *pszVendor = '\0';
558
559 /* The product name. */
560 char szProduct[128];
561 char *pszProduct = &szProduct[0];
562 ValueRef = CFDictionaryGetValue(DevCharRef, CFSTR(kIOPropertyProductNameKey));
563 if ( ValueRef
564 && CFGetTypeID(ValueRef) == CFStringGetTypeID()
565 && CFStringGetCString((CFStringRef)ValueRef, szProduct, sizeof(szProduct), kCFStringEncodingUTF8))
566 pszProduct = RTStrStrip(szProduct);
567 else
568 *pszProduct = '\0';
569
570 /* Construct the two names and compare thwm with the one we're searching for. */
571 char szName1[256 + 32];
572 char szName2[256 + 32];
573 if (*pszVendor || *pszProduct)
574 {
575 if (*pszVendor && *pszProduct)
576 {
577 RTStrPrintf(szName1, sizeof(szName1), "%s %s", pszVendor, pszProduct);
578 RTStrPrintf(szName2, sizeof(szName2), "%s %s (#%u)", pszVendor, pszProduct, i);
579 }
580 else
581 {
582 strcpy(szName1, *pszVendor ? pszVendor : pszProduct);
583 RTStrPrintf(szName2, sizeof(szName2), "%s (#%u)", *pszVendor ? pszVendor : pszProduct, i);
584 }
585 }
586 else
587 {
588 RTStrPrintf(szName1, sizeof(szName1), "(#%u)", i);
589 strcpy(szName2, szName1);
590 }
591
592 if ( !strcmp(szName1, pThis->pszDevice)
593 || !strcmp(szName2, pThis->pszDevice))
594 {
595 /*
596 * Found it! Now, get the client interface and stuff.
597 * Note that we could also query kIOSCSITaskDeviceUserClientTypeID here if the
598 * MMC client plugin is missing. For now we assume this won't be necessary.
599 */
600 SInt32 Score = 0;
601 IOCFPlugInInterface **ppPlugInInterface = NULL;
602 krc = IOCreatePlugInInterfaceForService(DVDService, kIOMMCDeviceUserClientTypeID, kIOCFPlugInInterfaceID,
603 &ppPlugInInterface, &Score);
604 if (krc == KERN_SUCCESS)
605 {
606 HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface,
607 CFUUIDGetUUIDBytes(kIOMMCDeviceInterfaceID),
608 (LPVOID *)&pThis->Os.ppMMCDI);
609 (*ppPlugInInterface)->Release(ppPlugInInterface);
610 ppPlugInInterface = NULL;
611 if (hrc == S_OK)
612 {
613 pThis->Os.ppScsiTaskDI = (*pThis->Os.ppMMCDI)->GetSCSITaskDeviceInterface(pThis->Os.ppMMCDI);
614 if (pThis->Os.ppScsiTaskDI)
615 rc = VINF_SUCCESS;
616 else
617 {
618 LogRel(("GetSCSITaskDeviceInterface failed on '%s'\n", pThis->pszDevice));
619 rc = VERR_NOT_SUPPORTED;
620 (*pThis->Os.ppMMCDI)->Release(pThis->Os.ppMMCDI);
621 }
622 }
623 else
624 {
625 rc = VERR_GENERAL_FAILURE;//RTErrConvertFromDarwinCOM(krc);
626 pThis->Os.ppMMCDI = NULL;
627 }
628 }
629 else /* Check for kIOSCSITaskDeviceUserClientTypeID? */
630 rc = VERR_GENERAL_FAILURE;//RTErrConvertFromDarwinKern(krc);
631
632 /* Obtain exclusive access to the device so we can send SCSI commands. */
633 if (RT_SUCCESS(rc))
634 rc = drvHostBaseObtainExclusiveAccess(pThis, DVDService);
635
636 /* Cleanup on failure. */
637 if (RT_FAILURE(rc))
638 {
639 if (pThis->Os.ppScsiTaskDI)
640 {
641 (*pThis->Os.ppScsiTaskDI)->Release(pThis->Os.ppScsiTaskDI);
642 pThis->Os.ppScsiTaskDI = NULL;
643 }
644 if (pThis->Os.ppMMCDI)
645 {
646 (*pThis->Os.ppMMCDI)->Release(pThis->Os.ppMMCDI);
647 pThis->Os.ppMMCDI = NULL;
648 }
649 }
650
651 IOObjectRelease(DVDService);
652 break;
653 }
654 }
655 CFRelease(PropsRef);
656 }
657 else
658 AssertMsgFailed(("krc=%#x\n", krc));
659
660 IOObjectRelease(DVDService);
661 i++;
662 }
663
664 IOObjectRelease(DVDServices);
665 return rc;
666
667}
668
669
670DECLHIDDEN(int) drvHostBaseMediaRefreshOs(PDRVHOSTBASE pThis)
671{
672 RT_NOREF(pThis);
673 return VINF_SUCCESS;
674}
675
676
677DECLHIDDEN(bool) drvHostBaseIsMediaPollingRequiredOs(PDRVHOSTBASE pThis)
678{
679 if (pThis->enmType == PDMMEDIATYPE_CDROM || pThis->enmType == PDMMEDIATYPE_DVD)
680 return true;
681
682 AssertMsgFailed(("Darwin supports only CD/DVD host drive access\n"));
683 return false;
684}
685
686
687DECLHIDDEN(void) drvHostBaseDestructOs(PDRVHOSTBASE pThis)
688{
689 /*
690 * Unlock the drive if we've locked it or we're in passthru mode.
691 */
692 if ( ( pThis->fLocked
693 || pThis->IMedia.pfnSendCmd)
694 && pThis->Os.ppScsiTaskDI
695 && pThis->pfnDoLock)
696 {
697 int rc = pThis->pfnDoLock(pThis, false);
698 if (RT_SUCCESS(rc))
699 pThis->fLocked = false;
700 }
701
702 /*
703 * The unclaiming doesn't seem to mean much, the DVD is actually
704 * remounted when we release exclusive access. I'm not quite sure
705 * if I should put the unclaim first or not...
706 *
707 * Anyway, that it's automatically remounted very good news for us,
708 * because that means we don't have to mess with that ourselves. Of
709 * course there is the unlikely scenario that we've succeeded in claiming
710 * and umount the DVD but somehow failed to gain exclusive scsi access...
711 */
712 if (pThis->Os.ppScsiTaskDI)
713 {
714 LogFlow(("%s-%d: releasing exclusive scsi access!\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
715 (*pThis->Os.ppScsiTaskDI)->ReleaseExclusiveAccess(pThis->Os.ppScsiTaskDI);
716 (*pThis->Os.ppScsiTaskDI)->Release(pThis->Os.ppScsiTaskDI);
717 pThis->Os.ppScsiTaskDI = NULL;
718 }
719 if (pThis->Os.pDADisk)
720 {
721 LogFlow(("%s-%d: unclaiming the disk!\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
722 DADiskUnclaim(pThis->Os.pDADisk);
723 CFRelease(pThis->Os.pDADisk);
724 pThis->Os.pDADisk = NULL;
725 }
726 if (pThis->Os.ppMMCDI)
727 {
728 LogFlow(("%s-%d: releasing the MMC object!\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
729 (*pThis->Os.ppMMCDI)->Release(pThis->Os.ppMMCDI);
730 pThis->Os.ppMMCDI = NULL;
731 }
732 if (pThis->Os.MasterPort != IO_OBJECT_NULL)
733 {
734 mach_port_deallocate(mach_task_self(), pThis->Os.MasterPort);
735 pThis->Os.MasterPort = IO_OBJECT_NULL;
736 }
737 if (pThis->Os.pDASession)
738 {
739 LogFlow(("%s-%d: releasing the DA session!\n", pThis->pDrvIns->pReg->szName, pThis->pDrvIns->iInstance));
740 CFRelease(pThis->Os.pDASession);
741 pThis->Os.pDASession = NULL;
742 }
743}
744
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