 Chromium Code Reviews
 Chromium Code Reviews Issue 803653003:
  Use USB hotplug events from libusb when possible.  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master
    
  
    Issue 803653003:
  Use USB hotplug events from libusb when possible.  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master| Index: device/usb/usb_service_impl.cc | 
| diff --git a/device/usb/usb_service_impl.cc b/device/usb/usb_service_impl.cc | 
| index 215aba1817af302542804f3c510ecc01aa08706e..8542d05f413aea010c7d1d383410db9b162dcd66 100644 | 
| --- a/device/usb/usb_service_impl.cc | 
| +++ b/device/usb/usb_service_impl.cc | 
| @@ -7,10 +7,12 @@ | 
| #include <map> | 
| #include <set> | 
| +#include "base/bind.h" | 
| #include "base/lazy_instance.h" | 
| #include "base/message_loop/message_loop.h" | 
| #include "base/single_thread_task_runner.h" | 
| #include "base/stl_util.h" | 
| +#include "base/thread_task_runner_handle.h" | 
| #include "device/usb/usb_context.h" | 
| #include "device/usb/usb_device_impl.h" | 
| #include "device/usb/usb_error.h" | 
| @@ -47,13 +49,26 @@ class UsbServiceImpl : public UsbService, | 
| // Enumerate USB devices from OS and update devices_ map. | 
| void RefreshDevices(); | 
| + static int LIBUSB_CALL HotplugCallback(libusb_context* context, | 
| + libusb_device* device, | 
| + libusb_hotplug_event event, | 
| + void* user_data); | 
| + void OnDeviceAdded(PlatformUsbDevice device); | 
| + void OnDeviceRemoved(PlatformUsbDevice device); | 
| + | 
| scoped_refptr<UsbContext> context_; | 
| scoped_refptr<base::SingleThreadTaskRunner> task_runner_; | 
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner_; | 
| - // TODO(reillyg): Figure out a better solution. | 
| + // TODO(reillyg): Figure out a better solution for device IDs. | 
| uint32 next_unique_id_; | 
| + // When available the device list will be updated when new devices are | 
| + // connected instead of only when a full enumeration is requested. | 
| + // TODO(reillyg): Support this on all platforms. crbug.com/411715 | 
| + bool hotplug_enabled_; | 
| + libusb_hotplug_callback_handle hotplug_handle_; | 
| + | 
| // The map from unique IDs to UsbDevices. | 
| typedef std::map<uint32, scoped_refptr<UsbDeviceImpl> > DeviceMap; | 
| DeviceMap devices_; | 
| @@ -80,10 +95,13 @@ void UsbServiceImpl::GetDevices( | 
| std::vector<scoped_refptr<UsbDevice> >* devices) { | 
| DCHECK(CalledOnValidThread()); | 
| STLClearObject(devices); | 
| - RefreshDevices(); | 
| - for (DeviceMap::iterator it = devices_.begin(); it != devices_.end(); ++it) { | 
| - devices->push_back(it->second); | 
| + if (!hotplug_enabled_) { | 
| + RefreshDevices(); | 
| + } | 
| + | 
| + for (const auto& map_entry : devices_) { | 
| + devices->push_back(map_entry.second); | 
| } | 
| } | 
| @@ -97,14 +115,29 @@ UsbServiceImpl::UsbServiceImpl( | 
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) | 
| : context_(new UsbContext(context)), | 
| ui_task_runner_(ui_task_runner), | 
| - next_unique_id_(0) { | 
| + next_unique_id_(0), | 
| + hotplug_enabled_(false) { | 
| base::MessageLoop::current()->AddDestructionObserver(this); | 
| + task_runner_ = base::ThreadTaskRunnerHandle::Get(); | 
| + int rv = libusb_hotplug_register_callback( | 
| + context_->context(), | 
| + static_cast<libusb_hotplug_event>(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; | 
| + } | 
| } | 
| UsbServiceImpl::~UsbServiceImpl() { | 
| base::MessageLoop::current()->RemoveDestructionObserver(this); | 
| - for (DeviceMap::iterator it = devices_.begin(); it != devices_.end(); ++it) { | 
| - it->second->OnDisconnect(); | 
| + if (hotplug_enabled_) { | 
| + libusb_hotplug_deregister_callback(context_->context(), hotplug_handle_); | 
| + } | 
| + for (const auto& map_entry : devices_) { | 
| + map_entry.second->OnDisconnect(); | 
| } | 
| } | 
| @@ -177,6 +210,82 @@ void UsbServiceImpl::RefreshDevices() { | 
| } | 
| // static | 
| +int LIBUSB_CALL UsbServiceImpl::HotplugCallback(libusb_context* context, | 
| + libusb_device* device, | 
| + libusb_hotplug_event event, | 
| + void* user_data) { | 
| + UsbServiceImpl* self = reinterpret_cast<UsbServiceImpl*>(user_data); | 
| + switch (event) { | 
| + case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: | 
| + if (self->task_runner_->BelongsToCurrentThread()) { | 
| + self->OnDeviceAdded(device); | 
| + } else { | 
| + self->task_runner_->PostTask( | 
| + FROM_HERE, base::Bind(&UsbServiceImpl::OnDeviceAdded, | 
| + base::Unretained(self), device)); | 
| 
rpaquay
2014/12/18 21:08:54
nit: Comment why using Unretained is ok?
Also, how
 
Reilly Grant (use Gerrit)
2014/12/18 22:28:13
libusb is nice and guarantees this is safe for us.
 | 
| + } | 
| + break; | 
| + case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: | 
| + if (self->task_runner_->BelongsToCurrentThread()) { | 
| + self->OnDeviceRemoved(device); | 
| + } else { | 
| + self->task_runner_->PostTask( | 
| + FROM_HERE, base::Bind(&UsbServiceImpl::OnDeviceRemoved, | 
| + base::Unretained(self), device)); | 
| 
rpaquay
2014/12/18 21:08:54
nit: Comment why using Unretained is ok?
Also, how
 | 
| + } | 
| + break; | 
| + default: | 
| + NOTREACHED(); | 
| + } | 
| + | 
| + return 0; | 
| +} | 
| + | 
| +void UsbServiceImpl::OnDeviceAdded(PlatformUsbDevice platform_device) { | 
| + DCHECK(CalledOnValidThread()); | 
| + DCHECK(!ContainsKey(platform_devices_, platform_device)); | 
| + | 
| + libusb_device_descriptor descriptor; | 
| + const int rv = libusb_get_device_descriptor(platform_device, &descriptor); | 
| + // This test is needed. A valid vendor/produce pair is required. | 
| + if (rv != LIBUSB_SUCCESS) { | 
| + VLOG(1) << "Failed to get device descriptor: " | 
| + << ConvertPlatformUsbErrorToString(rv); | 
| + return; | 
| + } | 
| + | 
| + uint32 unique_id; | 
| + do { | 
| + unique_id = ++next_unique_id_; | 
| + } while (devices_.find(unique_id) != devices_.end()); | 
| + | 
| + scoped_refptr<UsbDeviceImpl> 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; | 
| +} | 
| + | 
| +void UsbServiceImpl::OnDeviceRemoved(PlatformUsbDevice platform_device) { | 
| + DCHECK(CalledOnValidThread()); | 
| + | 
| + PlatformDeviceMap::iterator it = platform_devices_.find(platform_device); | 
| + if (it != platform_devices_.end()) { | 
| + scoped_refptr<UsbDeviceImpl> device = it->second; | 
| + DeviceMap::iterator dev_it = devices_.find(device->unique_id()); | 
| + if (dev_it != devices_.end()) { | 
| + devices_.erase(dev_it); | 
| + } else { | 
| + NOTREACHED(); | 
| + } | 
| + device->OnDisconnect(); | 
| + platform_devices_.erase(it); | 
| + } else { | 
| + NOTREACHED(); | 
| + } | 
| +} | 
| + | 
| +// static | 
| UsbService* UsbService::GetInstance( | 
| scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner) { | 
| UsbService* instance = g_usb_service_instance.Get().get(); |