// 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 #include "base/bind.h" #include "base/location.h" #include "base/memory/weak_ptr.h" #include "base/single_thread_task_runner.h" #include "base/stl_util.h" #include "base/thread_task_runner_handle.h" #include "components/device_event_log/device_event_log.h" #include "device/usb/usb_error.h" #if defined(OS_WIN) #include #include "base/scoped_observer.h" #include "device/core/device_monitor_win.h" #endif // OS_WIN namespace device { #if defined(OS_WIN) // This class lives on the application main thread so that it can listen for // device change notification window messages. It registers for notifications // regarding devices implementating the "UsbDevice" interface, which represents // most of the devices the UsbService will enumerate. class UsbServiceImpl::UIThreadHelper final : private DeviceMonitorWin::Observer { public: UIThreadHelper(base::WeakPtr usb_service) : task_runner_(base::ThreadTaskRunnerHandle::Get()), usb_service_(usb_service), device_observer_(this) {} ~UIThreadHelper() {} void Start() { DeviceMonitorWin* device_monitor = DeviceMonitorWin::GetForDeviceInterface(GUID_DEVINTERFACE_USB_DEVICE); if (device_monitor) { device_observer_.Add(device_monitor); } } private: void OnDeviceAdded(const std::string& device_path) override { task_runner_->PostTask( FROM_HERE, base::Bind(&UsbServiceImpl::RefreshDevices, usb_service_)); } void OnDeviceRemoved(const std::string& device_path) override { task_runner_->PostTask( FROM_HERE, base::Bind(&UsbServiceImpl::RefreshDevices, usb_service_)); } scoped_refptr task_runner_; base::WeakPtr usb_service_; ScopedObserver device_observer_; }; #endif // static UsbService* UsbServiceImpl::Create( scoped_refptr ui_task_runner) { PlatformUsbContext context = NULL; const int rv = libusb_init(&context); if (rv != LIBUSB_SUCCESS) { USB_LOG(ERROR) << "Failed to initialize libusb: " << ConvertPlatformUsbErrorToString(rv); return nullptr; } if (!context) { return nullptr; } return new UsbServiceImpl(context, ui_task_runner); } scoped_refptr UsbServiceImpl::GetDeviceById(uint32 unique_id) { DCHECK(CalledOnValidThread()); RefreshDevices(); DeviceMap::iterator it = devices_.find(unique_id); if (it != devices_.end()) { return it->second; } return NULL; } void UsbServiceImpl::GetDevices( std::vector >* devices) { DCHECK(CalledOnValidThread()); STLClearObject(devices); if (!hotplug_enabled_) { RefreshDevices(); } for (const auto& map_entry : devices_) { devices->push_back(map_entry.second); } } UsbServiceImpl::UsbServiceImpl( PlatformUsbContext context, scoped_refptr ui_task_runner) : context_(new UsbContext(context)), ui_task_runner_(ui_task_runner), next_unique_id_(0), hotplug_enabled_(false), weak_factory_(this) { task_runner_ = base::ThreadTaskRunnerHandle::Get(); int rv = libusb_hotplug_register_callback( context_->context(), static_cast(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), LIBUSB_HOTPLUG_ENUMERATE, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY, &UsbServiceImpl::HotplugCallback, this, &hotplug_handle_); if (rv == LIBUSB_SUCCESS) { hotplug_enabled_ = true; } else { #if defined(OS_WIN) ui_thread_helper_ = new UIThreadHelper(weak_factory_.GetWeakPtr()); ui_task_runner_->PostTask(FROM_HERE, base::Bind(&UIThreadHelper::Start, base::Unretained(ui_thread_helper_))); #endif // OS_WIN } } UsbServiceImpl::~UsbServiceImpl() { if (hotplug_enabled_) { libusb_hotplug_deregister_callback(context_->context(), hotplug_handle_); } #if defined(OS_WIN) if (ui_thread_helper_) { ui_task_runner_->DeleteSoon(FROM_HERE, ui_thread_helper_); } #endif // OS_WIN for (const auto& map_entry : devices_) { map_entry.second->OnDisconnect(); } } void UsbServiceImpl::RefreshDevices() { DCHECK(CalledOnValidThread()); libusb_device** platform_devices = NULL; const ssize_t device_count = libusb_get_device_list(context_->context(), &platform_devices); if (device_count < 0) { USB_LOG(ERROR) << "Failed to get device list: " << ConvertPlatformUsbErrorToString(device_count); } std::set connected_devices; std::vector disconnected_devices; // Populates new devices. for (ssize_t i = 0; i < device_count; ++i) { if (!ContainsKey(platform_devices_, platform_devices[i])) { scoped_refptr new_device = AddDevice(platform_devices[i]); if (new_device) { connected_devices.insert(new_device.get()); } } else { connected_devices.insert(platform_devices_[platform_devices[i]].get()); } } // Find disconnected devices. for (const auto& map_entry : platform_devices_) { PlatformUsbDevice platform_device = map_entry.first; scoped_refptr device = map_entry.second; if (!ContainsKey(connected_devices, device.get())) { disconnected_devices.push_back(platform_device); devices_.erase(device->unique_id()); NotifyDeviceRemoved(device); device->OnDisconnect(); } } // Remove disconnected devices from platform_devices_. for (const PlatformUsbDevice& platform_device : disconnected_devices) { // UsbDevice will be destroyed after this. The corresponding // PlatformUsbDevice will be unref'ed during this process. platform_devices_.erase(platform_device); } libusb_free_device_list(platform_devices, true); } scoped_refptr UsbServiceImpl::AddDevice( PlatformUsbDevice platform_device) { libusb_device_descriptor descriptor; int rv = libusb_get_device_descriptor(platform_device, &descriptor); if (rv == LIBUSB_SUCCESS) { uint32 unique_id; do { unique_id = ++next_unique_id_; } while (devices_.find(unique_id) != devices_.end()); scoped_refptr new_device(new UsbDeviceImpl( context_, ui_task_runner_, platform_device, descriptor.idVendor, descriptor.idProduct, unique_id)); platform_devices_[platform_device] = new_device; devices_[unique_id] = new_device; NotifyDeviceAdded(new_device); return new_device; } else { USB_LOG(EVENT) << "Failed to get device descriptor: " << ConvertPlatformUsbErrorToString(rv); return nullptr; } } // 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(user_data); switch (event) { case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: libusb_ref_device(device); // Released in OnDeviceAdded. if (self->task_runner_->BelongsToCurrentThread()) { self->OnDeviceAdded(device); } else { self->task_runner_->PostTask( FROM_HERE, base::Bind(&UsbServiceImpl::OnDeviceAdded, base::Unretained(self), device)); } break; case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: libusb_ref_device(device); // Released in OnDeviceRemoved. if (self->task_runner_->BelongsToCurrentThread()) { self->OnDeviceRemoved(device); } else { self->task_runner_->PostTask( FROM_HERE, base::Bind(&UsbServiceImpl::OnDeviceRemoved, base::Unretained(self), device)); } break; default: NOTREACHED(); } return 0; } void UsbServiceImpl::OnDeviceAdded(PlatformUsbDevice platform_device) { DCHECK(CalledOnValidThread()); DCHECK(!ContainsKey(platform_devices_, platform_device)); AddDevice(platform_device); libusb_unref_device(platform_device); } void UsbServiceImpl::OnDeviceRemoved(PlatformUsbDevice platform_device) { DCHECK(CalledOnValidThread()); PlatformDeviceMap::iterator it = platform_devices_.find(platform_device); if (it != platform_devices_.end()) { scoped_refptr device = it->second; DeviceMap::iterator dev_it = devices_.find(device->unique_id()); if (dev_it != devices_.end()) { devices_.erase(dev_it); } else { NOTREACHED(); } platform_devices_.erase(it); NotifyDeviceRemoved(device); device->OnDisconnect(); } else { NOTREACHED(); } libusb_unref_device(platform_device); } } // namespace device