Section 10.7. A Programming Tour of the IO Kit s Functionality


10.7. A Programming Tour of the I/O Kit's Functionality

In this section, we will look at a variety of examples of programmatic interaction with the I/O Kit, both from user space and within the kernel.

10.7.1. Rotating a Framebuffer

The IOServiceRequestProbe() function in the I/O Kit framework can be used to request a bus to be rescanned for family-specific device changes. The function takes two arguments: an IOService object to be used to request the scan and an options mask that is interpreted by the object's family. In this example, we will use IOServiceRequestProbe() to rotate the framebuffer corresponding to a display (that is, the displayed desktop) in its entirety. We will use CGDisplayIOServicePort() to retrieve the I/O Kit service port of the displaythe port represents the IOService object of interest. The options mask is constructed based on the desired angle of rotation. Figure 1022 shows how a user-program invocation of IOServiceRequestProbe() is communicated to the appropriate familyIOGraphics in this case.

Figure 1022. Processing involved in user-program-induced framebuffer rotation


As Figure 1022 shows, the 32-bit options mask value for framebuffer rotation (and in general, for a supported framebuffer transform) consists of the constant kIOFBSetTransform in its lower 16 bits and an encoding of the desired transform in the upper 16 bits. For example, the constants kIOScaleRotate90, kIOScaleRotate180, and kIOScaleRotate270 rotate the framebuffer by 90, 180, and 270 degrees, respectively, while scaling it appropriately.

The program shown in Figure 1023 rotates the specified display by the given angle, which must be a multiple of 90 degrees. The target display is specified to the program by the display's unique ID assigned by the Quartz layer. The program's -l option can be used to list the display ID and resolution of each online display. Moreover, specifying 0 as the display ID rotates the main display.

Figure 1023. Programmatically rotating a framebuffer

// fb-rotate.c #include <getopt.h> #include <IOKit/graphics/IOGraphicsLib.h> #include <ApplicationServices/ApplicationServices.h> #define PROGNAME "fb-rotate" #define MAX_DISPLAYS 16 // kIOFBSetTransform comes from <IOKit/graphics/IOGraphicsTypesPrivate.h> // in the source for the IOGraphics family enum {     kIOFBSetTransform = 0x00000400, }; void usage(void) {     fprintf(stderr, "usage: %s -l\n"                     "       %s -d <display ID> -r <0|90|180|270>\n",                     PROGNAME, PROGNAME);     exit(1); } void listDisplays(void) {     CGDisplayErr      dErr;     CGDisplayCount    displayCount, i;     CGDirectDisplayID mainDisplay;     CGDisplayCount    maxDisplays = MAX_DISPLAYS;     CGDirectDisplayID onlineDisplays[MAX_DISPLAYS];     mainDisplay = CGMainDisplayID();     dErr = CGGetOnlineDisplayList(maxDisplays, onlineDisplays, &displayCount);     if (dErr != kCGErrorSuccess) {         fprintf(stderr, "CGGetOnlineDisplayList: error %d.\n", dErr);         exit(1);     }     printf("Display ID       Resolution\n");     for (i = 0; i < displayCount; i++) {         CGDirectDisplayID dID = onlineDisplays[i];         printf("%-16p %lux%lu %32s", dID,                CGDisplayPixelsWide(dID), CGDisplayPixelsHigh(dID),                (dID == mainDisplay) ? "[main display]\n" : "\n");     }     exit(0); } IOOptionBits angle2options(long angle) {     static IOOptionBits anglebits[] = {                (kIOFBSetTransform | (kIOScaleRotate0)   << 16),                (kIOFBSetTransform | (kIOScaleRotate90)  << 16),                (kIOFBSetTransform | (kIOScaleRotate180) << 16),                (kIOFBSetTransform | (kIOScaleRotate270) << 16)            };     if ((angle % 90) != 0) // Map arbitrary angles to a rotation reset         return anglebits[0];     return anglebits[(angle / 90) % 4]; } int main(int argc, char **argv) {     int  i;     long angle = 0;     io_service_t      service;     CGDisplayErr      dErr;     CGDirectDisplayID targetDisplay = 0;     IOOptionBits      options;     while ((i = getopt(argc, argv, "d:lr:")) != -1) {         switch (i) {         case 'd':             targetDisplay = (CGDirectDisplayID)strtol(optarg, NULL, 16);             if (targetDisplay == 0)                 targetDisplay = CGMainDisplayID();             break;         case 'l':             listDisplays();             break;         case 'r':             angle = strtol(optarg, NULL, 10);             break;         default:             break;         }     }     if (targetDisplay == 0)         usage();     options = angle2options(angle);     // Get the I/O Kit service port of the target display     // Since the port is owned by the graphics system, we should not destroy it     service = CGDisplayIOServicePort(targetDisplay);     // We will get an error if the target display doesn't support the     // kIOFBSetTransform option for IOServiceRequestProbe()     dErr = IOServiceRequestProbe(service, options);     if (dErr != kCGErrorSuccess) {         fprintf(stderr, "IOServiceRequestProbe: error %d\n", dErr);         exit(1);     }     exit(0); } $ gcc -Wall -o fb-rotate fb-rotate.c -framework IOKit \     -framework ApplicationServices $ ./fb-rotate -l Display ID       Resolution 0x4248edd        1920x1200                  [main display] 0x74880f18       1600x1200 $ ./fb-rotate -d 0x4248edd -r 90 # rotates given display by 90 degrees $ ./fb-rotate -d 0x4248edd -r 0  # restores to original

10.7.2. Accessing Framebuffer Memory

We saw how to access the contents of framebuffer memory through the diagnostics system call interface in Section 6.8.8.2. The Quartz Services function CGDisplayBaseAddress() returns the base address of a display's framebuffer. Given this address, the framebuffer memory can be read or written using the read() and write() system calls, respectively.

Quartz Services

As we saw in Chapter 2, the majority of what constitutes the windowing and graphics system of Mac OS X is collectively referred to as Quartz. The Quartz Services API provides a set of low-level window server features. In particular, display hardware can be accessed and manipulated through this API.


Figure 1024 shows a program that dumps the entire contents of a given display's framebuffer to a file. It assumes 32 bits per pixel. The contents can be converted to a viewable image using the same approach as in the screenshot-capturing example from Section 6.8.8.2. Note that the program uses the listDisplays() function from Figure 1023.

