VirtualBox

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

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