[go: nahoru, domu]

blob: eb48fa5161146c9fd737e37b9e4318f60fcff7b3 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/device/usb/usb_service_mac.h"
#include <CoreFoundation/CFBase.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/IOReturn.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/apple/foundation_util.h"
#include "base/mac/scoped_ioplugininterface.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "components/device_event_log/device_event_log.h"
#include "services/device/usb/usb_descriptors.h"
#include "services/device/usb/usb_device_mac.h"
#include "services/device/utils/mac_utils.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace device {
namespace {
// USB class codes are defined by the USB specification.
// https://www.usb.org/defined-class-codes
constexpr uint8_t kDeviceClassHub = 0x09;
} // namespace
UsbServiceMac::UsbServiceMac() {
notify_port_.reset(IONotificationPortCreate(kIOMasterPortDefault));
CFRunLoopAddSource(CFRunLoopGetMain(),
IONotificationPortGetRunLoopSource(notify_port_.get()),
kCFRunLoopDefaultMode);
IOReturn result = IOServiceAddMatchingNotification(
notify_port_.get(), kIOFirstMatchNotification,
IOServiceMatching(kIOUSBDeviceClassName), FirstMatchCallback, this,
devices_added_iterator_.InitializeInto());
if (result != kIOReturnSuccess) {
USB_LOG(ERROR) << "Failed to listen for device arrival: " << std::hex
<< result << ".";
return;
}
// Drain |devices_added_iterator_| to arm the notification.
AddDevices();
result = IOServiceAddMatchingNotification(
notify_port_.get(), kIOTerminatedNotification,
IOServiceMatching(kIOUSBDeviceClassName), TerminatedCallback, this,
devices_removed_iterator_.InitializeInto());
if (result != kIOReturnSuccess) {
USB_LOG(ERROR) << "Failed to listen for device removal: " << std::hex
<< result << ".";
return;
}
// Drain |devices_removed_iterator_| to arm the notification.
RemoveDevices();
}
UsbServiceMac::~UsbServiceMac() = default;
// static
void UsbServiceMac::FirstMatchCallback(void* context, io_iterator_t iterator) {
DCHECK_EQ(CFRunLoopGetMain(), CFRunLoopGetCurrent());
UsbServiceMac* service = reinterpret_cast<UsbServiceMac*>(context);
DCHECK_EQ(service->devices_added_iterator_, iterator);
service->AddDevices();
}
// static
void UsbServiceMac::TerminatedCallback(void* context, io_iterator_t iterator) {
DCHECK_EQ(CFRunLoopGetMain(), CFRunLoopGetCurrent());
UsbServiceMac* service = reinterpret_cast<UsbServiceMac*>(context);
DCHECK_EQ(service->devices_removed_iterator_, iterator);
service->RemoveDevices();
}
void UsbServiceMac::AddDevices() {
base::mac::ScopedIOObject<io_service_t> device;
while (device.reset(IOIteratorNext(devices_added_iterator_)), device) {
AddDevice(device);
}
}
void UsbServiceMac::AddDevice(io_service_t device) {
base::mac::ScopedIOPluginInterface<IOCFPlugInInterface> plugin_interface;
int32_t score;
// This call fails sometimes due to a resource shortage.
// TODO(richardmachado): Figure out what is causing this failure.
IOReturn kr = IOCreatePlugInInterfaceForService(
device, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID,
plugin_interface.InitializeInto(), &score);
if ((kr != kIOReturnSuccess) || !plugin_interface.get()) {
USB_LOG(ERROR) << "Unable to create a plug-in: " << std::hex << kr << ".";
return;
}
base::mac::ScopedIOPluginInterface<IOUSBDeviceInterface182> device_interface;
kr = (*plugin_interface)
->QueryInterface(
plugin_interface.get(),
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
reinterpret_cast<LPVOID*>(device_interface.InitializeInto()));
if (kr != kIOReturnSuccess || !device_interface) {
USB_LOG(ERROR) << "Couldn’t create a device interface.";
return;
}
uint8_t device_class;
if ((*device_interface)->GetDeviceClass(device_interface, &device_class) !=
kIOReturnSuccess) {
return;
}
// We don't want to enumerate hubs.
if (device_class == kDeviceClassHub)
return;
uint16_t vendor_id;
if ((*device_interface)->GetDeviceVendor(device_interface, &vendor_id) !=
kIOReturnSuccess) {
return;
}
uint16_t product_id;
if ((*device_interface)->GetDeviceProduct(device_interface, &product_id) !=
kIOReturnSuccess) {
return;
}
uint8_t device_protocol;
if ((*device_interface)
->GetDeviceProtocol(device_interface, &device_protocol) !=
kIOReturnSuccess) {
return;
}
uint8_t device_subclass;
if ((*device_interface)
->GetDeviceSubClass(device_interface, &device_subclass) !=
kIOReturnSuccess) {
return;
}
uint16_t device_version;
if ((*device_interface)
->GetDeviceReleaseNumber(device_interface, &device_version) !=
kIOReturnSuccess) {
return;
}
uint32_t location_id;
if ((*device_interface)->GetLocationID(device_interface, &location_id) !=
kIOReturnSuccess) {
return;
}
uint64_t entry_id;
if (IORegistryEntryGetRegistryEntryID(device, &entry_id) != kIOReturnSuccess)
return;
absl::optional<uint8_t> property_uint8 =
GetIntegerProperty<uint8_t>(device, CFSTR("PortNum"));
if (!property_uint8.has_value())
return;
uint8_t port_number = property_uint8.value();
absl::optional<uint16_t> property_uint16 =
GetIntegerProperty<uint16_t>(device, CFSTR("bcdUSB"));
uint16_t usb_version;
if (!property_uint16.has_value())
return;
usb_version = property_uint16.value();
absl::optional<std::u16string> property_string16 =
GetStringProperty<std::u16string>(device, CFSTR(kUSBVendorString));
std::u16string manufacturer_string;
if (property_string16.has_value())
manufacturer_string = property_string16.value();
property_string16 =
GetStringProperty<std::u16string>(device, CFSTR(kUSBSerialNumberString));
std::u16string serial_number_string;
if (property_string16.has_value())
serial_number_string = property_string16.value();
property_string16 =
GetStringProperty<std::u16string>(device, CFSTR(kUSBProductString));
std::u16string product_string;
if (property_string16.has_value())
product_string = property_string16.value();
uint8_t num_config;
if ((*device_interface)
->GetNumberOfConfigurations(device_interface, &num_config) !=
kIOReturnSuccess) {
return;
}
// Populate device descriptor with all necessary configuration info.
auto descriptor = std::make_unique<UsbDeviceDescriptor>();
IOUSBConfigurationDescriptorPtr desc;
for (uint8_t i = 0; i < num_config; i++) {
if ((*device_interface)
->GetConfigurationDescriptorPtr(device_interface, i, &desc) !=
kIOReturnSuccess) {
return;
}
if (!descriptor->Parse(base::make_span(reinterpret_cast<uint8_t*>(desc),
desc->wTotalLength))) {
return;
}
}
descriptor->device_info->usb_version_major = usb_version >> 8;
descriptor->device_info->usb_version_minor = usb_version >> 4 & 0xf;
descriptor->device_info->usb_version_subminor = usb_version & 0xf;
descriptor->device_info->class_code = device_class;
descriptor->device_info->subclass_code = device_subclass;
descriptor->device_info->protocol_code = device_protocol;
descriptor->device_info->vendor_id = vendor_id;
descriptor->device_info->product_id = product_id;
descriptor->device_info->device_version_major = device_version >> 8;
descriptor->device_info->device_version_minor = device_version >> 4 & 0xf;
descriptor->device_info->device_version_subminor = device_version & 0xf;
descriptor->device_info->manufacturer_name = manufacturer_string;
descriptor->device_info->product_name = product_string;
descriptor->device_info->serial_number = serial_number_string;
descriptor->device_info->bus_number = location_id >> 24;
descriptor->device_info->port_number = port_number;
scoped_refptr<UsbDeviceMac> mac_device =
new UsbDeviceMac(entry_id, std::move(descriptor->device_info));
device_map_[entry_id] = mac_device;
devices()[mac_device->guid()] = mac_device;
NotifyDeviceAdded(mac_device);
}
void UsbServiceMac::RemoveDevices() {
base::mac::ScopedIOObject<io_service_t> device;
while (device.reset(IOIteratorNext(devices_removed_iterator_)), device) {
uint64_t entry_id;
if (kIOReturnSuccess !=
IORegistryEntryGetRegistryEntryID(device, &entry_id)) {
continue;
}
auto it = device_map_.find(entry_id);
if (it == device_map_.end())
continue;
auto mac_device = it->second;
device_map_.erase(it);
auto by_guid_it = devices().find(mac_device->guid());
devices().erase(by_guid_it);
NotifyDeviceRemoved(mac_device);
mac_device->OnDisconnect();
}
}
} // namespace device