Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / device / usb / usb_service_impl.cc
blob2261ad6930bdd1b246daf7e3abc957a9994131a9
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "device/usb/usb_service_impl.h"
7 #include <algorithm>
8 #include <list>
9 #include <set>
11 #include "base/barrier_closure.h"
12 #include "base/bind.h"
13 #include "base/location.h"
14 #include "base/memory/weak_ptr.h"
15 #include "base/single_thread_task_runner.h"
16 #include "base/stl_util.h"
17 #include "base/strings/string_number_conversions.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "base/thread_task_runner_handle.h"
20 #include "components/device_event_log/device_event_log.h"
21 #include "device/usb/usb_device_handle.h"
22 #include "device/usb/usb_error.h"
23 #include "third_party/libusb/src/libusb/libusb.h"
25 #if defined(OS_WIN)
26 #include <setupapi.h>
27 #include <usbiodef.h>
29 #include "base/strings/string_util.h"
30 #endif // OS_WIN
32 #if defined(USE_UDEV)
33 #include "device/udev_linux/scoped_udev.h"
34 #endif // USE_UDEV
36 namespace device {
38 namespace {
40 #if defined(OS_WIN)
42 // Wrapper around a HDEVINFO that automatically destroys it.
43 class ScopedDeviceInfoList {
44 public:
45 explicit ScopedDeviceInfoList(HDEVINFO handle) : handle_(handle) {}
47 ~ScopedDeviceInfoList() {
48 if (valid()) {
49 SetupDiDestroyDeviceInfoList(handle_);
53 bool valid() { return handle_ != INVALID_HANDLE_VALUE; }
55 HDEVINFO get() { return handle_; }
57 private:
58 HDEVINFO handle_;
60 DISALLOW_COPY_AND_ASSIGN(ScopedDeviceInfoList);
63 // Wrapper around an SP_DEVINFO_DATA that initializes it properly and
64 // automatically deletes it.
65 class ScopedDeviceInfo {
66 public:
67 ScopedDeviceInfo() {
68 memset(&dev_info_data_, 0, sizeof(dev_info_data_));
69 dev_info_data_.cbSize = sizeof(dev_info_data_);
72 ~ScopedDeviceInfo() {
73 if (dev_info_set_ != INVALID_HANDLE_VALUE) {
74 SetupDiDeleteDeviceInfo(dev_info_set_, &dev_info_data_);
78 // Once the SP_DEVINFO_DATA has been populated it must be freed using the
79 // HDEVINFO it was created from.
80 void set_valid(HDEVINFO dev_info_set) {
81 DCHECK(dev_info_set_ == INVALID_HANDLE_VALUE);
82 DCHECK(dev_info_set != INVALID_HANDLE_VALUE);
83 dev_info_set_ = dev_info_set;
86 PSP_DEVINFO_DATA get() { return &dev_info_data_; }
88 private:
89 HDEVINFO dev_info_set_ = INVALID_HANDLE_VALUE;
90 SP_DEVINFO_DATA dev_info_data_;
93 bool IsWinUsbInterface(const std::string& device_path) {
94 ScopedDeviceInfoList dev_info_list(SetupDiCreateDeviceInfoList(NULL, NULL));
95 if (!dev_info_list.valid()) {
96 USB_PLOG(ERROR) << "Failed to create a device information set";
97 return false;
100 // This will add the device to |dev_info_list| so we can query driver info.
101 if (!SetupDiOpenDeviceInterfaceA(dev_info_list.get(), device_path.c_str(), 0,
102 NULL)) {
103 USB_PLOG(ERROR) << "Failed to get device interface data for "
104 << device_path;
105 return false;
108 ScopedDeviceInfo dev_info;
109 if (!SetupDiEnumDeviceInfo(dev_info_list.get(), 0, dev_info.get())) {
110 USB_PLOG(ERROR) << "Failed to get device info for " << device_path;
111 return false;
113 dev_info.set_valid(dev_info_list.get());
115 DWORD reg_data_type;
116 BYTE buffer[256];
117 if (!SetupDiGetDeviceRegistryPropertyA(dev_info_list.get(), dev_info.get(),
118 SPDRP_SERVICE, &reg_data_type,
119 &buffer[0], sizeof buffer, NULL)) {
120 USB_PLOG(ERROR) << "Failed to get device service property";
121 return false;
123 if (reg_data_type != REG_SZ) {
124 USB_LOG(ERROR) << "Unexpected data type for driver service: "
125 << reg_data_type;
126 return false;
129 USB_LOG(DEBUG) << "Driver for " << device_path << " is " << buffer << ".";
130 if (base::StartsWith(reinterpret_cast<const char*>(buffer), "WinUSB",
131 base::CompareCase::INSENSITIVE_ASCII))
132 return true;
133 return false;
136 #endif // OS_WIN
138 void GetDeviceListOnBlockingThread(
139 const std::string& new_device_path,
140 scoped_refptr<UsbContext> usb_context,
141 scoped_refptr<base::SequencedTaskRunner> task_runner,
142 base::Callback<void(libusb_device**, size_t)> callback) {
143 #if defined(OS_WIN)
144 if (!new_device_path.empty()) {
145 if (!IsWinUsbInterface(new_device_path)) {
146 // Wait to call libusb_get_device_list until libusb will be able to find
147 // a WinUSB interface for the device.
148 task_runner->PostTask(FROM_HERE, base::Bind(callback, nullptr, 0));
149 return;
152 #endif // defined(OS_WIN)
154 libusb_device** platform_devices = NULL;
155 const ssize_t device_count =
156 libusb_get_device_list(usb_context->context(), &platform_devices);
157 if (device_count < 0) {
158 USB_LOG(ERROR) << "Failed to get device list: "
159 << ConvertPlatformUsbErrorToString(device_count);
160 task_runner->PostTask(FROM_HERE, base::Bind(callback, nullptr, 0));
161 return;
164 task_runner->PostTask(FROM_HERE,
165 base::Bind(callback, platform_devices, device_count));
168 #if defined(USE_UDEV)
170 void EnumerateUdevDevice(scoped_refptr<UsbDeviceImpl> device,
171 scoped_refptr<base::SequencedTaskRunner> task_runner,
172 const base::Closure& success_closure,
173 const base::Closure& failure_closure) {
174 ScopedUdevPtr udev(udev_new());
175 ScopedUdevEnumeratePtr udev_enumerate(udev_enumerate_new(udev.get()));
177 udev_enumerate_add_match_subsystem(udev_enumerate.get(), "usb");
178 if (udev_enumerate_scan_devices(udev_enumerate.get()) != 0) {
179 task_runner->PostTask(FROM_HERE, failure_closure);
180 return;
183 std::string bus_number =
184 base::IntToString(libusb_get_bus_number(device->platform_device()));
185 std::string device_address =
186 base::IntToString(libusb_get_device_address(device->platform_device()));
187 udev_list_entry* devices =
188 udev_enumerate_get_list_entry(udev_enumerate.get());
189 for (udev_list_entry* i = devices; i != NULL;
190 i = udev_list_entry_get_next(i)) {
191 ScopedUdevDevicePtr udev_device(
192 udev_device_new_from_syspath(udev.get(), udev_list_entry_get_name(i)));
193 if (udev_device) {
194 const char* value =
195 udev_device_get_sysattr_value(udev_device.get(), "busnum");
196 if (!value || bus_number != value) {
197 continue;
199 value = udev_device_get_sysattr_value(udev_device.get(), "devnum");
200 if (!value || device_address != value) {
201 continue;
204 value = udev_device_get_sysattr_value(udev_device.get(), "manufacturer");
205 if (value) {
206 device->set_manufacturer_string(base::UTF8ToUTF16(value));
208 value = udev_device_get_sysattr_value(udev_device.get(), "product");
209 if (value) {
210 device->set_product_string(base::UTF8ToUTF16(value));
212 value = udev_device_get_sysattr_value(udev_device.get(), "serial");
213 if (value) {
214 device->set_serial_number(base::UTF8ToUTF16(value));
217 value = udev_device_get_devnode(udev_device.get());
218 if (value) {
219 device->set_device_path(value);
220 task_runner->PostTask(FROM_HERE, success_closure);
221 return;
224 break;
228 task_runner->PostTask(FROM_HERE, failure_closure);
231 #else
233 void OnReadStringDescriptor(
234 const base::Callback<void(const base::string16&)>& callback,
235 UsbTransferStatus status,
236 scoped_refptr<net::IOBuffer> buffer,
237 size_t length) {
238 if (status != USB_TRANSFER_COMPLETED || length < 2) {
239 callback.Run(base::string16());
240 } else {
241 // Take the lesser of the length of data returned by the device and the
242 // length reported in the descriptor.
243 size_t internal_length = reinterpret_cast<uint8*>(buffer->data())[0];
244 length = std::min(length, internal_length);
245 // Cut off the first 2 bytes of the descriptor which are the length and
246 // descriptor type (always STRING).
247 callback.Run(base::string16(
248 reinterpret_cast<base::char16*>(buffer->data() + 2), length / 2 - 1));
252 void ReadStringDescriptor(
253 scoped_refptr<UsbDeviceHandle> device_handle,
254 uint8 index,
255 uint16 language_id,
256 const base::Callback<void(const base::string16&)>& callback) {
257 scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(256);
258 device_handle->ControlTransfer(
259 USB_DIRECTION_INBOUND, UsbDeviceHandle::STANDARD, UsbDeviceHandle::DEVICE,
260 6 /* GET_DESCRIPTOR */, 3 /* STRING */ << 8 | index, language_id, buffer,
261 256, 60, base::Bind(&OnReadStringDescriptor, callback));
264 void CloseHandleAndRunContinuation(scoped_refptr<UsbDeviceHandle> device_handle,
265 const base::Closure& continuation) {
266 device_handle->Close();
267 continuation.Run();
270 void SaveStringAndRunContinuation(
271 const base::Callback<void(const base::string16&)>& save_callback,
272 const base::Closure& continuation,
273 const base::string16& value) {
274 if (!value.empty()) {
275 save_callback.Run(value);
277 continuation.Run();
280 void OnReadLanguageIds(scoped_refptr<UsbDeviceHandle> device_handle,
281 uint8 manufacturer,
282 uint8 product,
283 uint8 serial_number,
284 const base::Closure& success_closure,
285 const base::string16& languages) {
286 // Default to English unless the device provides a language and then just pick
287 // the first one.
288 uint16 language_id = 0x0409;
289 if (!languages.empty()) {
290 language_id = languages[0];
293 scoped_refptr<UsbDeviceImpl> device =
294 static_cast<UsbDeviceImpl*>(device_handle->GetDevice().get());
295 base::Closure continuation =
296 base::BarrierClosure(3, base::Bind(&CloseHandleAndRunContinuation,
297 device_handle, success_closure));
299 if (manufacturer == 0) {
300 continuation.Run();
301 } else {
302 ReadStringDescriptor(
303 device_handle, manufacturer, language_id,
304 base::Bind(&SaveStringAndRunContinuation,
305 base::Bind(&UsbDeviceImpl::set_manufacturer_string, device),
306 continuation));
309 if (product == 0) {
310 continuation.Run();
311 } else {
312 ReadStringDescriptor(
313 device_handle, product, language_id,
314 base::Bind(&SaveStringAndRunContinuation,
315 base::Bind(&UsbDeviceImpl::set_product_string, device),
316 continuation));
319 if (serial_number == 0) {
320 continuation.Run();
321 } else {
322 ReadStringDescriptor(
323 device_handle, serial_number, language_id,
324 base::Bind(&SaveStringAndRunContinuation,
325 base::Bind(&UsbDeviceImpl::set_serial_number, device),
326 continuation));
330 void ReadDeviceLanguage(uint8 manufacturer,
331 uint8 product,
332 uint8 serial_number,
333 const base::Closure& success_closure,
334 const base::Closure& failure_closure,
335 scoped_refptr<UsbDeviceHandle> device_handle) {
336 if (device_handle) {
337 ReadStringDescriptor(
338 device_handle, 0, 0,
339 base::Bind(&OnReadLanguageIds, device_handle, manufacturer, product,
340 serial_number, success_closure));
341 } else {
342 failure_closure.Run();
346 #endif // USE_UDEV
348 } // namespace
350 // static
351 UsbService* UsbServiceImpl::Create(
352 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) {
353 PlatformUsbContext context = NULL;
354 const int rv = libusb_init(&context);
355 if (rv != LIBUSB_SUCCESS) {
356 USB_LOG(ERROR) << "Failed to initialize libusb: "
357 << ConvertPlatformUsbErrorToString(rv);
358 return nullptr;
360 if (!context) {
361 return nullptr;
364 return new UsbServiceImpl(context, blocking_task_runner);
367 UsbServiceImpl::UsbServiceImpl(
368 PlatformUsbContext context,
369 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
370 : context_(new UsbContext(context)),
371 task_runner_(base::ThreadTaskRunnerHandle::Get()),
372 blocking_task_runner_(blocking_task_runner),
373 #if defined(OS_WIN)
374 device_observer_(this),
375 #endif
376 weak_factory_(this) {
377 base::MessageLoop::current()->AddDestructionObserver(this);
379 int rv = libusb_hotplug_register_callback(
380 context_->context(),
381 static_cast<libusb_hotplug_event>(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
382 LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT),
383 static_cast<libusb_hotplug_flag>(0), LIBUSB_HOTPLUG_MATCH_ANY,
384 LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
385 &UsbServiceImpl::HotplugCallback, this, &hotplug_handle_);
386 if (rv == LIBUSB_SUCCESS) {
387 hotplug_enabled_ = true;
390 RefreshDevices();
391 #if defined(OS_WIN)
392 DeviceMonitorWin* device_monitor = DeviceMonitorWin::GetForAllInterfaces();
393 if (device_monitor) {
394 device_observer_.Add(device_monitor);
396 #endif // OS_WIN
399 UsbServiceImpl::~UsbServiceImpl() {
400 base::MessageLoop::current()->RemoveDestructionObserver(this);
402 if (hotplug_enabled_) {
403 libusb_hotplug_deregister_callback(context_->context(), hotplug_handle_);
405 for (const auto& map_entry : devices_) {
406 map_entry.second->OnDisconnect();
410 scoped_refptr<UsbDevice> UsbServiceImpl::GetDevice(const std::string& guid) {
411 DCHECK(CalledOnValidThread());
412 DeviceMap::iterator it = devices_.find(guid);
413 if (it != devices_.end()) {
414 return it->second;
416 return NULL;
419 void UsbServiceImpl::GetDevices(const GetDevicesCallback& callback) {
420 DCHECK(CalledOnValidThread());
422 if (hotplug_enabled_ && !enumeration_in_progress_) {
423 // The device list is updated live when hotplug events are supported.
424 std::vector<scoped_refptr<UsbDevice>> devices;
425 for (const auto& map_entry : devices_) {
426 devices.push_back(map_entry.second);
428 callback.Run(devices);
429 } else {
430 pending_enumeration_callbacks_.push_back(callback);
431 RefreshDevices();
435 #if defined(OS_WIN)
437 void UsbServiceImpl::OnDeviceAdded(const GUID& class_guid,
438 const std::string& device_path) {
439 // Only the root node of a composite USB device has the class GUID
440 // GUID_DEVINTERFACE_USB_DEVICE but we want to wait until WinUSB is loaded.
441 // This first pass filter will catch anything that's sitting on the USB bus
442 // (including devices on 3rd party USB controllers) to avoid the more
443 // expensive driver check that needs to be done on the FILE thread.
444 if (device_path.find("usb") != std::string::npos) {
445 pending_path_enumerations_.push(device_path);
446 RefreshDevices();
450 void UsbServiceImpl::OnDeviceRemoved(const GUID& class_guid,
451 const std::string& device_path) {
452 // The root USB device node is removed last.
453 if (class_guid == GUID_DEVINTERFACE_USB_DEVICE) {
454 RefreshDevices();
458 #endif // OS_WIN
460 void UsbServiceImpl::WillDestroyCurrentMessageLoop() {
461 DCHECK(CalledOnValidThread());
462 delete this;
465 void UsbServiceImpl::RefreshDevices() {
466 DCHECK(CalledOnValidThread());
468 if (enumeration_in_progress_) {
469 return;
472 enumeration_in_progress_ = true;
473 DCHECK(devices_being_enumerated_.empty());
475 std::string device_path;
476 if (!pending_path_enumerations_.empty()) {
477 device_path = pending_path_enumerations_.front();
478 pending_path_enumerations_.pop();
481 blocking_task_runner_->PostTask(
482 FROM_HERE,
483 base::Bind(&GetDeviceListOnBlockingThread, device_path, context_,
484 task_runner_, base::Bind(&UsbServiceImpl::OnDeviceList,
485 weak_factory_.GetWeakPtr())));
488 void UsbServiceImpl::OnDeviceList(libusb_device** platform_devices,
489 size_t device_count) {
490 DCHECK(CalledOnValidThread());
491 if (!platform_devices) {
492 RefreshDevicesComplete();
493 return;
496 base::Closure refresh_complete =
497 base::BarrierClosure(static_cast<int>(device_count),
498 base::Bind(&UsbServiceImpl::RefreshDevicesComplete,
499 weak_factory_.GetWeakPtr()));
500 std::list<PlatformUsbDevice> new_devices;
502 // Look for new and existing devices.
503 for (size_t i = 0; i < device_count; ++i) {
504 PlatformUsbDevice platform_device = platform_devices[i];
505 auto it = platform_devices_.find(platform_device);
507 if (it == platform_devices_.end()) {
508 libusb_ref_device(platform_device);
509 new_devices.push_back(platform_device);
510 } else {
511 it->second->set_visited(true);
512 refresh_complete.Run();
516 // Remove devices not seen in this enumeration.
517 for (PlatformDeviceMap::iterator it = platform_devices_.begin();
518 it != platform_devices_.end();
519 /* incremented internally */) {
520 PlatformDeviceMap::iterator current = it++;
521 const scoped_refptr<UsbDeviceImpl>& device = current->second;
522 if (device->was_visited()) {
523 device->set_visited(false);
524 } else {
525 RemoveDevice(device);
529 for (PlatformUsbDevice platform_device : new_devices) {
530 EnumerateDevice(platform_device, refresh_complete);
533 libusb_free_device_list(platform_devices, true);
536 void UsbServiceImpl::RefreshDevicesComplete() {
537 DCHECK(CalledOnValidThread());
538 DCHECK(enumeration_in_progress_);
540 enumeration_ready_ = true;
541 enumeration_in_progress_ = false;
542 devices_being_enumerated_.clear();
544 if (!pending_enumeration_callbacks_.empty()) {
545 std::vector<scoped_refptr<UsbDevice>> devices;
546 for (const auto& map_entry : devices_) {
547 devices.push_back(map_entry.second);
550 std::vector<GetDevicesCallback> callbacks;
551 callbacks.swap(pending_enumeration_callbacks_);
552 for (const GetDevicesCallback& callback : callbacks) {
553 callback.Run(devices);
557 if (!pending_path_enumerations_.empty()) {
558 RefreshDevices();
562 void UsbServiceImpl::EnumerateDevice(PlatformUsbDevice platform_device,
563 const base::Closure& refresh_complete) {
564 devices_being_enumerated_.insert(platform_device);
566 libusb_device_descriptor descriptor;
567 int rv = libusb_get_device_descriptor(platform_device, &descriptor);
568 if (rv == LIBUSB_SUCCESS) {
569 scoped_refptr<UsbDeviceImpl> device(
570 new UsbDeviceImpl(context_, platform_device, descriptor.idVendor,
571 descriptor.idProduct, blocking_task_runner_));
573 base::Closure add_device =
574 base::Bind(&UsbServiceImpl::AddDevice, weak_factory_.GetWeakPtr(),
575 refresh_complete, device);
577 #if defined(USE_UDEV)
578 blocking_task_runner_->PostTask(
579 FROM_HERE, base::Bind(&EnumerateUdevDevice, device, task_runner_,
580 add_device, refresh_complete));
581 #else
582 if (descriptor.iManufacturer == 0 && descriptor.iProduct == 0 &&
583 descriptor.iSerialNumber == 0) {
584 // Don't bother disturbing the device if it has no string descriptors to
585 // offer.
586 add_device.Run();
587 } else {
588 device->Open(base::Bind(&ReadDeviceLanguage, descriptor.iManufacturer,
589 descriptor.iProduct, descriptor.iSerialNumber,
590 add_device, refresh_complete));
592 #endif
593 } else {
594 USB_LOG(EVENT) << "Failed to get device descriptor: "
595 << ConvertPlatformUsbErrorToString(rv);
596 refresh_complete.Run();
600 void UsbServiceImpl::AddDevice(const base::Closure& refresh_complete,
601 scoped_refptr<UsbDeviceImpl> device) {
602 auto it = devices_being_enumerated_.find(device->platform_device());
603 if (it == devices_being_enumerated_.end()) {
604 // Device was removed while being enumerated.
605 refresh_complete.Run();
606 return;
609 platform_devices_[device->platform_device()] = device;
610 DCHECK(!ContainsKey(devices_, device->guid()));
611 devices_[device->guid()] = device;
613 USB_LOG(USER) << "USB device added: vendor=" << device->vendor_id() << " \""
614 << device->manufacturer_string()
615 << "\", product=" << device->product_id() << " \""
616 << device->product_string() << "\", serial=\""
617 << device->serial_number() << "\", guid=" << device->guid();
619 if (enumeration_ready_) {
620 NotifyDeviceAdded(device);
623 refresh_complete.Run();
626 void UsbServiceImpl::RemoveDevice(scoped_refptr<UsbDeviceImpl> device) {
627 platform_devices_.erase(device->platform_device());
628 devices_.erase(device->guid());
630 USB_LOG(USER) << "USB device removed: guid=" << device->guid();
632 NotifyDeviceRemoved(device);
633 device->OnDisconnect();
636 // static
637 int LIBUSB_CALL UsbServiceImpl::HotplugCallback(libusb_context* context,
638 PlatformUsbDevice device,
639 libusb_hotplug_event event,
640 void* user_data) {
641 // It is safe to access the UsbServiceImpl* here because libusb takes a lock
642 // around registering, deregistering and calling hotplug callback functions
643 // and so guarantees that this function will not be called by the event
644 // processing thread after it has been deregistered.
645 UsbServiceImpl* self = reinterpret_cast<UsbServiceImpl*>(user_data);
646 switch (event) {
647 case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
648 libusb_ref_device(device); // Released in OnPlatformDeviceAdded.
649 if (self->task_runner_->BelongsToCurrentThread()) {
650 self->OnPlatformDeviceAdded(device);
651 } else {
652 self->task_runner_->PostTask(
653 FROM_HERE, base::Bind(&UsbServiceImpl::OnPlatformDeviceAdded,
654 base::Unretained(self), device));
656 break;
657 case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
658 libusb_ref_device(device); // Released in OnPlatformDeviceRemoved.
659 if (self->task_runner_->BelongsToCurrentThread()) {
660 self->OnPlatformDeviceRemoved(device);
661 } else {
662 self->task_runner_->PostTask(
663 FROM_HERE, base::Bind(&UsbServiceImpl::OnPlatformDeviceRemoved,
664 base::Unretained(self), device));
666 break;
667 default:
668 NOTREACHED();
671 return 0;
674 void UsbServiceImpl::OnPlatformDeviceAdded(PlatformUsbDevice platform_device) {
675 DCHECK(CalledOnValidThread());
676 DCHECK(!ContainsKey(platform_devices_, platform_device));
677 EnumerateDevice(platform_device, base::Bind(&base::DoNothing));
678 libusb_unref_device(platform_device);
681 void UsbServiceImpl::OnPlatformDeviceRemoved(
682 PlatformUsbDevice platform_device) {
683 DCHECK(CalledOnValidThread());
684 PlatformDeviceMap::iterator it = platform_devices_.find(platform_device);
685 if (it != platform_devices_.end()) {
686 RemoveDevice(it->second);
687 } else {
688 DCHECK(ContainsKey(devices_being_enumerated_, platform_device));
689 devices_being_enumerated_.erase(platform_device);
691 libusb_unref_device(platform_device);
694 } // namespace device