Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(71)

Side by Side Diff: device/hid/hid_connection_linux.cc

Issue 771393002: Migrate HidServiceLinux and HidConnectionLinux to BrowserThread::UI. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix HID receive buffer size. Created 6 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "device/hid/hid_connection_linux.h" 5 #include "device/hid/hid_connection_linux.h"
6 6
7 #include <errno.h> 7 #include <errno.h>
8 #include <fcntl.h>
9 #include <linux/hidraw.h> 8 #include <linux/hidraw.h>
10 #include <sys/ioctl.h> 9 #include <sys/ioctl.h>
11 10
12 #include <string> 11 #include <string>
13 12
13 #include "base/bind.h"
14 #include "base/files/file_path.h" 14 #include "base/files/file_path.h"
15 #include "base/message_loop/message_loop.h" 15 #include "base/message_loop/message_loop.h"
16 #include "base/message_loop/message_pump_libevent.h"
16 #include "base/posix/eintr_wrapper.h" 17 #include "base/posix/eintr_wrapper.h"
18 #include "base/thread_task_runner_handle.h"
19 #include "base/threading/non_thread_safe.h"
17 #include "base/threading/thread_restrictions.h" 20 #include "base/threading/thread_restrictions.h"
18 #include "base/tuple.h" 21 #include "base/tuple.h"
19 #include "device/hid/hid_service.h" 22 #include "device/hid/hid_service.h"
20 23
21 // These are already defined in newer versions of linux/hidraw.h. 24 // These are already defined in newer versions of linux/hidraw.h.
22 #ifndef HIDIOCSFEATURE 25 #ifndef HIDIOCSFEATURE
23 #define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x06, len) 26 #define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x06, len)
24 #endif 27 #endif
25 #ifndef HIDIOCGFEATURE 28 #ifndef HIDIOCGFEATURE
26 #define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x07, len) 29 #define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x07, len)
27 #endif 30 #endif
28 31
29 namespace device { 32 namespace device {
30 33
31 HidConnectionLinux::HidConnectionLinux(HidDeviceInfo device_info, 34 class HidConnectionLinux::Helper : public base::MessagePumpLibevent::Watcher,
32 std::string dev_node) 35 public base::NonThreadSafe {
Ken Rockot(use gerrit already) 2014/12/03 22:35:09 nit: I think you can compose a base::ThreadChecker
Reilly Grant (use Gerrit) 2014/12/03 23:10:28 Done.
33 : HidConnection(device_info) { 36 public:
34 int flags = base::File::FLAG_OPEN | 37 Helper(base::PlatformFile platform_file,
35 base::File::FLAG_READ | 38 const HidDeviceInfo& device_info,
36 base::File::FLAG_WRITE; 39 base::WeakPtr<HidConnectionLinux> connection,
40 scoped_refptr<base::SingleThreadTaskRunner> task_runner)
41 : platform_file_(platform_file),
42 connection_(connection),
43 task_runner_(task_runner) {
44 if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
45 platform_file_, true, base::MessageLoopForIO::WATCH_READ,
46 &file_watcher_, this)) {
47 LOG(ERROR) << "Failed to start watching device file.";
48 }
37 49
38 base::File device_file(base::FilePath(dev_node), flags); 50 // Report buffers must always have room for the report ID.
39 if (!device_file.IsValid()) { 51 report_buffer_size_ = device_info.max_input_report_size + 1;
40 base::File::Error file_error = device_file.error_details(); 52 has_report_id_ = device_info.has_report_id;
41
42 if (file_error == base::File::FILE_ERROR_ACCESS_DENIED) {
43 VLOG(1) << "Access denied opening device read-write, trying read-only.";
44
45 flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
46
47 device_file = base::File(base::FilePath(dev_node), flags);
48 }
49 }
50 if (!device_file.IsValid()) {
51 LOG(ERROR) << "Failed to open '" << dev_node << "': "
52 << base::File::ErrorToString(device_file.error_details());
53 return;
54 } 53 }
55 54
56 if (fcntl(device_file.GetPlatformFile(), F_SETFL, 55 virtual ~Helper() {}
57 fcntl(device_file.GetPlatformFile(), F_GETFL) | O_NONBLOCK)) { 56
58 PLOG(ERROR) << "Failed to set non-blocking flag to device file"; 57 private:
59 return; 58 // base::MessagePumpLibevent::Watcher implementation.
59 void OnFileCanReadWithoutBlocking(int fd) override {
60 DCHECK(CalledOnValidThread());
61 DCHECK_EQ(fd, platform_file_);
62
63 scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(report_buffer_size_));
64 char* data = buffer->data();
65 size_t length = report_buffer_size_;
66 if (!has_report_id_) {
67 // Linux will not prefix the buffer with a report ID if report IDs are not
68 // used by the device. Prefix the buffer with 0.
69 *data++ = 0;
70 length--;
71 }
72
73 ssize_t bytes_read = HANDLE_EINTR(read(platform_file_, data, length));
74 if (bytes_read < 0) {
75 if (errno == EAGAIN) {
76 return;
77 }
78 VPLOG(1) << "Read failed";
79 return;
80 }
81 if (!has_report_id_) {
82 // Behave as if the byte prefixed above as the the report ID was read.
83 bytes_read++;
84 }
85
86 task_runner_->PostTask(FROM_HERE,
87 base::Bind(&HidConnectionLinux::ProcessInputReport,
88 connection_, buffer, bytes_read));
60 } 89 }
90
91 void OnFileCanWriteWithoutBlocking(int fd) override {
92 NOTREACHED(); // Only listening for reads.
93 }
94
95 base::PlatformFile platform_file_;
96 size_t report_buffer_size_;
97 bool has_report_id_;
98 base::WeakPtr<HidConnectionLinux> connection_;
99 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
100 base::MessagePumpLibevent::FileDescriptorWatcher file_watcher_;
101 };
102
103 HidConnectionLinux::HidConnectionLinux(
104 HidDeviceInfo device_info,
105 base::File device_file,
106 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner)
107 : HidConnection(device_info),
108 file_task_runner_(file_task_runner),
109 weak_factory_(this) {
110 task_runner_ = base::ThreadTaskRunnerHandle::Get();
61 device_file_ = device_file.Pass(); 111 device_file_ = device_file.Pass();
62 112
63 if (!base::MessageLoopForIO::current()->WatchFileDescriptor( 113 // The helper is passed a weak pointer to this connection so that it can be
64 device_file_.GetPlatformFile(), 114 // cleaned up after the connection is closed. The weak pointer must be
65 true, 115 // constructed on this thread.
66 base::MessageLoopForIO::WATCH_READ_WRITE, 116 file_task_runner_->PostTask(FROM_HERE,
67 &device_file_watcher_, 117 base::Bind(&HidConnectionLinux::StartHelper, this,
68 this)) { 118 weak_factory_.GetWeakPtr()));
69 LOG(ERROR) << "Failed to start watching device file.";
70 }
71 } 119 }
72 120
73 HidConnectionLinux::~HidConnectionLinux() { 121 HidConnectionLinux::~HidConnectionLinux() {
74 } 122 }
75 123
76 void HidConnectionLinux::PlatformClose() { 124 void HidConnectionLinux::PlatformClose() {
77 Disconnect(); 125 // By closing the device file on the FILE thread (1) the requirement that
78 Flush(); 126 // base::File::Close is called on a thread where I/O is allowed is satisfied
127 // and (2) any tasks posted to this task runner that refer to this file will
128 // complete before it is closed.
129 file_task_runner_->DeleteSoon(FROM_HERE, helper_.release());
130 file_task_runner_->PostTask(FROM_HERE,
131 base::Bind(&HidConnectionLinux::CloseDevice,
132 base::Passed(&device_file_)));
133
134 while (!pending_reads_.empty()) {
135 pending_reads_.front().callback.Run(false, NULL, 0);
136 pending_reads_.pop();
137 }
79 } 138 }
80 139
81 void HidConnectionLinux::PlatformRead(const ReadCallback& callback) { 140 void HidConnectionLinux::PlatformRead(const ReadCallback& callback) {
82 PendingHidRead pending_read; 141 PendingHidRead pending_read;
83 pending_read.callback = callback; 142 pending_read.callback = callback;
84 pending_reads_.push(pending_read); 143 pending_reads_.push(pending_read);
85 ProcessReadQueue(); 144 ProcessReadQueue();
86 } 145 }
87 146
88 void HidConnectionLinux::PlatformWrite(scoped_refptr<net::IOBuffer> buffer, 147 void HidConnectionLinux::PlatformWrite(scoped_refptr<net::IOBuffer> buffer,
89 size_t size, 148 size_t size,
90 const WriteCallback& callback) { 149 const WriteCallback& callback) {
91 // Linux expects the first byte of the buffer to always be a report ID so the 150 // Linux expects the first byte of the buffer to always be a report ID so the
92 // buffer can be used directly. 151 // buffer can be used directly.
93 const ssize_t bytes_written = 152 file_task_runner_->PostTask(
94 HANDLE_EINTR(write(device_file_.GetPlatformFile(), buffer->data(), size)); 153 FROM_HERE,
95 if (bytes_written < 0) { 154 base::Bind(&HidConnectionLinux::BlockingWrite,
96 VPLOG(1) << "Write failed"; 155 device_file_.GetPlatformFile(), buffer, size,
97 Disconnect(); 156 base::Bind(&HidConnectionLinux::FinishWrite,
98 callback.Run(false); 157 weak_factory_.GetWeakPtr(), size, callback),
99 } else { 158 task_runner_));
100 if (static_cast<size_t>(bytes_written) != size) {
101 LOG(WARNING) << "Incomplete HID write: " << bytes_written
102 << " != " << size;
103 }
104 callback.Run(true);
105 }
106 } 159 }
107 160
108 void HidConnectionLinux::PlatformGetFeatureReport( 161 void HidConnectionLinux::PlatformGetFeatureReport(
109 uint8_t report_id, 162 uint8_t report_id,
110 const ReadCallback& callback) { 163 const ReadCallback& callback) {
111 // The first byte of the destination buffer is the report ID being requested 164 // The first byte of the destination buffer is the report ID being requested
112 // and is overwritten by the feature report. 165 // and is overwritten by the feature report.
113 DCHECK_GT(device_info().max_feature_report_size, 0u); 166 DCHECK_GT(device_info().max_feature_report_size, 0u);
114 scoped_refptr<net::IOBufferWithSize> buffer( 167 scoped_refptr<net::IOBufferWithSize> buffer(
115 new net::IOBufferWithSize(device_info().max_feature_report_size + 1)); 168 new net::IOBufferWithSize(device_info().max_feature_report_size + 1));
116 buffer->data()[0] = report_id; 169 buffer->data()[0] = report_id;
117 170
118 int result = ioctl(device_file_.GetPlatformFile(), 171 file_task_runner_->PostTask(
119 HIDIOCGFEATURE(buffer->size()), 172 FROM_HERE,
120 buffer->data()); 173 base::Bind(
174 &HidConnectionLinux::BlockingIoctl, device_file_.GetPlatformFile(),
175 HIDIOCGFEATURE(buffer->size()), buffer,
176 base::Bind(&HidConnectionLinux::FinishGetFeatureReport,
177 weak_factory_.GetWeakPtr(), report_id, buffer, callback),
178 task_runner_));
179 }
180
181 void HidConnectionLinux::PlatformSendFeatureReport(
182 scoped_refptr<net::IOBuffer> buffer,
183 size_t size,
184 const WriteCallback& callback) {
185 // Linux expects the first byte of the buffer to always be a report ID so the
186 // buffer can be used directly.
187 file_task_runner_->PostTask(
188 FROM_HERE,
189 base::Bind(&HidConnectionLinux::BlockingIoctl,
190 device_file_.GetPlatformFile(), HIDIOCSFEATURE(size), buffer,
191 base::Bind(&HidConnectionLinux::FinishSendFeatureReport,
192 weak_factory_.GetWeakPtr(), callback),
193 task_runner_));
194 }
195
196 void HidConnectionLinux::FinishWrite(size_t expected_size,
197 const WriteCallback& callback,
198 ssize_t result) {
199 if (result < 0) {
200 VPLOG(1) << "Write failed";
201 callback.Run(false);
202 } else {
203 if (static_cast<size_t>(result) != expected_size) {
204 LOG(WARNING) << "Incomplete HID write: " << result
205 << " != " << expected_size;
206 }
207 callback.Run(true);
208 }
209 }
210
211 void HidConnectionLinux::FinishGetFeatureReport(
212 uint8_t report_id,
213 scoped_refptr<net::IOBuffer> buffer,
214 const ReadCallback& callback,
215 int result) {
121 if (result < 0) { 216 if (result < 0) {
122 VPLOG(1) << "Failed to get feature report"; 217 VPLOG(1) << "Failed to get feature report";
123 callback.Run(false, NULL, 0); 218 callback.Run(false, NULL, 0);
124 } else if (result == 0) { 219 } else if (result == 0) {
125 VLOG(1) << "Get feature result too short."; 220 VLOG(1) << "Get feature result too short.";
126 callback.Run(false, NULL, 0); 221 callback.Run(false, NULL, 0);
127 } else if (report_id == 0) { 222 } else if (report_id == 0) {
128 // Linux adds a 0 to the beginning of the data received from the device. 223 // Linux adds a 0 to the beginning of the data received from the device.
129 scoped_refptr<net::IOBuffer> copied_buffer(new net::IOBuffer(result - 1)); 224 scoped_refptr<net::IOBuffer> copied_buffer(new net::IOBuffer(result - 1));
130 memcpy(copied_buffer->data(), buffer->data() + 1, result - 1); 225 memcpy(copied_buffer->data(), buffer->data() + 1, result - 1);
131 callback.Run(true, copied_buffer, result - 1); 226 callback.Run(true, copied_buffer, result - 1);
132 } else { 227 } else {
133 callback.Run(true, buffer, result); 228 callback.Run(true, buffer, result);
134 } 229 }
135 } 230 }
136 231
137 void HidConnectionLinux::PlatformSendFeatureReport( 232 void HidConnectionLinux::FinishSendFeatureReport(const WriteCallback& callback,
138 scoped_refptr<net::IOBuffer> buffer, 233 int result) {
139 size_t size,
140 const WriteCallback& callback) {
141 // Linux expects the first byte of the buffer to always be a report ID so the
142 // buffer can be used directly.
143 int result = ioctl(
144 device_file_.GetPlatformFile(), HIDIOCSFEATURE(size), buffer->data());
145 if (result < 0) { 234 if (result < 0) {
146 VPLOG(1) << "Failed to send feature report"; 235 VPLOG(1) << "Failed to send feature report";
147 callback.Run(false); 236 callback.Run(false);
148 } else { 237 } else {
149 callback.Run(true); 238 callback.Run(true);
150 } 239 }
151 } 240 }
152 241
153 void HidConnectionLinux::OnFileCanReadWithoutBlocking(int fd) { 242 void HidConnectionLinux::StartHelper(
154 DCHECK(thread_checker().CalledOnValidThread()); 243 base::WeakPtr<HidConnectionLinux> weak_ptr) {
155 DCHECK_EQ(fd, device_file_.GetPlatformFile()); 244 base::ThreadRestrictions::AssertIOAllowed();
156 245
157 size_t expected_report_size = device_info().max_input_report_size + 1; 246 helper_.reset(new Helper(device_file_.GetPlatformFile(), device_info(),
158 scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(expected_report_size)); 247 weak_ptr, task_runner_));
159 char* data = buffer->data();
160 if (!device_info().has_report_id) {
161 // Linux will not prefix the buffer with a report ID if they are not used
162 // by the device.
163 data[0] = 0;
164 data++;
165 expected_report_size--;
166 }
167
168 ssize_t bytes_read = HANDLE_EINTR(
169 read(device_file_.GetPlatformFile(), data, expected_report_size));
170 if (bytes_read < 0) {
171 if (errno == EAGAIN) {
172 return;
173 }
174 VPLOG(1) << "Read failed";
175 Disconnect();
176 return;
177 }
178 if (!device_info().has_report_id) {
179 // Include the byte prepended earlier.
180 bytes_read++;
181 }
182
183 ProcessInputReport(buffer, bytes_read);
184 } 248 }
185 249
186 void HidConnectionLinux::OnFileCanWriteWithoutBlocking(int fd) { 250 // static
251 void HidConnectionLinux::BlockingWrite(
252 base::PlatformFile platform_file,
253 scoped_refptr<net::IOBuffer> buffer,
254 size_t size,
255 const InternalWriteCallback& callback,
256 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
257 base::ThreadRestrictions::AssertIOAllowed();
258 ssize_t result = HANDLE_EINTR(write(platform_file, buffer->data(), size));
259 task_runner->PostTask(FROM_HERE, base::Bind(callback, result));
187 } 260 }
188 261
189 void HidConnectionLinux::Disconnect() { 262 // static
190 DCHECK(thread_checker().CalledOnValidThread()); 263 void HidConnectionLinux::BlockingIoctl(
191 device_file_watcher_.StopWatchingFileDescriptor(); 264 base::PlatformFile platform_file,
192 device_file_.Close(); 265 int request,
193 266 scoped_refptr<net::IOBuffer> buffer,
194 Flush(); 267 const IoctlCallback& callback,
268 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
269 base::ThreadRestrictions::AssertIOAllowed();
270 int result = ioctl(platform_file, request, buffer->data());
271 task_runner->PostTask(FROM_HERE, base::Bind(callback, result));
195 } 272 }
196 273
197 void HidConnectionLinux::Flush() { 274 // static
198 while (!pending_reads_.empty()) { 275 void HidConnectionLinux::CloseDevice(base::File device_file) {
199 pending_reads_.front().callback.Run(false, NULL, 0); 276 device_file.Close();
200 pending_reads_.pop();
201 }
202 } 277 }
203 278
204 void HidConnectionLinux::ProcessInputReport(scoped_refptr<net::IOBuffer> buffer, 279 void HidConnectionLinux::ProcessInputReport(scoped_refptr<net::IOBuffer> buffer,
205 size_t size) { 280 size_t size) {
206 DCHECK(thread_checker().CalledOnValidThread()); 281 DCHECK(thread_checker().CalledOnValidThread());
207 PendingHidReport report; 282 PendingHidReport report;
208 report.buffer = buffer; 283 report.buffer = buffer;
209 report.size = size; 284 report.size = size;
210 pending_reports_.push(report); 285 pending_reports_.push(report);
211 ProcessReadQueue(); 286 ProcessReadQueue();
212 } 287 }
213 288
214 void HidConnectionLinux::ProcessReadQueue() { 289 void HidConnectionLinux::ProcessReadQueue() {
215 DCHECK(thread_checker().CalledOnValidThread()); 290 DCHECK(thread_checker().CalledOnValidThread());
216 while (pending_reads_.size() && pending_reports_.size()) { 291 while (pending_reads_.size() && pending_reports_.size()) {
217 PendingHidRead read = pending_reads_.front(); 292 PendingHidRead read = pending_reads_.front();
218 PendingHidReport report = pending_reports_.front(); 293 PendingHidReport report = pending_reports_.front();
219 294
220 pending_reports_.pop(); 295 pending_reports_.pop();
221 if (CompleteRead(report.buffer, report.size, read.callback)) { 296 if (CompleteRead(report.buffer, report.size, read.callback)) {
222 pending_reads_.pop(); 297 pending_reads_.pop();
223 } 298 }
224 } 299 }
225 } 300 }
226 301
227 } // namespace device 302 } // namespace device
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698