Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "device/hid/hid_connection_linux.h" | 5 #include "device/hid/hid_connection_linux.h" |
| 6 | 6 |
| 7 #include <errno.h> | 7 #include <errno.h> |
| 8 #include <fcntl.h> | 8 #include <fcntl.h> |
| 9 #include <libudev.h> | 9 #include <libudev.h> |
| 10 #include <linux/hidraw.h> | 10 #include <linux/hidraw.h> |
| 11 #include <sys/ioctl.h> | |
| 12 | |
| 11 #include <string> | 13 #include <string> |
| 12 | 14 |
| 13 #include "base/threading/thread_restrictions.h" | 15 #include "base/threading/thread_restrictions.h" |
| 14 #include "base/tuple.h" | 16 #include "base/tuple.h" |
| 15 #include "device/hid/hid_service.h" | 17 #include "device/hid/hid_service.h" |
| 16 #include "device/hid/hid_service_linux.h" | 18 #include "device/hid/hid_service_linux.h" |
| 17 | 19 |
| 20 // These are already defined in newer versions of linux/hidraw.h. | |
| 21 #ifndef HIDIOCSFEATURE | |
| 22 #define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x06, len) | |
| 23 #endif | |
| 24 #ifndef HIDIOCGFEATURE | |
| 25 #define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x07, len) | |
| 26 #endif | |
| 18 | 27 |
| 19 namespace device { | 28 namespace device { |
| 20 | 29 |
| 21 namespace { | 30 namespace { |
| 22 | 31 |
| 32 // Copies a buffer into a new one a report ID byte inserted at the front. | |
|
Mark Mentovai
2014/02/26 17:41:44
This comment is missing a word?
Ken Rockot(use gerrit already)
2014/02/26 22:36:39
Done.
| |
| 33 scoped_refptr<net::IOBufferWithSize> CopyBufferWithReportId( | |
| 34 scoped_refptr<net::IOBufferWithSize> buffer, | |
| 35 uint8_t report_id) { | |
| 36 scoped_refptr<net::IOBufferWithSize> new_buffer( | |
| 37 new net::IOBufferWithSize(buffer->size() + 1)); | |
| 38 new_buffer->data()[0] = report_id; | |
| 39 memcpy(new_buffer->data() + 1, buffer->data(), buffer->size()); | |
| 40 return new_buffer; | |
| 41 } | |
| 42 | |
| 23 const char kHidrawSubsystem[] = "hidraw"; | 43 const char kHidrawSubsystem[] = "hidraw"; |
| 24 | 44 |
| 25 } // namespace | 45 } // namespace |
| 26 | 46 |
| 27 HidConnectionLinux::HidConnectionLinux(HidDeviceInfo device_info, | 47 HidConnectionLinux::HidConnectionLinux(HidDeviceInfo device_info, |
| 28 ScopedUdevDevicePtr udev_raw_device) | 48 ScopedUdevDevicePtr udev_raw_device) |
| 29 : HidConnection(device_info), | 49 : HidConnection(device_info) { |
| 30 initialized_(false) { | |
| 31 DCHECK(thread_checker_.CalledOnValidThread()); | 50 DCHECK(thread_checker_.CalledOnValidThread()); |
| 32 | 51 |
| 33 udev_device* dev = udev_raw_device.get(); | 52 udev_device* dev = udev_raw_device.get(); |
| 34 std::string dev_node; | 53 std::string dev_node; |
| 35 if (!FindHidrawDevNode(dev, &dev_node)) { | 54 if (!FindHidrawDevNode(dev, &dev_node)) { |
| 36 LOG(ERROR) << "Cannot open HID device as hidraw device."; | 55 LOG(ERROR) << "Cannot open HID device as hidraw device."; |
| 37 return; | 56 return; |
| 38 } | 57 } |
| 39 | 58 |
| 40 int flags = base::File::FLAG_OPEN | | 59 int flags = base::File::FLAG_OPEN | |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 54 return; | 73 return; |
| 55 } | 74 } |
| 56 device_file_ = device_file.Pass(); | 75 device_file_ = device_file.Pass(); |
| 57 | 76 |
| 58 if (!base::MessageLoopForIO::current()->WatchFileDescriptor( | 77 if (!base::MessageLoopForIO::current()->WatchFileDescriptor( |
| 59 device_file_.GetPlatformFile(), | 78 device_file_.GetPlatformFile(), |
| 60 true, | 79 true, |
| 61 base::MessageLoopForIO::WATCH_READ_WRITE, | 80 base::MessageLoopForIO::WATCH_READ_WRITE, |
| 62 &device_file_watcher_, | 81 &device_file_watcher_, |
| 63 this)) { | 82 this)) { |
| 64 LOG(ERROR) << "Cannot start watching file descriptor."; | 83 LOG(ERROR) << "Failed to start watching device file."; |
| 65 return; | |
| 66 } | 84 } |
| 67 | |
| 68 initialized_ = true; | |
| 69 } | 85 } |
| 70 | 86 |
| 71 HidConnectionLinux::~HidConnectionLinux() { | 87 HidConnectionLinux::~HidConnectionLinux() { |
| 72 DCHECK(thread_checker_.CalledOnValidThread()); | 88 DCHECK(thread_checker_.CalledOnValidThread()); |
| 73 Disconnect(); | 89 Disconnect(); |
| 74 } | 90 } |
| 75 | 91 |
| 76 void HidConnectionLinux::OnFileCanReadWithoutBlocking(int fd) { | 92 void HidConnectionLinux::OnFileCanReadWithoutBlocking(int fd) { |
| 77 DCHECK(thread_checker_.CalledOnValidThread()); | 93 DCHECK(thread_checker_.CalledOnValidThread()); |
| 78 DCHECK_EQ(fd, device_file_.GetPlatformFile()); | 94 DCHECK_EQ(fd, device_file_.GetPlatformFile()); |
| 79 DCHECK(initialized_); | |
| 80 | 95 |
| 81 uint8 buffer[1024] = {0}; | 96 uint8 buffer[1024] = {0}; |
| 82 int bytes = read(device_file_.GetPlatformFile(), buffer, 1024); | 97 int bytes_read = read(device_file_.GetPlatformFile(), buffer, 1024); |
| 83 if (bytes < 0) { | 98 if (bytes_read < 0) { |
| 84 if (errno == EAGAIN) { | 99 if (errno == EAGAIN) { |
| 85 return; | 100 return; |
| 86 } | 101 } |
| 87 Disconnect(); | 102 Disconnect(); |
| 88 return; | 103 return; |
| 89 } | 104 } |
| 90 scoped_refptr<net::IOBuffer> io_buffer(new net::IOBuffer(bytes)); | |
| 91 memcpy(io_buffer->data(), buffer, bytes); | |
| 92 input_reports_.push(std::make_pair(io_buffer, bytes)); | |
| 93 | 105 |
| 106 PendingHidReport report; | |
| 107 report.buffer = new net::IOBufferWithSize(bytes_read); | |
| 108 memcpy(report.buffer->data(), buffer, bytes_read); | |
| 109 pending_reports_.push(report); | |
| 94 ProcessReadQueue(); | 110 ProcessReadQueue(); |
| 95 } | 111 } |
| 96 | 112 |
| 97 void HidConnectionLinux::OnFileCanWriteWithoutBlocking(int fd) {} | 113 void HidConnectionLinux::OnFileCanWriteWithoutBlocking(int fd) {} |
| 98 | 114 |
| 99 void HidConnectionLinux::Disconnect() { | 115 void HidConnectionLinux::Disconnect() { |
| 100 DCHECK(thread_checker_.CalledOnValidThread()); | 116 DCHECK(thread_checker_.CalledOnValidThread()); |
| 101 if (!initialized_) | |
| 102 return; | |
| 103 | |
| 104 initialized_ = false; | |
| 105 device_file_watcher_.StopWatchingFileDescriptor(); | 117 device_file_watcher_.StopWatchingFileDescriptor(); |
| 106 device_file_.Close(); | 118 device_file_.Close(); |
| 107 while (!read_queue_.empty()) { | 119 while (!pending_reads_.empty()) { |
| 108 PendingRequest callback = read_queue_.front(); | 120 PendingHidRead pending_read = pending_reads_.front(); |
| 109 read_queue_.pop(); | 121 pending_reads_.pop(); |
| 110 callback.c.Run(false, 0); | 122 pending_read.callback.Run(false, 0); |
| 111 } | 123 } |
| 112 } | 124 } |
| 113 | 125 |
| 114 void HidConnectionLinux::Read(scoped_refptr<net::IOBuffer> buffer, | 126 void HidConnectionLinux::Read(scoped_refptr<net::IOBufferWithSize> buffer, |
| 115 size_t size, | |
| 116 const IOCallback& callback) { | 127 const IOCallback& callback) { |
| 117 DCHECK(thread_checker_.CalledOnValidThread()); | 128 DCHECK(thread_checker_.CalledOnValidThread()); |
| 118 if (!initialized_) { | 129 PendingHidRead pending_read; |
| 119 DCHECK(read_queue_.empty()); | 130 pending_read.buffer = buffer; |
| 120 // There might be unread reports. | 131 pending_read.callback = callback; |
| 121 if (!input_reports_.empty()){ | 132 pending_reads_.push(pending_read); |
| 122 read_queue_.push(MakeTuple(buffer, size, callback)); | 133 ProcessReadQueue(); |
| 123 ProcessReadQueue(); | 134 } |
| 124 } | 135 |
| 136 void HidConnectionLinux::Write(uint8_t report_id, | |
| 137 scoped_refptr<net::IOBufferWithSize> buffer, | |
| 138 const IOCallback& callback) { | |
| 139 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 140 // If report ID is non-zero, insert it into a new copy of the buffer. | |
| 141 if (report_id != 0) | |
| 142 buffer = CopyBufferWithReportId(buffer, report_id); | |
| 143 int bytes_written = | |
| 144 write(device_file_.GetPlatformFile(), buffer->data(), buffer->size()); | |
|
Mark Mentovai
2014/02/26 17:41:44
Do you need to handle a short write or EINTR? Mayb
Ken Rockot(use gerrit already)
2014/02/26 22:36:39
I lean against treating short reads or writes as e
| |
| 145 if (bytes_written < 0) { | |
| 146 Disconnect(); | |
| 125 callback.Run(false, 0); | 147 callback.Run(false, 0); |
| 126 return; | |
| 127 } else { | 148 } else { |
| 128 read_queue_.push(MakeTuple(buffer, size, callback)); | 149 callback.Run(true, bytes_written); |
| 129 ProcessReadQueue(); | |
| 130 } | 150 } |
| 131 } | 151 } |
| 132 | 152 |
| 133 void HidConnectionLinux::Write(scoped_refptr<net::IOBuffer> buffer, | 153 void HidConnectionLinux::GetFeatureReport( |
| 134 size_t size, | 154 uint8_t report_id, |
| 135 const IOCallback& callback) { | 155 scoped_refptr<net::IOBufferWithSize> buffer, |
| 156 const IOCallback& callback) { | |
| 136 DCHECK(thread_checker_.CalledOnValidThread()); | 157 DCHECK(thread_checker_.CalledOnValidThread()); |
| 137 if (!initialized_) { | 158 |
| 159 if (buffer->size() == 0) { | |
| 138 callback.Run(false, 0); | 160 callback.Run(false, 0); |
| 139 return; | 161 return; |
| 140 } else { | 162 } |
| 141 int bytes = write(device_file_.GetPlatformFile(), buffer->data(), size); | 163 |
| 142 if (bytes < 0) { | 164 // The first byte of the destination buffer is the report ID being requested. |
| 143 Disconnect(); | 165 buffer->data()[0] = report_id; |
| 144 callback.Run(false, 0); | 166 int result = ioctl(device_file_.GetPlatformFile(), |
| 167 HIDIOCGFEATURE(buffer->size()), | |
| 168 buffer->data()); | |
| 169 if (result < 0) | |
| 170 callback.Run(false, 0); | |
| 171 else | |
| 172 callback.Run(true, result); | |
| 173 } | |
| 174 | |
| 175 void HidConnectionLinux::SendFeatureReport( | |
| 176 uint8_t report_id, | |
| 177 scoped_refptr<net::IOBufferWithSize> buffer, | |
| 178 const IOCallback& callback) { | |
| 179 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 180 if (report_id != 0) | |
| 181 buffer = CopyBufferWithReportId(buffer, report_id); | |
| 182 int result = ioctl(device_file_.GetPlatformFile(), | |
| 183 HIDIOCSFEATURE(buffer->size()), | |
| 184 buffer->data()); | |
| 185 if (result < 0) | |
| 186 callback.Run(false, 0); | |
| 187 else | |
| 188 callback.Run(true, result); | |
| 189 } | |
| 190 | |
| 191 void HidConnectionLinux::ProcessReadQueue() { | |
| 192 while (pending_reads_.size() && pending_reports_.size()) { | |
| 193 PendingHidRead read = pending_reads_.front(); | |
| 194 pending_reads_.pop(); | |
| 195 PendingHidReport report = pending_reports_.front(); | |
| 196 if (report.buffer->size() > read.buffer->size()) { | |
| 197 read.callback.Run(false, report.buffer->size()); | |
| 145 } else { | 198 } else { |
| 146 callback.Run(true, bytes); | 199 memcpy(read.buffer->data(), report.buffer->data(), report.buffer->size()); |
| 200 pending_reports_.pop(); | |
| 201 read.callback.Run(true, report.buffer->size()); | |
| 147 } | 202 } |
| 148 } | 203 } |
| 149 } | 204 } |
| 150 | |
| 151 void HidConnectionLinux::GetFeatureReport(scoped_refptr<net::IOBuffer> buffer, | |
| 152 size_t size, | |
| 153 const IOCallback& callback) { | |
| 154 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 155 if (!initialized_) { | |
| 156 callback.Run(false, 0); | |
| 157 return; | |
| 158 } | |
| 159 NOTIMPLEMENTED(); | |
| 160 } | |
| 161 | |
| 162 void HidConnectionLinux::SendFeatureReport(scoped_refptr<net::IOBuffer> buffer, | |
| 163 size_t size, | |
| 164 const IOCallback& callback) { | |
| 165 DCHECK(thread_checker_.CalledOnValidThread()); | |
| 166 if (!initialized_) { | |
| 167 callback.Run(false, 0); | |
| 168 return; | |
| 169 } | |
| 170 NOTIMPLEMENTED(); | |
| 171 } | |
| 172 | |
| 173 void HidConnectionLinux::ProcessReadQueue() { | |
| 174 while(read_queue_.size() && input_reports_.size()) { | |
| 175 PendingRequest request = read_queue_.front(); | |
| 176 read_queue_.pop(); | |
| 177 PendingReport report = input_reports_.front(); | |
| 178 if (report.second > request.b) { | |
| 179 request.c.Run(false, report.second); | |
| 180 } else { | |
| 181 memcpy(request.a->data(), report.first->data(), report.second); | |
| 182 input_reports_.pop(); | |
| 183 request.c.Run(true, report.second); | |
| 184 } | |
| 185 } | |
| 186 } | |
| 187 | 205 |
| 188 bool HidConnectionLinux::FindHidrawDevNode(udev_device* parent, | 206 bool HidConnectionLinux::FindHidrawDevNode(udev_device* parent, |
| 189 std::string* result) { | 207 std::string* result) { |
| 190 udev* udev = udev_device_get_udev(parent); | 208 udev* udev = udev_device_get_udev(parent); |
| 191 if (!udev) | 209 if (!udev) |
| 192 return false; | 210 return false; |
| 193 | 211 |
| 194 ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev)); | 212 ScopedUdevEnumeratePtr enumerate(udev_enumerate_new(udev)); |
| 195 if (!enumerate) | 213 if (!enumerate) |
| 196 return false; | 214 return false; |
| 197 | 215 |
| 198 if (udev_enumerate_add_match_subsystem(enumerate.get(), kHidrawSubsystem)) { | 216 if (udev_enumerate_add_match_subsystem(enumerate.get(), kHidrawSubsystem)) { |
| 199 return false; | 217 return false; |
| 200 } | 218 } |
| 201 if (udev_enumerate_scan_devices(enumerate.get())) { | 219 if (udev_enumerate_scan_devices(enumerate.get())) { |
| 202 return false; | 220 return false; |
| 203 } | 221 } |
| 204 | 222 |
| 205 const char* parent_path = udev_device_get_devpath(parent); | 223 const char* parent_path = udev_device_get_devpath(parent); |
| 206 udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate.get()); | 224 udev_list_entry* devices = udev_enumerate_get_list_entry(enumerate.get()); |
| 207 for (udev_list_entry* i = devices; i != NULL; | 225 for (udev_list_entry* i = devices; i != NULL; |
| 208 i = udev_list_entry_get_next(i)) { | 226 i = udev_list_entry_get_next(i)) { |
| 209 ScopedUdevDevicePtr hid_dev( | 227 ScopedUdevDevicePtr hid_dev( |
| 210 udev_device_new_from_syspath(udev, udev_list_entry_get_name(i))); | 228 udev_device_new_from_syspath(udev, udev_list_entry_get_name(i))); |
| 211 const char* raw_path = udev_device_get_devnode(hid_dev.get()); | 229 const char* raw_path = udev_device_get_devnode(hid_dev.get()); |
| 212 if (strncmp(parent_path, | 230 if (strncmp(parent_path, |
| 213 udev_device_get_devpath(hid_dev.get()), | 231 udev_device_get_devpath(hid_dev.get()), |
| 214 strlen(parent_path)) == 0 && | 232 strlen(parent_path)) == 0 && |
|
Mark Mentovai
2014/02/26 17:41:44
Compute strlen(parent_path) outside this loop.
Bu
Ken Rockot(use gerrit already)
2014/02/26 22:36:39
Done.
| |
| 215 raw_path) { | 233 raw_path) { |
| 216 *result = raw_path; | 234 *result = raw_path; |
| 217 return true; | 235 return true; |
| 218 } | 236 } |
| 219 } | 237 } |
| 220 | 238 |
| 221 return false; | 239 return false; |
| 222 } | 240 } |
| 223 | 241 |
| 224 } // namespace device | 242 } // namespace device |
| OLD | NEW |