Hi,
Here's a new version of my patch that's slightly less complicated. I
didn't get a response last time, so I'm sending it again. Please,
someone review my patch. I won't send it to wine-patches until I know I
have this right (yes, I am a bit of a perfectionist).
To reiterate what I said about this patch last time, this patch will
implement the CDROM_GetInterfaceInfo() function on Mac OS, which allows
clients to, among other things, get a SCSI address to do whatever with.
(If this is over your head, you can stop reading now. :)
I'd like to re-emphasize that if I thought this would get in, I'd send
it to wine-patches. But I don't, so I'm asking for your help. I
understand if you have better things to do (and, to be honest, I know
most of you do), but I'd really appreciate some input here. Please don't
disappoint me this time.
Thanks.
Chip
---
dlls/ntdll/cdrom.c | 354
+++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 353 insertions(+), 1 deletions(-)
diff --git a/dlls/ntdll/cdrom.c b/dlls/ntdll/cdrom.c
index 1bda4a8..4f81020 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
@@ -392,6 +394,42 @@ next:
}
/******************************************************************
+ * 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 ))
+ {
+ *svc = service;
+ status = STATUS_SUCCESS;
+ break;
+ }
+
+ 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.
@@ -420,6 +458,136 @@ static NTSTATUS get_parent_device( int fd, char *name, size_t len )
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
@@ -648,6 +816,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;
@@ -3009,7 +3355,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);