Using IOKit to Detect Graphics Hardware

After Seasonality Core 2 was released a couple of weeks ago, I received email from a few users reporting problems they were experiencing with the app. The common thread in all the problems was having a single graphics card (in this case, it was the nVidia 7300). When the application launched, there would be several graphics artifacts in the map view (which is now written in OpenGL), and even outside the Seasonality Core window. It really sounded like I was trying to use OpenGL to do something that wasn’t compatible with the nVidia 7300.

I’m still in the process of working around the problem, but I wanted to make sure that any work-around would not affect the other 99% of my users who don’t have this graphics card. So I set out to try and find a method of detecting which graphics cards are installed in a user’s Mac. You can use the system_profiler terminal command to do this:

system_profiler SPDisplaysDataType

But running an external process from within the app is slow, and it can be difficult to parse the data reliably. Plus, if the system_profiler command goes away, the application code won’t work. I continued looking…

Eventually, I found that I might be able to get this information from IOKit. If you run the command ioreg -l, you’ll get a lengthy tree of hardware present in your Mac. I’ve used IOKit in my code before, so I figured I would try to do that again. Here is the solution I came up with:

// Check the PCI devices for video cards.
CFMutableDictionaryRef match_dictionary = IOServiceMatching("IOPCIDevice");

// Create a iterator to go through the found devices.
io_iterator_t entry_iterator;
if (IOServiceGetMatchingServices(kIOMasterPortDefault,
match_dictionary,
&entry_iterator) == kIOReturnSuccess)
{
// Actually iterate through the found devices.
io_registry_entry_t serviceObject;
while ((serviceObject = IOIteratorNext(entry_iterator))) {
// Put this services object into a dictionary object.
CFMutableDictionaryRef serviceDictionary;
if (IORegistryEntryCreateCFProperties(serviceObject,
&serviceDictionary,
kCFAllocatorDefault,
kNilOptions) != kIOReturnSuccess)
{
// Failed to create a service dictionary, release and go on.
IOObjectRelease(serviceObject);
continue;
}
// If this is a GPU listing, it will have a "model" key
// that points to a CFDataRef.
const void *model = CFDictionaryGetValue(serviceDictionary, @"model");
if (model != nil) {
if (CFGetTypeID(model) == CFDataGetTypeID()) {
// Create a string from the CFDataRef.
NSString *s = [[NSString alloc] initWithData:(NSData *)model
encoding:NSASCIIStringEncoding];
NSLog(@"Found GPU: %@", s);
[s release];
}
}
// Release the dictionary created by IORegistryEntryCreateCFProperties.
CFRelease(serviceDictionary);

// Release the serviceObject returned by IOIteratorNext.
IOObjectRelease(serviceObject);
}

// Release the entry_iterator created by IOServiceGetMatchingServices.
IOObjectRelease(entry_iterator);
}