Search
lxdream.org :: lxdream/src/drivers/osx_iokit.m
lxdream 0.9.1
released Jun 29
Download Now
filename src/drivers/osx_iokit.m
changeset 1297:7e98a164b2d9
prev1023:264e2fd90be8
author nkeynes
date Fri May 29 18:47:05 2015 +1000 (8 years ago)
permissions -rw-r--r--
last change Fix test case
view annotate diff log raw
     1 /**
     2  * $Id$
     3  *
     4  * OSX support functions for handling the IOKit registry. 
     5  * Currently this manages access to CD/DVD drives + media, plus HID devices.
     6  * 
     7  * The HID part is much simpler...
     8  *
     9  * Copyright (c) 2008 Nathan Keynes.
    10  *
    11  * This program is free software; you can redistribute it and/or modify
    12  * it under the terms of the GNU General Public License as published by
    13  * the Free Software Foundation; either version 2 of the License, or
    14  * (at your option) any later version.
    15  *
    16  * This program is distributed in the hope that it will be useful,
    17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    19  * GNU General Public License for more details.
    20  */
    22 #include <glib.h>
    23 #include <sys/param.h>
    24 #include <paths.h>
    25 #include <string.h>
    26 #include <unistd.h>
    27 #include <stdio.h>
    28 #include <fcntl.h>
    29 #include <CoreFoundation/CFRunLoop.h>
    30 #include <IOKit/IOMessage.h>
    31 #include <IOKit/hid/IOHIDLib.h>
    32 #include "osx_iokit.h"
    36 static IONotificationPortRef notify_port = 0;
    37 static io_iterator_t iokit_iterators[4] = {0,0,0,0};
    39 struct osx_cdrom_drive {
    40     io_string_t ioservice_path;
    41     io_string_t vendor_name;
    42     io_string_t product_name;
    43     char media_path[MAXPATHLEN]; // BSD device path if media present, otherwise the empty string.
    44     io_iterator_t media_load_iterator;
    45     io_iterator_t media_unload_iterator;
    46     int media_fh; // BSD device handle if open, otherwise -1
    47     media_changed_callback_t media_changed;
    48     void *media_changed_user_data;
    49 };
    51 static gboolean get_bsdname_for_iomedia( io_object_t iomedia, char *buf, int buflen );
    52 static gboolean get_boolean_property( io_object_t io, CFStringRef key, gboolean def );
    54 /***************** IOKit Callbacks ******************/
    56 /**
    57  * Called from IOKit for any IOMessages on an IOMedia. Currently the only message
    58  * we're interested in is service termination.
    59  */
    60 static void osx_cdrom_media_notify( void *ref, io_service_t service, uint32_t msgType,
    61                                     void *msgArgument )
    62 {
    63     if( msgType == kIOMessageServiceIsTerminated ) {
    64         osx_cdrom_drive_t drive = (osx_cdrom_drive_t)ref;
    65         if( drive->media_changed != NULL ) {
    66             drive->media_changed( drive, FALSE, drive->media_changed_user_data );
    67         }
    68         if( drive->media_fh != -1 ) {
    69             close(drive->media_fh);
    70             drive->media_fh = -1;
    71         }
    72         drive->media_path[0] = '\0';
    73         IOObjectRelease( drive->media_unload_iterator );
    74     }
    75 }
    77 /**
    78  * Called from IOKit when an IOMedia is inserted that we have be interested in.
    79  * FIXME: Can the matcher be restricted to descendents of the drive node? currently
    80  * we watch for all IOMedia events and compare the device path to see if it's one we
    81  * care about.
    82  * FIXME: We assume for now that a drive has at most one piece of media at a time. 
    83  * If this isn't the case, the system may get a little confused.
    84  */
    85 static void osx_cdrom_media_inserted( void *ref, io_iterator_t iterator )
    86 {
    87     osx_cdrom_drive_t drive = (osx_cdrom_drive_t)ref;
    89     io_object_t object;
    90     while( (object = IOIteratorNext(iterator)) != 0 ) {
    91         io_string_t iopath = "";
    92         IORegistryEntryGetPath( object, kIOServicePlane, iopath );
    93         if( drive != NULL && g_str_has_prefix(iopath, drive->ioservice_path ) &&
    94             get_boolean_property(object, CFSTR("Whole"), TRUE) &&
    95             get_bsdname_for_iomedia(object, drive->media_path, sizeof(drive->media_path)) ) {
    96             // A disc was inserted within the drive of interest
    97             if( drive->media_fh != -1 ) {
    98                 close(drive->media_fh);
    99                 drive->media_fh = -1;
   100             }
   102             if( drive->media_changed != NULL ) {
   103                 drive->media_changed(drive, TRUE, drive->media_changed_user_data);
   104             }
   105             // Add a notification listener to get removal events.
   106             IOServiceAddInterestNotification( notify_port, object, kIOGeneralInterest, 
   107                                               osx_cdrom_media_notify, drive, &drive->media_unload_iterator ); 
   109         }
   110         IOObjectRelease( object );
   111     }
   112 }
   114 static void osx_drives_changed( void *ref, io_iterator_t iterator )
   115 {
   116     io_object_t object;
   117     while( (object = IOIteratorNext(iterator)) != 0 ) {
   118         IOObjectRelease(object);
   119     }
   121 }
   123 /******************** Support functions *********************/
   125 /**
   126  * Determine the BSD device name (ie "/dev/rdisk1") for a given IO object.
   127  * @return TRUE if the device name was retrieved, FALSE if the request failed.
   128  */
   129 static gboolean get_bsdname_for_iomedia( io_object_t iomedia, char *buf, int buflen )
   130 {
   131     gboolean result = FALSE;
   132     CFTypeRef pathRef = IORegistryEntryCreateCFProperty(iomedia, CFSTR(kIOBSDNameKey),
   133             kCFAllocatorDefault, 0 );
   134     if( pathRef ) {
   135         char pathlen;
   136         strcpy( buf, _PATH_DEV "r" );
   137         pathlen = strlen(buf);
   138         if( CFStringGetCString( pathRef, buf + pathlen, buflen-pathlen,
   139                 kCFStringEncodingASCII ) != noErr ) {
   140             result = TRUE;
   141         }
   142         CFRelease(pathRef);
   143     }
   144     return result;
   145 }
   147 /**
   148  * Retrieve a boolean property from the io object, and return as a gboolean. If
   149  * the key is not present, return def instead.
   150  */
   151 static gboolean get_boolean_property( io_object_t io, CFStringRef key, gboolean def )
   152 {
   153     gboolean result = def;
   154     CFTypeRef ref = IORegistryEntryCreateCFProperty(io, key, kCFAllocatorDefault, 0 );
   155     if( ref ) {
   156     	result = CFBooleanGetValue(ref);
   157         CFRelease(ref);
   158     }
   159     return result;
   160  }
   162 static gboolean osx_cdrom_drive_get_name( io_object_t object, char *vendor, int vendor_len,
   163                                           char *product, int product_len )
   164 {
   165     gboolean result = FALSE;
   166     CFMutableDictionaryRef props = 0;
   167     if( IORegistryEntryCreateCFProperties(object, &props, kCFAllocatorDefault, kNilOptions) == KERN_SUCCESS ) {
   168         CFDictionaryRef dict = 
   169             (CFDictionaryRef)CFDictionaryGetValue(props, CFSTR(kIOPropertyDeviceCharacteristicsKey));
   170         if( dict != NULL ) {
   171             CFTypeRef value = CFDictionaryGetValue(dict, CFSTR(kIOPropertyVendorNameKey));
   172             if( value && CFGetTypeID(value) == CFStringGetTypeID() ) {
   173                 CFStringGetCString( (CFStringRef)value, vendor, vendor_len, kCFStringEncodingUTF8 );
   174             } else {
   175                 vendor[0] = 0;
   176             }
   178             value = CFDictionaryGetValue(dict, CFSTR(kIOPropertyProductNameKey));
   179             if ( value && CFGetTypeID(value) == CFStringGetTypeID() ) {
   180                 CFStringGetCString( (CFStringRef)value, product, product_len, kCFStringEncodingUTF8 );
   181             } else {
   182                 product[0] = 0;
   183             }
   184             result = TRUE;
   185         }
   187         CFRelease(props);
   188     }
   189     return result;
   190 }
   192 /**
   193  * Construct and initialize a new osx_cdrom_drive object, including registering
   194  * it's media inserted notification.
   195  */
   196 static osx_cdrom_drive_t osx_cdrom_drive_new( io_object_t device )  
   197 {
   198     osx_cdrom_drive_t drive = g_malloc0(sizeof(struct osx_cdrom_drive));
   200     IORegistryEntryGetPath( device, kIOServicePlane, drive->ioservice_path );
   201     osx_cdrom_drive_get_name( device, drive->vendor_name, sizeof(drive->vendor_name),
   202                               drive->product_name, sizeof(drive->product_name) );
   203     drive->media_path[0] = '\0';
   204     drive->media_changed = NULL;
   205     drive->media_changed_user_data = NULL;
   206     drive->media_fh = -1;
   208     IOServiceAddMatchingNotification( notify_port, kIOFirstPublishNotification, 
   209                                       IOServiceMatching("IOMedia"),
   210                                       osx_cdrom_media_inserted, drive, 
   211                                       &drive->media_load_iterator );
   212     osx_cdrom_media_inserted( drive, drive->media_load_iterator );
   213     return drive;
   214 }
   216 /************************ Exported functions *************************/ 
   218 osx_cdrom_drive_t osx_cdrom_open_drive( const char *devname )
   219 {
   220     io_object_t object = IORegistryEntryFromPath( kIOMasterPortDefault, devname );
   221     if( object == MACH_PORT_NULL ) {
   222         return NULL;
   223     }
   225     osx_cdrom_drive_t drive = osx_cdrom_drive_new( object );
   226     IOObjectRelease( object );
   227     return drive;
   228 }
   230 void osx_cdrom_set_media_changed_callback( osx_cdrom_drive_t drive, 
   231                                            media_changed_callback_t callback,
   232                                            void *user_data )
   233 {
   234     drive->media_changed = callback;
   235     drive->media_changed_user_data = user_data;
   236 }
   238 void osx_cdrom_close_drive( osx_cdrom_drive_t drive )
   239 {
   240     IOObjectRelease( drive->media_load_iterator );
   241     IOObjectRelease( drive->media_unload_iterator );
   242     if( drive->media_fh != -1 ) {
   243         close(drive->media_fh);
   244         drive->media_fh = -1;
   245     }
   246     g_free( drive );
   247 }
   249 int osx_cdrom_get_media_handle( osx_cdrom_drive_t drive )
   250 {
   251     if( drive->media_fh == -1 ) {
   252         if( drive->media_path[0] != '\0' ) {
   253             drive->media_fh = open( drive->media_path, O_RDONLY|O_NONBLOCK );
   254         }
   255     }
   256     return drive->media_fh;
   257 }
   259 void osx_cdrom_release_media_handle( osx_cdrom_drive_t drive )
   260 {
   261     if( drive->media_fh != -1 ) {
   262         close( drive->media_fh );
   263         drive->media_fh = -1; 
   264     }
   265 }
   267 static io_object_t iterator_find_cdrom( io_object_t iterator, find_drive_callback_t callback, void *user_data )
   268 {
   269     io_object_t object;
   270     while( (object = IOIteratorNext(iterator)) != 0 ) {
   271         io_string_t iopath = "";
   272         char product[256], vendor[256];
   273         IORegistryEntryGetPath( object, kIOServicePlane, iopath );
   274         osx_cdrom_drive_get_name( object, vendor, sizeof(vendor), product, sizeof(product) );
   275         if( callback( object, vendor, product, iopath, user_data ) ) {
   276             IOObjectRelease(iterator);
   277             return object;
   278         }
   279         IOObjectRelease(object);
   280     }
   281     IOObjectRelease(iterator);
   282     return 0;
   283 }
   286 /**
   287  * Search for a CD or DVD drive (instance of IODVDServices or IOCompactDiscServices).
   288  * The callback will be called repeatedly until either it returns TRUE, or all drives
   289  * have been iterated over.
   290  * 
   291  * @return an IO registry entry for the matched drive, or 0 if no drives matched.
   292  * 
   293  * Note: Use of IOCompactDiscServices is somewhat tentative since I don't have a Mac
   294  * with a CD-Rom drive.
   295  */ 
   296 io_object_t find_cdrom_drive( find_drive_callback_t callback, void *user_data )
   297 {
   298     mach_port_t master_port;
   299     CFMutableDictionaryRef match;
   300     io_iterator_t services;
   301     io_object_t result;
   303     if( IOMasterPort( MACH_PORT_NULL, &master_port ) != KERN_SUCCESS ) {
   304         return 0; // Failed to get the master port?
   305     }
   307     match = IOServiceMatching("IODVDServices");
   308     if( IOServiceGetMatchingServices(master_port, match, &services) != kIOReturnSuccess ) {
   309         return 0;
   310     }
   312     result = iterator_find_cdrom( services, callback, user_data );
   313     if( result != 0 ) {
   314         return result;
   315     }
   317     match = IOServiceMatching("IOCompactDiscServices");
   318     if( IOServiceGetMatchingServices(master_port, match, &services) != kIOReturnSuccess ) {
   319         return 0;
   320     }
   321     return iterator_find_cdrom( services, callback, user_data );
   322 }
   325 // *********************** Notification management ************************/
   327 static void osx_hid_inserted( void *ref, io_iterator_t iterator )
   328 {
   329     io_object_t object;
   330     while( (object = IOIteratorNext(iterator)) != 0 ) {
   331         io_string_t iopath = "";
   332         IORegistryEntryGetPath( object, kIOServicePlane, iopath );
   333         IOObjectRelease( object );
   334     }
   335 }
   337 gboolean osx_register_iokit_notifications()
   338 {
   339     notify_port = IONotificationPortCreate( kIOMasterPortDefault );
   340     CFRunLoopSourceRef runloop_source = IONotificationPortGetRunLoopSource( notify_port );
   341     CFRunLoopAddSource( CFRunLoopGetCurrent(), runloop_source, kCFRunLoopCommonModes );
   343     // Drive notifications
   344     if( IOServiceAddMatchingNotification( notify_port, kIOFirstPublishNotification,
   345             IOServiceMatching("IOCompactDiscServies"),
   346             osx_drives_changed, NULL, &iokit_iterators[0] ) != kIOReturnSuccess ) {
   347         ERROR( "IOServiceAddMatchingNotification failed" );
   348     }
   349     osx_drives_changed(NULL, iokit_iterators[0]);
   350     if( IOServiceAddMatchingNotification( notify_port, kIOFirstPublishNotification,
   351             IOServiceMatching("IODVDServies"),
   352             osx_drives_changed, NULL, &iokit_iterators[1] ) != kIOReturnSuccess ) {
   353         ERROR( "IOServiceAddMatchingNotification failed" );
   354     }
   355     osx_drives_changed(NULL, iokit_iterators[1]);
   357     if( IOServiceAddMatchingNotification( notify_port, kIOFirstPublishNotification, 
   358             IOServiceMatching(kIOHIDDeviceKey),
   359             osx_hid_inserted, NULL, &iokit_iterators[2] ) != kIOReturnSuccess ) {
   360         ERROR( "IOServiceAddMatchingNotification failed" );
   361     }
   362     osx_hid_inserted(NULL, iokit_iterators[2]);
   363     return TRUE;
   364 }
   366 void osx_unregister_iokit_notifications()
   367 {
   368     CFRunLoopSourceRef runloop_source = IONotificationPortGetRunLoopSource( notify_port );
   369     CFRunLoopRemoveSource( CFRunLoopGetCurrent(), runloop_source, kCFRunLoopCommonModes );
   370     IONotificationPortDestroy( notify_port );
   371     notify_port = 0;
   372 }
.