mmc_device_darwin.c 12.5 KB
Newer Older
npzacs's avatar
npzacs committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */
/*
 * This file is part of libaacs
 * Copyright (C) 2009-2010  Obliter0n
 * Copyright (C) 2010-2015  npzacs
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see
 * <http://www.gnu.org/licenses/>.
 */

#if HAVE_CONFIG_H
#include "config.h"
#endif

#include "mmc_device.h"

#include "util/logging.h"
npzacs's avatar
npzacs committed
29
30
#include "util/macro.h"
#include "util/strutl.h"
npzacs's avatar
npzacs committed
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

#include <stdlib.h>
#include <string.h>

#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h>

#include <DiskArbitration/DiskArbitration.h>

/* need to undefine VERSION as one of the members of struct
   SCSICmd_INQUIRY_StandardData is named VERSION (see
   IOKit/scsi/SCSICmds_INQUIRY_Definitions.h) */
#undef VERSION
#include <IOKit/scsi/SCSITaskLib.h>

#include <IOKit/storage/IOBDMediaBSDClient.h>

#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif

#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif

#ifdef HAVE_SYS_MOUNT_H
#include <sys/mount.h>
#endif

#ifdef HAVE_LIBGEN_H
#include <libgen.h>
#endif

#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif


/*
 *
 */

struct mmcdev {
    MMCDeviceInterface **mmcInterface;
    SCSITaskDeviceInterface **taskInterface;

    /* device short name (ie disk1) */
78
    char bsd_name[MNAMELEN];
npzacs's avatar
npzacs committed
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

    /* for mounting/unmounting the disc */
    DADiskRef disk;
    DASessionRef session;
    bool is_mounted;
};

int device_send_cmd(MMCDEV *mmc, const uint8_t *cmd, uint8_t *buf, size_t tx, size_t rx)
{
    SCSITaskInterface **task = NULL;
    SCSI_Sense_Data sense;
    SCSITaskStatus status;
    SCSITaskSGElement iov;
    UInt8 direction;
    UInt64 sent;
    int rc;

    if (NULL == mmc->taskInterface) {
        return 0;
    }

    do {
        task = (*mmc->taskInterface)->CreateSCSITask (mmc->taskInterface);
        if (NULL == task) {
npzacs's avatar
npzacs committed
103
            BD_DEBUG(DBG_MMC, "Could not create SCSI Task\n");
npzacs's avatar
npzacs committed
104
105
106
107
108
109
110
111
112
113
114
115
116
            break;
        }

        iov.address = (uintptr_t) buf;
        iov.length  = tx ? tx : rx;

        if (buf) {
            direction = tx ? kSCSIDataTransfer_FromInitiatorToTarget :
                kSCSIDataTransfer_FromTargetToInitiator;
        } else {
            direction = kSCSIDataTransfer_NoDataTransfer;
        }

117
118
119
120
        SCSICommandDescriptorBlock cdb = {0};
        memcpy(cdb, cmd, sizeof(cdb));

        rc = (*task)->SetCommandDescriptorBlock (task, cdb, kSCSICDBSize_16Byte);
npzacs's avatar
npzacs committed
121
        if (kIOReturnSuccess != rc) {
npzacs's avatar
npzacs committed
122
            BD_DEBUG(DBG_MMC, "Error setting SCSI command\n");
npzacs's avatar
npzacs committed
123
124
125
126
127
            break;
        }

        rc = (*task)->SetScatterGatherEntries (task, &iov, 1, iov.length, direction);
        if (kIOReturnSuccess != rc) {
npzacs's avatar
npzacs committed
128
            BD_DEBUG(DBG_MMC, "Error setting SCSI scatter gather entries\n");
npzacs's avatar
npzacs committed
129
130
131
132
133
            break;
        }

        rc = (*task)->SetTimeoutDuration (task, 5000000);
        if (kIOReturnSuccess != rc) {
npzacs's avatar
npzacs committed
134
            BD_DEBUG(DBG_MMC, "Error setting SCSI command timeout\n");
npzacs's avatar
npzacs committed
135
136
137
138
139
140
141
142
            break;
        }

        memset (&sense, 0, sizeof (sense));

        rc = (*task)->ExecuteTaskSync (task, &sense, &status, &sent);

        char str[512];
npzacs's avatar
npzacs committed
143
        BD_DEBUG(DBG_MMC, "Send SCSI MMC cmd %s:\n", str_print_hex(str, cmd, 16));
npzacs's avatar
npzacs committed
144
        if (tx) {
npzacs's avatar
npzacs committed
145
            BD_DEBUG(DBG_MMC, "  Buffer: %s ->\n", str_print_hex(str, buf, tx>255?255:tx));
npzacs's avatar
npzacs committed
146
        } else {
npzacs's avatar
npzacs committed
147
            BD_DEBUG(DBG_MMC, "  Buffer: %s <-\n", str_print_hex(str, buf, rx>255?255:rx));
npzacs's avatar
npzacs committed
148
149
150
        }

        if (kIOReturnSuccess != rc || status != 0) {
npzacs's avatar
npzacs committed
151
            BD_DEBUG(DBG_MMC, "  Send failed!\n");
npzacs's avatar
npzacs committed
152
153
            break;
        } else {
npzacs's avatar
npzacs committed
154
            BD_DEBUG(DBG_MMC, "  Send succeeded! sent = %lld status = %u. response = %x\n",
npzacs's avatar
npzacs committed
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
                  (unsigned long long) sent, status, sense.VALID_RESPONSE_CODE);
        }

        (*task)->Release (task);

        return 1;
    } while (0);

    if (task) {
        (*task)->Release (task);
    }

    return 0;
}

