ac2e554f95db24aedc6d94eae2524f3b50d7787a
[platform/framework/web/crosswalk.git] / src / device / hid / hid_connection_linux.cc
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.
4
5 #include "device/hid/hid_connection_linux.h"
6
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <libudev.h>
10 #include <linux/hidraw.h>
11 #include <sys/ioctl.h>
12
13 #include <string>
14
15 #include "base/files/file_path.h"
16 #include "base/message_loop/message_loop.h"
17 #include "base/posix/eintr_wrapper.h"
18 #include "base/threading/thread_restrictions.h"
19 #include "base/tuple.h"
20 #include "device/hid/hid_service.h"
21
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
29
30 namespace device {
31
32 HidConnectionLinux::HidConnectionLinux(HidDeviceInfo device_info,
33                                        std::string dev_node)
34     : HidConnection(device_info) {
35   int flags = base::File::FLAG_OPEN |
36               base::File::FLAG_READ |
37               base::File::FLAG_WRITE;
38
39   base::File device_file(base::FilePath(dev_node), flags);
40   if (!device_file.IsValid()) {
41     base::File::Error file_error = device_file.error_details();
42
43     if (file_error == base::File::FILE_ERROR_ACCESS_DENIED) {
44       VLOG(1) << "Access denied opening device read-write, trying read-only.";
45
46       flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
47
48       device_file = base::File(base::FilePath(dev_node), flags);
49     }
50   }
51   if (!device_file.IsValid()) {
52     LOG(ERROR) << "Failed to open '" << dev_node << "': "
53         << base::File::ErrorToString(device_file.error_details());
54     return;
55   }
56
57   if (fcntl(device_file.GetPlatformFile(), F_SETFL,
58             fcntl(device_file.GetPlatformFile(), F_GETFL) | O_NONBLOCK)) {
59     PLOG(ERROR) << "Failed to set non-blocking flag to device file";
60     return;
61   }
62   device_file_ = device_file.Pass();
63
64   if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
65       device_file_.GetPlatformFile(),
66       true,
67       base::MessageLoopForIO::WATCH_READ_WRITE,
68       &device_file_watcher_,
69       this)) {
70     LOG(ERROR) << "Failed to start watching device file.";
71   }
72 }
73
74 HidConnectionLinux::~HidConnectionLinux() {
75   Disconnect();
76   Flush();
77 }
78
79 void HidConnectionLinux::PlatformRead(const ReadCallback& callback) {
80   PendingHidRead pending_read;
81   pending_read.callback = callback;
82   pending_reads_.push(pending_read);
83   ProcessReadQueue();
84 }
85
86 void HidConnectionLinux::PlatformWrite(scoped_refptr<net::IOBuffer> buffer,
87                                        size_t size,
88                                        const WriteCallback& callback) {
89   // Linux expects the first byte of the buffer to always be a report ID so the
90   // buffer can be used directly.
91   const ssize_t bytes_written =
92       HANDLE_EINTR(write(device_file_.GetPlatformFile(), buffer->data(), size));
93   if (bytes_written < 0) {
94     VPLOG(1) << "Write failed";
95     Disconnect();
96     callback.Run(false);
97   } else {
98     if (static_cast<size_t>(bytes_written) != size) {
99       LOG(WARNING) << "Incomplete HID write: " << bytes_written
100                    << " != " << size;
101     }
102     callback.Run(true);
103   }
104 }
105
106 void HidConnectionLinux::PlatformGetFeatureReport(
107     uint8_t report_id,
108     const ReadCallback& callback) {
109   // The first byte of the destination buffer is the report ID being requested
110   // and is overwritten by the feature report.
111   DCHECK_GT(device_info().max_feature_report_size, 0);
112   scoped_refptr<net::IOBufferWithSize> buffer(
113       new net::IOBufferWithSize(device_info().max_feature_report_size));
114   buffer->data()[0] = report_id;
115
116   int result = ioctl(device_file_.GetPlatformFile(),
117                      HIDIOCGFEATURE(buffer->size()),
118                      buffer->data());
119   if (result < 0) {
120     VPLOG(1) << "Failed to get feature report";
121     callback.Run(false, NULL, 0);
122   } else {
123     callback.Run(true, buffer, result);
124   }
125 }
126
127 void HidConnectionLinux::PlatformSendFeatureReport(
128     scoped_refptr<net::IOBuffer> buffer,
129     size_t size,
130     const WriteCallback& callback) {
131   // Linux expects the first byte of the buffer to always be a report ID so the
132   // buffer can be used directly.
133   int result = ioctl(
134       device_file_.GetPlatformFile(), HIDIOCSFEATURE(size), buffer->data());
135   if (result < 0) {
136     VPLOG(1) << "Failed to send feature report";
137     callback.Run(false);
138   } else {
139     callback.Run(true);
140   }
141 }
142
143 void HidConnectionLinux::OnFileCanReadWithoutBlocking(int fd) {
144   DCHECK(thread_checker().CalledOnValidThread());
145   DCHECK_EQ(fd, device_file_.GetPlatformFile());
146
147   size_t expected_report_size = device_info().max_input_report_size + 1;
148   scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(expected_report_size));
149   char* data = buffer->data();
150   if (!device_info().has_report_id) {
151     // Linux will not prefix the buffer with a report ID if they are not used
152     // by the device.
153     data[0] = 0;
154     data++;
155     expected_report_size--;
156   }
157
158   ssize_t bytes_read = HANDLE_EINTR(
159       read(device_file_.GetPlatformFile(), data, expected_report_size));
160   if (bytes_read < 0) {
161     if (errno == EAGAIN) {
162       return;
163     }
164     VPLOG(1) << "Read failed";
165     Disconnect();
166     return;
167   }
168   if (!device_info().has_report_id) {
169     // Include the byte prepended earlier.
170     bytes_read++;
171   }
172
173   ProcessInputReport(buffer, bytes_read);
174 }
175
176 void HidConnectionLinux::OnFileCanWriteWithoutBlocking(int fd) {
177 }
178
179 void HidConnectionLinux::Disconnect() {
180   DCHECK(thread_checker().CalledOnValidThread());
181   device_file_watcher_.StopWatchingFileDescriptor();
182   device_file_.Close();
183
184   Flush();
185 }
186
187 void HidConnectionLinux::Flush() {
188   while (!pending_reads_.empty()) {
189     pending_reads_.front().callback.Run(false, NULL, 0);
190     pending_reads_.pop();
191   }
192 }
193
194 void HidConnectionLinux::ProcessInputReport(scoped_refptr<net::IOBuffer> buffer,
195                                             size_t size) {
196   DCHECK(thread_checker().CalledOnValidThread());
197   PendingHidReport report;
198   report.buffer = buffer;
199   report.size = size;
200   pending_reports_.push(report);
201   ProcessReadQueue();
202 }
203
204 void HidConnectionLinux::ProcessReadQueue() {
205   DCHECK(thread_checker().CalledOnValidThread());
206   while (pending_reads_.size() && pending_reports_.size()) {
207     PendingHidRead read = pending_reads_.front();
208     PendingHidReport report = pending_reports_.front();
209
210     pending_reports_.pop();
211     if (CompleteRead(report.buffer, report.size, read.callback)) {
212       pending_reads_.pop();
213     }
214   }
215 }
216
217 }  // namespace device