Chromium Code Reviews| 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..0c31bdba8fde85f26a9789e9d48a430512006749 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 |task_runner_|, where that |
|
scheib
2017/02/15 22:41:54
to |service_task_runner_|,
|
| + // 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 |