1 // Copyright (c) 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. 4 5 #include "device/hid/hid_connection_win.h" 6 7 #include <cstring> 8 9 #include "base/files/file.h" 10 #include "base/message_loop/message_loop.h" 11 #include "base/stl_util.h" 12 #include "base/threading/thread_restrictions.h" 13 #include "base/win/object_watcher.h" 14 #include "base/win/scoped_handle.h" 15 #include "device/hid/hid_service.h" 16 #include "device/hid/hid_service_win.h" 17 18 #define INITGUID 19 20 #include <windows.h> 21 #include <hidclass.h> 22 23 extern "C" { 24 #include <hidsdi.h> 25 } 26 27 #include <setupapi.h> 28 #include <winioctl.h> 29 30 namespace device { 31 32 struct PendingHidTransfer : public base::RefCounted<PendingHidTransfer>, 33 public base::win::ObjectWatcher::Delegate, 34 public base::MessageLoop::DestructionObserver { 35 PendingHidTransfer(scoped_refptr<HidConnectionWin> connection, 36 scoped_refptr<net::IOBufferWithSize> target_buffer, 37 scoped_refptr<net::IOBufferWithSize> receive_buffer, 38 HidConnection::IOCallback callback); 39 40 void TakeResultFromWindowsAPI(BOOL result); 41 42 OVERLAPPED* GetOverlapped() { return &overlapped_; } 43 44 // Implements base::win::ObjectWatcher::Delegate. 45 virtual void OnObjectSignaled(HANDLE object) OVERRIDE; 46 47 // Implements base::MessageLoop::DestructionObserver 48 virtual void WillDestroyCurrentMessageLoop() OVERRIDE; 49 50 scoped_refptr<HidConnectionWin> connection_; 51 scoped_refptr<net::IOBufferWithSize> target_buffer_; 52 scoped_refptr<net::IOBufferWithSize> receive_buffer_; 53 HidConnection::IOCallback callback_; 54 OVERLAPPED overlapped_; 55 base::win::ScopedHandle event_; 56 base::win::ObjectWatcher watcher_; 57 58 private: 59 friend class base::RefCounted<PendingHidTransfer>; 60 61 virtual ~PendingHidTransfer(); 62 63 DISALLOW_COPY_AND_ASSIGN(PendingHidTransfer); 64 }; 65 66 PendingHidTransfer::PendingHidTransfer( 67 scoped_refptr<HidConnectionWin> connection, 68 scoped_refptr<net::IOBufferWithSize> target_buffer, 69 scoped_refptr<net::IOBufferWithSize> receive_buffer, 70 HidConnection::IOCallback callback) 71 : connection_(connection), 72 target_buffer_(target_buffer), 73 receive_buffer_(receive_buffer), 74 callback_(callback), 75 event_(CreateEvent(NULL, FALSE, FALSE, NULL)) { 76 memset(&overlapped_, 0, sizeof(OVERLAPPED)); 77 overlapped_.hEvent = event_.Get(); 78 } 79 80 PendingHidTransfer::~PendingHidTransfer() { 81 base::MessageLoop::current()->RemoveDestructionObserver(this); 82 } 83 84 void PendingHidTransfer::TakeResultFromWindowsAPI(BOOL result) { 85 if (result || GetLastError() != ERROR_IO_PENDING) { 86 connection_->OnTransferFinished(this); 87 } else { 88 base::MessageLoop::current()->AddDestructionObserver(this); 89 AddRef(); 90 watcher_.StartWatching(event_.Get(), this); 91 } 92 } 93 94 void PendingHidTransfer::OnObjectSignaled(HANDLE event_handle) { 95 connection_->OnTransferFinished(this); 96 Release(); 97 } 98 99 void PendingHidTransfer::WillDestroyCurrentMessageLoop() { 100 watcher_.StopWatching(); 101 connection_->OnTransferCanceled(this); 102 } 103 104 HidConnectionWin::HidConnectionWin(const HidDeviceInfo& device_info) 105 : HidConnection(device_info) { 106 DCHECK(thread_checker_.CalledOnValidThread()); 107 file_.Set(CreateFileA(device_info.device_id.c_str(), 108 GENERIC_WRITE | GENERIC_READ, 109 FILE_SHARE_READ | FILE_SHARE_WRITE, 110 NULL, 111 OPEN_EXISTING, 112 FILE_FLAG_OVERLAPPED, 113 NULL)); 114 115 if (!file_.IsValid() && 116 GetLastError() == base::File::FILE_ERROR_ACCESS_DENIED) { 117 file_.Set(CreateFileA(device_info.device_id.c_str(), 118 GENERIC_READ, 119 FILE_SHARE_READ, 120 NULL, 121 OPEN_EXISTING, 122 FILE_FLAG_OVERLAPPED, 123 NULL)); 124 } 125 } 126 127 bool HidConnectionWin::available() const { 128 return file_.IsValid(); 129 } 130 131 HidConnectionWin::~HidConnectionWin() { 132 DCHECK(thread_checker_.CalledOnValidThread()); 133 CancelIo(file_.Get()); 134 } 135 136 void HidConnectionWin::Read(scoped_refptr<net::IOBufferWithSize> buffer, 137 const HidConnection::IOCallback& callback) { 138 DCHECK(thread_checker_.CalledOnValidThread()); 139 if (device_info().input_report_size == 0) { 140 // The device does not support input reports. 141 callback.Run(false, 0); 142 return; 143 } 144 145 // This fairly awkward logic is correct: If Windows does not expect a device 146 // to supply a report ID in its input reports, it requires the buffer to be 147 // 1 byte larger than what the device actually sends. 148 int receive_buffer_size = device_info().input_report_size; 149 int expected_buffer_size = receive_buffer_size; 150 if (!device_info().has_report_id) 151 expected_buffer_size -= 1; 152 153 if (buffer->size() < expected_buffer_size) { 154 callback.Run(false, 0); 155 return; 156 } 157 158 scoped_refptr<net::IOBufferWithSize> receive_buffer(buffer); 159 if (receive_buffer_size != expected_buffer_size) 160 receive_buffer = new net::IOBufferWithSize(receive_buffer_size); 161 scoped_refptr<PendingHidTransfer> transfer( 162 new PendingHidTransfer(this, buffer, receive_buffer, callback)); 163 transfers_.insert(transfer); 164 transfer->TakeResultFromWindowsAPI( 165 ReadFile(file_.Get(), 166 receive_buffer->data(), 167 static_cast<DWORD>(receive_buffer->size()), 168 NULL, 169 transfer->GetOverlapped())); 170 } 171 172 void HidConnectionWin::Write(uint8_t report_id, 173 scoped_refptr<net::IOBufferWithSize> buffer, 174 const HidConnection::IOCallback& callback) { 175 DCHECK(thread_checker_.CalledOnValidThread()); 176 if (device_info().output_report_size == 0) { 177 // The device does not support output reports. 178 callback.Run(false, 0); 179 return; 180 } 181 182 // The Windows API always wants either a report ID (if supported) or 183 // zero at the front of every output report. 184 scoped_refptr<net::IOBufferWithSize> output_buffer(buffer); 185 output_buffer = new net::IOBufferWithSize(buffer->size() + 1); 186 output_buffer->data()[0] = report_id; 187 memcpy(output_buffer->data() + 1, buffer->data(), buffer->size()); 188 189 scoped_refptr<PendingHidTransfer> transfer( 190 new PendingHidTransfer(this, output_buffer, NULL, callback)); 191 transfers_.insert(transfer); 192 transfer->TakeResultFromWindowsAPI( 193 WriteFile(file_.Get(), 194 output_buffer->data(), 195 static_cast<DWORD>(output_buffer->size()), 196 NULL, 197 transfer->GetOverlapped())); 198 } 199 200 void HidConnectionWin::GetFeatureReport( 201 uint8_t report_id, 202 scoped_refptr<net::IOBufferWithSize> buffer, 203 const IOCallback& callback) { 204 DCHECK(thread_checker_.CalledOnValidThread()); 205 if (device_info().feature_report_size == 0) { 206 // The device does not support feature reports. 207 callback.Run(false, 0); 208 return; 209 } 210 211 int receive_buffer_size = device_info().feature_report_size; 212 int expected_buffer_size = receive_buffer_size; 213 if (!device_info().has_report_id) 214 expected_buffer_size -= 1; 215 if (buffer->size() < expected_buffer_size) { 216 callback.Run(false, 0); 217 return; 218 } 219 220 scoped_refptr<net::IOBufferWithSize> receive_buffer(buffer); 221 if (receive_buffer_size != expected_buffer_size) 222 receive_buffer = new net::IOBufferWithSize(receive_buffer_size); 223 224 // The first byte of the destination buffer is the report ID being requested. 225 receive_buffer->data()[0] = report_id; 226 scoped_refptr<PendingHidTransfer> transfer( 227 new PendingHidTransfer(this, buffer, receive_buffer, callback)); 228 transfers_.insert(transfer); 229 transfer->TakeResultFromWindowsAPI( 230 DeviceIoControl(file_.Get(), 231 IOCTL_HID_GET_FEATURE, 232 NULL, 233 0, 234 receive_buffer->data(), 235 static_cast<DWORD>(receive_buffer->size()), 236 NULL, 237 transfer->GetOverlapped())); 238 } 239 240 void HidConnectionWin::SendFeatureReport( 241 uint8_t report_id, 242 scoped_refptr<net::IOBufferWithSize> buffer, 243 const IOCallback& callback) { 244 DCHECK(thread_checker_.CalledOnValidThread()); 245 if (device_info().feature_report_size == 0) { 246 // The device does not support feature reports. 247 callback.Run(false, 0); 248 return; 249 } 250 251 // The Windows API always wants either a report ID (if supported) or 252 // zero at the front of every output report. 253 scoped_refptr<net::IOBufferWithSize> output_buffer(buffer); 254 output_buffer = new net::IOBufferWithSize(buffer->size() + 1); 255 output_buffer->data()[0] = report_id; 256 memcpy(output_buffer->data() + 1, buffer->data(), buffer->size()); 257 258 scoped_refptr<PendingHidTransfer> transfer( 259 new PendingHidTransfer(this, output_buffer, NULL, callback)); 260 transfer->TakeResultFromWindowsAPI( 261 DeviceIoControl(file_.Get(), 262 IOCTL_HID_SET_FEATURE, 263 output_buffer->data(), 264 static_cast<DWORD>(output_buffer->size()), 265 NULL, 266 0, 267 NULL, 268 transfer->GetOverlapped())); 269 } 270 271 void HidConnectionWin::OnTransferFinished( 272 scoped_refptr<PendingHidTransfer> transfer) { 273 DWORD bytes_transferred; 274 transfers_.erase(transfer); 275 if (GetOverlappedResult( 276 file_, transfer->GetOverlapped(), &bytes_transferred, FALSE)) { 277 if (bytes_transferred == 0) 278 transfer->callback_.Run(true, 0); 279 // If this is an input transfer and the receive buffer is not the same as 280 // the target buffer, we need to copy the receive buffer into the target 281 // buffer, discarding the first byte. This is because the target buffer's 282 // owner is not expecting a report ID but Windows will always provide one. 283 if (transfer->receive_buffer_ && 284 transfer->receive_buffer_ != transfer->target_buffer_) { 285 // Move one byte forward. 286 --bytes_transferred; 287 memcpy(transfer->target_buffer_->data(), 288 transfer->receive_buffer_->data() + 1, 289 bytes_transferred); 290 } 291 transfer->callback_.Run(true, bytes_transferred); 292 } else { 293 transfer->callback_.Run(false, 0); 294 } 295 } 296 297 void HidConnectionWin::OnTransferCanceled( 298 scoped_refptr<PendingHidTransfer> transfer) { 299 transfers_.erase(transfer); 300 transfer->callback_.Run(false, 0); 301 } 302 303 } // namespace device 304