Hi,

This patch will implement CDROM_GetInterfaceInfo() (and its associated
IOCTL) on Mac OS X. The problem is that it's big, it's complicated, and
it uses I/O Kit. From what I've seen, Alexandre doesn't exactly like I/O
Kit that much. Anyway, I want someone to review the patch.

No, I could not find a system ioctl that did what I wanted. Perhaps I
should hold off on this until we have that driver I mentioned earlier
ready...? In any case, here's my current patch.

Chip

---
 dlls/ntdll/cdrom.c |  428
+++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 411 insertions(+), 17 deletions(-)



diff --git a/dlls/ntdll/cdrom.c b/dlls/ntdll/cdrom.c
index 069ac01..4571b8a 100644
--- a/dlls/ntdll/cdrom.c
+++ b/dlls/ntdll/cdrom.c
@@ -83,6 +83,8 @@
 # include <IOKit/storage/IOMedia.h>
 # include <IOKit/storage/IOCDMediaBSDClient.h>
 # include <IOKit/storage/IODVDMediaBSDClient.h>
+# include <IOKit/storage/IOBlockStorageDevice.h>
+# include <IOKit/storage/IOStorageProtocolCharacteristics.h>
 # include <IOKit/scsi/SCSICmds_REQUEST_SENSE_Defs.h>
 # define SENSEBUFLEN kSenseDefaultSize
 #endif
@@ -310,19 +312,15 @@ static int CDROM_MediaChanged(int dev)
 
 
 /******************************************************************
- *		get_parent_device
+ *		get_device_service
  *
- * On Mac OS, get the device for the whole disk from a fd that points to a partition.
- * This is ugly and inefficient, but we have no choice since the partition fd doesn't
- * support the eject ioctl.
+ * On Mac OS, get the IOService object for a device from a fd.
  */
 #ifdef __APPLE__
-static NTSTATUS get_parent_device( int fd, char *name, size_t len )
+static NTSTATUS get_device_service( int fd, io_service_t* svc )
 {
-    NTSTATUS status = STATUS_NO_SUCH_FILE;
     struct stat st;
     int i;
-    io_service_t service;
     CFMutableDictionaryRef dict;
     CFTypeRef val;
 
@@ -345,15 +343,37 @@ static NTSTATUS get_parent_device( int fd, char *name, size_t len )
 
     CFDictionaryAddValue( dict, CFSTR("Removable"), kCFBooleanTrue );
 
-    service = IOServiceGetMatchingService( kIOMasterPortDefault, dict );
+    *svc = IOServiceGetMatchingService( kIOMasterPortDefault, dict );
+
+    if (!*svc) return STATUS_NO_SUCH_FILE;
+    return STATUS_SUCCESS;
+}
+
+/******************************************************************
+ *		get_parent_device_service
+ *
+ * On Mac OS, get the IOService object for the device for the whole
+ * disk from a fd that points to a partition. This is ugly and inefficient,
+ * but we have no choice since the partition fd doesn't support the eject or
+ * TOC ioctls, and we need this to determine certain characteristics of the
+ * CD-ROM device (bus type, location, etc.).
+ */
+static NTSTATUS get_parent_device_service( int fd, io_service_t* svc )
+{
+    NTSTATUS status;
+    io_service_t service;
+
+    /* get the IOService object for the partition */
+    status = get_device_service( fd, &service );
+    if (status != STATUS_SUCCESS) return status;
 
     /* now look for the parent that has the "Whole" attribute set to TRUE */
 
+    status = STATUS_NO_SUCH_FILE;
     while (service)
     {
         io_service_t parent = 0;
         CFBooleanRef whole;
-        CFStringRef str;
         int ok;
 
         if (!IOObjectConformsTo( service, kIOMediaClass ))
@@ -364,23 +384,213 @@ static NTSTATUS get_parent_device( int fd, char *name, size_t len )
         CFRelease( whole );
         if (!ok) goto next;
 
-        if ((str = IORegistryEntryCreateCFProperty( service, CFSTR("BSD Name"), NULL, 0 )))
+        *svc = service;
+        status = STATUS_SUCCESS;
+        break;
+
+    next:
+        IORegistryEntryGetParentEntry( service, kIOServicePlane, &parent );
+        IOObjectRelease( service );
+        service = parent;
+    }
+    return status;
+}
+
+/******************************************************************
+ *		get_block_device_service
+ *
+ * On Mac OS, get the IOBlockStorageDevice above the disk object.
+ * This is ugly and inefficient, but we need this to get the
+ * SCSITaskDevice and MMCDevice objects.
+ */
+static NTSTATUS get_block_device_service( int fd, io_service_t* svc )
+{
+    NTSTATUS status;
+    io_service_t service;
+
+    /* get the IOService object for the disk */
+    status = get_parent_device_service( fd, &service );
+    if (status != STATUS_SUCCESS) return status;
+
+    /* now look for the parent conforming to the "IOBlockStorageDevice" class */
+    status = STATUS_NO_SUCH_FILE;
+    while (service)
+    {
+        io_service_t parent = 0;
+
+        if (IOObjectConformsTo( service, kIOBlockStorageDeviceClass ))
         {
-            strcpy( name, "/dev/r" );
-            CFStringGetCString( str, name + 6, len - 6, kCFStringEncodingUTF8 );
-            CFRelease( str );
+            *svc = service;
             status = STATUS_SUCCESS;
+            break;
         }
-        IOObjectRelease( service );
-        break;
 
-next:
         IORegistryEntryGetParentEntry( service, kIOServicePlane, &parent );
         IOObjectRelease( service );
         service = parent;
     }
     return status;
 }
