hid_connection_linux.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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_linux.h" 6 7#include <errno.h> 8#include <fcntl.h> 9#include <libudev.h> 10#include <linux/hidraw.h> 11#include <sys/ioctl.h> 12 13#include <string> 14 15#include "base/files/file_path.h" 16#include "base/posix/eintr_wrapper.h" 17#include "base/threading/thread_restrictions.h" 18#include "base/tuple.h" 19#include "device/hid/hid_service.h" 20#include "device/hid/hid_service_linux.h" 21 22// These are already defined in newer versions of linux/hidraw.h. 23#ifndef HIDIOCSFEATURE 24#define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x06, len) 25#endif 26#ifndef HIDIOCGFEATURE 27#define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x07, len) 28#endif 29 30namespace device { 31 32namespace { 33 34// Copies a buffer into a new one with a report ID byte inserted at the front. 35scoped_refptr<net::IOBufferWithSize> CopyBufferWithReportId( 36 scoped_refptr<net::IOBufferWithSize> buffer, 37 uint8_t report_id) { 38 scoped_refptr<net::IOBufferWithSize> new_buffer( 39 new net::IOBufferWithSize(buffer->size() + 1)); 40 new_buffer->data()[0] = report_id; 41 memcpy(new_buffer->data() + 1, buffer->data(), buffer->size()); 42 return new_buffer; 43} 44 45} // namespace 46 47HidConnectionLinux::HidConnectionLinux(HidDeviceInfo device_info, 48 std::string dev_node) 49 : HidConnection(device_info) { 50 DCHECK(thread_checker_.CalledOnValidThread()); 51 52 int flags = base::File::FLAG_OPEN | 53 base::File::FLAG_READ | 54 base::File::FLAG_WRITE; 55 56 base::File device_file(base::FilePath(dev_node), flags); 57 if (!device_file.IsValid()) { 58 base::File::Error file_error = device_file.error_details(); 59 60 if (file_error == base::File::FILE_ERROR_ACCESS_DENIED) { 61 flags = base::File::FLAG_OPEN | base::File::FLAG_READ; 62 63 base::File device_file(base::FilePath(dev_node), flags); 64 if (!device_file.IsValid()) { 65 LOG(ERROR) << device_file.error_details(); 66 return; 67 } 68 } else { 69 LOG(ERROR) << file_error; 70 return; 71 } 72 } 73 if (fcntl(device_file.GetPlatformFile(), F_SETFL, 74 fcntl(device_file.GetPlatformFile(), F_GETFL) | O_NONBLOCK)) { 75 PLOG(ERROR) << "Failed to set non-blocking flag to device file."; 76 return; 77 } 78 device_file_ = device_file.Pass(); 79 80 if (!base::MessageLoopForIO::current()->WatchFileDescriptor( 81 device_file_.GetPlatformFile(), 82 true, 83 base::MessageLoopForIO::WATCH_READ_WRITE, 84 &device_file_watcher_, 85 this)) { 86 LOG(ERROR) << "Failed to start watching device file."; 87 } 88} 89 90HidConnectionLinux::~HidConnectionLinux() { 91 DCHECK(thread_checker_.CalledOnValidThread()); 92 Disconnect(); 93} 94 95void HidConnectionLinux::OnFileCanReadWithoutBlocking(int fd) { 96 DCHECK(thread_checker_.CalledOnValidThread()); 97 DCHECK_EQ(fd, device_file_.GetPlatformFile()); 98 99 uint8 buffer[1024] = {0}; 100 int bytes_read = 101 HANDLE_EINTR(read(device_file_.GetPlatformFile(), buffer, 1024)); 102 if (bytes_read < 0) { 103 if (errno == EAGAIN) { 104 return; 105 } 106 Disconnect(); 107 return; 108 } 109 110 PendingHidReport report; 111 report.buffer = new net::IOBufferWithSize(bytes_read); 112 memcpy(report.buffer->data(), buffer, bytes_read); 113 pending_reports_.push(report); 114 ProcessReadQueue(); 115} 116 117void HidConnectionLinux::OnFileCanWriteWithoutBlocking(int fd) {} 118 119void HidConnectionLinux::Disconnect() { 120 DCHECK(thread_checker_.CalledOnValidThread()); 121 device_file_watcher_.StopWatchingFileDescriptor(); 122 device_file_.Close(); 123 while (!pending_reads_.empty()) { 124 PendingHidRead pending_read = pending_reads_.front(); 125 pending_reads_.pop(); 126 pending_read.callback.Run(false, 0); 127 } 128} 129 130void HidConnectionLinux::Read(scoped_refptr<net::IOBufferWithSize> buffer, 131 const IOCallback& callback) { 132 DCHECK(thread_checker_.CalledOnValidThread()); 133 PendingHidRead pending_read; 134 pending_read.buffer = buffer; 135 pending_read.callback = callback; 136 pending_reads_.push(pending_read); 137 ProcessReadQueue(); 138} 139 140void HidConnectionLinux::Write(uint8_t report_id, 141 scoped_refptr<net::IOBufferWithSize> buffer, 142 const IOCallback& callback) { 143 DCHECK(thread_checker_.CalledOnValidThread()); 144 // If report ID is non-zero, insert it into a new copy of the buffer. 145 if (report_id != 0) 146 buffer = CopyBufferWithReportId(buffer, report_id); 147 int bytes_written = HANDLE_EINTR( 148 write(device_file_.GetPlatformFile(), buffer->data(), buffer->size())); 149 if (bytes_written < 0) { 150 Disconnect(); 151 callback.Run(false, 0); 152 } else { 153 callback.Run(true, bytes_written); 154 } 155} 156 157void HidConnectionLinux::GetFeatureReport( 158 uint8_t report_id, 159 scoped_refptr<net::IOBufferWithSize> buffer, 160 const IOCallback& callback) { 161 DCHECK(thread_checker_.CalledOnValidThread()); 162 163 if (buffer->size() == 0) { 164 callback.Run(false, 0); 165 return; 166 } 167 168 // The first byte of the destination buffer is the report ID being requested. 169 buffer->data()[0] = report_id; 170 int result = ioctl(device_file_.GetPlatformFile(), 171 HIDIOCGFEATURE(buffer->size()), 172 buffer->data()); 173 if (result < 0) 174 callback.Run(false, 0); 175 else 176 callback.Run(true, result); 177} 178 179void HidConnectionLinux::SendFeatureReport( 180 uint8_t report_id, 181 scoped_refptr<net::IOBufferWithSize> buffer, 182 const IOCallback& callback) { 183 DCHECK(thread_checker_.CalledOnValidThread()); 184 if (report_id != 0) 185 buffer = CopyBufferWithReportId(buffer, report_id); 186 int result = ioctl(device_file_.GetPlatformFile(), 187 HIDIOCSFEATURE(buffer->size()), 188 buffer->data()); 189 if (result < 0) 190 callback.Run(false, 0); 191 else 192 callback.Run(true, result); 193} 194 195void HidConnectionLinux::ProcessReadQueue() { 196 while (pending_reads_.size() && pending_reports_.size()) { 197 PendingHidRead read = pending_reads_.front(); 198 pending_reads_.pop(); 199 PendingHidReport report = pending_reports_.front(); 200 if (report.buffer->size() > read.buffer->size()) { 201 read.callback.Run(false, report.buffer->size()); 202 } else { 203 memcpy(read.buffer->data(), report.buffer->data(), report.buffer->size()); 204 pending_reports_.pop(); 205 read.callback.Run(true, report.buffer->size()); 206 } 207 } 208} 209 210} // namespace device 211