summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcharliea <charliea@chromium.org>2015-10-20 14:41:08 -0700
committerCommit bot <commit-bot@chromium.org>2015-10-20 21:41:56 +0000
commit65e7fba107d7af4b65e8b4af4eaec0b04423bb29 (patch)
tree3c06e3c30ccea75b1e3ced58e46124c5991fd936
parent963643f95dadabce52ed9a3df0a29d7faf601d58 (diff)
downloadchromium_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.cc194
-rw-r--r--tools/metrics/histograms/histograms.xml12
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>