1 | /* $Id: DrvHostBase-darwin.cpp 93115 2022-01-01 11:31:46Z vboxsync $ */
|
---|
2 | /** @file
|
---|
3 | * DrvHostBase - Host base drive access driver, OS X specifics.
|
---|
4 | */
|
---|
5 |
|
---|
6 | /*
|
---|
7 | * Copyright (C) 2006-2022 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 | */
|
---|
40 | typedef 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. */
|
---|
58 | typedef DRVHOSTBASEOS *PDRVHOSBASEOS;
|
---|
59 | AssertCompile(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 | */
|
---|
91 | static 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 | */
|
---|
128 | static 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 | */
|
---|
147 | static 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 |
|
---|
247 | DECLHIDDEN(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 |
|
---|
341 | DECLHIDDEN(size_t) drvHostBaseScsiCmdGetBufLimitOs(PDRVHOSTBASE pThis)
|
---|
342 | {
|
---|
343 | RT_NOREF(pThis);
|
---|
344 |
|
---|
345 | return DARWIN_SCSI_MAX_BUFFER_SIZE;
|
---|
346 | }
|
---|
347 |
|
---|
348 |
|
---|
349 | DECLHIDDEN(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 |
|
---|
381 | DECLHIDDEN(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 |
|
---|
421 | DECLHIDDEN(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 |
|
---|
428 | DECLHIDDEN(int) drvHostBaseFlushOs(PDRVHOSTBASE pThis)
|
---|
429 | {
|
---|
430 | RT_NOREF1(pThis);
|
---|
431 | return VINF_SUCCESS;
|
---|
432 | }
|
---|
433 |
|
---|
434 |
|
---|
435 | DECLHIDDEN(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 |
|
---|
446 | DECLHIDDEN(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 |
|
---|
457 | DECLHIDDEN(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 |
|
---|
492 | DECLHIDDEN(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 |
|
---|
503 | DECLHIDDEN(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 |
|
---|
684 | DECLHIDDEN(int) drvHostBaseMediaRefreshOs(PDRVHOSTBASE pThis)
|
---|
685 | {
|
---|
686 | RT_NOREF(pThis);
|
---|
687 | return VINF_SUCCESS;
|
---|
688 | }
|
---|
689 |
|
---|
690 |
|
---|
691 | DECLHIDDEN(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 |
|
---|
701 | DECLHIDDEN(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 |
|
---|