Figure 1024. Accessing framebuffer memory

// fb-dump.c #include <getopt.h> #include <IOKit/graphics/IOGraphicsLib.h> #include <ApplicationServices/ApplicationServices.h> #define PROGNAME          "fb-dump" #define DUMPFILE_TMPDIR   "/tmp/" #define DUMPFILE_TEMPLATE "fb-dump.XXXXXX" ... int main(int argc, char * argv[]) {     int      i, saveFD = -1;     char     template[] = DUMPFILE_TMPDIR DUMPFILE_TEMPLATE;     uint32_t width, height, rowBytes, rowUInt32s, *screen;     CGDirectDisplayID targetDisplay = 0;     // populate targetDisplay as in Figure 1023     // use listDisplays() from Figure 1023     ...     screen = (uint32_t *)CGDisplayBaseAddress(targetDisplay);     rowBytes = CGDisplayBytesPerRow(targetDisplay);     rowUInt32s = rowBytes / 4;     width = CGDisplayPixelsWide(targetDisplay);     height = CGDisplayPixelsHigh(targetDisplay);     if ((saveFD = mkstemp(template)) < 0) {         perror("mkstemps");         exit(1);     }     for (i = 0; i < height; i++)         write(saveFD, screen + i * rowUInt32s, width * sizeof(uint32_t));     close(saveFD);     exit(0); }

10.7.3. Retrieving the List of Firmware Variables

In this example, we will contact the I/O Kit to retrieve and display the list of firmware variables. As we saw in Section 4.10.3, the options device in the Open Firmware device tree contains NVRAM-resident system configuration variables. The contents of this device are available in the I/O Registry as the properties of the entry called options in the Device Tree plane. Similarly, on EFI-based Macintosh systems, EFI NVRAM variables are available through the options property. We can use IORegistryEntryFromPath() to look up the registry entry given its path. Once we have the entry, we can use IORegistryEntryCreateCFProperties() to construct a Core Foundation dictionary from the entry's properties. The program shown in Figure 1025 performs these steps. Additionally, it displays an XML representation of the dictionary's contents.

Figure 1025. Retrieving the list of firmware variables from the I/O Registry

// lsfirmware.c #include <unistd.h> #include <IOKit/IOKitLib.h> #include <CoreFoundation/CoreFoundation.h> #define PROGNAME "lsfirmware" void printDictionaryAsXML(CFDictionaryRef dict) {     CFDataRef xml = CFPropertyListCreateXMLData(kCFAllocatorDefault,                                                 (CFPropertyListRef)dict);     if (xml) {         write(STDOUT_FILENO, CFDataGetBytePtr(xml), CFDataGetLength(xml));         CFRelease(xml);     } } int main(void) {     io_registry_entry_t    options;     CFMutableDictionaryRef optionsDict;     kern_return_t          kr = KERN_FAILURE;     options = IORegistryEntryFromPath(kIOMasterPortDefault,                                       kIODeviceTreePlane ":/options");     if (options) {         kr = IORegistryEntryCreateCFProperties(options, &optionsDict, 0, 0);         if (kr == KERN_SUCCESS) {             printDictionaryAsXML(optionsDict);             CFRelease(optionsDict);         }         IOObjectRelease(options);     }     if (kr != KERN_SUCCESS)         fprintf(stderr, "failed to retrieve firmware variables\n");     exit(kr); } $ gcc -Wall -o lsfirmware lsfirmware.c -framework IOKit \     -framework CoreFoundation $ ./lsfirmware # PowerPC ...         <key>boot-command</key>         <string>mac-boot</string>         <key>boot-device</key>         <string>hd:,\\:tbxi</string> ... $ ./lsfirmware # x86 ...         <key>SystemAudioVolume</key>         <data>         cg==         </data>         <key>efi-boot-device</key>         <data> ...

10.7.4. Retrieving Information about Loaded Kernel Extensions

We can retrieve information about loaded kernel extensionsa list of kmod_info_t structuresusing the kmod_get_info() routine that is part of the Mach host interface. Figure 1026 shows a program that retrieves and displays this information.

Figure 1026. Retrieving information about loaded kernel extensions

// lskmod.c #include <stdio.h> #include <mach/mach.h> int main(void) {     kern_return_t           kr;     kmod_info_array_t       kmods;     mach_msg_type_number_t  kmodBytes = 0;     int                     kmodCount = 0;     kmod_info_t            *kmodp;     mach_port_t             host_port = mach_host_self();     kr = kmod_get_info(host_port, (void *)&kmods, &kmodBytes);     (void)mach_port_deallocate(mach_task_self(), host_port);     if (kr != KERN_SUCCESS) {         mach_error("kmod_get_info:", kr);         return kr;     }     for (kmodp = (kmod_info_t *)kmods; kmodp->next; kmodp++, kmodCount++) {         printf("%5d %4d %-10p %-10p %-10p %s (%s)\n",                kmodp->id,                kmodp->reference_count,                (void *)kmodp->address,                (void *)kmodp->size,                (void *)(kmodp->size - kmodp->hdr_size),                kmodp->name,                kmodp->version);     }     vm_deallocate(mach_task_self(), (vm_address_t)kmods, kmodBytes);     return kr; } $ gcc -Wall -o lskmod lskmod.c $ ./lskmod ...    27   0 0x761000  0x5000   0x4000   com.apple.driver.AppleRTC (1.0.2)    26   0 0x86a000  0x3000   0x2000   com.apple.driver.AppleHPET (1.0.0d1)    25   0 0x7f6000  0x4000   0x3000   com.apple.driver.AppleACPIButtons (1.0.3)    24   0 0x7fa000  0x4000   0x3000   com.apple.driver.AppleSMBIOS (1.0.7) ...

10.7.5. Retrieving Accelerometer Data from the Sudden Motion Sensor

Apple added a feature called the Sudden Motion Sensor (SMS)[11] to the PowerBook line of computers in early 2005.[12] Eventually, the feature was added to all Apple notebook computers. The sensor is used as part of a mechanism for attempting to prevent data loss by parking the heads of an active disk drive after detecting a sudden motion.

