blob: 9590eb2858fa8eae6a20d3a7a15c415e69af4aca [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/bind_helpers.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/post_task.h"
#include "base/threading/scoped_blocking_call.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
scoped_refptr<UsbContext> InitializeUsbContextBlocking() {
PlatformUsbContext platform_context = nullptr;
int rv = libusb_init(&platform_context);
if (rv == LIBUSB_SUCCESS && platform_context) {
return base::MakeRefCounted<UsbContext>(platform_context);
}
USB_LOG(DEBUG) << "Failed to initialize libusb: "
<< ConvertPlatformUsbErrorToString(rv);
return nullptr;
}
base::Optional<std::vector<ScopedLibusbDeviceRef>> GetDeviceListBlocking(
const std::string& new_device_path,
scoped_refptr<UsbContext> usb_context) {
base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK);
#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.
return base::nullopt;
}
}
#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);
return base::nullopt;
}
std::vector<ScopedLibusbDeviceRef> scoped_devices;
scoped_devices.reserve(device_count);
for (ssize_t i = 0; i < device_count; ++i)
scoped_devices.emplace_back(platform_devices[i], usb_context);
// Free the list but don't unref the devices because ownership has been
// been transfered to the elements of |scoped_devices|.
libusb_free_device_list(platform_devices, false);
return scoped_devices;
}
void CloseHandleAndRunContinuation(scoped_refptr<UsbDeviceHandle> device_handle,
base::OnceClosure continuation) {
device_handle->Close();
std::move(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,
base::OnceClosure success_closure,
base::OnceClosure 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::RepeatingClosure barrier = base::BarrierClosure(
count, base::BindOnce(&CloseHandleAndRunContinuation, device_handle,
std::move(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 {
std::move(failure_closure).Run();
}
}
} // namespace
UsbServiceImpl::UsbServiceImpl()
: UsbService(),
task_runner_(base::SequencedTaskRunnerHandle::Get()),
#if defined(OS_WIN)
device_observer_(this),
#endif
weak_factory_(this) {
weak_self_ = weak_factory_.GetWeakPtr();
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, kBlockingTaskTraits,
base::BindOnce(&InitializeUsbContextBlocking),
base::BindOnce(&UsbServiceImpl::OnUsbContext,
weak_factory_.GetWeakPtr()));
}
UsbServiceImpl::~UsbServiceImpl() {
if (hotplug_enabled_)
libusb_hotplug_deregister_callback(context_->context(), hotplug_handle_);
}
void UsbServiceImpl::GetDevices(const GetDevicesCallback& callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (usb_unavailable_) {
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(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::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE, kBlockingTaskTraits,
base::BindOnce(&GetDeviceListBlocking, device_path, context_),
base::BindOnce(&UsbServiceImpl::OnDeviceList,
weak_factory_.GetWeakPtr()));
}
void UsbServiceImpl::OnDeviceList(
base::Optional<std::vector<ScopedLibusbDeviceRef>> devices) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!devices) {
RefreshDevicesComplete();
return;
}
std::vector<ScopedLibusbDeviceRef> new_devices;
// Look for new and existing devices.
for (auto& device : *devices) {
// Ignore devices that have failed enumeration previously.
if (base::ContainsValue(ignored_devices_, device.get()))
continue;
auto it = platform_devices_.find(device.get());
if (it == platform_devices_.end()) {
new_devices.push_back(std::move(device));
} else {
// Mark the existing device object visited and remove it from the list so
// it will not be ignored.
it->second->set_visited(true);
device.Reset();
}
}
// 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);
}
}
// Remaining devices are being ignored. Clear the old list so that devices
// that have been removed don't remain in |ignored_devices_| indefinitely.
ignored_devices_.clear();
for (auto& device : *devices) {
if (device.IsValid())
ignored_devices_.push_back(std::move(device));
}
// Enumerate new devices.
base::RepeatingClosure refresh_complete = base::BarrierClosure(
new_devices.size(),
base::BindOnce(&UsbServiceImpl::RefreshDevicesComplete,
weak_factory_.GetWeakPtr()));
for (auto& device : new_devices)
EnumerateDevice(std::move(device), refresh_complete);
}
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(ScopedLibusbDeviceRef platform_device,
const base::Closure& refresh_complete) {
DCHECK(context_);
libusb_device_descriptor descriptor;
int rv = libusb_get_device_descriptor(platform_device.get(), &descriptor);
if (rv != LIBUSB_SUCCESS) {
USB_LOG(EVENT) << "Failed to get device descriptor: "
<< ConvertPlatformUsbErrorToString(rv);
EnumerationFailed(std::move(platform_device), refresh_complete);
return;
}
if (descriptor.bDeviceClass == LIBUSB_CLASS_HUB) {
// Don't try to enumerate hubs. We never want to connect to a hub.
EnumerationFailed(std::move(platform_device), refresh_complete);
return;
}
devices_being_enumerated_.insert(platform_device.get());
auto device = base::MakeRefCounted<UsbDeviceImpl>(std::move(platform_device),
descriptor);
base::OnceClosure add_device =
base::BindOnce(&UsbServiceImpl::AddDevice, weak_factory_.GetWeakPtr(),
refresh_complete, device);
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.
std::move(add_device).Run();
} else {
// Take an additional reference to the libusb_device object that will be
// owned by this callback.
libusb_ref_device(device->platform_device());
base::OnceClosure enumeration_failed = base::BindOnce(
&UsbServiceImpl::EnumerationFailed, weak_factory_.GetWeakPtr(),
ScopedLibusbDeviceRef(device->platform_device(), context_),
refresh_complete);
device->Open(base::BindOnce(
&OnDeviceOpenedReadDescriptors, descriptor.iManufacturer,
descriptor.iProduct, descriptor.iSerialNumber, read_bos_descriptors,
std::move(add_device), std::move(enumeration_failed)));
}
}
void UsbServiceImpl::AddDevice(const base::Closure& refresh_complete,
scoped_refptr<UsbDeviceImpl> device) {
if (!base::ContainsKey(devices_being_enumerated_,
device->platform_device())) {
// Device was removed while being enumerated.
refresh_complete.Run();
return;
}
DCHECK(!base::ContainsKey(platform_devices_, device->platform_device()));
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,
libusb_device* device_raw,
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);
// libusb does not transfer ownership of |device_raw| to this function so a
// reference must be taken here.
libusb_ref_device(device_raw);
ScopedLibusbDeviceRef device(device_raw, self->context_);
switch (event) {
case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
self->task_runner_->PostTask(
FROM_HERE, base::BindOnce(&UsbServiceImpl::OnPlatformDeviceAdded,
self->weak_self_, std::move(device)));
break;
case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
self->task_runner_->PostTask(
FROM_HERE, base::BindOnce(&UsbServiceImpl::OnPlatformDeviceRemoved,
self->weak_self_, std::move(device)));
break;
default:
NOTREACHED();
}
return 0;
}
void UsbServiceImpl::OnPlatformDeviceAdded(
ScopedLibusbDeviceRef platform_device) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!base::ContainsKey(platform_devices_, platform_device.get()));
EnumerateDevice(std::move(platform_device), base::DoNothing());
}
void UsbServiceImpl::OnPlatformDeviceRemoved(
ScopedLibusbDeviceRef platform_device) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = platform_devices_.find(platform_device.get());
if (it == platform_devices_.end())
devices_being_enumerated_.erase(platform_device.get());
else
RemoveDevice(it->second);
}
void UsbServiceImpl::EnumerationFailed(ScopedLibusbDeviceRef platform_device,
const base::Closure& refresh_complete) {
ignored_devices_.push_back(std::move(platform_device));
refresh_complete.Run();
}
} // namespace device