static int get_mounted_device_from_path (MMCDEV *mmc, const char *path) {
  struct statfs stat_info;
  int rc;

  rc = statfs (path, &stat_info);
  if (0 != rc) {
    return rc;
  }

  strncpy (mmc->bsd_name, basename (stat_info.f_mntfromname), sizeof (mmc->bsd_name));

  return 0;
}

static void iokit_unmount_complete (DADiskRef disk, DADissenterRef dissenter,
                                    void *context) {
    (void)disk; /* suppress warning */

    if (dissenter) {
npzacs's avatar
npzacs committed
189
        BD_DEBUG(DBG_MMC, "Could not unmount the disc\n");
npzacs's avatar
npzacs committed
190
    } else {
npzacs's avatar
npzacs committed
191
        BD_DEBUG(DBG_MMC, "Disc unmounted\n");
npzacs's avatar
npzacs committed
192
193
194
195
196
197
198
199
200
        ((MMCDEV *)context)->is_mounted = 0;
    }
}

static void iokit_mount_complete (DADiskRef disk, DADissenterRef dissenter,
                                  void *context) {
    (void) disk; /* suppress warning */
    (void) dissenter; /* suppress warning */

201
202
203
204
205
206
207
208
209
210
211
212
213
214
    if (dissenter) {
        BD_DEBUG(DBG_MMC, "Could not mount the disc\n");
    } else {
        BD_DEBUG(DBG_MMC, "Disc mounted\n");
    }

    /* FIXME: The disc does not actually mount whether there is
     * a dissenter or not, the OS mounts the disc automatically
     * kind of racing against us mounting the disc.
     * It is pure luck if the disc is mounted or not, sometimes
     * we are lucky enough, especially because the runloop is
     * running for 10 seconds, which most of the time is long
     * enough for the OS to mount the disc again.
     */
npzacs's avatar
npzacs committed
215
216
217
218
219
220
221
222
    ((MMCDEV *)context)->is_mounted = 1;
}

static int iokit_unmount (MMCDEV *mmc) {
    if (0 == mmc->is_mounted) {
        return 0; /* nothing to do */
    }

npzacs's avatar
npzacs committed
223
    BD_DEBUG(DBG_MMC, "Unmounting disk\n");
npzacs's avatar
npzacs committed
224
225
226

    mmc->session = DASessionCreate (kCFAllocatorDefault);
    if (NULL == mmc->session) {
npzacs's avatar
npzacs committed
227
        BD_DEBUG(DBG_MMC, "Could not create a disc arbitration session\n");
npzacs's avatar
npzacs committed
228
229
230
231
232
        return -1;
    }

    mmc->disk = DADiskCreateFromBSDName (kCFAllocatorDefault, mmc->session, mmc->bsd_name);
    if (NULL == mmc->disk) {
npzacs's avatar
npzacs committed
233
        BD_DEBUG(DBG_MMC, "Could not create a disc arbitration disc for the device\n");
npzacs's avatar
npzacs committed
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
        CFRelease (mmc->session);
        mmc->session = NULL;
        return -1;
    }

    DAApprovalSessionScheduleWithRunLoop (mmc->session, CFRunLoopGetCurrent (),
                                          kCFRunLoopDefaultMode);

    DADiskUnmount (mmc->disk, kDADiskUnmountOptionForce, iokit_unmount_complete, mmc);

    CFRunLoopRunInMode (kCFRunLoopDefaultMode, 10, true);

    return mmc->is_mounted ? -1 : 0;
}

