[Password Manager] Use successful window.fetch() submission as a signal for password...
[chromium-blink-merge.git] / device / usb / usb_service_impl.cc
blobea7ad7f498108d213619935c2de2ddbb107b6fa1
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::strncasecmp("WinUSB", (const char*)&buffer[0], sizeof "WinUSB") ==
131 0) {
132 return true;
134 return false;
137 #endif // OS_WIN
139 void GetDeviceListOnBlockingThread(
140 const std::string& new_device_path,
141 scoped_refptr<UsbContext> usb_context,
142 scoped_refptr<base::SequencedTaskRunner> task_runner,
143 base::Callback<void(libusb_device**, size_t)> callback) {
144 #if defined(OS_WIN)
145 if (!new_device_path.empty()) {
146 if (!IsWinUsbInterface(new_device_path)) {
147 // Wait to call libusb_get_device_list until libusb will be able to find
148 // a WinUSB interface for the device.
149 task_runner->PostTask(FROM_HERE, base::Bind(callback, nullptr, 0));
150 return;
153 #endif // defined(OS_WIN)
155 libusb_device** platform_devices = NULL;
156 const ssize_t device_count =
157 libusb_get_device_list(usb_context->context(), &platform_devices);
158 if (device_count < 0) {
159 USB_LOG(ERROR) << "Failed to get device list: "
160 << ConvertPlatformUsbErrorToString(device_count);
161 task_runner->PostTask(FROM_HERE, base::Bind(callback, nullptr, 0));
162 return;
165 task_runner->PostTask(FROM_HERE,
166 base::Bind(callback, platform_devices, device_count));
169 #if defined(USE_UDEV)
171 void EnumerateUdevDevice(scoped_refptr<UsbDeviceImpl> device,
172 scoped_refptr<base::SequencedTaskRunner> task_runner,
173 const base::Closure& success_closure,
174 const base::Closure& failure_closure) {
175 ScopedUdevPtr udev(udev_new());
176 ScopedUdevEnumeratePtr udev_enumerate(udev_enumerate_new(udev.get()));
178 udev_enumerate_add_match_subsystem(udev_enumerate.get(), "usb");
179 if (udev_enumerate_scan_devices(udev_enumerate.get()) != 0) {
180 task_runner->PostTask(FROM_HERE, failure_closure);
181 return;
184 std::string bus_number =
185 base::IntToString(libusb_get_bus_number(device->platform_device()));
186 std::string device_address =
187 base::IntToString(libusb_get_device_address(device->platform_device()));
188 udev_list_entry* devices =
189 udev_enumerate_get_list_entry(udev_enumerate.get());
190 for (udev_list_entry* i = devices; i != NULL;
191 i = udev_list_entry_get_next(i)) {
192 ScopedUdevDevicePtr udev_device(
193 udev_device_new_from_syspath(udev.get(), udev_list_entry_get_name(i)));
194 if (udev_device) {
195 const char* value =
196 udev_device_get_sysattr_value(udev_device.get(), "busnum");
197 if (!value || bus_number != value) {
198 continue;
200 value = udev_device_get_sysattr_value(udev_device.get(), "devnum");
201 if (!value || device_address != value) {
202 continue;
205 value = udev_device_get_sysattr_value(udev_device.get(), "manufacturer");
206 if (value) {
207 device->set_manufacturer_string(base::UTF8ToUTF16(value));
209 value = udev_device_get_sysattr_value(udev_device.get(), "product");
210 if (value) {
211 device->set_product_string(base::UTF8ToUTF16(value));
213 value = udev_device_get_sysattr_value(udev_device.get(), "serial");
214 if (value) {
215 device->set_serial_number(base::UTF8ToUTF16(value));
218 value = udev_device_get_devnode(udev_device.get());
219 if (value) {
220 device->set_device_path(value);
221 task_runner->PostTask(FROM_HERE, success_closure);
222 return;
225 break;
229 task_runner->PostTask(FROM_HERE, failure_closure);
232 #else
234 void OnReadStringDescriptor(
235 const base::Callback<void(const base::string16&)>& callback,
236 UsbTransferStatus status,
237 scoped_refptr<net::IOBuffer> buffer,
238 size_t length) {
239 if (status != USB_TRANSFER_COMPLETED || length < 2) {
240 callback.Run(base::string16());
241 } else {
242 // Take the lesser of the length of data returned by the device and the
243 // length reported in the descriptor.
244 size_t internal_length = reinterpret_cast<uint8*>(buffer->data())[0];
245 length = std::min(length, internal_length);
246 // Cut off the first 2 bytes of the descriptor which are the length and
247 // descriptor type (always STRING).
248 callback.Run(base::string16(
249 reinterpret_cast<base::char16*>(buffer->data() + 2), length / 2 - 1));
253 void ReadStringDescriptor(
254 scoped_refptr<UsbDeviceHandle> device_handle,
255 uint8 index,
256 uint16 language_id,
257 const base::Callback<void(const base::string16&)>& callback) {
258 scoped_refptr<net::IOBuffer> buffer = new net::IOBuffer(256);
259 device_handle->ControlTransfer(
260 USB_DIRECTION_INBOUND, UsbDeviceHandle::STANDARD, UsbDeviceHandle::DEVICE,
261 6 /* GET_DESCRIPTOR */, 3 /* STRING */ << 8 | index, language_id, buffer,
262 256, 60, base::Bind(&OnReadStringDescriptor, callback));
265 void CloseHandleAndRunContinuation(scoped_refptr<UsbDeviceHandle> device_handle,
266 const base::Closure& continuation) {
267 device_handle->Close();
268 continuation.Run();
271 void SaveStringAndRunContinuation(
272 const base::Callback<void(const base::string16&)>& save_callback,
273 const base::Closure& continuation,
274 const base::string16& value) {
275 if (!value.empty()) {
276 save_callback.Run(value);
278 continuation.Run();
281 void OnReadLanguageIds(scoped_refptr<UsbDeviceHandle> device_handle,
282 uint8 manufacturer,
283 uint8 product,
284 uint8 serial_number,
285 const base::Closure& success_closure,
286 const base::string16& languages) {
287 // Default to English unless the device provides a language and then just pick
288 // the first one.
289 uint16 language_id = 0x0409;
290 if (!languages.empty()) {
291 language_id = languages[0];
294 scoped_refptr<UsbDeviceImpl> device =
295 static_cast<UsbDeviceImpl*>(device_handle->GetDevice().get());
296 base::Closure continuation =
297 base::BarrierClosure(3, base::Bind(&CloseHandleAndRunContinuation,
298 device_handle, success_closure));
300 if (manufacturer == 0) {
301 continuation.Run();
302 } else {
303 ReadStringDescriptor(
304 device_handle, manufacturer, language_id,
305 base::Bind(&SaveStringAndRunContinuation,
306 base::Bind(&UsbDeviceImpl::set_manufacturer_string, device),
307 continuation));
310 if (product == 0) {
311 continuation.Run();
312 } else {
313 ReadStringDescriptor(
314 device_handle, product, language_id,
315 base::Bind(&SaveStringAndRunContinuation,
316 base::Bind(&UsbDeviceImpl::set_product_string, device),
317 continuation));
320 if (serial_number == 0) {
321 continuation.Run();
322 } else {
323 ReadStringDescriptor(
324 device_handle, serial_number, language_id,
325 base::Bind(&SaveStringAndRunContinuation,
326 base::Bind(&UsbDeviceImpl::set_serial_number, device),
327 continuation));
331 void ReadDeviceLanguage(uint8 manufacturer,
332 uint8 product,
333 uint8 serial_number,
334 const base::Closure& success_closure,
335 const base::Closure& failure_closure,
336 scoped_refptr<UsbDeviceHandle> device_handle) {
337 if (device_handle) {
338 ReadStringDescriptor(
339 device_handle, 0, 0,
340 base::Bind(&OnReadLanguageIds, device_handle, manufacturer, product,
341 serial_number, success_closure));
342 } else {
343 failure_closure.Run();
347 #endif // USE_UDEV
349 } // namespace
351 // static
352 UsbService* UsbServiceImpl::Create(
353 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) {
354 PlatformUsbContext context = NULL;
355 const int rv = libusb_init(&context);
356 if (rv != LIBUSB_SUCCESS) {
357 USB_LOG(ERROR) << "Failed to initialize libusb: "
358 << ConvertPlatformUsbErrorToString(rv);
359 return nullptr;
361 if (!context) {
362 return nullptr;
365 return new UsbServiceImpl(context, blocking_task_runner);
368 UsbServiceImpl::UsbServiceImpl(
369 PlatformUsbContext context,
370 scoped_refptr<base::SequencedTaskRunner> blocking_task_runner)
371 : context_(new UsbContext(context)),
372 task_runner_(base::ThreadTaskRunnerHandle::Get()),
373 blocking_task_runner_(blocking_task_runner),
374 #if defined(OS_WIN)
375 device_observer_(this),
376 #endif
377 weak_factory_(this) {
378 base::MessageLoop::current()->AddDestructionObserver(this);
380 int rv = libusb_hotplug_register_callback(
381 context_->context(),
382 static_cast<libusb_hotplug_event>(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
383 LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT),
384 static_cast<libusb_hotplug_flag>(0), LIBUSB_HOTPLUG_MATCH_ANY,
385 LIBUSB_HOTPLUG_MATCH_ANY, LIBUSB_HOTPLUG_MATCH_ANY,
386 &UsbServiceImpl::HotplugCallback, this, &hotplug_handle_);
387 if (rv == LIBUSB_SUCCESS) {
388 hotplug_enabled_ = true;
391 RefreshDevices();
392 #if defined(OS_WIN)
393 DeviceMonitorWin* device_monitor = DeviceMonitorWin::GetForAllInterfaces();
394 if (device_monitor) {
395 device_observer_.Add(device_monitor);
397 #endif // OS_WIN
400 UsbServiceImpl::~UsbServiceImpl() {
401 base::MessageLoop::current()->RemoveDestructionObserver(this);
403 if (hotplug_enabled_) {
404 libusb_hotplug_deregister_callback(context_->context(), hotplug_handle_);
406 for (const auto& map_entry : devices_) {
407 map_entry.second->OnDisconnect();
411 scoped_refptr<UsbDevice> UsbServiceImpl::GetDevice(const std::string& guid) {
412 DCHECK(CalledOnValidThread());
413 DeviceMap::iterator it = devices_.find(guid);
414 if (it != devices_.end()) {
415 return it->second;
417 return NULL;
420 void UsbServiceImpl::GetDevices(const GetDevicesCallback& callback) {
421 DCHECK(CalledOnValidThread());
423 if (hotplug_enabled_ && !enumeration_in_progress_) {
424 // The device list is updated live when hotplug events are supported.
425 std::vector<scoped_refptr<UsbDevice>> devices;
426 for (const auto& map_entry : devices_) {
427 devices.push_back(map_entry.second);
429 callback.Run(devices);
430 } else {
431 pending_enumeration_callbacks_.push_back(callback);
432 RefreshDevices();
436 #if defined(OS_WIN)
438 void UsbServiceImpl::OnDeviceAdded(const GUID& class_guid,
439 const std::string& device_path) {
440 // Only the root node of a composite USB device has the class GUID
441 // GUID_DEVINTERFACE_USB_DEVICE but we want to wait until WinUSB is loaded.
442 // This first pass filter will catch anything that's sitting on the USB bus
443 // (including devices on 3rd party USB controllers) to avoid the more
444 // expensive driver check that needs to be done on the FILE thread.
445 if (device_path.find("usb") != std::string::npos) {
446 pending_path_enumerations_.push(device_path);
447 RefreshDevices();
451 void UsbServiceImpl::OnDeviceRemoved(const GUID& class_guid,
452 const std::string& device_path) {
453 // The root USB device node is removed last.
454 if (class_guid == GUID_DEVINTERFACE_USB_DEVICE) {
455 RefreshDevices();
459 #endif // OS_WIN
461 void UsbServiceImpl::WillDestroyCurrentMessageLoop() {
462 DCHECK(CalledOnValidThread());
463 delete this;
466 void UsbServiceImpl::RefreshDevices() {
467 DCHECK(CalledOnValidThread());
469 if (enumeration_in_progress_) {
470 return;
473 enumeration_in_progress_ = true;
474 DCHECK(devices_being_enumerated_.empty());
476 std::string device_path;
477 if (!pending_path_enumerations_.empty()) {
478 device_path = pending_path_enumerations_.front();
479 pending_path_enumerations_.pop();
482 blocking_task_runner_->PostTask(
483 FROM_HERE,
484 base::Bind(&GetDeviceListOnBlockingThread, device_path, context_,
485 task_runner_, base::Bind(&UsbServiceImpl::OnDeviceList,
486 weak_factory_.GetWeakPtr())));
489 void UsbServiceImpl::OnDeviceList(libusb_device** platform_devices,
490 size_t device_count) {
491 DCHECK(CalledOnValidThread());
492 if (!platform_devices) {
493 RefreshDevicesComplete();
494 return;
497 base::Closure refresh_complete =
498 base::BarrierClosure(static_cast<int>(device_count),
499 base::Bind(&UsbServiceImpl::RefreshDevicesComplete,
500 weak_factory_.GetWeakPtr()));
501 std::list<PlatformUsbDevice> new_devices;
503 // Look for new and existing devices.
504 for (size_t i = 0; i < device_count; ++i) {
505 PlatformUsbDevice platform_device = platform_devices[i];
506 auto it = platform_devices_.find(platform_device);
508 if (it == platform_devices_.end()) {
509 libusb_ref_device(platform_device);
510 new_devices.push_back(platform_device);
511 } else {
512 it->second->set_visited(true);
513 refresh_complete.Run();
517 // Remove devices not seen in this enumeration.
518 for (PlatformDeviceMap::iterator it = platform_devices_.begin();
519 it != platform_devices_.end();
520 /* incremented internally */) {
521 PlatformDeviceMap::iterator current = it++;
522 const scoped_refptr<UsbDeviceImpl>& device = current->second;
523 if (device->was_visited()) {
524 device->set_visited(false);
525 } else {
526 RemoveDevice(device);
530 for (PlatformUsbDevice platform_device : new_devices) {
531 EnumerateDevice(platform_device, refresh_complete);
534 libusb_free_device_list(platform_devices, true);
537 void UsbServiceImpl::RefreshDevicesComplete() {
538 DCHECK(CalledOnValidThread());
539 DCHECK(enumeration_in_progress_);
541 enumeration_ready_ = true;
542 enumeration_in_progress_ = false;
543 devices_being_enumerated_.clear();
545 if (!pending_enumeration_callbacks_.empty()) {
546 std::vector<scoped_refptr<UsbDevice>> devices;
547 for (const auto& map_entry : devices_) {
548 devices.push_back(map_entry.second);
551 std::vector<GetDevicesCallback> callbacks;
552 callbacks.swap(pending_enumeration_callbacks_);
553 for (const GetDevicesCallback& callback : callbacks) {
554 callback.Run(devices);
558 if (!pending_path_enumerations_.empty()) {
559 RefreshDevices();
563 void UsbServiceImpl::EnumerateDevice(PlatformUsbDevice platform_device,
564 const base::Closure& refresh_complete) {
565 devices_being_enumerated_.insert(platform_device);
567 libusb_device_descriptor descriptor;
568 int rv = libusb_get_device_descriptor(platform_device, &descriptor);
569 if (rv == LIBUSB_SUCCESS) {
570 scoped_refptr<UsbDeviceImpl> device(
571 new UsbDeviceImpl(context_, platform_device, descriptor.idVendor,
572 descriptor.idProduct, blocking_task_runner_));
574 base::Closure add_device =
575 base::Bind(&UsbServiceImpl::AddDevice, weak_factory_.GetWeakPtr(),
576 refresh_complete, device);
578 #if defined(USE_UDEV)
579 blocking_task_runner_->PostTask(
580 FROM_HERE, base::Bind(&EnumerateUdevDevice, device, task_runner_,
581 add_device, refresh_complete));
582 #else
583 if (descriptor.iManufacturer == 0 && descriptor.iProduct == 0 &&
584 descriptor.iSerialNumber == 0) {
585 // Don't bother disturbing the device if it has no string descriptors to
586 // offer.
587 add_device.Run();
588 } else {
589 device->Open(base::Bind(&ReadDeviceLanguage, descriptor.iManufacturer,
590 descriptor.iProduct, descriptor.iSerialNumber,
591 add_device, refresh_complete));
593 #endif
594 } else {
595 USB_LOG(EVENT) << "Failed to get device descriptor: "
596 << ConvertPlatformUsbErrorToString(rv);
597 refresh_complete.Run();
601 void UsbServiceImpl::AddDevice(const base::Closure& refresh_complete,
602 scoped_refptr<UsbDeviceImpl> device) {
603 auto it = devices_being_enumerated_.find(device->platform_device());
604 if (it == devices_being_enumerated_.end()) {
605 // Device was removed while being enumerated.
606 refresh_complete.Run();
607 return;
610 platform_devices_[device->platform_device()] = device;
611 DCHECK(!ContainsKey(devices_, device->guid()));
612 devices_[device->guid()] = device;
614 USB_LOG(USER) << "USB device added: vendor=" << device->vendor_id() << " \""
615 << device->manufacturer_string()
616 << "\", product=" << device->product_id() << " \""
617 << device->product_string() << "\", serial=\""
618 << device->serial_number() << "\", guid=" << device->guid();
620 if (enumeration_ready_) {
621 NotifyDeviceAdded(device);
624 refresh_complete.Run();
627 void UsbServiceImpl::RemoveDevice(scoped_refptr<UsbDeviceImpl> device) {
628 platform_devices_.erase(device->platform_device());
629 devices_.erase(device->guid());
631 USB_LOG(USER) << "USB device removed: guid=" << device->guid();
633 NotifyDeviceRemoved(device);
634 device->OnDisconnect();
637 // static
638 int LIBUSB_CALL UsbServiceImpl::HotplugCallback(libusb_context* context,
639 PlatformUsbDevice device,
640 libusb_hotplug_event event,
641 void* user_data) {
642 // It is safe to access the UsbServiceImpl* here because libusb takes a lock
643 // around registering, deregistering and calling hotplug callback functions
644 // and so guarantees that this function will not be called by the event
645 // processing thread after it has been deregistered.
646 UsbServiceImpl* self = reinterpret_cast<UsbServiceImpl*>(user_data);
647 switch (event) {
648 case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED:
649 libusb_ref_device(device); // Released in OnPlatformDeviceAdded.
650 if (self->task_runner_->BelongsToCurrentThread()) {
651 self->OnPlatformDeviceAdded(device);
652 } else {
653 self->task_runner_->PostTask(
654 FROM_HERE, base::Bind(&UsbServiceImpl::OnPlatformDeviceAdded,
655 base::Unretained(self), device));
657 break;
658 case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT:
659 libusb_ref_device(device); // Released in OnPlatformDeviceRemoved.
660 if (self->task_runner_->BelongsToCurrentThread()) {
661 self->OnPlatformDeviceRemoved(device);
662 } else {
663 self->task_runner_->PostTask(
664 FROM_HERE, base::Bind(&UsbServiceImpl::OnPlatformDeviceRemoved,
665 base::Unretained(self), device));
667 break;
668 default:
669 NOTREACHED();
672 return 0;
675 void UsbServiceImpl::OnPlatformDeviceAdded(PlatformUsbDevice platform_device) {
676 DCHECK(CalledOnValidThread());
677 DCHECK(!ContainsKey(platform_devices_, platform_device));
678 EnumerateDevice(platform_device, base::Bind(&base::DoNothing));
679 libusb_unref_device(platform_device);
682 void UsbServiceImpl::OnPlatformDeviceRemoved(
683 PlatformUsbDevice platform_device) {
684 DCHECK(CalledOnValidThread());
685 PlatformDeviceMap::iterator it = platform_devices_.find(platform_device);
686 if (it != platform_devices_.end()) {
687 RemoveDevice(it->second);
688 } else {
689 DCHECK(ContainsKey(devices_being_enumerated_, platform_device));
690 devices_being_enumerated_.erase(platform_device);
692 libusb_unref_device(platform_device);
695 } // namespace device