VirtualBox

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

Last change on this file since 90778 was 82968, checked in by vboxsync, 4 years ago

Copyright year updates by scm.

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

© 2023 Oracle
ContactPrivacy policyTerms of Use