Use bucket parameter in GetIfChanged for support binaries.
[chromium-blink-merge.git] / device / hid / hid_connection_linux.cc
blob27975e2eb4a610b4d4e11b8f9c35cab0b6eb1a51
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.
5 #include "device/hid/hid_connection_linux.h"
7 #include <errno.h>
8 #include <linux/hidraw.h>
9 #include <sys/ioctl.h>
11 #include <string>
13 #include "base/bind.h"
14 #include "base/files/file_path.h"
15 #include "base/message_loop/message_loop.h"
16 #include "base/message_loop/message_pump_libevent.h"
17 #include "base/posix/eintr_wrapper.h"
18 #include "base/thread_task_runner_handle.h"
19 #include "base/threading/thread_restrictions.h"
20 #include "device/hid/hid_service.h"
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
30 namespace device {
32 class HidConnectionLinux::Helper : public base::MessagePumpLibevent::Watcher {
33 public:
34 Helper(base::PlatformFile platform_file,
35 const HidDeviceInfo& device_info,
36 base::WeakPtr<HidConnectionLinux> connection,
37 scoped_refptr<base::SingleThreadTaskRunner> task_runner)
38 : platform_file_(platform_file),
39 connection_(connection),
40 task_runner_(task_runner) {
41 // Report buffers must always have room for the report ID.
42 report_buffer_size_ = device_info.max_input_report_size + 1;
43 has_report_id_ = device_info.has_report_id;
46 ~Helper() override { DCHECK(thread_checker_.CalledOnValidThread()); }
48 // Starts the FileDescriptorWatcher that reads input events from the device.
49 // Must be called on a thread that has a base::MessageLoopForIO. The helper
50 // object is owned by the thread where it was started.
51 void Start() {
52 base::ThreadRestrictions::AssertIOAllowed();
53 thread_checker_.DetachFromThread();
54 if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
55 platform_file_, true, base::MessageLoopForIO::WATCH_READ,
56 &file_watcher_, this)) {
57 LOG(ERROR) << "Failed to start watching device file.";
61 private:
62 // base::MessagePumpLibevent::Watcher implementation.
63 void OnFileCanReadWithoutBlocking(int fd) override {
64 DCHECK(thread_checker_.CalledOnValidThread());
65 DCHECK_EQ(fd, platform_file_);
67 scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(report_buffer_size_));
68 char* data = buffer->data();
69 size_t length = report_buffer_size_;
70 if (!has_report_id_) {
71 // Linux will not prefix the buffer with a report ID if report IDs are not
72 // used by the device. Prefix the buffer with 0.
73 *data++ = 0;
74 length--;
77 ssize_t bytes_read = HANDLE_EINTR(read(platform_file_, data, length));
78 if (bytes_read < 0) {
79 if (errno != EAGAIN) {
80 VPLOG(1) << "Read failed";
81 // This assumes that the error is unrecoverable and disables reading
82 // from the device until it has been re-opened.
83 // TODO(reillyg): Investigate starting and stopping the file descriptor
84 // watcher in response to pending read requests so that per-request
85 // errors can be returned to the client.
86 file_watcher_.StopWatchingFileDescriptor();
88 return;
90 if (!has_report_id_) {
91 // Behave as if the byte prefixed above as the the report ID was read.
92 bytes_read++;
95 task_runner_->PostTask(FROM_HERE,
96 base::Bind(&HidConnectionLinux::ProcessInputReport,
97 connection_, buffer, bytes_read));
100 void OnFileCanWriteWithoutBlocking(int fd) override {
101 NOTREACHED(); // Only listening for reads.
104 base::ThreadChecker thread_checker_;
105 base::PlatformFile platform_file_;
106 size_t report_buffer_size_;
107 bool has_report_id_;
108 base::WeakPtr<HidConnectionLinux> connection_;
109 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
110 base::MessagePumpLibevent::FileDescriptorWatcher file_watcher_;
113 HidConnectionLinux::HidConnectionLinux(
114 const HidDeviceInfo& device_info,
115 base::File device_file,
116 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner)
117 : HidConnection(device_info),
118 file_task_runner_(file_task_runner),
119 weak_factory_(this) {
120 task_runner_ = base::ThreadTaskRunnerHandle::Get();
121 device_file_ = device_file.Pass();
123 // The helper is passed a weak pointer to this connection so that it can be
124 // cleaned up after the connection is closed.
125 helper_ = new Helper(device_file_.GetPlatformFile(), device_info,
126 weak_factory_.GetWeakPtr(), task_runner_);
127 file_task_runner_->PostTask(
128 FROM_HERE, base::Bind(&Helper::Start, base::Unretained(helper_)));
131 HidConnectionLinux::~HidConnectionLinux() {
132 DCHECK(helper_ == nullptr);
135 void HidConnectionLinux::PlatformClose() {
136 // By closing the device file on the FILE thread (1) the requirement that
137 // base::File::Close is called on a thread where I/O is allowed is satisfied
138 // and (2) any tasks posted to this task runner that refer to this file will
139 // complete before it is closed.
140 file_task_runner_->DeleteSoon(FROM_HERE, helper_);
141 helper_ = nullptr;
142 file_task_runner_->PostTask(FROM_HERE,
143 base::Bind(&HidConnectionLinux::CloseDevice,
144 base::Passed(&device_file_)));
146 while (!pending_reads_.empty()) {
147 pending_reads_.front().callback.Run(false, NULL, 0);
148 pending_reads_.pop();
152 void HidConnectionLinux::PlatformRead(const ReadCallback& callback) {
153 PendingHidRead pending_read;
154 pending_read.callback = callback;
155 pending_reads_.push(pending_read);
156 ProcessReadQueue();
159 void HidConnectionLinux::PlatformWrite(scoped_refptr<net::IOBuffer> buffer,
160 size_t size,
161 const WriteCallback& callback) {
162 // Linux expects the first byte of the buffer to always be a report ID so the
163 // buffer can be used directly.
164 file_task_runner_->PostTask(
165 FROM_HERE,
166 base::Bind(&HidConnectionLinux::BlockingWrite,
167 device_file_.GetPlatformFile(), buffer, size,
168 base::Bind(&HidConnectionLinux::FinishWrite,
169 weak_factory_.GetWeakPtr(), size, callback),
170 task_runner_));
173 void HidConnectionLinux::PlatformGetFeatureReport(
174 uint8_t report_id,
175 const ReadCallback& callback) {
176 // The first byte of the destination buffer is the report ID being requested
177 // and is overwritten by the feature report.
178 DCHECK_GT(device_info().max_feature_report_size, 0u);
179 scoped_refptr<net::IOBufferWithSize> buffer(
180 new net::IOBufferWithSize(device_info().max_feature_report_size + 1));
181 buffer->data()[0] = report_id;
183 file_task_runner_->PostTask(
184 FROM_HERE,
185 base::Bind(
186 &HidConnectionLinux::BlockingIoctl, device_file_.GetPlatformFile(),
187 HIDIOCGFEATURE(buffer->size()), buffer,
188 base::Bind(&HidConnectionLinux::FinishGetFeatureReport,
189 weak_factory_.GetWeakPtr(), report_id, buffer, callback),
190 task_runner_));
193 void HidConnectionLinux::PlatformSendFeatureReport(
194 scoped_refptr<net::IOBuffer> buffer,
195 size_t size,
196 const WriteCallback& callback) {
197 // Linux expects the first byte of the buffer to always be a report ID so the
198 // buffer can be used directly.
199 file_task_runner_->PostTask(
200 FROM_HERE,
201 base::Bind(&HidConnectionLinux::BlockingIoctl,
202 device_file_.GetPlatformFile(), HIDIOCSFEATURE(size), buffer,
203 base::Bind(&HidConnectionLinux::FinishSendFeatureReport,
204 weak_factory_.GetWeakPtr(), callback),
205 task_runner_));
208 void HidConnectionLinux::FinishWrite(size_t expected_size,
209 const WriteCallback& callback,
210 ssize_t result) {
211 if (result < 0) {
212 VPLOG(1) << "Write failed";
213 callback.Run(false);
214 } else {
215 if (static_cast<size_t>(result) != expected_size) {
216 LOG(WARNING) << "Incomplete HID write: " << result
217 << " != " << expected_size;
219 callback.Run(true);
223 void HidConnectionLinux::FinishGetFeatureReport(
224 uint8_t report_id,
225 scoped_refptr<net::IOBuffer> buffer,
226 const ReadCallback& callback,
227 int result) {
228 if (result < 0) {
229 VPLOG(1) << "Failed to get feature report";
230 callback.Run(false, NULL, 0);
231 } else if (result == 0) {
232 VLOG(1) << "Get feature result too short.";
233 callback.Run(false, NULL, 0);
234 } else if (report_id == 0) {
235 // Linux adds a 0 to the beginning of the data received from the device.
236 scoped_refptr<net::IOBuffer> copied_buffer(new net::IOBuffer(result - 1));
237 memcpy(copied_buffer->data(), buffer->data() + 1, result - 1);
238 callback.Run(true, copied_buffer, result - 1);
239 } else {
240 callback.Run(true, buffer, result);
244 void HidConnectionLinux::FinishSendFeatureReport(const WriteCallback& callback,
245 int result) {
246 if (result < 0) {
247 VPLOG(1) << "Failed to send feature report";
248 callback.Run(false);
249 } else {
250 callback.Run(true);
254 // static
255 void HidConnectionLinux::BlockingWrite(
256 base::PlatformFile platform_file,
257 scoped_refptr<net::IOBuffer> buffer,
258 size_t size,
259 const InternalWriteCallback& callback,
260 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
261 base::ThreadRestrictions::AssertIOAllowed();
262 ssize_t result = HANDLE_EINTR(write(platform_file, buffer->data(), size));
263 task_runner->PostTask(FROM_HERE, base::Bind(callback, result));
266 // static
267 void HidConnectionLinux::BlockingIoctl(
268 base::PlatformFile platform_file,
269 int request,
270 scoped_refptr<net::IOBuffer> buffer,
271 const IoctlCallback& callback,
272 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
273 base::ThreadRestrictions::AssertIOAllowed();
274 int result = ioctl(platform_file, request, buffer->data());
275 task_runner->PostTask(FROM_HERE, base::Bind(callback, result));
278 // static
279 void HidConnectionLinux::CloseDevice(base::File device_file) {
280 device_file.Close();
283 void HidConnectionLinux::ProcessInputReport(scoped_refptr<net::IOBuffer> buffer,
284 size_t size) {
285 DCHECK(thread_checker().CalledOnValidThread());
286 PendingHidReport report;
287 report.buffer = buffer;
288 report.size = size;
289 pending_reports_.push(report);
290 ProcessReadQueue();
293 void HidConnectionLinux::ProcessReadQueue() {
294 DCHECK(thread_checker().CalledOnValidThread());
295 while (pending_reads_.size() && pending_reports_.size()) {
296 PendingHidRead read = pending_reads_.front();
297 PendingHidReport report = pending_reports_.front();
299 pending_reports_.pop();
300 if (CompleteRead(report.buffer, report.size, read.callback)) {
301 pending_reads_.pop();
306 } // namespace device