[11] The SMS is also called the Mobile Motion Module or the Apple Motion Sensor (AMS).

[12] IBM had been offering a conceptually identical feature in ThinkPad notebooks before Apple introduced the SMS. The ThinkPad sensor can be programmatically accessed as well.

The Background behind the Sudden Motion Sensor

In modern disk drives, the "flying height" between a platter and a head is very small. This increases the possibility of a disturbed head colliding with a platter. Modern drives support parking their heads in a safe position under various circumstances. In particular, heads are automatically parked when the system is powered off or is asleep. SMS adds the ability to park the heads in the event of an accidental drop, strong vibrations, or other accelerated movement. The mechanism works by using a tri-axis accelerometer to detect sudden motion. When the threshold for emergency action is reachedsay, because of a shock or free fallan interrupt is generated. In processing this interrupt, the SMS driver (such as IOI2CMotionSensor.kext, PMUMotionSensor.kext, or SMCMotionSensor.kext) may send a "park" command to the disk drive, thereby reducing the possibility of damage to the drive on impact. Conversely, when the SMS detects that the computer is once again level and not under acceleration, it unlocks the drive heads so that the system can continue to use the disk normally.

On some models, the accelerometer is an integrated feature of the main logic boardspecifically, an Inter-Integrated Circuit (I2C) device[13] that is not tied to a specific disk drive. Typically, such an accelerometer uses a silicon sensor based on integrated microelectromechanical systems (iMEMS) technology. Acceleration or inclination causes an electrical property (say, capacitance) of the sensor to be altered. The sensor's interface can then translate these tiny changes to present them as acceleration readings.

It is interesting to note that depending on your working environment, the default sensitivity of the SMS may be too aggressive. For example, loud musicperhaps music with rather high bass and the consequential vibrationscan activate SMS undesirably. If the computer in question is itself involved in the generation or recording of such music, this may cause unacceptable interruptions. The mechanism can be disabled using the pmset power management configuration utility.


[13] Philips developed the I2C bus in the early 1980s. I2C is a multimaster control bus using which various ICs in a system can communicate with each other. It uses only two control lines and has a software-defined protocol.

The SMS driver implements an I/O Kit user client, which allows several types of operations to be performed from user space, such as:

  • Querying miscellaneous information such as vendor, version, and status

  • Retrieving and setting sensitivity

  • Resetting the hardware

  • Retrieving orientation values

The orientation values consist of a triplet (x, y, z) that is related to the acceleration vector acting on the computer. The individual components of this vector change when external acceleration is applied to the computer (such as in a sideways sudden motion) or when the computer is rotated (thereby changing the angle of the gravity vector). Note the following points regarding the vector's components.

  • The value of x is zero when the computer's bottom is parallel to the ground and it is not under lateral acceleration along its long edge. Rotating the computer's base around an axis parallel to its short edge changes the value of x.

  • The value of y is zero when the computer's bottom is parallel to the ground and it is not under lateral acceleration along its short edge. Rotating the computer's base around an axis parallel to its long edge changes the value of y.

  • The value of z changes when the computer is rotated such that its bottom does not remain in the horizontal plane.

  • A perfectly calibrated SMS accelerometer would read (0, 0, 0) when the computer is in free fall in a vacuum.

In practice, an SMS unit may not be calibrated perfectly, and different units may have different calibrations. For example, the x and y values reported by the SMS hardware may not be zeros when the computer is parallel to the ground. Moreover, depending on the surroundings and the configured sensitivity of the hardware, minor fluctuations may be seen even when there is no perceptible movement of the computer.

Let us now see how to retrieve orientation data by communicating with the SMS driver. To invoke a user client method, we need to know the method's identifier and the types of its parameters.

$ cd /System/Library/Extensions/IOI2CMotionSensor.kext/Contents/MacOS $ nm IOI2CMotionSensor | c++filt ... 00003934 T IOI2CMotionSensor::getOrientationUC(paramStruct*, paramStruct*,                                                unsigned long, unsigned long*) ...


Since the getOrientationUC() method is exported by the user client, its address0x3934 in this casemust also appear in the array of IOExternalMethod structures within the driver executable. The position of the structure within the array will give us the index of the method, whereas the structure's contents will indicate the sizes of the structures we need to provide while calling the method.

// iokit/IOKit/IOUserClient.h struct IOExternalMethod {     IOService    *object;     IOMethod      func;     IOOptionBits  flags;     IOByteCount   count0;     IOByteCount   count1; };


Figure 1027 shows the contents of the IOExternalMethod structure corresponding to getOrientationUC(). The index of this particular structure in the method array can be different based on the driver version. Although not shown in the figure, in this case (version 1.0.3 of both IOI2CMotionSensor and PMUMotionSensor), the index is 21. The index is 5 for SMCMotionSensor.

Figure 1027. Relevant contents of the IOExternalMethod structure corresponding to getOrientationUC()


Note in Figure 1027 that the value of flags is kIOUCStructIStructO, which means that the method has one structure input parameter and one structure output parameter. The count0 and count1 values, which are 60 bytes each (40 bytes for SMCMotionSensor), represent the sizes of the input and output structures, respectively. We can invoke such a method using the IOConnectMethodStructureIStructureO() function from the I/O Kit framework, which provides this function and other similar functions (such as IOConnectMethodScalarIStructureO()) to pass untyped data across the user-kernel boundary.

