VirtualBox

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

Last change on this file since 76384 was 76384, checked in by vboxsync, 6 years ago

include/VBox/vmm/pdmifs.h: Don't include hgcmsvc.h just for VBOXHGCMSVCPARM as it drags in all kinds of stuff. bugref:9344

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