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