static int iokit_mount (MMCDEV *mmc) {
    if (0 == mmc->is_mounted) {
        if (mmc->disk && mmc->session) {
            DADiskMount (mmc->disk, NULL, kDADiskMountOptionDefault, iokit_mount_complete, mmc);

            CFRunLoopRunInMode (kCFRunLoopDefaultMode, 10, true);

            DAApprovalSessionUnscheduleFromRunLoop (mmc->session, CFRunLoopGetCurrent (),
                                                    kCFRunLoopDefaultMode);
        }

        if (mmc->disk) {
            CFRelease (mmc->disk);
            mmc->disk = NULL;
        }

        if (mmc->session) {
            CFRelease (mmc->session);
            mmc->session = NULL;
        }
    }

    return mmc->is_mounted ? 0 : -1;
}

static int iokit_find_service_matching (MMCDEV *mmc, io_service_t *servp) {
    CFMutableDictionaryRef matchingDict = IOServiceMatching("IOBDServices");
    io_iterator_t deviceIterator;
    io_service_t service;
    int rc;

    assert (NULL != servp);

    *servp = 0;

    if (!matchingDict) {
npzacs's avatar
npzacs committed
285
        BD_DEBUG(DBG_MMC, "Could not create a matching dictionary for IOBDServices\n");
npzacs's avatar
npzacs committed
286
287
288
289
290
291
        return -1;
    }

    /* this call consumes the reference to the matchingDict. we do not need to release it */
    rc = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &deviceIterator);
    if (kIOReturnSuccess != rc) {
npzacs's avatar
npzacs committed
292
        BD_DEBUG(DBG_MMC, "Could not create device iterator\n");
npzacs's avatar
npzacs committed
293
294
295
296
297
        return -1;
    }

    while (0 != (service = IOIteratorNext (deviceIterator))) {
        CFStringRef data;
298
        char name[MNAMELEN] = "";
npzacs's avatar
npzacs committed
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332

        data = IORegistryEntrySearchCFProperty (service, kIOServicePlane, CFSTR("BSD Name"),
                                                kCFAllocatorDefault, kIORegistryIterateRecursively);

        if (NULL != data) {
            rc = CFStringGetCString (data, name, sizeof (name), kCFStringEncodingASCII);
            CFRelease (data);
            if (0 == strcmp (name, mmc->bsd_name)) {
                break;
            }
        }

        (void) IOObjectRelease (service);
    }

    IOObjectRelease (deviceIterator);

    *servp = service;

    return (service) ? 0 : -1;
}

static int iokit_find_interfaces (MMCDEV *mmc, io_service_t service) {
    IOCFPlugInInterface **plugInInterface = NULL;
    SInt32 score;
    int rc;

    rc = IOCreatePlugInInterfaceForService (service, kIOMMCDeviceUserClientTypeID,
                                            kIOCFPlugInInterfaceID, &plugInInterface,
                                            &score);
    if (kIOReturnSuccess != rc || NULL == plugInInterface) {
        return -1;
    }

npzacs's avatar
npzacs committed
333
    BD_DEBUG(DBG_MMC, "Getting MMC interface\n");
npzacs's avatar
npzacs committed
334
335
336
337
338
339
340

    rc = (*plugInInterface)->QueryInterface(plugInInterface,
                                            CFUUIDGetUUIDBytes(kIOMMCDeviceInterfaceID),
                                            (LPVOID)&mmc->mmcInterface);
    /* call release instead of IODestroyPlugInInterface to avoid stopping IOBDServices */
    (*plugInInterface)->Release(plugInInterface);
    if (kIOReturnSuccess != rc || NULL == mmc->mmcInterface) {
npzacs's avatar
npzacs committed
341
        BD_DEBUG(DBG_MMC, "Could not get multimedia commands (MMC) interface\n");
npzacs's avatar
npzacs committed
342
343
344
        return -1;
    }

npzacs's avatar
npzacs committed
345
    BD_DEBUG(DBG_MMC, "Have an MMC interface (%p). Getting a SCSI task interface...\n", (void*)mmc->mmcInterface);
npzacs's avatar
npzacs committed
346
347
348

    mmc->taskInterface = (*mmc->mmcInterface)->GetSCSITaskDeviceInterface (mmc->mmcInterface);
    if (NULL == mmc->taskInterface) {
npzacs's avatar
npzacs committed
349
        BD_DEBUG(DBG_MMC, "Could not get SCSI task device interface\n");
npzacs's avatar
npzacs committed
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
        return -1;
    }

    return 0;
}