+
+/******************************************************************
+ *		get_parent_device
+ *
+ * On Mac OS, get the device for the whole disk from a fd that points to a partition.
+ * This is ugly and inefficient, but we have no choice since the partition
+ * fd doesn't support the eject or TOC ioctls.
+ */
+static NTSTATUS get_parent_device( int fd, char *name, size_t len )
+{
+    NTSTATUS status;
+    CFStringRef str;
+    io_service_t service;
+
+    /* get the IOService object for the disk */
+    status = get_parent_device_service( fd, &service );
+    if (status != STATUS_SUCCESS) return status;
+
+    if ((str = IORegistryEntryCreateCFProperty( service, CFSTR("BSD Name"), NULL, 0 )))
+    {
+        strcpy( name, "/dev/r" );
+        CFStringGetCString( str, name + 6, len - 6, kCFStringEncodingUTF8 );
+        CFRelease( str );
+        status = STATUS_SUCCESS;
+    }
+    else
+        status = STATUS_NO_MEMORY;
+    IOObjectRelease( service );
+    return status;
+}
+
+/* You may think that we want an array here. We would, if we wanted to get a
+ * controller given a port ID. But we want to get a port ID, given a controller.
+ * So, we use a CFDictionary to store port IDs indexed by their respective
+ * controller.
+ * In this dictionary, the keys are the IOKit registry paths to each controller
+ * object, stored as CFStrings, and the values are the corresponding port
+ * numbers, stored as raw integers.
+ */
+
+static CFMutableDictionaryRef scsi_controllers = NULL;
+
+/******************************************************************
+ *		CDROM_FindDevices
+ *
+ * On Mac OS, enumerates the SCSI devices in a system and their
+ * controllers, and stores the controllers in a dictionary for
+ * easy access. This allows us to retrieve the controller ID
+ * later in CDROM_GetInterfaceInfo().
+ */
+static void CDROM_FindDevices(void)
+{
+    CFDictionaryRef dict;
+    CFStringRef str;
+    io_iterator_t it;
+    io_service_t ent, ctlr, chan, whole;
+    io_string_t buf;
+
+    /* Make sure there's a dictionary of controllers */
+    if (!scsi_controllers)
+    {
+        scsi_controllers = CFDictionaryCreateMutable( NULL, 0, &kCFTypeDictionaryKeyCallBacks, NULL );
+        if (!scsi_controllers) return;
+    }
+    IOServiceGetMatchingServices( kIOMasterPortDefault, IOServiceMatching( "IOSCSIPeripheralDeviceNub" ), &it );
+    while ((ent = IOIteratorNext( it )))
+    {
+        /* Now read the "Protocol Characteristics" dictionary */
+        dict = IORegistryEntryCreateCFProperty( ent, CFSTR(kIOPropertyProtocolCharacteristicsKey), NULL, 0 );
+        if (!dict)
+        {
+            FIXME("couldn't read \"Protocol Characteristics\" dictionary\n");
+            return;
+        }
+        /* What we do now depends on the connection type */
+        str = CFDictionaryGetValue( dict, CFSTR(kIOPropertyPhysicalInterconnectTypeKey) );
+        if (CFStringCompare( str, CFSTR(kIOPropertyPhysicalInterconnectTypeATAPI), 0 ) == kCFCompareEqualTo)
+        {
+            /* Look for an IOATAController object above this one */
+            ctlr = ent;
+            while (!IOObjectConformsTo( ctlr, "IOATAController" ))
+            {
+                io_service_t parent;
+                IORegistryEntryGetParentEntry( ctlr, kIOServicePlane, &parent );
+                if (ctlr != ent) IOObjectRelease( ctlr );
+                ctlr = parent;
+                if (!ctlr)
+                {
+                    FIXME("ATAPI device with no IOATAController object\n");
+                    CFRelease( dict );
+                    return;
+                }
+            }
+        }
+        else if (CFStringCompare( str, CFSTR(kIOPropertyPhysicalInterconnectTypeSCSIParallel), 0 ) == kCFCompareEqualTo)
+        {
+            /* Look for an IOSCSIParallelInterfaceController object above this one */
+            ctlr = ent;
+            while (!IOObjectConformsTo( ctlr, "IOSCSIParallelInterfaceController" ))
+            {
+                io_service_t parent;
+                IORegistryEntryGetParentEntry( ctlr, kIOServicePlane, &parent );
+                if (ctlr != ent) IOObjectRelease( ctlr );
+                ctlr = parent;
+                if (!ctlr)
+                {
+                    FIXME("SCSI-Parallel device with no IOSPIController object\n");
+                    CFRelease( dict );
+                    return;
+                }
+            }
+        }
+        else
+        {
+            FIXME("Got unknown bus type\n");
+            CFRelease( dict );
+            return;
+        }
+        whole = ctlr;
+        /* One major problem with this setup is that Mac OS does not see con-
+         * trollers with multiple channels; instead it sees each physical
+         * channel of each controller as a controller in its own right. How-
+         * ever, "controllers" that are really just channels belonging to true
+         * controllers tend to have device nubs that aren't of the usual type
+         * provided by the bus on which the controller resides.
+         *
+         * Right above this is a device nub published by the driver providing
+         * us. Now if the nub is NOT of type "IOPCIDevice", then this particular
+         * "controller" is most likely just a channel provided by the true
+         * controller. This is a safe assumption because:
+         * 1) The only internal peripheral buses in use right now are PCI
+         *    and PCI Express.
+         * 2) All variants of PCI and PCI Express use the IOPCI family to
+         *    drive their devices.
+         */
+        str = IORegistryEntryCreateCFProperty( ctlr, CFSTR("IOProviderClass"), NULL, 0 );
+        if (CFStringCompare( str, CFSTR("IOPCIDevice"), 0 ) != kCFCompareEqualTo)
+        {
+            whole = 0;
+            IORegistryEntryGetParentEntry( ctlr, kIOServicePlane, &chan );
+            IORegistryEntryGetParentEntry( chan, kIOServicePlane, &whole );
+            IOObjectRelease( chan );
+            IOObjectRelease( ctlr );
+        }
+        CFRelease( str );
+        /* Now look up the controller device in the dictionary */
+        IORegistryEntryGetPath( ent, kIOServicePlane, buf );
+        str = CFStringCreateWithCString( NULL, buf, kCFStringEncodingUTF8 );
+        if (!CFDictionaryGetCountOfKey( scsi_controllers, str ))
+        {
+            /* Not in the dictionary, so add it */
+            CFDictionaryAddValue( scsi_controllers, str, (const void*)CFDictionaryGetCount( scsi_controllers ) );
+        }
+        CFRelease( str );
+        IOObjectRelease( whole );
+        CFRelease( dict );
+        IOObjectRelease( ent );
+    }
+    IOObjectRelease( it );
+}
 #endif
 
 
