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

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

Powered by Google App Engine
This is Rietveld 408576698