static int mmc_open_iokit (const char *path, MMCDEV *mmc) {
    io_service_t service;
    int rc;

    mmc->mmcInterface = NULL;
    mmc->taskInterface = NULL;
    mmc->disk = NULL;
    mmc->session = NULL;
    mmc->is_mounted = true;

    /* get the bsd name associated with this mount */
    rc = get_mounted_device_from_path (mmc, path);
    if (0 != rc) {
npzacs's avatar
npzacs committed
369
        BD_DEBUG(DBG_MMC, "Could not locate mounted device associated with %s\n", path);
npzacs's avatar
npzacs committed
370
371
372
373
374
375
        return rc;
    }

    /* find a matching io service (IOBDServices) */
    rc = iokit_find_service_matching (mmc, &service);
    if (0 != rc) {
npzacs's avatar
npzacs committed
376
        BD_DEBUG(DBG_MMC, "Could not find matching IOBDServices mounted @ %s\n", path);
npzacs's avatar
npzacs committed
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
        return rc;
    }

    /* find mmc and scsi task interfaces */
    rc = iokit_find_interfaces (mmc, service);

    /* done with the ioservice. release it */
    (void) IOObjectRelease (service);

    if (0 != rc) {
        return rc;
    }

    /* unmount the disk so exclusive access can be obtained (this is required
       to use the scsi task interface) */
    rc = iokit_unmount (mmc);
    if (0 != rc) {
        return rc;
    }

    /* finally, obtain exclusive access */
    rc = (*mmc->taskInterface)->ObtainExclusiveAccess (mmc->taskInterface);
    if (kIOReturnSuccess != rc) {
npzacs's avatar
npzacs committed
400
        BD_DEBUG(DBG_MMC, "Failed to obtain exclusive access. rc = %x\n", rc);
npzacs's avatar
npzacs committed
401
402
403
        return -1;
    }

npzacs's avatar
npzacs committed
404
    BD_DEBUG(DBG_MMC, "MMC Open complete\n");
npzacs's avatar
npzacs committed
405
406
407
408
409
410

    return 0;
}

MMCDEV *device_open(const char *path)
{
411
    MMCDEV *mmc;
npzacs's avatar
npzacs committed
412
413
    int     rc;

414
415
    mmc = calloc(1, sizeof(MMCDEV));
    if (!mmc) {
npzacs's avatar
npzacs committed
416
417
418
        BD_DEBUG(DBG_MKB | DBG_CRIT, "out of memory\n");
        return NULL;
    }
npzacs's avatar
npzacs committed
419

420
    rc = mmc_open_iokit (path, mmc);
npzacs's avatar
npzacs committed
421
    if (0 != rc) {
422
        device_close (&mmc);
npzacs's avatar
npzacs committed
423
424
425
        return NULL;
    }

426
    return mmc;
npzacs's avatar
npzacs committed
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
}

void device_close(MMCDEV **pp)
{
    if (pp && *pp) {
        MMCDEV *mmc = *pp;

        if (mmc->taskInterface) {
            (*mmc->taskInterface)->ReleaseExclusiveAccess (mmc->taskInterface);
            (*mmc->taskInterface)->Release (mmc->taskInterface);
            mmc->taskInterface = NULL;
        }

        if (mmc->mmcInterface) {
            (*mmc->mmcInterface)->Release (mmc->mmcInterface);
            mmc->mmcInterface = NULL;
        }

        (void) iokit_mount (mmc);

        X_FREE(*pp);
    }
}