Add ICU message format support
[chromium-blink-merge.git] / device / hid / hid_connection_linux.cc
blobb2a2fc50362a27f75e2ba1f05a201784926a0fcb
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:
36 FileThreadHelper(base::PlatformFile platform_file,
37 scoped_refptr<HidDeviceInfo> device_info,
38 base::WeakPtr<HidConnectionLinux> connection,
39 scoped_refptr<base::SingleThreadTaskRunner> task_runner)
40 : platform_file_(platform_file),
41 connection_(connection),
42 task_runner_(task_runner) {
43 // Report buffers must always have room for the report ID.
44 report_buffer_size_ = device_info->max_input_report_size() + 1;
45 has_report_id_ = device_info->has_report_id();
48 ~FileThreadHelper() override {
49 DCHECK(thread_checker_.CalledOnValidThread());
52 // Starts the FileDescriptorWatcher that reads input events from the device.
53 // Must be called on a thread that has a base::MessageLoopForIO. The helper
54 // object is owned by the thread where it was started.
55 void Start() {
56 base::ThreadRestrictions::AssertIOAllowed();
57 thread_checker_.DetachFromThread();
58 if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
59 platform_file_, true, base::MessageLoopForIO::WATCH_READ,
60 &file_watcher_, this)) {
61 HID_LOG(ERROR) << "Failed to start watching device file.";
65 private:
66 // base::MessagePumpLibevent::Watcher implementation.
67 void OnFileCanReadWithoutBlocking(int fd) override {
68 DCHECK(thread_checker_.CalledOnValidThread());
69 DCHECK_EQ(fd, platform_file_);
71 scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(report_buffer_size_));
72 char* data = buffer->data();
73 size_t length = report_buffer_size_;
74 if (!has_report_id_) {
75 // Linux will not prefix the buffer with a report ID if report IDs are not
76 // used by the device. Prefix the buffer with 0.
77 *data++ = 0;
78 length--;
81 ssize_t bytes_read = HANDLE_EINTR(read(platform_file_, data, length));
82 if (bytes_read < 0) {
83 if (errno != EAGAIN) {
84 HID_PLOG(EVENT) << "Read failed";
85 // This assumes that the error is unrecoverable and disables reading
86 // from the device until it has been re-opened.
87 // TODO(reillyg): Investigate starting and stopping the file descriptor
88 // watcher in response to pending read requests so that per-request
89 // errors can be returned to the client.
90 file_watcher_.StopWatchingFileDescriptor();
92 return;
94 if (!has_report_id_) {
95 // Behave as if the byte prefixed above as the the report ID was read.
96 bytes_read++;
99 task_runner_->PostTask(FROM_HERE,
100 base::Bind(&HidConnectionLinux::ProcessInputReport,
101 connection_, buffer, bytes_read));
104 void OnFileCanWriteWithoutBlocking(int fd) override {
105 NOTREACHED(); // Only listening for reads.
108 base::ThreadChecker thread_checker_;
109 base::PlatformFile platform_file_;
110 size_t report_buffer_size_;
111 bool has_report_id_;
112 base::WeakPtr<HidConnectionLinux> connection_;
113 scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
114 base::MessagePumpLibevent::FileDescriptorWatcher file_watcher_;
117 HidConnectionLinux::HidConnectionLinux(
118 scoped_refptr<HidDeviceInfo> device_info,
119 base::File device_file,
120 scoped_refptr<base::SingleThreadTaskRunner> file_task_runner)
121 : HidConnection(device_info),
122 file_task_runner_(file_task_runner),
123 weak_factory_(this) {
124 task_runner_ = base::ThreadTaskRunnerHandle::Get();
125 device_file_ = device_file.Pass();
127 // The helper is passed a weak pointer to this connection so that it can be
128 // cleaned up after the connection is closed.
129 helper_ = new FileThreadHelper(device_file_.GetPlatformFile(), device_info,
130 weak_factory_.GetWeakPtr(), task_runner_);
131 file_task_runner_->PostTask(FROM_HERE, base::Bind(&FileThreadHelper::Start,
132 base::Unretained(helper_)));
135 HidConnectionLinux::~HidConnectionLinux() {
136 DCHECK(helper_ == nullptr);
139 void HidConnectionLinux::PlatformClose() {
140 // By closing the device file on the FILE thread (1) the requirement that
141 // base::File::Close is called on a thread where I/O is allowed is satisfied
142 // and (2) any tasks posted to this task runner that refer to this file will
143 // complete before it is closed.
144 file_task_runner_->DeleteSoon(FROM_HERE, helper_);
145 helper_ = nullptr;
146 file_task_runner_->PostTask(FROM_HERE,
147 base::Bind(&HidConnectionLinux::CloseDevice,
148 base::Passed(&device_file_)));
150 while (!pending_reads_.empty()) {
151 pending_reads_.front().callback.Run(false, NULL, 0);
152 pending_reads_.pop();
156 void HidConnectionLinux::PlatformRead(const ReadCallback& callback) {
157 PendingHidRead pending_read;
158 pending_read.callback = callback;
159 pending_reads_.push(pending_read);
160 ProcessReadQueue();
163 void HidConnectionLinux::PlatformWrite(scoped_refptr<net::IOBuffer> buffer,
164 size_t size,
165 const WriteCallback& callback) {
166 // Linux expects the first byte of the buffer to always be a report ID so the
167 // buffer can be used directly.
168 file_task_runner_->PostTask(
169 FROM_HERE,
170 base::Bind(&HidConnectionLinux::BlockingWrite,
171 device_file_.GetPlatformFile(), buffer, size,
172 base::Bind(&HidConnectionLinux::FinishWrite,
173 weak_factory_.GetWeakPtr(), size, callback),
174 task_runner_));
177 void HidConnectionLinux::PlatformGetFeatureReport(
178 uint8_t report_id,
179 const ReadCallback& callback) {
180 // The first byte of the destination buffer is the report ID being requested
181 // and is overwritten by the feature report.
182 DCHECK_GT(device_info()->max_feature_report_size(), 0u);
183 scoped_refptr<net::IOBufferWithSize> buffer(
184 new net::IOBufferWithSize(device_info()->max_feature_report_size() + 1));
185 buffer->data()[0] = report_id;
187 file_task_runner_->PostTask(
188 FROM_HERE,
189 base::Bind(
190 &HidConnectionLinux::BlockingIoctl, device_file_.GetPlatformFile(),
191 HIDIOCGFEATURE(buffer->size()), buffer,
192 base::Bind(&HidConnectionLinux::FinishGetFeatureReport,
193 weak_factory_.GetWeakPtr(), report_id, buffer, callback),
194 task_runner_));
197 void HidConnectionLinux::PlatformSendFeatureReport(
198 scoped_refptr<net::IOBuffer> buffer,
199 size_t size,
200 const WriteCallback& callback) {
201 // Linux expects the first byte of the buffer to always be a report ID so the
202 // buffer can be used directly.
203 file_task_runner_->PostTask(
204 FROM_HERE,
205 base::Bind(&HidConnectionLinux::BlockingIoctl,
206 device_file_.GetPlatformFile(), HIDIOCSFEATURE(size), buffer,
207 base::Bind(&HidConnectionLinux::FinishSendFeatureReport,
208 weak_factory_.GetWeakPtr(), callback),
209 task_runner_));
212 void HidConnectionLinux::FinishWrite(size_t expected_size,
213 const WriteCallback& callback,
214 ssize_t result) {
215 if (result < 0) {
216 HID_PLOG(EVENT) << "Write failed";
217 callback.Run(false);
218 } else {
219 if (static_cast<size_t>(result) != expected_size) {
220 HID_LOG(EVENT) << "Incomplete HID write: " << result
221 << " != " << expected_size;
223 callback.Run(true);
227 void HidConnectionLinux::FinishGetFeatureReport(
228 uint8_t report_id,
229 scoped_refptr<net::IOBuffer> buffer,
230 const ReadCallback& callback,
231 int result) {
232 if (result < 0) {
233 HID_PLOG(EVENT) << "Failed to get feature report";
234 callback.Run(false, NULL, 0);
235 } else if (result == 0) {
236 HID_LOG(EVENT) << "Get feature result too short.";
237 callback.Run(false, NULL, 0);
238 } else if (report_id == 0) {
239 // Linux adds a 0 to the beginning of the data received from the device.
240 scoped_refptr<net::IOBuffer> copied_buffer(new net::IOBuffer(result - 1));
241 memcpy(copied_buffer->data(), buffer->data() + 1, result - 1);
242 callback.Run(true, copied_buffer, result - 1);
243 } else {
244 callback.Run(true, buffer, result);
248 void HidConnectionLinux::FinishSendFeatureReport(const WriteCallback& callback,
249 int result) {
250 if (result < 0) {
251 HID_PLOG(EVENT) << "Failed to send feature report";
252 callback.Run(false);
253 } else {
254 callback.Run(true);
258 // static
259 void HidConnectionLinux::BlockingWrite(
260 base::PlatformFile platform_file,
261 scoped_refptr<net::IOBuffer> buffer,
262 size_t size,
263 const InternalWriteCallback& callback,
264 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
265 base::ThreadRestrictions::AssertIOAllowed();
266 ssize_t result = HANDLE_EINTR(write(platform_file, buffer->data(), size));
267 task_runner->PostTask(FROM_HERE, base::Bind(callback, result));
270 // static
271 void HidConnectionLinux::BlockingIoctl(
272 base::PlatformFile platform_file,
273 int request,
274 scoped_refptr<net::IOBuffer> buffer,
275 const IoctlCallback& callback,
276 scoped_refptr<base::SingleThreadTaskRunner> task_runner) {
277 base::ThreadRestrictions::AssertIOAllowed();
278 int result = ioctl(platform_file, request, buffer->data());
279 task_runner->PostTask(FROM_HERE, base::Bind(callback, result));
282 // static
283 void HidConnectionLinux::CloseDevice(base::File device_file) {
284 device_file.Close();
287 void HidConnectionLinux::ProcessInputReport(scoped_refptr<net::IOBuffer> buffer,
288 size_t size) {
289 DCHECK(thread_checker().CalledOnValidThread());
290 PendingHidReport report;
291 report.buffer = buffer;
292 report.size = size;
293 pending_reports_.push(report);
294 ProcessReadQueue();
297 void HidConnectionLinux::ProcessReadQueue() {
298 DCHECK(thread_checker().CalledOnValidThread());
299 while (pending_reads_.size() && pending_reports_.size()) {
300 PendingHidRead read = pending_reads_.front();
301 PendingHidReport report = pending_reports_.front();
303 pending_reports_.pop();
304 if (CompleteRead(report.buffer, report.size, read.callback)) {
305 pending_reads_.pop();
310 } // namespace device