blob: c6114b1cd4e39ed79cd4dcc157a30994886b7d77 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "device/usb/usb_service_impl.h"
#include <stdint.h>
#include <list>
#include <memory>
#include <set>
#include <utility>
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/memory/ref_counted_memory.h"
#include "base/memory/weak_ptr.h"
#include "base/sequenced_task_runner.h"
#include "base/single_thread_task_runner.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_scheduler/post_task.h"
#include "build/build_config.h"
#include "components/device_event_log/device_event_log.h"
#include "device/usb/usb_device_handle.h"
#include "device/usb/usb_error.h"
#include "device/usb/webusb_descriptors.h"
#include "third_party/libusb/src/libusb/libusb.h"
#if defined(OS_WIN)
#define INITGUID
#include <devpkey.h>
#include <setupapi.h>
#include <usbiodef.h>
#include "base/strings/string_util.h"
#include "device/base/device_info_query_win.h"
#endif // OS_WIN
namespace device {
namespace {
// Standard USB requests and descriptor types:
const uint16_t kUsbVersion2_1 = 0x0210;
#if defined(OS_WIN)
bool IsWinUsbInterface(const std::string& device_path) {
DeviceInfoQueryWin device_info_query;
if (!device_info_query.device_info_list_valid()) {
USB_PLOG(ERROR) << "Failed to create a device information set";
return false;
}
// This will add the device so we can query driver info.
if (!device_info_query.AddDevice(device_path)) {
USB_PLOG(ERROR) << "Failed to get device interface data for "
<< device_path;
return false;
}
if (!device_info_query.GetDeviceInfo()) {
USB_PLOG(ERROR) << "Failed to get device info for " << device_path;
return false;
}
std::string buffer;
if (!device_info_query.GetDeviceStringProperty(DEVPKEY_Device_Service,
&buffer)) {
USB_PLOG(ERROR) << "Failed to get device service property";
return false;
}
USB_LOG(DEBUG) << "Driver for " << device_path << " is " << buffer << ".";
if (base::StartsWith(buffer, "WinUSB", base::CompareCase::INSENSITIVE_ASCII))
return true;
return false;
}
#endif // OS_WIN
void InitializeUsbContextOnBlockingThread(
scoped_refptr<base::SequencedTaskRunner> task_runner,
const base::Callback<void(scoped_refptr<UsbContext>)>& callback) {
scoped_refptr<UsbContext> context;
PlatformUsbContext platform_context = nullptr;
int rv = libusb_init(&platform_context);
if (rv == LIBUSB_SUCCESS && platform_context) {
context = new UsbContext(platform_context);
} else {
USB_LOG(DEBUG) << "Failed to initialize libusb: "
<< ConvertPlatformUsbErrorToString(rv);
}
task_runner->PostTask(FROM_HERE,
base::Bind(callback, base::Passed(&context)));
}
void GetDeviceListOnBlockingThread(
const std::string& new_device_path,
scoped_refptr<UsbContext> usb_context,
scoped_refptr<base::SequencedTaskRunner> task_runner,
const base::Callback<void(libusb_device**, size_t)>& callback) {
#if defined(OS_WIN)
if (!new_device_path.empty()) {
if (!IsWinUsbInterface(new_device_path)) {
// Wait to call libusb_get_device_list until libusb will be able to find
// a WinUSB interface for the device.
task_runner->PostTask(FROM_HERE, base::Bind(callback, nullptr, 0));
return;
}
}
#endif // defined(OS_WIN)
libusb_device** platform_devices = NULL;
const ssize_t device_count =
libusb_get_device_list(usb_context->context(), &platform_devices);
if (device_count < 0) {
USB_LOG(ERROR) << "Failed to get device list: "
<< ConvertPlatformUsbErrorToString(device_count);
task_runner->PostTask(FROM_HERE, base::Bind(callback, nullptr, 0));
return;
}
task_runner->PostTask(FROM_HERE,
base::Bind(callback, platform_devices, device_count));
}
void CloseHandleAndRunContinuation(scoped_refptr<UsbDeviceHandle> device_handle,
const base::Closure& continuation) {
device_handle->Close();
continuation.Run();
}
void SaveStringsAndRunContinuation(
scoped_refptr<UsbDeviceImpl> device,
uint8_t manufacturer,
uint8_t product,
uint8_t serial_number,
const base::Closure& continuation,
std::unique_ptr<std::map<uint8_t, base::string16>> string_map) {
if (manufacturer != 0)
device->set_manufacturer_string((*string_map)[manufacturer]);
if (product != 0)
device->set_product_string((*string_map)[product]);
if (serial_number != 0)
device->set_serial_number((*string_map)[serial_number]);
continuation.Run();
}
void OnReadBosDescriptor(scoped_refptr<UsbDeviceHandle> device_handle,
const base::Closure& barrier,
const GURL& landing_page) {
scoped_refptr<UsbDeviceImpl> device =
static_cast<UsbDeviceImpl*>(device_handle->GetDevice().get());
if (landing_page.is_valid())
device->set_webusb_landing_page(landing_page);
barrier.Run();
}
void OnDeviceOpenedReadDescriptors(
uint8_t manufacturer,
uint8_t product,
uint8_t serial_number,
bool read_bos_descriptors,
const base::Closure& success_closure,
const base::Closure& failure_closure,
scoped_refptr<UsbDeviceHandle> device_handle) {
if (device_handle) {
std::unique_ptr<std::map<uint8_t, base::string16>> string_map(
new std::map<uint8_t, base::string16>());
if (manufacturer != 0)
(*string_map)[manufacturer] = base::string16();
if (product != 0)
(*string_map)[product] = base::string16();
if (serial_number != 0)
(*string_map)[serial_number] = base::string16();
int count = 0;
if (!string_map->empty())
count++;
if (read_bos_descriptors)
count++;
DCHECK_GT(count, 0);
base::Closure barrier =
base::BarrierClosure(count, base::Bind(&CloseHandleAndRunContinuation,
device_handle, success_closure));
if (!string_map->empty()) {
scoped_refptr<UsbDeviceImpl> device =
static_cast<UsbDeviceImpl*>(device_handle->GetDevice().get());
ReadUsbStringDescriptors(
device_handle, std::move(string_map),
base::Bind(&SaveStringsAndRunContinuation, device, manufacturer,
product, serial_number, barrier));
}
if (read_bos_descriptors) {
ReadWebUsbDescriptors(device_handle, base::Bind(&OnReadBosDescriptor,
device_handle, barrier));
}
} else {
failure_closure.Run();
}
}
} // namespace
UsbServiceImpl::UsbServiceImpl()
: UsbService(nullptr),
#if defined(OS_WIN)
device_observer_(this),
#endif
weak_factory_(this) {
base::PostTaskWithTraits(
FROM_HERE, kBlockingTaskTraits,
base::Bind(&InitializeUsbContextOnBlockingThread, task_runner(),
base::Bind(&UsbServiceImpl::OnUsbContext,
weak_factory_.GetWeakPtr())));
}
UsbServiceImpl::~UsbServiceImpl() {
if (hotplug_enabled_)
libusb_hotplug_deregister_callback(context_->context(), hotplug_handle_);
for (auto* platform_device : ignored_devices_)
libusb_unref_device(platform_device);
}
void UsbServiceImpl::GetDevices(const GetDevicesCallback& callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (usb_unavailable_) {
task_runner()->PostTask(
FROM_HERE,
base::Bind(callback, std::vector<scoped_refptr<UsbDevice>>()));
return;
}
if (hotplug_enabled_ && !enumeration_in_progress_) {
// The device list is updated live when hotplug events are supported.
UsbService::GetDevices(callback);
} else {
pending_enumeration_callbacks_.push_back(callback);
RefreshDevices();
}
}
#if defined(OS_WIN)
void UsbServiceImpl::OnDeviceAdded(const GUID& class_guid,
const std::string& device_path) {
// Only the root node of a composite USB device has the class GUID
// GUID_DEVINTERFACE_USB_DEVICE but we want to wait until WinUSB is loaded.
// This first pass filter will catch anything that's sitting on the USB bus
// (including devices on 3rd party USB controllers) to avoid the more
// expensive driver check that needs to be done on the FILE thread.
if (device_path.find("usb") != std::string::npos) {
pending_path_enumerations_.push(device_path);
RefreshDevices();
}
}
void UsbServiceImpl::OnDeviceRemoved(const GUID& class_guid,
const std::string& device_path) {
// The root USB device node is removed last.
if (class_guid == GUID_DEVINTERFACE_USB_DEVICE) {
RefreshDevices();
}
}
#endif // OS_WIN
void UsbServiceImpl::OnUsbContext(scoped_refptr<UsbContext> context) {
if (!context) {
usb_unavailable_ = true;
return;
}
context_ = std::move(context);
int rv = libusb_hotplug_register_callback(
context_->context(),
static_cast<libusb_hotplug_event>(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT),
static_cast<libusb_hotplug_flag>(0), LIBUSB_HOTPLUG_MATCH_ANY,
LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
&UsbServiceImpl::HotplugCallback, this, &hotplug_handle_);
if (rv == LIBUSB_SUCCESS)
hotplug_enabled_ = true;
// This will call any enumeration callbacks queued while initializing.
RefreshDevices();
#if defined(OS_WIN)
DeviceMonitorWin* device_monitor = DeviceMonitorWin::GetForAllInterfaces();
if (device_monitor)
device_observer_.Add(device_monitor);
#endif // OS_WIN
}
void UsbServiceImpl::RefreshDevices() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!context_ || enumeration_in_progress_)
return;
enumeration_in_progress_ = true;
DCHECK(devices_being_enumerated_.empty());
std::string device_path;
if (!pending_path_enumerations_.empty()) {
device_path = pending_path_enumerations_.front();
pending_path_enumerations_.pop();
}
base::PostTaskWithTraits(FROM_HERE, kBlockingTaskTraits,
base::Bind(&GetDeviceListOnBlockingThread,
device_path, context_, task_runner(),
base::Bind(&UsbServiceImpl::OnDeviceList,
weak_factory_.GetWeakPtr())));
}
void UsbServiceImpl::OnDeviceList(libusb_device** platform_devices,
size_t device_count) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!platform_devices) {
RefreshDevicesComplete();
return;
}
base::Closure refresh_complete =
base::BarrierClosure(static_cast<int>(device_count),
base::Bind(&UsbServiceImpl::RefreshDevicesComplete,
weak_factory_.GetWeakPtr()));
std::list<PlatformUsbDevice> new_devices;
std::set<PlatformUsbDevice> existing_ignored_devices;
// Look for new and existing devices.
for (size_t i = 0; i < device_count; ++i) {
PlatformUsbDevice platform_device = platform_devices[i];
// Ignore some devices.
if (base::ContainsKey(ignored_devices_, platform_device)) {
existing_ignored_devices.insert(platform_device);
refresh_complete.Run();
continue;
}
auto it = platform_devices_.find(platform_device);
if (it == platform_devices_.end()) {
new_devices.push_back(platform_device);
} else {
it->second->set_visited(true);
refresh_complete.Run();
}
}
// Remove devices not seen in this enumeration.
for (PlatformDeviceMap::iterator it = platform_devices_.begin();
it != platform_devices_.end();
/* incremented internally */) {
PlatformDeviceMap::iterator current = it++;
const scoped_refptr<UsbDeviceImpl>& device = current->second;
if (device->was_visited()) {
device->set_visited(false);
} else {
RemoveDevice(device);
}
}
// Remove devices not seen in this enumeration from |ignored_devices_|.
for (auto it = ignored_devices_.begin(); it != ignored_devices_.end();
/* incremented internally */) {
auto current = it++;
if (!base::ContainsKey(existing_ignored_devices, *current)) {
libusb_unref_device(*current);
ignored_devices_.erase(current);
}
}
for (PlatformUsbDevice platform_device : new_devices) {
EnumerateDevice(platform_device, refresh_complete);
}
libusb_free_device_list(platform_devices, true);
}
void UsbServiceImpl::RefreshDevicesComplete() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(enumeration_in_progress_);
enumeration_ready_ = true;
enumeration_in_progress_ = false;
devices_being_enumerated_.clear();
if (!pending_enumeration_callbacks_.empty()) {
std::vector<scoped_refptr<UsbDevice>> result;
result.reserve(devices().size());
for (const auto& map_entry : devices())
result.push_back(map_entry.second);
std::vector<GetDevicesCallback> callbacks;
callbacks.swap(pending_enumeration_callbacks_);
for (const GetDevicesCallback& callback : callbacks)
callback.Run(result);
}
if (!pending_path_enumerations_.empty()) {
RefreshDevices();
}
}
void UsbServiceImpl::EnumerateDevice(PlatformUsbDevice platform_device,
const base::Closure& refresh_complete) {
DCHECK(context_);
devices_being_enumerated_.insert(platform_device);
libusb_device_descriptor descriptor;
int rv = libusb_get_device_descriptor(platform_device, &descriptor);
if (rv == LIBUSB_SUCCESS) {
if (descriptor.bDeviceClass == LIBUSB_CLASS_HUB) {
// Don't try to enumerate hubs. We never want to connect to a hub.
libusb_ref_device(platform_device);
ignored_devices_.insert(platform_device);
refresh_complete.Run();
return;
}
scoped_refptr<UsbDeviceImpl> device(
new UsbDeviceImpl(context_, platform_device, descriptor));
base::Closure add_device =
base::Bind(&UsbServiceImpl::AddDevice, weak_factory_.GetWeakPtr(),
refresh_complete, device);
base::Closure enumeration_failed = base::Bind(
&UsbServiceImpl::EnumerationFailed, weak_factory_.GetWeakPtr(),
platform_device, refresh_complete);
bool read_bos_descriptors = descriptor.bcdUSB >= kUsbVersion2_1;
if (descriptor.iManufacturer == 0 && descriptor.iProduct == 0 &&
descriptor.iSerialNumber == 0 && !read_bos_descriptors) {
// Don't bother disturbing the device if it has no descriptors to offer.
add_device.Run();
} else {
device->Open(base::Bind(&OnDeviceOpenedReadDescriptors,
descriptor.iManufacturer, descriptor.iProduct,
descriptor.iSerialNumber, read_bos_descriptors,
add_device, enumeration_failed));
}
} else {
USB_LOG(EVENT) << "Failed to get device descriptor: "
<< ConvertPlatformUsbErrorToString(rv);
refresh_complete.Run();
}
}
void UsbServiceImpl::AddDevice(const base::Closure& refresh_complete,
scoped_refptr<UsbDeviceImpl> device) {
auto it = devices_being_enumerated_.find(device->platform_device());
if (it == devices_being_enumerated_.end()) {
// Device was removed while being enumerated.
refresh_complete.Run();
return;
}
platform_devices_[device->platform_device()] = device;
DCHECK(!base::ContainsKey(devices(), device->guid()));
devices()[device->guid()] = device;
USB_LOG(USER) << "USB device added: vendor=" << device->vendor_id() << " \""
<< device->manufacturer_string()
<< "\", product=" << device->product_id() << " \""
<< device->product_string() << "\", serial=\""
<< device->serial_number() << "\", guid=" << device->guid();
if (enumeration_ready_) {
NotifyDeviceAdded(device);
}
refresh_complete.Run();
}
void UsbServiceImpl::RemoveDevice(scoped_refptr<UsbDeviceImpl> device) {
platform_devices_.erase(device->platform_device());
devices().erase(device->guid());
USB_LOG(USER) << "USB device removed: guid=" << device->guid();
NotifyDeviceRemoved(device);
device->OnDisconnect();
}
// static
int LIBUSB_CALL UsbServiceImpl::HotplugCallback(libusb_context* context,
PlatformUsbDevice device,
libusb_hotplug_event event,
void* user_data) {
// It is safe to access the UsbServiceImpl* here because libusb takes a lock
// around registering, deregistering and calling hotplug callback functions
// and so guarantees that this function will not be called by the event
// processing thread after it has been deregistered.
UsbServiceImpl* self = reinterpret_cast<UsbServiceImpl*>(user_data);
DCHECK(!self->task_runner()->BelongsToCurrentThread());
switch (event) {
case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
libusb_ref_device(device); // Released in OnPlatformDeviceAdded.
self->task_runner()->PostTask(
FROM_HERE, base::Bind(&UsbServiceImpl::OnPlatformDeviceAdded,
base::Unretained(self), device));
break;
case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
libusb_ref_device(device); // Released in OnPlatformDeviceRemoved.
self->task_runner()->PostTask(
FROM_HERE, base::Bind(&UsbServiceImpl::OnPlatformDeviceRemoved,
base::Unretained(self), device));
break;
default:
NOTREACHED();
}
return 0;
}
void UsbServiceImpl::OnPlatformDeviceAdded(PlatformUsbDevice platform_device) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!base::ContainsKey(platform_devices_, platform_device));
EnumerateDevice(platform_device, base::Bind(&base::DoNothing));
libusb_unref_device(platform_device);
}
void UsbServiceImpl::OnPlatformDeviceRemoved(
PlatformUsbDevice platform_device) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PlatformDeviceMap::iterator it = platform_devices_.find(platform_device);
if (it != platform_devices_.end()) {
RemoveDevice(it->second);
} else {
devices_being_enumerated_.erase(platform_device);
}
libusb_unref_device(platform_device);
}
void UsbServiceImpl::EnumerationFailed(PlatformUsbDevice platform_device,
const base::Closure& refresh_complete) {
libusb_ref_device(platform_device);
ignored_devices_.insert(platform_device);
refresh_complete.Run();
}
} // namespace device