hid_connection_linux.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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/posix/eintr_wrapper.h"
17#include "base/threading/thread_restrictions.h"
18#include "base/tuple.h"
19#include "device/hid/hid_service.h"
20#include "device/hid/hid_service_linux.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
30namespace device {
31
32namespace {
33
34// Copies a buffer into a new one with a report ID byte inserted at the front.
35scoped_refptr<net::IOBufferWithSize> CopyBufferWithReportId(
36    scoped_refptr<net::IOBufferWithSize> buffer,
37    uint8_t report_id) {
38  scoped_refptr<net::IOBufferWithSize> new_buffer(
39      new net::IOBufferWithSize(buffer->size() + 1));
40  new_buffer->data()[0] = report_id;
41  memcpy(new_buffer->data() + 1, buffer->data(), buffer->size());
42  return new_buffer;
43}
44
45}  // namespace
46
47HidConnectionLinux::HidConnectionLinux(HidDeviceInfo device_info,
48                                       std::string dev_node)
49    : HidConnection(device_info) {
50  DCHECK(thread_checker_.CalledOnValidThread());
51
52  int flags = base::File::FLAG_OPEN |
53              base::File::FLAG_READ |
54              base::File::FLAG_WRITE;
55
56  base::File device_file(base::FilePath(dev_node), flags);
57  if (!device_file.IsValid()) {
58    base::File::Error file_error = device_file.error_details();
59
60    if (file_error == base::File::FILE_ERROR_ACCESS_DENIED) {
61      flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
62
63      base::File device_file(base::FilePath(dev_node), flags);
64      if (!device_file.IsValid()) {
65        LOG(ERROR) << device_file.error_details();
66        return;
67      }
68    } else {
69      LOG(ERROR) << file_error;
70      return;
71    }
72  }
73  if (fcntl(device_file.GetPlatformFile(), F_SETFL,
74            fcntl(device_file.GetPlatformFile(), F_GETFL) | O_NONBLOCK)) {
75    PLOG(ERROR) << "Failed to set non-blocking flag to device file.";
76    return;
77  }
78  device_file_ = device_file.Pass();
79
80  if (!base::MessageLoopForIO::current()->WatchFileDescriptor(
81      device_file_.GetPlatformFile(),
82      true,
83      base::MessageLoopForIO::WATCH_READ_WRITE,
84      &device_file_watcher_,
85      this)) {
86    LOG(ERROR) << "Failed to start watching device file.";
87  }
88}
89
90HidConnectionLinux::~HidConnectionLinux() {
91  DCHECK(thread_checker_.CalledOnValidThread());
92  Disconnect();
93}
94
95void HidConnectionLinux::OnFileCanReadWithoutBlocking(int fd) {
96  DCHECK(thread_checker_.CalledOnValidThread());
97  DCHECK_EQ(fd, device_file_.GetPlatformFile());
98
99  uint8 buffer[1024] = {0};
100  int bytes_read =
101      HANDLE_EINTR(read(device_file_.GetPlatformFile(), buffer, 1024));
102  if (bytes_read < 0) {
103    if (errno == EAGAIN) {
104      return;
105    }
106    Disconnect();
107    return;
108  }
109
110  PendingHidReport report;
111  report.buffer = new net::IOBufferWithSize(bytes_read);
112  memcpy(report.buffer->data(), buffer, bytes_read);
113  pending_reports_.push(report);
114  ProcessReadQueue();
115}
116
117void HidConnectionLinux::OnFileCanWriteWithoutBlocking(int fd) {}
118
119void HidConnectionLinux::Disconnect() {
120  DCHECK(thread_checker_.CalledOnValidThread());
121  device_file_watcher_.StopWatchingFileDescriptor();
122  device_file_.Close();
123  while (!pending_reads_.empty()) {
124    PendingHidRead pending_read = pending_reads_.front();
125    pending_reads_.pop();
126    pending_read.callback.Run(false, 0);
127  }
128}
129
130void HidConnectionLinux::Read(scoped_refptr<net::IOBufferWithSize> buffer,
131                              const IOCallback& callback) {
132  DCHECK(thread_checker_.CalledOnValidThread());
133  PendingHidRead pending_read;
134  pending_read.buffer = buffer;
135  pending_read.callback = callback;
136  pending_reads_.push(pending_read);
137  ProcessReadQueue();
138}
139
140void HidConnectionLinux::Write(uint8_t report_id,
141                               scoped_refptr<net::IOBufferWithSize> buffer,
142                               const IOCallback& callback) {
143  DCHECK(thread_checker_.CalledOnValidThread());
144  // If report ID is non-zero, insert it into a new copy of the buffer.
145  if (report_id != 0)
146    buffer = CopyBufferWithReportId(buffer, report_id);
147  int bytes_written = HANDLE_EINTR(
148      write(device_file_.GetPlatformFile(), buffer->data(), buffer->size()));
149  if (bytes_written < 0) {
150    Disconnect();
151    callback.Run(false, 0);
152  } else {
153    callback.Run(true, bytes_written);
154  }
155}
156
157void HidConnectionLinux::GetFeatureReport(
158    uint8_t report_id,
159    scoped_refptr<net::IOBufferWithSize> buffer,
160    const IOCallback& callback) {
161  DCHECK(thread_checker_.CalledOnValidThread());
162
163  if (buffer->size() == 0) {
164    callback.Run(false, 0);
165    return;
166  }
167
168  // The first byte of the destination buffer is the report ID being requested.
169  buffer->data()[0] = report_id;
170  int result = ioctl(device_file_.GetPlatformFile(),
171                     HIDIOCGFEATURE(buffer->size()),
172                     buffer->data());
173  if (result < 0)
174    callback.Run(false, 0);
175  else
176    callback.Run(true, result);
177}
178
179void HidConnectionLinux::SendFeatureReport(
180    uint8_t report_id,
181    scoped_refptr<net::IOBufferWithSize> buffer,
182    const IOCallback& callback) {
183  DCHECK(thread_checker_.CalledOnValidThread());
184  if (report_id != 0)
185    buffer = CopyBufferWithReportId(buffer, report_id);
186  int result = ioctl(device_file_.GetPlatformFile(),
187                     HIDIOCSFEATURE(buffer->size()),
188                     buffer->data());
189  if (result < 0)
190    callback.Run(false, 0);
191  else
192    callback.Run(true, result);
193}
194
195void HidConnectionLinux::ProcessReadQueue() {
196  while (pending_reads_.size() && pending_reports_.size()) {
197    PendingHidRead read = pending_reads_.front();
198    pending_reads_.pop();
199    PendingHidReport report = pending_reports_.front();
200    if (report.buffer->size() > read.buffer->size()) {
201      read.callback.Run(false, report.buffer->size());
202    } else {
203      memcpy(read.buffer->data(), report.buffer->data(), report.buffer->size());
204      pending_reports_.pop();
205      read.callback.Run(true, report.buffer->size());
206    }
207  }
208}
209
210}  // namespace device
211