1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "device/serial/serial_device_enumerator_mac.h"
7 #include <IOKit/serial/IOSerialKeys.h>
8 #include <IOKit/usb/IOUSBLib.h>
10 #include "base/files/file_enumerator.h"
11 #include "base/files/file_path.h"
12 #include "base/files/file_util.h"
13 #include "base/mac/scoped_cftyperef.h"
14 #include "base/mac/scoped_ioobject.h"
15 #include "base/memory/scoped_ptr.h"
16 #include "base/strings/pattern.h"
17 #include "base/strings/string_util.h"
18 #include "base/strings/sys_string_conversions.h"
24 // Searches a service and all ancestor services for a property with the
25 // specified key, returning NULL if no such key was found.
26 CFTypeRef
GetCFProperty(io_service_t service
, const CFStringRef key
) {
27 // We search for the specified property not only on the specified service, but
28 // all ancestors of that service. This is important because if a device is
29 // both serial and USB, in the registry tree it appears as a serial service
30 // with a USB service as its ancestor. Without searching ancestors services
31 // for the specified property, we'd miss all USB properties.
32 return IORegistryEntrySearchCFProperty(
33 service
, kIOServicePlane
, key
, NULL
,
34 kIORegistryIterateRecursively
| kIORegistryIterateParents
);
37 // Searches a service and all ancestor services for a string property with the
38 // specified key, returning NULL if no such key was found.
39 CFStringRef
GetCFStringProperty(io_service_t service
, const CFStringRef key
) {
40 CFTypeRef value
= GetCFProperty(service
, key
);
41 if (value
&& (CFGetTypeID(value
) == CFStringGetTypeID()))
42 return static_cast<CFStringRef
>(value
);
47 // Searches a service and all ancestor services for a number property with the
48 // specified key, returning NULL if no such key was found.
49 CFNumberRef
GetCFNumberProperty(io_service_t service
, const CFStringRef key
) {
50 CFTypeRef value
= GetCFProperty(service
, key
);
51 if (value
&& (CFGetTypeID(value
) == CFNumberGetTypeID()))
52 return static_cast<CFNumberRef
>(value
);
57 // Searches the specified service for a string property with the specified key,
58 // sets value to that property's value, and returns whether the operation was
60 bool GetStringProperty(io_service_t service
,
61 const CFStringRef key
,
62 mojo::String
* value
) {
63 CFStringRef propValue
= GetCFStringProperty(service
, key
);
65 *value
= base::SysCFStringRefToUTF8(propValue
);
72 // Searches the specified service for a uint16 property with the specified key,
73 // sets value to that property's value, and returns whether the operation was
75 bool GetUInt16Property(io_service_t service
,
76 const CFStringRef key
,
78 CFNumberRef propValue
= GetCFNumberProperty(service
, key
);
81 if (CFNumberGetValue(propValue
, kCFNumberIntType
, &intValue
)) {
82 *value
= static_cast<uint16_t>(intValue
);
93 scoped_ptr
<SerialDeviceEnumerator
> SerialDeviceEnumerator::Create() {
94 return scoped_ptr
<SerialDeviceEnumerator
>(new SerialDeviceEnumeratorMac());
97 SerialDeviceEnumeratorMac::SerialDeviceEnumeratorMac() {}
99 SerialDeviceEnumeratorMac::~SerialDeviceEnumeratorMac() {}
101 mojo::Array
<serial::DeviceInfoPtr
> SerialDeviceEnumeratorMac::GetDevices() {
102 mojo::Array
<serial::DeviceInfoPtr
> devices(0);
104 // Make a service query to find all serial devices.
105 CFMutableDictionaryRef matchingDict
=
106 IOServiceMatching(kIOSerialBSDServiceValue
);
108 return devices
.Pass();
112 IOServiceGetMatchingServices(kIOMasterPortDefault
, matchingDict
, &it
);
113 if (kr
!= KERN_SUCCESS
)
114 return devices
.Pass();
116 base::mac::ScopedIOObject
<io_iterator_t
> scoped_it(it
);
117 base::mac::ScopedIOObject
<io_service_t
> scoped_device
;
118 while (scoped_device
.reset(IOIteratorNext(scoped_it
.get())), scoped_device
) {
119 serial::DeviceInfoPtr
callout_info(serial::DeviceInfo::New());
122 if (GetUInt16Property(scoped_device
.get(), CFSTR(kUSBVendorID
),
124 callout_info
->has_vendor_id
= true;
125 callout_info
->vendor_id
= vendorId
;
129 if (GetUInt16Property(scoped_device
.get(), CFSTR(kUSBProductID
),
131 callout_info
->has_product_id
= true;
132 callout_info
->product_id
= productId
;
135 mojo::String displayName
;
136 if (GetStringProperty(scoped_device
.get(), CFSTR(kUSBProductString
),
138 callout_info
->display_name
= displayName
;
141 // Each serial device has two "paths" in /dev/ associated with it: a
142 // "dialin" path starting with "tty" and a "callout" path starting with
143 // "cu". Each of these is considered a different device from Chrome's
144 // standpoint, but both should share the device's USB properties.
145 mojo::String dialinDevice
;
146 if (GetStringProperty(scoped_device
.get(), CFSTR(kIODialinDeviceKey
),
148 serial::DeviceInfoPtr dialin_info
= callout_info
.Clone();
149 dialin_info
->path
= dialinDevice
;
150 devices
.push_back(dialin_info
.Pass());
153 mojo::String calloutDevice
;
154 if (GetStringProperty(scoped_device
.get(), CFSTR(kIOCalloutDeviceKey
),
156 callout_info
->path
= calloutDevice
;
157 devices
.push_back(callout_info
.Pass());
161 return devices
.Pass();
164 } // namespace device