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);