@@ -609,6 +819,184 @@ static int CDROM_GetInterfaceInfo(int fd, UCHAR* iface, UCHAR* port, UCHAR* devi
 #elif defined(__FreeBSD__)
     FIXME("not implemented for BSD\n");
     return 0;
+#elif defined(__APPLE__)
+    io_service_t service;
+    CFDictionaryRef dict;
+    CFStringRef str;
+    int ret;
+    io_service_t ctlr, whole;
+
+    *port = *device = *iface = *lun = 0;
+    if (get_block_device_service( fd, &service ) != STATUS_SUCCESS) return 0;
+
+    /* The IOBlockStorageDevice object has a property, "Protocol Characteristics,"
+     * that describes how the device is connected to the computer.
+     * Get this dictionary object and examine it.
+     */
+    dict = IORegistryEntryCreateCFProperty( service, CFSTR(kIOPropertyProtocolCharacteristicsKey), NULL, 0 );
+    if (!dict)
+    {
+        IOObjectRelease(service);
+        return 0;
+    }
+    str = CFDictionaryGetValue( dict, CFSTR(kIOPropertyPhysicalInterconnectTypeKey) );
+    if (!str)
+    {
+        CFRelease(dict);
+        IOObjectRelease(service);
+        return 0;
+    }
+    if (CFStringCompare( str, CFSTR(kIOPropertyPhysicalInterconnectTypeATAPI), 0 ) == kCFCompareEqualTo)
+    {
+        /* ATAPI CD-ROM device */
+        CFNumberRef val;
+        /* Somewhere above this object is an IOATAController.
+         * The object we're looking for, however, is an instance of a class
+         * derived from IOATAController. So, we use IOObjectConformsTo()
+         * to check the object type of each service.
+         */
+        ctlr = service;
+        while (!IOObjectConformsTo( ctlr, "IOATAController" ))
+        {
+            io_service_t temp;
+
+            if (IORegistryEntryGetParentEntry( ctlr, kIOServicePlane, &temp ) != KERN_SUCCESS)
+            {
+                ERR("got ATAPI CD-ROM device with no ATA controller\n");
+                CFRelease( dict );
+                if (ctlr != service) IOObjectRelease( ctlr );
+                IOObjectRelease( service );
+                return 0;
+            }
+            if (ctlr != service) IOObjectRelease( ctlr );
+            ctlr = temp;
+        }
+
+        /* In the dictionary we got, there is an undocumented key "unit number"
+         * which gives the unit number on the ATAPI bus.
+         */
+        WARN("got ATAPI CD-ROM device; using undoc'd \"unit number\" key\n");
+        val = CFDictionaryGetValue( dict, CFSTR("unit number") );
+        CFNumberGetValue( val, kCFNumberIntType, device );
+
+        ret = 1;
+    }
+    else if (CFStringCompare( str, CFSTR(kIOPropertyPhysicalInterconnectTypeSCSIParallel), 0 ) == kCFCompareEqualTo ||
+        CFStringCompare( str, CFSTR(kIOPropertyPhysicalInterconnectTypeFibreChannel), 0 ) == kCFCompareEqualTo)
+    {
+        /* SCSI CD-ROM device */
+        CFNumberRef val;
+        /* Somewhere above this object is an IOSCSIParallelInterfaceController.
+         * The object we're looking for, however, is an instance of a class
+         * derived from IOSCSIParallelInterfaceController. So, we use IOObjectConformsTo()
+         * to check the object type of each service.
+         */
+        ctlr = service;
+        while (!IOObjectConformsTo( ctlr, "IOSCSIParallelInterfaceController" ))
+        {
+            io_service_t temp;
+
+            if (IORegistryEntryGetParentEntry( ctlr, kIOServicePlane, &temp ) != KERN_SUCCESS)
+            {
+                ERR("got SCSI-Parallel CD-ROM device with no SPI controller\n");
+                CFRelease( dict );
+                if (ctlr != service) IOObjectRelease( ctlr );
+                IOObjectRelease( service );
+                return 0;
+            }
+            if (ctlr != service) IOObjectRelease( ctlr );
+            ctlr = temp;
+        }
+
+        /* Unlike ATAPI CD-ROMs, SCSI disks have several well defined keys right
+         * in the "Protocol Characteristics" dictionary.
+         */
+        val = CFDictionaryGetValue( dict, CFSTR(kIOPropertySCSITargetIdentifierKey) );
+        CFNumberGetValue( val, kCFNumberIntType, device );
+        val = CFDictionaryGetValue( dict, CFSTR(kIOPropertySCSILogicalUnitNumberKey) );
+        CFNumberGetValue( val, kCFNumberIntType, lun );
+
+        ret = 1;
+    }
+    else
+    {
+        /* CD-ROM device on unknown/unhandled bus type */
+        /* Known but unhandled types: Serial ATA, Serial Attached SCSI,
+         * iSCSI, USB, FireWire (IEEE 1394).
+         */
+        char* buf;
+        size_t len = CFStringGetLength( str ) + 1;
+        buf = RtlAllocateHeap( GetProcessHeap(), 0, len );
+        if (buf)
+        {
+            CFStringGetCString( str, buf, len, kCFStringEncodingUTF8 );
+            buf[len-1] = 0;
+            FIXME( "got unhandled bus type %s\n", debugstr_a(buf) );
+            RtlFreeHeap( GetProcessHeap(), 0, buf );
+        }
+        else
+        {
+            FIXME("got unhandled bus type and ran out of memory\n");
+        }
+
+        ret = 0;
+    }
+    if (ret)
+    {
+        io_string_t buf;
+        /* Now determine the port and bus numbers */
+        whole = ctlr;
+        /* First see if this is just a bus on a much larger controller */
+        str = IORegistryEntryCreateCFProperty( ctlr, CFSTR("IOProviderClass"), NULL, 0 );
+        if (str)
+        {
+            if(CFStringCompare( str, CFSTR("IOPCIDevice"), 0 ) != kCFCompareEqualTo)
+            {
+                CFNumberRef num;
+                io_service_t chan;
+
+                whole = 0;
+                IORegistryEntryGetParentEntry( ctlr, kIOServicePlane, &chan );
+                IORegistryEntryGetParentEntry( chan, kIOServicePlane, &whole );
+                /* Some channel device objects have "Channel Number" keys.
+                 * Check for it.
+                 */
+                num = IORegistryEntryCreateCFProperty( chan, CFSTR("Channel Number"), NULL, 0 );
+                if (num)
+                {
+                    /* Get its value */
+                    CFNumberGetValue( num, kCFNumberIntType, port );
+                    CFRelease( num );
+                }
+                else
+                {
+                    /* Great, now we have to count channels */
+                    io_iterator_t it;
+                    io_service_t cur;
+
+                    IORegistryEntryGetChildIterator( whole, kIOServicePlane, &it );
+                    while ((cur = IOIteratorNext( it )))
+                    {
+                        if (IOObjectIsEqualTo( cur, chan )) break;
+                        (*port)++;
+                    }
+                }
+                IOObjectRelease( chan );
+                IOObjectRelease( ctlr );
+            }
+            CFRelease( str );
+        }
+        /* Get the path */
+        IORegistryEntryGetPath( whole, kIOServicePlane, buf );
+        str = CFStringCreateWithCString( NULL, buf, kCFStringEncodingUTF8 );
+        /* Now get the port number */
+        *iface = (int)CFDictionaryGetValue( scsi_controllers, str );
+        CFRelease( str );
+        IOObjectRelease( whole );
+    }
+    CFRelease( dict );
+    IOObjectRelease( service );
+    return ret;
 #else
     FIXME("not implemented for nonlinux\n");
     return 0;
@@ -2943,7 +3331,13 @@ NTSTATUS CDROM_DeviceIoControl(HANDLE hDevice,
         sz = sizeof(SCSI_ADDRESS);
         if (lpInBuffer != NULL || nInBufferSize != 0) status = STATUS_INVALID_PARAMETER;
         else if (nOutBufferSize < sz) status = STATUS_BUFFER_TOO_SMALL;
-        else status = CDROM_GetAddress(fd, lpOutBuffer);
+        else
+        {
+#ifdef __APPLE__
+            if (scsi_controllers == NULL) CDROM_FindDevices();
+#endif
+            status = CDROM_GetAddress(fd, lpOutBuffer);
+        }
         break;
     case IOCTL_SCSI_PASS_THROUGH_DIRECT:
         sz = sizeof(SCSI_PASS_THROUGH_DIRECT);




Reply via email to