Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / device / hid / hid_connection_linux.cc
blobec47bf707ce760e2006e7c3fa212c4da7ec733ba
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 "components/device_event_log/device_event_log.h"
21 #include "device/hid/hid_service.h"
23 // These are already defined in newer versions of linux/hidraw.h.
24 #ifndef HIDIOCSFEATURE
25 #define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x06, len)
26 #endif
27 #ifndef HIDIOCGFEATURE
28 #define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x07, len)
29 #endif
31 namespace device {
33 class HidConnectionLinux::FileThreadHelper
34 : public base::MessagePumpLibevent::Watcher,
35 public base::MessageLoop::DestructionObserver {
36 public:
37 FileThreadHelper(base::PlatformFile platform_file,
38 scoped_refptr<HidDeviceInfo> device_info,
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 // Report buffers must always have room for the report ID.
45 report_buffer_size_ = device_info->max_input_report_size() + 1;
46 has_report_id_ = device_info->has_report_id();
49 ~FileThreadHelper() override {
50 DCHECK(thread_checker_.CalledOnValidThread());
51 base::MessageLoop::current()->RemoveDestructionObserver(this);
54 // Starts the FileDescriptorWatcher that reads input events from the device.
55 // Must be called on a thread that has a base::MessageLoopForIO.
56 static void Start(scoped_ptr<FileThreadHelper> self) {
57 base::ThreadRestrictions::AssertIOAllowed();
58 self->thread_checker_.DetachFromThread();
60 if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
61 self->platform_file_, true, base::MessageLoopForIO::WATCH_READ,
62 &self->file_watcher_, self.get())) {
63 HID_LOG(ERROR) << "Failed to start watching device file.";
66 // |self| is now owned by the current message loop.
67 base::MessageLoop::current()->AddDestructionObserver(self.release());
70 private:
71 // base::MessagePumpLibevent::Watcher implementation.
72 void OnFileCanReadWithoutBlocking(int fd) override {
73 DCHECK(thread_checker_.CalledOnValidThread());
74 DCHECK_EQ(fd, platform_file_);
76 scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(report_buffer_size_));
77 char* data = buffer->data();
78 size_t length = report_buffer_size_;
79 if (!has_report_id_) {
80 // Linux will not prefix the buffer with a report ID if report IDs are not
81 // used by the device. Prefix the buffer with 0.
82 *data++ = 0;
83 length--;
86 ssize_t bytes_read = HANDLE_EINTR(read(platform_file_, data, length));
87 if (bytes_read < 0) {
88 if (errno != EAGAIN) {
89 HID_PLOG(EVENT) << "Read failed";
90 // This assumes that the error is unrecoverable and disables reading
91 // from the device until it has been re-opened.
92 // TODO(reillyg): Investigate starting and stopping the file descriptor
93 // watcher in response to pending read requests so that per-request
94 // errors can be returned to the client.
95 file_watcher_.StopWatchingFileDescriptor();
97 return;
99 if (!has_report_id_) {
100 // Behave as if the byte prefixed above as the the report ID was read.
101 bytes_read++;
104 task_runner_->PostTask(FROM_HERE,
105 base::Bind(&HidConnectionLinux::ProcessInputReport,
106 connection_, buffer, bytes_read));
109 void OnFileCanWriteWithoutBlocking(int fd) override {
110 NOTREACHED(); // Only listening for reads.
113 // base::MessageLoop::DestructionObserver:
114 void WillDestroyCurrentMessageLoop() override {
115 DCHECK(thread_checker_.CalledOnValidThread());
116 delete this;
119 base::ThreadChecker thread_checker_;
120 base::PlatformFile platform_file_;
121 size_t report_buffer_size_;
122 bool has_report_id_;
123 base::WeakPtr<HidConnectionLinux> connection_;
124 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
125 base::MessagePumpLibevent::FileDescriptorWatcher file_watcher_;
127 DISALLOW_COPY_AND_ASSIGN(FileThreadHelper);
130 HidConnectionLinux::HidConnectionLinux(
131 scoped_refptr<HidDeviceInfo> device_info,
132 base::File device_file,
133 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner)
134 : HidConnection(device_info),
135 file_task_runner_(file_task_runner),
136 weak_factory_(this) {
137 task_runner_ = base::ThreadTaskRunnerHandle::Get();
138 device_file_ = device_file.Pass();
140 // The helper is passed a weak pointer to this connection so that it can be
141 // cleaned up after the connection is closed.
142 scoped_ptr<FileThreadHelper> helper(
143 new FileThreadHelper(device_file_.GetPlatformFile(), device_info,
144 weak_factory_.GetWeakPtr(), task_runner_));
145 helper_ = helper.get();
146 file_task_runner_->PostTask(
147 FROM_HERE, base::Bind(&FileThreadHelper::Start, base::Passed(&helper)));
150 HidConnectionLinux::~HidConnectionLinux() {
151 DCHECK(helper_ == nullptr);
154 void HidConnectionLinux::PlatformClose() {
155 // By closing the device file on the FILE thread (1) the requirement that
156 // base::File::Close is called on a thread where I/O is allowed is satisfied
157 // and (2) any tasks posted to this task runner that refer to this file will
158 // complete before it is closed.
159 file_task_runner_->DeleteSoon(FROM_HERE, helper_);
160 helper_ = nullptr;
161 file_task_runner_->PostTask(FROM_HERE,
162 base::Bind(&HidConnectionLinux::CloseDevice,
163 base::Passed(&device_file_)));
165 while (!pending_reads_.empty()) {
166 pending_reads_.front().callback.Run(false, NULL, 0);
167 pending_reads_.pop();
171 void HidConnectionLinux::PlatformRead(const ReadCallback& callback) {
172 PendingHidRead pending_read;
173 pending_read.callback = callback;
174 pending_reads_.push(pending_read);
175 ProcessReadQueue();
178 void HidConnectionLinux::PlatformWrite(scoped_refptr<net::IOBuffer> buffer,
179 size_t size,
180 const WriteCallback& callback) {
181 // Linux expects the first byte of the buffer to always be a report ID so the
182 // buffer can be used directly.
183 file_task_runner_->PostTask(
184 FROM_HERE,
185 base::Bind(&HidConnectionLinux::BlockingWrite,
186 device_file_.GetPlatformFile(), buffer, size,
187 base::Bind(&HidConnectionLinux::FinishWrite,
188 weak_factory_.GetWeakPtr(), size, callback),
189 task_runner_));
192 void HidConnectionLinux::PlatformGetFeatureReport(
193 uint8_t report_id,
194 const ReadCallback& callback) {
195 // The first byte of the destination buffer is the report ID being requested
196 // and is overwritten by the feature report.
197 DCHECK_GT(device_info()->max_feature_report_size(), 0u);
198 scoped_refptr<net::IOBufferWithSize> buffer(
199 new net::IOBufferWithSize(device_info()->max_feature_report_size() + 1));
200 buffer->data()[0] = report_id;
202 file_task_runner_->PostTask(
203 FROM_HERE,
204 base::Bind(
205 &HidConnectionLinux::BlockingIoctl, device_file_.GetPlatformFile(),
206 HIDIOCGFEATURE(buffer->size()), buffer,
207 base::Bind(&HidConnectionLinux::FinishGetFeatureReport,
208 weak_factory_.GetWeakPtr(), report_id, buffer, callback),
209 task_runner_));
212 void HidConnectionLinux::PlatformSendFeatureReport(
213 scoped_refptr<net::IOBuffer> buffer,
214 size_t size,
215 const WriteCallback& callback) {
216 // Linux expects the first byte of the buffer to always be a report ID so the
217 // buffer can be used directly.
218 file_task_runner_->PostTask(
219 FROM_HERE,
220 base::Bind(&HidConnectionLinux::BlockingIoctl,
221 device_file_.GetPlatformFile(), HIDIOCSFEATURE(size), buffer,
222 base::Bind(&HidConnectionLinux::FinishSendFeatureReport,
223 weak_factory_.GetWeakPtr(), callback),
224 task_runner_));
227 void HidConnectionLinux::FinishWrite(size_t expected_size,
228 const WriteCallback& callback,
229 ssize_t result) {
230 if (result < 0) {
231 HID_PLOG(EVENT) << "Write failed";
232 callback.Run(false);
233 } else {
234 if (static_cast<size_t>(result) != expected_size) {
235 HID_LOG(EVENT) << "Incomplete HID write: " << result
236 << " != " << expected_size;
238 callback.Run(true);
242 void HidConnectionLinux::FinishGetFeatureReport(
243 uint8_t report_id,
244 scoped_refptr<net::IOBuffer> buffer,
245 const ReadCallback& callback,
246 int result) {
247 if (result < 0) {
248 HID_PLOG(EVENT) << "Failed to get feature report";
249 callback.Run(false, NULL, 0);
250 } else if (result == 0) {
251 HID_LOG(EVENT) << "Get feature result too short.";
252 callback.Run(false, NULL, 0);
253 } else if (report_id == 0) {
254 // Linux adds a 0 to the beginning of the data received from the device.
255 scoped_refptr<net::IOBuffer> copied_buffer(new net::IOBuffer(result - 1));
256 memcpy(copied_buffer->data(), buffer->data() + 1, result - 1);
257 callback.Run(true, copied_buffer, result - 1);
258 } else {
259 callback.Run(true, buffer, result);
263 void HidConnectionLinux::FinishSendFeatureReport(const WriteCallback& callback,
264 int result) {
265 if (result < 0) {
266 HID_PLOG(EVENT) << "Failed to send feature report";
267 callback.Run(false);
268 } else {
269 callback.Run(true);
273 // static
274 void HidConnectionLinux::BlockingWrite(
275 base::PlatformFile platform_file,
276 scoped_refptr<net::IOBuffer> buffer,
277 size_t size,
278 const InternalWriteCallback& callback,
279 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
280 base::ThreadRestrictions::AssertIOAllowed();
281 ssize_t result = HANDLE_EINTR(write(platform_file, buffer->data(), size));
282 task_runner->PostTask(FROM_HERE, base::Bind(callback, result));
285 // static
286 void HidConnectionLinux::BlockingIoctl(
287 base::PlatformFile platform_file,
288 int request,
289 scoped_refptr<net::IOBuffer> buffer,
290 const IoctlCallback& callback,
291 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
292 base::ThreadRestrictions::AssertIOAllowed();
293 int result = ioctl(platform_file, request, buffer->data());
294 task_runner->PostTask(FROM_HERE, base::Bind(callback, result));
297 // static
298 void HidConnectionLinux::CloseDevice(base::File device_file) {
299 device_file.Close();
302 void HidConnectionLinux::ProcessInputReport(scoped_refptr<net::IOBuffer> buffer,
303 size_t size) {
304 DCHECK(thread_checker().CalledOnValidThread());
305 PendingHidReport report;
306 report.buffer = buffer;
307 report.size = size;
308 pending_reports_.push(report);
309 ProcessReadQueue();
312 void HidConnectionLinux::ProcessReadQueue() {
313 DCHECK(thread_checker().CalledOnValidThread());
314 while (pending_reads_.size() && pending_reports_.size()) {
315 PendingHidRead read = pending_reads_.front();
316 PendingHidReport report = pending_reports_.front();
318 pending_reports_.pop();
319 if (CompleteRead(report.buffer, report.size, read.callback)) {
320 pending_reads_.pop();
325 } // namespace device