diff options
author | charliea <charliea@chromium.org> | 2015-10-20 14:41:08 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-10-20 21:41:56 +0000 |
commit | 65e7fba107d7af4b65e8b4af4eaec0b04423bb29 (patch) | |
tree | 3c06e3c30ccea75b1e3ced58e46124c5991fd936 | |
parent | 963643f95dadabce52ed9a3df0a29d7faf601d58 (diff) | |
download | chromium_src-65e7fba107d7af4b65e8b4af4eaec0b04423bb29.zip chromium_src-65e7fba107d7af4b65e8b4af4eaec0b04423bb29.tar.gz chromium_src-65e7fba107d7af4b65e8b4af4eaec0b04423bb29.tar.bz2 |
Extracts more information from serial devices on Mac
This is a modified reland of crrev.com/1306733008.
Originally, I completely replaced the old enumeration method with the
new one. After someone spotted a regression for the Windows code, I
reverted the enumeration code that yielded more detailed device info
and am now recommitting it as a hybrid of the old enumeration method
and new one. We merge the lists of new devices and old devices, giving
preference to the new ones (with more detailed info). I also added an
UMA metric to track the difference in the number of devices that each
method yields. When that metric shows that the two methods are always
returning the same number of devices, we can eliminate the old method
entirely.
BUG=522217
Review URL: https://codereview.chromium.org/1399023002
Cr-Commit-Position: refs/heads/master@{#355161}
-rw-r--r-- | device/serial/serial_device_enumerator_mac.cc | 194 | ||||
-rw-r--r-- | tools/metrics/histograms/histograms.xml | 12 |
2 files changed, 199 insertions, 7 deletions
diff --git a/device/serial/serial_device_enumerator_mac.cc b/device/serial/serial_device_enumerator_mac.cc index 8e56b91..671555d 100644 --- a/device/serial/serial_device_enumerator_mac.cc +++ b/device/serial/serial_device_enumerator_mac.cc @@ -4,26 +4,167 @@ #include "device/serial/serial_device_enumerator_mac.h" +#include <IOKit/serial/IOSerialKeys.h> +#include <IOKit/usb/IOUSBLib.h> + +#include <algorithm> + #include "base/files/file_enumerator.h" #include "base/files/file_path.h" #include "base/files/file_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/mac/scoped_ioobject.h" #include "base/memory/scoped_ptr.h" +#include "base/metrics/sparse_histogram.h" #include "base/strings/pattern.h" #include "base/strings/string_util.h" +#include "base/strings/sys_string_conversions.h" namespace device { -// static -scoped_ptr<SerialDeviceEnumerator> SerialDeviceEnumerator::Create() { - return scoped_ptr<SerialDeviceEnumerator>(new SerialDeviceEnumeratorMac()); +namespace { + +// Searches a service and all ancestor services for a property with the +// specified key, returning NULL if no such key was found. +CFTypeRef GetCFProperty(io_service_t service, const CFStringRef key) { + // We search for the specified property not only on the specified service, but + // all ancestors of that service. This is important because if a device is + // both serial and USB, in the registry tree it appears as a serial service + // with a USB service as its ancestor. Without searching ancestors services + // for the specified property, we'd miss all USB properties. + return IORegistryEntrySearchCFProperty( + service, kIOServicePlane, key, NULL, + kIORegistryIterateRecursively | kIORegistryIterateParents); } -SerialDeviceEnumeratorMac::SerialDeviceEnumeratorMac() {} +// Searches a service and all ancestor services for a string property with the +// specified key, returning NULL if no such key was found. +CFStringRef GetCFStringProperty(io_service_t service, const CFStringRef key) { + CFTypeRef value = GetCFProperty(service, key); + if (value && (CFGetTypeID(value) == CFStringGetTypeID())) + return static_cast<CFStringRef>(value); -SerialDeviceEnumeratorMac::~SerialDeviceEnumeratorMac() {} + return NULL; +} -// TODO(rockot): Use IOKit to enumerate serial interfaces. -mojo::Array<serial::DeviceInfoPtr> SerialDeviceEnumeratorMac::GetDevices() { +// Searches a service and all ancestor services for a number property with the +// specified key, returning NULL if no such key was found. +CFNumberRef GetCFNumberProperty(io_service_t service, const CFStringRef key) { + CFTypeRef value = GetCFProperty(service, key); + if (value && (CFGetTypeID(value) == CFNumberGetTypeID())) + return static_cast<CFNumberRef>(value); + + return NULL; +} + +// Searches the specified service for a string property with the specified key, +// sets value to that property's value, and returns whether the operation was +// successful. +bool GetStringProperty(io_service_t service, + const CFStringRef key, + mojo::String* value) { + CFStringRef propValue = GetCFStringProperty(service, key); + if (propValue) { + *value = base::SysCFStringRefToUTF8(propValue); + return true; + } + + return false; +} + +// Searches the specified service for a uint16 property with the specified key, +// sets value to that property's value, and returns whether the operation was +// successful. +bool GetUInt16Property(io_service_t service, + const CFStringRef key, + uint16_t* value) { + CFNumberRef propValue = GetCFNumberProperty(service, key); + if (propValue) { + int intValue; + if (CFNumberGetValue(propValue, kCFNumberIntType, &intValue)) { + *value = static_cast<uint16_t>(intValue); + return true; + } + } + + return false; +} + +// Returns value clamped to the range of [min, max]. +int Clamp(int value, int min, int max) { + return std::min(std::max(value, min), max); +} + +// Returns an array of devices as retrieved through the new method of +// enumerating serial devices (IOKit). This new method gives more information +// about the devices than the old method. +mojo::Array<serial::DeviceInfoPtr> GetDevicesNew() { + mojo::Array<serial::DeviceInfoPtr> devices(0); + + // Make a service query to find all serial devices. + CFMutableDictionaryRef matchingDict = + IOServiceMatching(kIOSerialBSDServiceValue); + if (!matchingDict) + return devices.Pass(); + + io_iterator_t it; + kern_return_t kr = + IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, &it); + if (kr != KERN_SUCCESS) + return devices.Pass(); + + base::mac::ScopedIOObject<io_iterator_t> scoped_it(it); + base::mac::ScopedIOObject<io_service_t> scoped_device; + while (scoped_device.reset(IOIteratorNext(scoped_it.get())), scoped_device) { + serial::DeviceInfoPtr callout_info(serial::DeviceInfo::New()); + + uint16_t vendorId; + if (GetUInt16Property(scoped_device.get(), CFSTR(kUSBVendorID), + &vendorId)) { + callout_info->has_vendor_id = true; + callout_info->vendor_id = vendorId; + } + + uint16_t productId; + if (GetUInt16Property(scoped_device.get(), CFSTR(kUSBProductID), + &productId)) { + callout_info->has_product_id = true; + callout_info->product_id = productId; + } + + mojo::String displayName; + if (GetStringProperty(scoped_device.get(), CFSTR(kUSBProductString), + &displayName)) { + callout_info->display_name = displayName; + } + + // Each serial device has two "paths" in /dev/ associated with it: a + // "dialin" path starting with "tty" and a "callout" path starting with + // "cu". Each of these is considered a different device from Chrome's + // standpoint, but both should share the device's USB properties. + mojo::String dialinDevice; + if (GetStringProperty(scoped_device.get(), CFSTR(kIODialinDeviceKey), + &dialinDevice)) { + serial::DeviceInfoPtr dialin_info = callout_info.Clone(); + dialin_info->path = dialinDevice; + devices.push_back(dialin_info.Pass()); + } + + mojo::String calloutDevice; + if (GetStringProperty(scoped_device.get(), CFSTR(kIOCalloutDeviceKey), + &calloutDevice)) { + callout_info->path = calloutDevice; + devices.push_back(callout_info.Pass()); + } + } + + return devices.Pass(); +} + +// Returns an array of devices as retrieved through the old method of +// enumerating serial devices (pattern matching in /dev/). This old method gives +// less information about the devices than the new method. +mojo::Array<serial::DeviceInfoPtr> GetDevicesOld() { const base::FilePath kDevRoot("/dev"); const int kFilesAndSymLinks = base::FileEnumerator::FILES | base::FileEnumerator::SHOW_SYM_LINKS; @@ -58,4 +199,43 @@ mojo::Array<serial::DeviceInfoPtr> SerialDeviceEnumeratorMac::GetDevices() { return devices.Pass(); } +} // namespace + +// static +scoped_ptr<SerialDeviceEnumerator> SerialDeviceEnumerator::Create() { + return scoped_ptr<SerialDeviceEnumerator>(new SerialDeviceEnumeratorMac()); +} + +SerialDeviceEnumeratorMac::SerialDeviceEnumeratorMac() {} + +SerialDeviceEnumeratorMac::~SerialDeviceEnumeratorMac() {} + +mojo::Array<serial::DeviceInfoPtr> SerialDeviceEnumeratorMac::GetDevices() { + mojo::Array<serial::DeviceInfoPtr> newDevices = GetDevicesNew(); + mojo::Array<serial::DeviceInfoPtr> oldDevices = GetDevicesOld(); + + UMA_HISTOGRAM_SPARSE_SLOWLY( + "Hardware.Serial.NewMinusOldDeviceListSize", + Clamp(newDevices.size() - oldDevices.size(), -10, 10)); + + // Add devices found from both the new and old methods of enumeration. If a + // device is found using both the new and the old enumeration method, then we + // take the device from the new enumeration method because it's able to + // collect more information. We do this by inserting the new devices first, + // because insertions are ignored if the key already exists. + mojo::Map<mojo::String, serial::DeviceInfoPtr> deviceMap; + for (unsigned long i = 0; i < newDevices.size(); i++) { + deviceMap.insert(newDevices[i]->path, newDevices[i].Clone()); + } + for (unsigned long i = 0; i < oldDevices.size(); i++) { + deviceMap.insert(oldDevices[i]->path, oldDevices[i].Clone()); + } + + mojo::Array<mojo::String> paths; + mojo::Array<serial::DeviceInfoPtr> devices; + deviceMap.DecomposeMapTo(&paths, &devices); + + return devices.Pass(); +} + } // namespace device diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index 23c04d9..1a223f2 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -14466,6 +14466,18 @@ http://cs/file:chrome/histograms.xml - but prefer this file for new entries. </summary> </histogram> +<histogram name="Hardware.Serial.NewMinusOldDeviceListSize"> + <owner>charliea@chromium.org</owner> + <summary> + On Windows and Mac, we're implementing new methods to enumerate serial + devices that provide us more information about the actual devices. This + metric measures the difference between the number of devices that the new + and old enumeration methods find. Once this metric gives us confidence that + the new and old methods are at parity, we can cut out the old method + altogether. + </summary> +</histogram> + <histogram name="HIDDetection.OOBEDevicesDetectedOnContinuePressed" enum="HIDContinueScenarioType"> <owner>merkulova@chromium.org</owner> |