Index: device/usb/usb_service_win.cc |
diff --git a/device/usb/usb_service_win.cc b/device/usb/usb_service_win.cc |
index 6feea8913d1652e068e91d23408273b213ffbfa5..131fdb3317c854a41f2b10d3020992611c8a2e25 100644 |
--- a/device/usb/usb_service_win.cc |
+++ b/device/usb/usb_service_win.cc |
@@ -4,12 +4,386 @@ |
#include "device/usb/usb_service_win.h" |
+#include <setupapi.h> |
+#include <stdint.h> |
+#include <usbiodef.h> |
+ |
+#define INITGUID |
+#include <devpkey.h> |
+ |
+#include "base/bind.h" |
+#include "base/location.h" |
+#include "base/memory/free_deleter.h" |
+#include "base/memory/ptr_util.h" |
+#include "base/scoped_generic.h" |
+#include "base/single_thread_task_runner.h" |
+#include "base/stl_util.h" |
+#include "base/strings/string_util.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "base/threading/thread_task_runner_handle.h" |
+#include "base/win/scoped_handle.h" |
+#include "components/device_event_log/device_event_log.h" |
+#include "device/usb/usb_descriptors.h" |
+#include "device/usb/usb_device_handle.h" |
+#include "device/usb/webusb_descriptors.h" |
+ |
namespace device { |
+namespace { |
+ |
+struct DevInfoScopedTraits { |
+ static HDEVINFO InvalidValue() { return INVALID_HANDLE_VALUE; } |
+ static void Free(HDEVINFO h) { SetupDiDestroyDeviceInfoList(h); } |
+}; |
+ |
+using ScopedDevInfo = base::ScopedGeneric<HDEVINFO, DevInfoScopedTraits>; |
+ |
+bool GetDeviceUint32Property(HDEVINFO dev_info, |
+ SP_DEVINFO_DATA* dev_info_data, |
+ const DEVPROPKEY& property, |
+ uint32_t* property_buffer) { |
+ DEVPROPTYPE property_type; |
+ if (!SetupDiGetDeviceProperty(dev_info, dev_info_data, &property, |
+ &property_type, |
+ reinterpret_cast<PBYTE>(property_buffer), |
+ sizeof(*property_buffer), nullptr, 0) || |
+ property_type != DEVPROP_TYPE_UINT32) { |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+bool GetDeviceStringProperty(HDEVINFO dev_info, |
+ SP_DEVINFO_DATA* dev_info_data, |
+ const DEVPROPKEY& property, |
+ std::string* property_buffer) { |
+ DEVPROPTYPE property_type; |
+ DWORD required_size; |
+ if (SetupDiGetDeviceProperty(dev_info, dev_info_data, &property, |
+ &property_type, nullptr, 0, &required_size, 0) || |
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER || |
+ property_type != DEVPROP_TYPE_STRING) { |
+ return false; |
+ } |
+ |
+ std::wstring wide_buffer; |
+ if (!SetupDiGetDeviceProperty( |
+ dev_info, dev_info_data, &property, &property_type, |
+ reinterpret_cast<PBYTE>(base::WriteInto(&wide_buffer, required_size)), |
+ required_size, nullptr, 0)) { |
+ return false; |
+ } |
+ |
+ *property_buffer = base::SysWideToUTF8(wide_buffer); |
+ return true; |
+} |
+ |
+bool GetDeviceInterfaceDetails(HDEVINFO dev_info, |
+ SP_DEVICE_INTERFACE_DATA* device_interface_data, |
+ std::string* device_path, |
+ uint32_t* port_number, |
+ std::string* parent_instance_id) { |
+ DWORD required_size; |
+ if (SetupDiGetDeviceInterfaceDetail(dev_info, device_interface_data, nullptr, |
+ 0, &required_size, nullptr) || |
+ GetLastError() != ERROR_INSUFFICIENT_BUFFER) { |
+ return false; |
+ } |
+ |
+ std::unique_ptr<SP_DEVICE_INTERFACE_DETAIL_DATA, base::FreeDeleter> |
+ device_interface_detail_data( |
+ static_cast<SP_DEVICE_INTERFACE_DETAIL_DATA*>(malloc(required_size))); |
+ device_interface_detail_data->cbSize = sizeof(*device_interface_detail_data); |
+ |
+ SP_DEVINFO_DATA dev_info_data; |
+ dev_info_data.cbSize = sizeof(dev_info_data); |
+ |
+ if (!SetupDiGetDeviceInterfaceDetail( |
+ dev_info, device_interface_data, device_interface_detail_data.get(), |
+ required_size, nullptr, &dev_info_data)) { |
+ USB_PLOG(ERROR) << "SetupDiGetDeviceInterfaceDetail"; |
+ return false; |
+ } |
+ |
+ if (device_path) { |
+ *device_path = |
+ base::SysWideToUTF8(device_interface_detail_data->DevicePath); |
+ } |
+ |
+ if (port_number) { |
+ if (!GetDeviceUint32Property(dev_info, &dev_info_data, |
+ DEVPKEY_Device_Address, port_number)) { |
+ USB_PLOG(ERROR) << "Failed to get device address"; |
+ return false; |
+ } |
+ } |
+ |
+ if (parent_instance_id) { |
+ if (!GetDeviceStringProperty(dev_info, &dev_info_data, |
+ DEVPKEY_Device_Parent, parent_instance_id)) { |
+ USB_PLOG(ERROR) << "Failed to get the device parent"; |
+ return false; |
+ } |
+ } |
+ |
+ return true; |
+} |
+ |
+bool GetHubDevicePath(const std::string& instance_id, |
+ std::string* device_path) { |
+ ScopedDevInfo dev_info( |
+ SetupDiGetClassDevsA(&GUID_DEVINTERFACE_USB_HUB, instance_id.c_str(), 0, |
+ DIGCF_DEVICEINTERFACE | DIGCF_PRESENT)); |
+ if (!dev_info.is_valid()) { |
+ USB_PLOG(ERROR) << "SetupDiGetClassDevs"; |
+ return false; |
+ } |
+ |
+ SP_DEVICE_INTERFACE_DATA device_interface_data; |
+ device_interface_data.cbSize = sizeof(device_interface_data); |
+ if (!SetupDiEnumDeviceInterfaces(dev_info.get(), nullptr, |
+ &GUID_DEVINTERFACE_USB_HUB, 0, |
+ &device_interface_data)) { |
+ USB_PLOG(ERROR) << "SetupDiEnumDeviceInterfaces"; |
+ return false; |
+ } |
+ |
+ return GetDeviceInterfaceDetails(dev_info.get(), &device_interface_data, |
+ device_path, nullptr, nullptr); |
+} |
+ |
+} // namespace |
+ |
+class UsbServiceWin::BlockingThreadHelper { |
+ public: |
+ explicit BlockingThreadHelper(base::WeakPtr<UsbServiceWin> service) |
+ : service_task_runner_(base::ThreadTaskRunnerHandle::Get()), |
+ service_(service) {} |
+ ~BlockingThreadHelper() {} |
+ |
+ void EnumerateDevices() { |
+ ScopedDevInfo dev_info( |
+ SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, nullptr, 0, |
+ DIGCF_DEVICEINTERFACE | DIGCF_PRESENT)); |
+ if (!dev_info.is_valid()) { |
+ USB_PLOG(ERROR) << "Failed to set up device enumeration"; |
+ service_task_runner_->PostTask( |
+ FROM_HERE, base::Bind(&UsbServiceWin::HelperStarted, service_)); |
+ return; |
+ } |
+ |
+ SP_DEVICE_INTERFACE_DATA device_interface_data; |
+ device_interface_data.cbSize = sizeof(device_interface_data); |
+ for (DWORD i = 0; SetupDiEnumDeviceInterfaces(dev_info.get(), nullptr, |
+ &GUID_DEVINTERFACE_USB_DEVICE, |
+ i, &device_interface_data); |
+ ++i) { |
+ std::string device_path; |
+ uint32_t port_number; |
+ std::string parent_instance_id; |
+ if (!GetDeviceInterfaceDetails(dev_info.get(), &device_interface_data, |
+ &device_path, &port_number, |
+ &parent_instance_id)) { |
+ continue; |
+ } |
+ |
+ std::string& hub_path = hub_paths_[parent_instance_id]; |
+ if (hub_path.empty()) { |
+ std::string parent_path; |
+ if (!GetHubDevicePath(parent_instance_id, &parent_path)) |
+ continue; |
+ |
+ hub_path = parent_path; |
+ } |
+ |
+ service_task_runner_->PostTask( |
+ FROM_HERE, base::Bind(&UsbServiceWin::CreateDeviceObject, service_, |
+ device_path, hub_path, port_number)); |
+ } |
+ |
+ if (GetLastError() != ERROR_NO_MORE_ITEMS) |
+ USB_PLOG(ERROR) << "Failed to enumerate devices"; |
+ |
+ service_task_runner_->PostTask( |
+ FROM_HERE, base::Bind(&UsbServiceWin::HelperStarted, service_)); |
+ } |
+ |
+ void EnumerateDevicePath(const std::string& device_path) { |
+ ScopedDevInfo dev_info( |
+ SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, nullptr, 0, |
+ DIGCF_DEVICEINTERFACE | DIGCF_PRESENT)); |
+ if (!dev_info.is_valid()) { |
+ USB_PLOG(ERROR) << "Failed to set up device enumeration"; |
+ return; |
+ } |
+ |
+ SP_DEVICE_INTERFACE_DATA device_interface_data; |
+ device_interface_data.cbSize = sizeof(device_interface_data); |
+ if (!SetupDiOpenDeviceInterfaceA(dev_info.get(), device_path.c_str(), 0, |
+ &device_interface_data)) { |
+ USB_PLOG(ERROR) << "Failed to add device interface: " << device_path; |
+ return; |
+ } |
+ |
+ uint32_t port_number; |
+ std::string parent_instance_id; |
+ if (!GetDeviceInterfaceDetails(dev_info.get(), &device_interface_data, |
+ nullptr, &port_number, |
+ &parent_instance_id)) { |
+ return; |
+ } |
+ |
+ std::string& hub_path = hub_paths_[parent_instance_id]; |
+ if (hub_path.empty()) { |
+ std::string parent_path; |
+ if (!GetHubDevicePath(parent_instance_id, &parent_path)) |
+ return; |
+ |
+ hub_path = parent_path; |
+ } |
+ |
+ service_task_runner_->PostTask( |
+ FROM_HERE, base::Bind(&UsbServiceWin::CreateDeviceObject, service_, |
+ device_path, hub_path, port_number)); |
+ } |
+ |
+ private: |
+ std::unordered_map<std::string, std::string> hub_paths_; |
+ |
+ // Calls back to |service_| must be posted to |service_task_runner_|, which |
+ // runs tasks on the thread where that object lives. |
+ scoped_refptr<base::SingleThreadTaskRunner> service_task_runner_; |
+ base::WeakPtr<UsbServiceWin> service_; |
+}; |
+ |
UsbServiceWin::UsbServiceWin( |
scoped_refptr<base::SequencedTaskRunner> blocking_task_runner) |
- : UsbService(blocking_task_runner) {} |
+ : UsbService(blocking_task_runner), |
+ device_observer_(this), |
+ weak_factory_(this) { |
+ DeviceMonitorWin* device_monitor = |
+ DeviceMonitorWin::GetForDeviceInterface(GUID_DEVINTERFACE_USB_DEVICE); |
+ if (device_monitor) |
+ device_observer_.Add(device_monitor); |
+ |
+ helper_ = new BlockingThreadHelper(weak_factory_.GetWeakPtr()); |
+ blocking_task_runner->PostTask( |
+ FROM_HERE, base::Bind(&BlockingThreadHelper::EnumerateDevices, |
+ base::Unretained(helper_))); |
+} |
UsbServiceWin::~UsbServiceWin() {} |
+void UsbServiceWin::GetDevices(const GetDevicesCallback& callback) { |
+ DCHECK(CalledOnValidThread()); |
+ if (enumeration_ready()) |
+ UsbService::GetDevices(callback); |
+ else |
+ enumeration_callbacks_.push_back(callback); |
+} |
+ |
+void UsbServiceWin::OnDeviceAdded(const GUID& class_guid, |
+ const std::string& device_path) { |
+ blocking_task_runner()->PostTask( |
+ FROM_HERE, base::Bind(&BlockingThreadHelper::EnumerateDevicePath, |
+ base::Unretained(helper_), device_path)); |
+} |
+ |
+void UsbServiceWin::OnDeviceRemoved(const GUID& class_guid, |
+ const std::string& device_path) { |
+ DCHECK(CalledOnValidThread()); |
+ auto by_path_it = devices_by_path_.find(device_path); |
+ if (by_path_it == devices_by_path_.end()) |
+ return; |
+ |
+ scoped_refptr<UsbDeviceWin> device = by_path_it->second; |
+ devices_by_path_.erase(by_path_it); |
+ device->OnDisconnect(); |
+ |
+ auto by_guid_it = devices().find(device->guid()); |
+ if (by_guid_it != devices().end() && enumeration_ready()) { |
+ USB_LOG(USER) << "USB device removed: path=" << device->device_path() |
+ << " guid=" << device->guid(); |
+ |
+ devices().erase(by_guid_it); |
+ NotifyDeviceRemoved(device); |
+ } |
+} |
+ |
+void UsbServiceWin::HelperStarted() { |
+ DCHECK(CalledOnValidThread()); |
+ helper_started_ = true; |
+ if (enumeration_ready()) { |
+ std::vector<scoped_refptr<UsbDevice>> result; |
+ result.reserve(devices().size()); |
+ for (const auto& map_entry : devices()) |
+ result.push_back(map_entry.second); |
+ for (const auto& callback : enumeration_callbacks_) |
+ callback.Run(result); |
+ enumeration_callbacks_.clear(); |
+ } |
+} |
+ |
+void UsbServiceWin::CreateDeviceObject(const std::string& device_path, |
+ const std::string& hub_path, |
+ int port_number) { |
+ // Devices that appear during initial enumeration are gathered into the first |
+ // result returned by GetDevices() and prevent device add/remove notifications |
+ // from being sent. |
+ if (!enumeration_ready()) |
+ ++first_enumeration_countdown_; |
+ |
+ scoped_refptr<UsbDeviceWin> device(new UsbDeviceWin( |
+ device_path, hub_path, port_number, blocking_task_runner())); |
+ devices_by_path_[device->device_path()] = device; |
+ |
+ // TODO(reillyg): Read device descriptors. |
+ DeviceReady(device, true); |
+} |
+ |
+void UsbServiceWin::DeviceReady(scoped_refptr<UsbDeviceWin> device, |
+ bool success) { |
+ DCHECK(CalledOnValidThread()); |
+ |
+ bool enumeration_became_ready = false; |
+ if (!enumeration_ready()) { |
+ DCHECK_GT(first_enumeration_countdown_, 0u); |
+ first_enumeration_countdown_--; |
+ if (enumeration_ready()) |
+ enumeration_became_ready = true; |
+ } |
+ |
+ // If |device| was disconnected while descriptors were being read then it |
+ // will have been removed from |devices_by_path_|. |
+ auto it = devices_by_path_.find(device->device_path()); |
+ if (it == devices_by_path_.end()) { |
+ success = false; |
+ } else if (success) { |
+ DCHECK(!base::ContainsKey(devices(), device->guid())); |
+ devices()[device->guid()] = device; |
+ |
+ USB_LOG(USER) << "USB device added: path=" << device->device_path() |
+ << " vendor=" << device->vendor_id() << " \"" |
+ << device->manufacturer_string() |
+ << "\", product=" << device->product_id() << " \"" |
+ << device->product_string() << "\", serial=\"" |
+ << device->serial_number() << "\", guid=" << device->guid(); |
+ } else { |
+ devices_by_path_.erase(it); |
+ } |
+ |
+ if (enumeration_became_ready) { |
+ std::vector<scoped_refptr<UsbDevice>> result; |
+ result.reserve(devices().size()); |
+ for (const auto& map_entry : devices()) |
+ result.push_back(map_entry.second); |
+ for (const auto& callback : enumeration_callbacks_) |
+ callback.Run(result); |
+ enumeration_callbacks_.clear(); |
+ } else if (success && enumeration_ready()) { |
+ NotifyDeviceAdded(device); |
+ } |
+} |
+ |
} // namespace device |