To a user space application, a serial port driver is accessed as a standard character device in the /dev
directory. This should be familiar territory for anyone who has accessed a serial port on any other UNIX system. Where the I/O Kit approach differs, however, is in how a user space application enumerates the serial ports that are present in a system. For many traditional UNIX applications, the user must specify the full path of the serial port's character file. The approach taken by Mac OS X is to shield users from the /dev
directory, and to present available serial ports through a descriptive name. This is where the I/O Kit comes in.
Since a serial port is implemented by an I/O Kit driver, its driver object can be found by user space applications in the I/O Registry, as described in Chapter 5. Like all entries in the I/O Registry, the entry for a serial port driver contains a property table that can be used to obtain a descriptive name for the serial port, and a full path to the serial port's character device file. Having obtained the path to the serial port's device file, the user space application can then proceed to open and access the device, as would be done by a traditional UNIX program.
As with any application that wishes to locate a driver through the I/O Registry, the first step in finding a serial port driver is to create a matching dictionary. The role of a matching dictionary is to locate entries in the I/O Registry that meet certain criteria, and filter out all other entries. A user space process accesses a serial port not through the serial port driver itself, but rather through the driver's associated IOSerialBSDClient
class. Therefore, to find serial ports in the system, a user space process just needs to create a matching dictionary to find all IOSerialBSDClient
objects in the registry. This can be done as follows:
#include <IOKit/serial/IOSerialKeys.h>
CFMutableDictionaryRef matchingDict;
matchingDict = IOServiceMatching(kIOSerialBSDServiceValue);
To further refine the matches, the user process can add the key kIOSerialBSDTypeKey
to the matching dictionary, and limit the results to modem devices (serial drivers that created an IOModemSerialStreamSync
object) or generic serial port devices (serial drivers that created an IORS232SerialStreamSync
object). For example, to limit the matches to modem devices, a user space application would create the following matching dictionary:
matchingDict = IOServiceMatching(kIOSerialBSDServiceValue);
CFDictionarySetValue(matchingDict, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDModemType));
Having created a matching dictionary to locate the serial devices that it is interested in, the process is then able to iterate the registry for drivers that meet the criteria specified by that dictionary. All instances of IOSerialBSDClient
contain registry properties that are specific to a serial port driver, namely:
kIOTTYDeviceKey
: a CFStringRef
containing a descriptive name for the serial portkIOCalloutDeviceKey
: a CFStringRef
containing the full path to the callout character device file for the serial portkIODialinDeviceKey
: a CFStringRef
containing the full path to the dial-in character device file for the serial portTo show how these properties can be used, the code in Listing 11-9 demonstrates how to enumerate all serial devices in the system and how to open each device.
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/serial/IOSerialKeys.h>
#include <sys/param.h>
#include <fcntl.h>
#include <unistd.h>
int main (int argc, const char * argv[])
{
CFMutableDictionaryRef matchingDict;
io_iterator_t iter = 0;
io_service_t service = 0;
kern_return_t kr;
// Create a matching dictionary that will find any serial device
matchingDict = IOServiceMatching(kIOSerialBSDServiceValue);
kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &iter);
if (kr != KERN_SUCCESS)
return -1;
// Iterate over all matching objects
while ((service = IOIteratorNext(iter)) != 0)
{
CFStringRef cfDeviceName;
CFStringRef cfCalloutPath;
Char deviceName[256];
Char calloutPath[MAXPATHLEN];
Int fd;
// Get the device name
cfDeviceName = IORegistryEntryCreateCFProperty(service, CFSTR(kIOTTYDeviceKey),
kCFAllocatorDefault, 0);
CFStringGetCString(cfDeviceName, deviceName, sizeof(deviceName),
kCFStringEncodingUTF8);
CFRelease(cfDeviceName);
// Get the character device path
cfCalloutPath = IORegistryEntryCreateCFProperty(service,
CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0);
CFStringGetCString(cfCalloutPath, calloutPath, sizeof(calloutPath),
kCFStringEncodingUTF8);
CFRelease(cfCalloutPath);
// The I/O Registry object is no longer needed
IOObjectRelease(service);
// Proceed to open and use the device at "calloutPath" as usual
printf("Found device %s at path %s
", deviceName, calloutPath);
fd = open(calloutPath, O_RDWR | O_NOCTTY | O_NONBLOCK);
// Clear the O_NONBLOCK flag so subsequent I/O will block
fcntl(fd, F_SETFL, 0);
// Configure serial device with tcsetattr()
// Read and write with read() / write()
close(fd);
}
// Release the I/O Registry iterator
IOObjectRelease(iter);
return 0;
}
18.118.121.54