kern_return_t IOConnectMethodStructureIStructureO(     io_connect_t connect,             // acquired by calling IOServiceOpen()     unsigned int index,               // index for kernel-resident method     IOItemCount  structureInputSize,  // size of the input struct parameter     IOByteCount *structureOutputSize, // size of the output structure (out)     void        *inputStructure,      // pointer to the input structure     void        *ouputStructure);     // pointer to the output structure


In this case, we are interested only in the output structure, the first three bytes of which contain the x, y, and z values.[14] We can define the output structure as follows:

[14] Note that these values are not raw acceleration valuesthey have been processed before we receive them. Nevertheless, they will change in direct correspondence to the computer's movement.

typedef struct {     char x;     char y;     char z;     // filler space to make size of the structure at least 60 bytes     pad[57]; } SuddenMotionSensorData_t;


First, we need to create a connection to the appropriate IOService instance. Figure 1028 shows the function that looks up the service and requests a connection to it.

Figure 1028. Opening a connection to the motion sensor service object

static io_connect_t dataPort = 0; kern_return_t sms_initialize(void) {     kern_return_t   kr;     CFDictionaryRef classToMatch;     io_service_t    service;     // create a matching dictionary given the class name, which depends on     // hardware: "IOI2CMotionSensor", "PMUMotionSensor", "SMCMotionSensor" ...     classToMatch = IOServiceMatching(kTargetIOKitClassName);     // look up the IOService object (must already be registered)     service = IOServiceGetMatchingService(kIOMasterPortDefault, classToMatch);     if (!service)         return KERN_FAILURE;     // create a connection to the IOService object     kr = IOServiceOpen(service,          // the IOService object                        mach_task_self(), // the task requesting the connection                        0,                // type of connection                        &dataPort);       // connection handle     IOObjectRelease(service);     return kr; }

Note that for this section's technique to work on a particular model of an SMS-equipped Apple computer, the I/O Service class name, the user client method index, and the sizes of the input/output parameters must be appropriate for the SMS driver being used on that computer.


Given a connection handle to the IOService instance, we can invoke the getOrientationUC() method as shown in Figure 1029.

Figure 1029. Invoking a user client method given an IOService connection handle

static const int getOrientationUC_methodID = 21; kern_return_t sms_getOrientation(MotionSensorData_t *data) {     kern_return_t      kr;     IOByteCount        size = 60;     MotionSensorData_t unused_struct_in = { 0 };     kr = IOConnectMethodStructureIStructureO(dataPort,                                              getOrientationUC_methodID,                                              size,                                              &size,                                              &unused_struct_in,                                              data);     return kr; }

Note that the orientation data received from the SMS driver can be used to map the physical tilting of the computer to mouse or keyboard input events. Such mapping can be readily used for purposes such as human input for games, multidirectional scrolling, and panning across large maps.

10.7.6. Listing PCI Devices

Since the I/O Registry maintains information about all devices in the system, it is rather straightforward to look up specific devices and their properties based on a variety of search criteria. Figure 1030 shows a program that lists all PCI devices in the system, along with the path of each device in the Service plane.

Figure 1030. Listing PCI devices in the system

// lspci.c #include <stdio.h> #include <IOKit/IOKitLib.h> int main(void) {     kern_return_t kr;     io_iterator_t pciDeviceList;     io_service_t  pciDevice;     io_name_t     deviceName;     io_string_t   devicePath;     // get an iterator for all PCI devices     if (IOServiceGetMatchingServices(kIOMasterPortDefault,                                      IOServiceMatching("IOPCIDevice"),                                      &pciDeviceList) != KERN_SUCCESS)         return 1;     while ((pciDevice = IOIteratorNext(pciDeviceList))) {         kr = IORegistryEntryGetName(pciDevice, deviceName);         if (kr != KERN_SUCCESS)             goto next;         kr = IORegistryEntryGetPath(pciDevice, kIOServicePlane, devicePath);         if (kr != KERN_SUCCESS)             goto next;         // don't print the plane name prefix in the device path         printf("%s (%s)\n", &devicePath[9], deviceName); next:         IOObjectRelease(pciDevice);     }     return kr; } $ gcc -Wall -o lspci lspci.c -framework IOKit -framework CoreFoundation $ ./lspci # PowerPC :/MacRISC4PE/pci@0,f0000000/AppleMacRiscAGP/ATY,WhelkParent@10 (ATY,WhelkParent) :/MacRISC4PE/ht@0,f2000000/AppleMacRiscHT/pci@1 (pci) ... :/MacRISC4PE/ht@0,f2000000/AppleMacRiscHT/pci@4/IOPCI2PCIBridge/usb@B,2 (usb) :/MacRISC4PE/ht@0,f2000000/AppleMacRiscHT/pci@5 (pci) :/MacRISC4PE/ht@0,f2000000/AppleMacRiscHT/pci@5/IOPCI2PCIBridge/ata-6@D (ata-6) ... $ ./lspci # x86 :/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/GFX0@2 (GFX0) :/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/HDEF@1B (HDEF) ...

10.7.7. Retrieving the Computer's Serial Number and Model Information

The program shown in Figure 1031 communicates with the I/O Registry to retrieve the computer's serial number and model information, both of which are maintained as properties of the I/O Registry entry corresponding to the Platform Expert.

Figure 1031. Retrieving the computer's serial number and model information

// lsunitinfo.c #include <IOKit/IOKitLib.h> #include <CoreFoundation/CoreFoundation.h> int main(void) {     kern_return_t kr;     io_service_t  pexpert;     CFStringRef   serial, model;     // get the Platform Expert object     pexpert = IOServiceGetMatchingService(kIOMasterPortDefault,                   IOServiceMatching("IOPlatformExpertDevice"));     if (!pexpert)         return KERN_FAILURE;     serial = IORegistryEntryCreateCFProperty(                  pexpert, CFSTR(kIOPlatformSerialNumberKey),                  kCFAllocatorDefault,kNilOptions);     if (serial) {         // note that this will go to stderr         CFShow(serial);         CFRelease(serial);     }     model = IORegistryEntryCreateCFProperty(                 pexpert, CFSTR("model"), kCFAllocatorDefault, kNilOptions);     if (model) {         printf("%s\n", CFDataGetBytePtr((CFDataRef)model));         CFRelease(model);     }     if (pexpert)         IOObjectRelease(pexpert);     return kr; } $ gcc -Wall -o lsunitinfo lsunitinfo.c -framework IOKit -framework CoreFoundation $ ./lsunitinfo G84XXXXXXPS PowerMac7,3

10.7.8. Retrieving Temperature Sensor Readings

As power and thermal management have become integral parts of computer system design, it is common to find several types of hardware sensors in a modern computer system. Depending on the model, an Apple computer may contain temperature sensors, voltage and current sensors, fan speed sensors, and so on. In certain types of systems, such as MacRISC4-based systems, the concept of a platform plug-in is used along with the Platform Expert. Whereas the Platform Expert is specific to system architecture, a platform plug-in is specific to a particular platform, which depends on the motherboard and usually changes more frequently than system architecture. In particular, the plug-in usually performs thermal management, which includes monitoring the various sensors and, based on their values, controlling processor and fan speeds. The available platform plug-ins reside within the AppleMacRISC4PE kernel extension bundle.

$ cd /System/Library/Extensions/AppleMacRISC4PE.kext/Contents/PlugIns $ ls IOPlatformPluginFamily.kext             PowerMac12_1_ThermalProfile.kext MacRISC4_PlatformPlugin.kext            PowerMac7_2_PlatformPlugin.kext PBG4_PlatformPlugin.kext                PowerMac8_1_ThermalProfile.kext PBG4_ThermalProfile.kext                PowerMac9_1_ThermalProfile.kext PowerMac11_2_PlatformPlugin.kext        RackMac3_1_PlatformPlugin.kext PowerMac11_2_ThermalProfile.kext        SMU_Neo2_PlatformPlugin.kext PowerMac12_1_PlatformPlugin.kext


If a system uses a platform plug-in, the properties (including current values) of all hardware sensors in the system are available in the I/O Registry as the IOHWSensors property of the system-specific platform plug-in class, which inherits from IOPlatformPlugin. A sensor is abstracted in the platform plug-in by an instance of the IOPlatformSensor class. Each hardware sensor's driver is an IOHWSensor object.

We can retrieve the readings of temperature sensors in a system either by looking up the IOHWSensors property of the platform plug-in (if there is one) or by looking up each IOHWSensor object whose type is temperature. The latter approach is more generic because it will work even if a system has no platform plug-in. Figure 1032 shows a program that uses this approach to display the locations and values of temperature sensors in a system.

Figure 1032. Retrieving temperature sensor readings

// lstemperature.c #include <unistd.h> #include <IOKit/IOKitLib.h> #include <CoreFoundation/CoreFoundation.h> #define kIOPPluginCurrentValueKey "current-value" // current measured value #define kIOPPluginLocationKey     "location"      // readable description #define kIOPPluginTypeKey         "type"          // sensor/control type #define kIOPPluginTypeTempSensor  "temperature"   // desired type value // macro to convert sensor temperature format (16.16) to integer (Celsius) #define SENSOR_TEMP_FMT_C(x)(double)((x) >> 16) // macro to convert sensor temperature format (16.16) to integer (Fahrenheit) #define SENSOR_TEMP_FMT_F(x) \     (double)((((double)((x) >> 16) * (double)9) / (double)5) + (double)32) void printTemperatureSensor(const void *sensorDict, CFStringEncoding encoding) {     SInt32      currentValue;     CFNumberRef sensorValue;     CFStringRef sensorType, sensorLocation;     if (!CFDictionaryGetValueIfPresent((CFDictionaryRef)sensorDict,                                        CFSTR(kIOPPluginTypeKey),                                        (void *)&sensorType))         return;     if (CFStringCompare(sensorType, CFSTR(kIOPPluginTypeTempSensor), 0) !=                         kCFCompareEqualTo) // we handle only temperature sensors         return;     sensorLocation = CFDictionaryGetValue((CFDictionaryRef)sensorDict,                                           CFSTR(kIOPPluginLocationKey));     sensorValue = CFDictionaryGetValue((CFDictionaryRef)sensorDict,                                        CFSTR(kIOPPluginCurrentValueKey));     (void)CFNumberGetValue(sensorValue, kCFNumberSInt32Type,                            (void *)&currentValue);     printf("%24s %7.1f C %9.1f F\n",            // see documentation for CFStringGetCStringPtr() caveat            CFStringGetCStringPtr(sensorLocation, encoding),            SENSOR_TEMP_FMT_C(currentValue),            SENSOR_TEMP_FMT_F(currentValue)); } int main(void) {     kern_return_t          kr;     io_iterator_t          io_hw_sensors;     io_service_t           io_hw_sensor;     CFMutableDictionaryRef sensor_properties;     CFStringEncoding       systemEncoding = CFStringGetSystemEncoding();     kr = IOServiceGetMatchingServices(kIOMasterPortDefault,              IOServiceNameMatching("IOHWSensor"), &io_hw_sensors);     while ((io_hw_sensor = IOIteratorNext(io_hw_sensors))) {         kr = IORegistryEntryCreateCFProperties(io_hw_sensor, &sensor_properties,                  kCFAllocatorDefault, kNilOptions);         if (kr == KERN_SUCCESS)             printTemperatureSensor(sensor_properties, systemEncoding);         CFRelease(sensor_properties);         IOObjectRelease(io_hw_sensor);     }     IOObjectRelease(io_hw_sensors);     exit(kr); } $ gcc -Wall -o lstemperature -framework IOKit \     -framework CoreFoundation $ sudo hwprefs machine_type # Power Mac G5 Dual 2.5 GHz PowerMac7,3 $ ./lstemperature                DRIVE BAY    25.0 C      77.0 F                 BACKSIDE    44.0 C     111.2 F              U3 HEATSINK    68.0 C     154.4 F         CPU A AD7417 AMB    49.0 C     120.2 F         CPU B AD7417 AMB    47.0 C     116.6 F $ sudo hwprefs machine_type # Xserve G5 Dual 2.0 GHz RackMac3,1 $ ./lstemperature        SYS CTRLR AMBIENT    35.0 C      95.0 F       SYS CTRLR INTERNAL    47.0 C     116.6 F         CPU A AD7417 AMB    28.0 C      82.4 F         CPU B AD7417 AMB    27.0 C      80.6 F                PCI SLOTS    26.0 C      78.8 F              CPU A INLET    19.0 C      66.2 F              CPU B INLET    20.0 C      68.0 F

The IOHWControls property of the platform plug-in contains, among other things, the current RPM readings of the fans in the system. The same information can also be obtained from the control-info property of the AppleFCU class instance, which represents a fan control unit. The fan control unit driver publishes control-info as an array containing data on all controls it is responsible for.

10.7.9. Retrieving MAC Addresses of Ethernet Interfaces

Figure 1033 shows a program that retrieves the MAC addresses of all Ethernet interfaces in the system. It iterates over the list of all instances of the IOEthernetInterface class, whose parent classan instance of IOEthernetControllercontains the MAC address as one of its properties (kIOMACAddress, which is defined as IOMACAddress). Note that an IOEthernetInterface instance contains various other interesting aspects of the network interface, such as its BSD name, information about active packet filters, and several types of statistics.

Figure 1033. Retrieving the MAC addresses of Ethernet interfaces in the system

// lsmacaddr.c #include <IOKit/IOKitLib.h> #include <IOKit/network/IOEthernetInterface.h> #include <IOKit/network/IOEthernetController.h> #include <CoreFoundation/CoreFoundation.h> typedef UInt8 MACAddress_t[kIOEthernetAddressSize]; void printMACAddress(MACAddress_t MACAddress) {     int i;     for (i = 0; i < kIOEthernetAddressSize - 1; i++)         printf("%02x:", MACAddress[i]);     printf("%x\n", MACAddress[i]); } int main(void) {     kern_return_t          kr;     CFMutableDictionaryRef classToMatch;     io_iterator_t          ethernet_interfaces;     io_object_t            ethernet_interface, ethernet_controller;     CFTypeRef              MACAddressAsCFData;     classToMatch = IOServiceMatching(kIOEthernetInterfaceClass);     kr = IOServiceGetMatchingServices(kIOMasterPortDefault, classToMatch,                                       &ethernet_interfaces);     if (kr != KERN_SUCCESS)         return kr;     while ((ethernet_interface = IOIteratorNext(ethernet_interfaces))) {         kr = IORegistryEntryGetParentEntry(ethernet_interface, kIOServicePlane,                                            &ethernet_controller);         if (kr != KERN_SUCCESS)             goto next;         MACAddressAsCFData = IORegistryEntryCreateCFProperty(                                  ethernet_controller,                                  CFSTR(kIOMACAddress),                                  kCFAllocatorDefault, 0);         if (MACAddressAsCFData) {             MACAddress_t address;             CFDataGetBytes(MACAddressAsCFData,                            CFRangeMake(0, kIOEthernetAddressSize), address);             CFRelease(MACAddressAsCFData);             printMACAddress(address);         }         IOObjectRelease(ethernet_controller); next:         IOObjectRelease(ethernet_interface);     }     IOObjectRelease(ethernet_interfaces);     return kr; } $ gcc -Wall -o lsmacaddr lsmacaddr.c -framework IOKit \     -framework CoreFoundation $ ./lsmacaddr 00:0d:xx:xx:xx:xx 00:0d:xx:xx:xx:xx

10.7.10. Implementing an Encrypted Disk Filter Scheme

In this example, we will create a mass-storage filter scheme driver that implements transparent encryption at the device level. The driver will facilitate encrypted volumes wherein all data (both user data and file system data) will be encrypted on the storage medium, but mounting such a volume will allow us to access it normally. We will assume familiarity with the concepts described in Apple's technical document titled "Mass Storage Device Driver Programming Guide." As discussed in this document, a filter scheme driver inherits from the IOStorage class and logically sits between two media objects, each of which is an instance of the IOMedia class. The driver allows mapping of one or more media objects to one or more different media objects. Our encryption filter is an example of a one-to-one mapping. A partition scheme driver maps one media object (say, representing a whole disk) to many media objects (each representing a partition on the disk). Conversely, a RAID scheme maps multiple media objects (RAID members) to a single media object.

An important consideration while writing a filter scheme driver is the specification of a media object's properties that the filter driver will match against. The set of target properties is specified in the filter scheme driver's personality. Examples of IOMedia properties include whether the media is ejectable, whether it is writable, the media's preferred block size in bytes, the media's entire size in bytes, the media's BSD device node name, and the media's content description (or content hint, as specified when the media object was created). In this example, we will arrange for our filter scheme driver to match all IOMedia objects whose content description is osxbook_HFSthis way, it will not inadvertently match existing volumes. To test the driver, we will explicitly create a volume on a disk image.

Let us call our driver SimpleCryptoDisk. We will begin with an Xcode project template for I/O Kit drivers. Figure 1034 shows the personality and dependency specifications from the driver's Info.plist file. Note that the personality includes a content-hint string.

Figure 1034. Personality and dependency list for the SimpleCryptoDisk I/O Kit driver

        ...         <key>IOKitPersonalities</key>         <dict>                 <key>SimpleCryptoDisk</key>                 <dict>                         <key>CFBundleIdentifier</key>                         <string>com.osxbook.driver.SimpleCryptoDisk</string>                         <key>Content Hint</key>                         <string>osxbook_HFS</string>                         <key>IOClass</key>                         <string>com_osxbook_driver_SimpleCryptoDisk</string>                         <key>IOMatchCategory</key>                         <string>IOStorage</string>                         <key>IOProviderClass</key>                         <string>IOMedia</string>                 </dict>         </dict>         <key>OSBundleLibraries</key>         <dict>                 <key>com.apple.iokit.IOStorageFamily</key>                 <string>1.5</string>                 <key>com.apple.kpi.iokit</key>                 <string>8.0.0</string>                 <key>com.apple.kpi.libkern</key>                 <string>8.0.0</string>         </dict> </dict> </plist>

Since the data is to be stored encrypted on disk, we will need to implement a read() method that performs decryption and a write() method that performs encryption. Both these methods are asynchronouswhen the I/O completes, the caller must be notified using the specified completion action. As we will shortly see, the asynchrony somewhat complicates the implementation of these methods because our driver must substitute its own completion action in place of the caller's actions, which it must eventually invoke. Figure 1035 shows the header file (SimpleCryptoDisk.h) for the driver.

Figure 1035. Header file for the SimpleCryptoDisk I/O Kit driver

// SimpleCryptoDisk.h #include <IOKit/storage/IOMedia.h> #include <IOKit/storage/IOStorage.h> class com_osxbook_driver_SimpleCryptoDisk : public IOStorage {     OSDeclareDefaultStructors(com_osxbook_driver_SimpleCryptoDisk) protected:         IOMedia *_filteredMedia;         virtual void free(void);         virtual bool handleOpen(IOService    *client,                                 IOOptionBits  options,                                 void         *access);         virtual bool handleIsOpen(const IOService *client) const;         virtual void handleClose(IOService *client, IOOptionBits options); public:         virtual bool init(OSDictionary *properties = 0);         virtual bool start(IOService *provider);         virtual void read(IOService          *client,                           UInt64              byteStart,                           IOMemoryDescriptor *buffer,                           IOStorageCompletion completion);         virtual void write(IOService          *client,                            UInt64              byteStart,                            IOMemoryDescriptor *buffer,                            IOStorageCompletion completion);         virtual IOReturn synchronizeCache(IOService *client);         virtual IOMedia *getProvider() const; };

Figure 1036 shows the driver's source (SimpleCryptoDisk.cpp). Besides relatively trivial method implementations that simply forward the invocation to the provider's methods, we implement the following important methods and functions.

  • com_osxbook_driver_SimpleCryptoDisk::start() initializes and publishes a new media object. Note that our filter scheme driver matches against a content hint of osxbook_HFS but publishes a media object with a content hint of Apple_HFS.

  • com_osxbook_driver_SimpleCryptoDisk::read() reads data from storage. Once the I/O finishes, the driver postprocesses the data read by decrypting it. We use a structure of type SimpleCryptoDiskContext to hold the context, including the caller's completion routine, for a read or write operation.

  • com_osxbook_driver_SimpleCryptoDisk::write() writes data to storage. The driver preprocesses the data to encrypt it.

  • fixBufferUserRead() is the encryption routine, where encryption is simply a logical NOT operation.

  • fixBufferUserWrite() is the decryption routine, where decryption is again a logical NOT operation.

  • SCDReadWriteCompletion() is the driver's completion routine. We replace the caller's completion routine for both reads and writes. It is clear that we cannot decrypt until a read completes. In the case of writes, we do not encrypt the caller's data buffer in placewe allocate a new data buffer for encryption and wrap it in two IOMemoryDescriptor instances: one with a direction of kIODirectionIn (used while encrypting) and another with a direction of kIODirectionOut (passed to the provider's write() method).

Figure 1036. Source for the SimpleCryptoDisk I/O Kit driver

// SimpleCryptoDisk.cpp #include <IOKit/assert.h> #include <IOKit/IOLib.h> #include "SimpleCryptoDisk.h" #define super IOStorage OSDefineMetaClassAndStructors(com_osxbook_driver_SimpleCryptoDisk, IOStorage) // Context structure for our read/write completion routines typedef struct {     IOMemoryDescriptor *buffer;     IOMemoryDescriptor *bufferRO;     IOMemoryDescriptor *bufferWO;     void               *memory;     vm_size_t           size;     IOStorageCompletion completion; } SimpleCryptoDiskContext; // Internal functions static void fixBufferUserRead(IOMemoryDescriptor *buffer); static void fixBufferUserWrite(IOMemoryDescriptor *bufferR,                                IOMemoryDescriptor *bufferW); static void SCDReadWriteCompletion(void *target, void *parameter,                                    IOReturn status, UInt64 actualByteCount); bool com_osxbook_driver_SimpleCryptoDisk::init(OSDictionary *properties) {     if (super::init(properties) == false)         return false;     _filteredMedia = 0;     return true; } void com_osxbook_driver_SimpleCryptoDisk::free(void) {     if (_filteredMedia)         _filteredMedia->release();     super::free(); } bool com_osxbook_driver_SimpleCryptoDisk::start(IOService *provider) {     IOMedia *media = (IOMedia *)provider;     assert(media);     if (super::start(provider) == false)         return false;     IOMedia *newMedia = new IOMedia;     if (!newMedia)         return false;     if (!newMedia->init(             0,                              // media offset in bytes             media->getSize(),               // media size in bytes             media->getPreferredBlockSize(), // natural block size in bytes             media->isEjectable(),           // is media ejectable?             false,                          // is it the whole disk?             media->isWritable(),            // is media writable?             "Apple_HFS")) {                 // hint of media's contents         newMedia->release();         newMedia = 0;         return false;     }     UInt32 partitionID = 1;     char name[32];     // Set a name for this partition.     sprintf(name, "osxbook_HFS %ld", partitionID);     newMedia->setName(name);     // Set a location value (partition #) for this partition.     char location[32];     sprintf(location, "%ld", partitionID);     newMedia->setLocation(location);     _filteredMedia = newMedia;     newMedia->attach(this);     newMedia->registerService();     return true; } bool com_osxbook_driver_SimpleCryptoDisk::handleOpen(IOService   *client,                                                 IOOptionBits options,                                                 void        *argument) {     return getProvider()->open(this, options, (IOStorageAccess)argument); } bool com_osxbook_driver_SimpleCryptoDisk::handleIsOpen(const IOService *client) const {     return getProvider()->isOpen(this); } void com_osxbook_driver_SimpleCryptoDisk::handleClose(IOService   *client,                                                  IOOptionBits options) {     getProvider()->close(this, options); } IOReturn com_osxbook_driver_SimpleCryptoDisk::synchronizeCache(IOService *client) {     return getProvider()->synchronizeCache(this); } IOMedia * com_osxbook_driver_SimpleCryptoDisk::getProvider(void) const {     return (IOMedia *)IOService::getProvider(); } void com_osxbook_driver_SimpleCryptoDisk::read(IOService          *client,                                           UInt64              byteStart,                                           IOMemoryDescriptor *buffer,                                           IOStorageCompletion completion) {     SimpleCryptoDiskContext *context =         (SimpleCryptoDiskContext *)IOMalloc(sizeof(SimpleCryptoDiskContext));     context->buffer     = buffer;     context->bufferRO   = NULL;     context->bufferWO   = NULL;     context->memory     = NULL;     context->size       = (vm_size_t)0;     // Save original completion function and insert our own.     context->completion  = completion;     completion.action    = (IOStorageCompletionAction)&SCDReadWriteCompletion;     completion.target    = (void *)this;     completion.parameter = (void *)context;     // Hand over to the provider.     return getProvider()->read(this, byteStart, buffer, completion); } void com_osxbook_driver_SimpleCryptoDisk::write(IOService          *client,                                            UInt64              byteStart,                                            IOMemoryDescriptor *buffer,                                            IOStorageCompletion completion) {     // The buffer passed to this function would have been created with a     // direction of kIODirectionOut. We need a new buffer that is created     // with a direction of kIODirectionIn to store the modified contents     // of the original buffer.     // Determine the original buffer's length.     IOByteCount length = buffer->getLength();     // Allocate memory for a new (temporary) buffer. Note that we would be     // passing this modified buffer (instead of the original) to our     // provider's write function. We need a kIODirectionOut "pointer",     // a new memory descriptor referring to the same memory, that we shall     // pass to the provider's write function.     void *memory = IOMalloc(length);     // We use this descriptor to modify contents of the original buffer.     IOMemoryDescriptor *bufferWO =         IOMemoryDescriptor::withAddress(memory, length, kIODirectionIn);     // We use this descriptor as the buffer argument in the provider's write().     IOMemoryDescriptor *bufferRO =         IOMemoryDescriptor::withSubRange(bufferWO, 0, length, kIODirectionOut);     SimpleCryptoDiskContext *context =         (SimpleCryptoDiskContext *)IOMalloc(sizeof(SimpleCryptoDiskContext));     context->buffer      = buffer;     context->bufferRO    = bufferRO;     context->bufferWO    = bufferWO;     context->memory      = memory;     context->size        = (vm_size_t)length;     // Save the original completion function and insert our own.     context->completion  = completion;     completion.action    = (IOStorageCompletionAction)&SCDReadWriteCompletion;     completion.target    = (void *)this;     completion.parameter = (void *)context;     // Fix buffer contents (apply simple "encryption").     fixBufferUserWrite(buffer, bufferWO);     // Hand over to the provider.     return getProvider()->write(this, byteStart, bufferRO, completion); } static void fixBufferUserRead(IOMemoryDescriptor *buffer) {     IOByteCount i, j;     IOByteCount length, count;     UInt64      byteBlock[64];     assert(buffer);     length = buffer->getLength();     assert(!(length % 512));     length /= 512;     buffer->prepare(kIODirectionOutIn);     for (i = 0; i < length; i++) {         count = buffer->readBytes(i * 512, (UInt8 *)byteBlock, 512);         for (j = 0; j < 64; j++)             byteBlock[j] = ~(byteBlock[j]);         count = buffer->writeBytes(i * 512, (UInt8 *)byteBlock, 512);     }     buffer->complete();     return; } static void fixBufferUserWrite(IOMemoryDescriptor *bufferR, IOMemoryDescriptor *bufferW) {     IOByteCount i, j;     IOByteCount length, count;     UInt64      byteBlock[64];     assert(bufferR);     assert(bufferW);     length = bufferR->getLength();     assert(!(length % 512));     length /= 512;     bufferR->prepare(kIODirectionOut);     bufferW->prepare(kIODirectionIn);     for (i = 0; i < length; i++) {         count = bufferR->readBytes(i * 512, (UInt8 *)byteBlock, 512);         for (j = 0; j < 64; j++)             byteBlock[j] = ~(byteBlock[j]);         count = bufferW->writeBytes(i * 512, (UInt8 *)byteBlock, 512);     }     bufferW->complete();     bufferR->complete();     return; } static void SCDReadWriteCompletion(void    *target,                        void    *parameter,                        IOReturn status,                        UInt64   actualByteCount) {     SimpleCryptoDiskContext *context = (SimpleCryptoDiskContext *)parameter;     if (context->bufferWO == NULL) { // this was a read         // Fix buffer contents (apply simple "decryption").         fixBufferUserRead(context->buffer);     } else { // This was a write.         // Release temporary memory descriptors and free memory that we had         // allocated in the write call.         (context->bufferRO)->release();         (context->bufferWO)->release();         IOFree(context->memory, context->size);     }     // Retrieve the original completion routine.     IOStorageCompletion completion = context->completion;     IOFree(context, sizeof(SimpleCryptoDiskContext));     // Run the original completion routine, if any.     if (completion.action)         (*completion.action)(completion.target, completion.parameter, status,                              actualByteCount); }

To test the SimpleCryptoDisk driver, we will create a disk image with a partition type of osxbook_HFS. We will also create a regular disk image so we can highlight the difference between encrypted and cleartext storage. Let us create a regular disk image first (Figure 1037).

Figure 1037. Reading the contents of cleartext storage directly from the storage medium

$ hdiutil create -size 32m -fs HFS+ -volname Clear /tmp/clear.dmg ... created: /private/tmp/clear.dmg $ open /tmp/clear.dmg # mount the volume contained in clear.dmg $ echo "Secret Message" > /Volumes/Clear/file.txt $ hdiutil detach /Volumes/Clear # unmount the volume ... $ strings /tmp/clear.dmg ... Secret Message ...

The use of the hdiutil command-line program to create and manipulate disk images is discussed in Chapter 11.


As Figure 1037 shows, we can see the contents of the text file we created on a cleartext volume by accessing the raw storage medium. Let us attempt to do the same in the case of an encrypted disk (Figure 1038).

Figure 1038. Using encrypted storage with the SimpleCryptoDisk filter scheme driver

$ hdiutil create -size 32m -partitionType osxbook_HFS /tmp/crypto.dmg ... created: /private/tmp/crypto.dmg $ sudo kextload -v SimpleCryptoDisk.kext kextload: extension SimpleCryptoDisk.kext appears to be valid kextload: loading extension SimpleCryptoDisk.kext kextload: SimpleCryptoDisk.kext loaded successfully kextload: loading personalities named: kextload:     SimpleCryptoDisk kextload: sending 1 personality to the kernel kextload: matching started for SimpleCryptoDisk.kext $ hdiutil attach -nomount /tmp/crypto.dmg /dev/disk10              Apple_partition_scheme /dev/disk10s1            Apple_partition_map /dev/disk10s2            osxbook_HFS /dev/disk10s2s1          Apple_HFS $ newfs_hfs -v Crypto /dev/rdisk10s2s1 Initialized /dev/rdisk10s2s1 as a 32 MB HFS Plus Volume $ hdiutil detach disk10 ... "disk10" ejected. $ open /tmp/crypto.dmg $ echo "Secret Message" > /Volumes/Crypto/file.txt $ cat /Volumes/Crypto/file.txt Secret Message $ hdiutil detach /Volumes/Crypto $ strings /tmp/crypto.dmg # the cleartext message is not seen

An experimental use of a filter scheme driver could be to analyze patterns of block reads and writes.





Mac OS X Internals. A Systems Approach
Mac OS X Internals: A Systems Approach
ISBN: 0321278542
EAN: 2147483647
Year: 2006
Pages: 161
Authors: Amit Singh

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net