// Copyright (c) 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "device/hid/hid_connection_linux.h" #include #include #include #include #include "base/bind.h" #include "base/files/file_path.h" #include "base/message_loop/message_loop.h" #include "base/message_loop/message_pump_libevent.h" #include "base/posix/eintr_wrapper.h" #include "base/thread_task_runner_handle.h" #include "base/threading/thread_restrictions.h" #include "components/device_event_log/device_event_log.h" #include "device/hid/hid_service.h" // These are already defined in newer versions of linux/hidraw.h. #ifndef HIDIOCSFEATURE #define HIDIOCSFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x06, len) #endif #ifndef HIDIOCGFEATURE #define HIDIOCGFEATURE(len) _IOC(_IOC_WRITE | _IOC_READ, 'H', 0x07, len) #endif namespace device { class HidConnectionLinux::FileThreadHelper : public base::MessagePumpLibevent::Watcher { public: FileThreadHelper(base::PlatformFile platform_file, scoped_refptr device_info, base::WeakPtr connection, scoped_refptr task_runner) : platform_file_(platform_file), connection_(connection), task_runner_(task_runner) { // Report buffers must always have room for the report ID. report_buffer_size_ = device_info->max_input_report_size() + 1; has_report_id_ = device_info->has_report_id(); } ~FileThreadHelper() override { DCHECK(thread_checker_.CalledOnValidThread()); } // Starts the FileDescriptorWatcher that reads input events from the device. // Must be called on a thread that has a base::MessageLoopForIO. The helper // object is owned by the thread where it was started. void Start() { base::ThreadRestrictions::AssertIOAllowed(); thread_checker_.DetachFromThread(); if (!base::MessageLoopForIO::current()->WatchFileDescriptor( platform_file_, true, base::MessageLoopForIO::WATCH_READ, &file_watcher_, this)) { HID_LOG(ERROR) << "Failed to start watching device file."; } } private: // base::MessagePumpLibevent::Watcher implementation. void OnFileCanReadWithoutBlocking(int fd) override { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK_EQ(fd, platform_file_); scoped_refptr buffer(new net::IOBuffer(report_buffer_size_)); char* data = buffer->data(); size_t length = report_buffer_size_; if (!has_report_id_) { // Linux will not prefix the buffer with a report ID if report IDs are not // used by the device. Prefix the buffer with 0. *data++ = 0; length--; } ssize_t bytes_read = HANDLE_EINTR(read(platform_file_, data, length)); if (bytes_read < 0) { if (errno != EAGAIN) { HID_PLOG(EVENT) << "Read failed"; // This assumes that the error is unrecoverable and disables reading // from the device until it has been re-opened. // TODO(reillyg): Investigate starting and stopping the file descriptor // watcher in response to pending read requests so that per-request // errors can be returned to the client. file_watcher_.StopWatchingFileDescriptor(); } return; } if (!has_report_id_) { // Behave as if the byte prefixed above as the the report ID was read. bytes_read++; } task_runner_->PostTask(FROM_HERE, base::Bind(&HidConnectionLinux::ProcessInputReport, connection_, buffer, bytes_read)); } void OnFileCanWriteWithoutBlocking(int fd) override { NOTREACHED(); // Only listening for reads. } base::ThreadChecker thread_checker_; base::PlatformFile platform_file_; size_t report_buffer_size_; bool has_report_id_; base::WeakPtr connection_; scoped_refptr task_runner_; base::MessagePumpLibevent::FileDescriptorWatcher file_watcher_; }; HidConnectionLinux::HidConnectionLinux( scoped_refptr device_info, base::File device_file, scoped_refptr file_task_runner) : HidConnection(device_info), file_task_runner_(file_task_runner), weak_factory_(this) { task_runner_ = base::ThreadTaskRunnerHandle::Get(); device_file_ = device_file.Pass(); // The helper is passed a weak pointer to this connection so that it can be // cleaned up after the connection is closed. helper_ = new FileThreadHelper(device_file_.GetPlatformFile(), device_info, weak_factory_.GetWeakPtr(), task_runner_); file_task_runner_->PostTask(FROM_HERE, base::Bind(&FileThreadHelper::Start, base::Unretained(helper_))); } HidConnectionLinux::~HidConnectionLinux() { DCHECK(helper_ == nullptr); } void HidConnectionLinux::PlatformClose() { // By closing the device file on the FILE thread (1) the requirement that // base::File::Close is called on a thread where I/O is allowed is satisfied // and (2) any tasks posted to this task runner that refer to this file will // complete before it is closed. file_task_runner_->DeleteSoon(FROM_HERE, helper_); helper_ = nullptr; file_task_runner_->PostTask(FROM_HERE, base::Bind(&HidConnectionLinux::CloseDevice, base::Passed(&device_file_))); while (!pending_reads_.empty()) { pending_reads_.front().callback.Run(false, NULL, 0); pending_reads_.pop(); } } void HidConnectionLinux::PlatformRead(const ReadCallback& callback) { PendingHidRead pending_read; pending_read.callback = callback; pending_reads_.push(pending_read); ProcessReadQueue(); } void HidConnectionLinux::PlatformWrite(scoped_refptr buffer, size_t size, const WriteCallback& callback) { // Linux expects the first byte of the buffer to always be a report ID so the // buffer can be used directly. file_task_runner_->PostTask( FROM_HERE, base::Bind(&HidConnectionLinux::BlockingWrite, device_file_.GetPlatformFile(), buffer, size, base::Bind(&HidConnectionLinux::FinishWrite, weak_factory_.GetWeakPtr(), size, callback), task_runner_)); } void HidConnectionLinux::PlatformGetFeatureReport( uint8_t report_id, const ReadCallback& callback) { // The first byte of the destination buffer is the report ID being requested // and is overwritten by the feature report. DCHECK_GT(device_info()->max_feature_report_size(), 0u); scoped_refptr buffer( new net::IOBufferWithSize(device_info()->max_feature_report_size() + 1)); buffer->data()[0] = report_id; file_task_runner_->PostTask( FROM_HERE, base::Bind( &HidConnectionLinux::BlockingIoctl, device_file_.GetPlatformFile(), HIDIOCGFEATURE(buffer->size()), buffer, base::Bind(&HidConnectionLinux::FinishGetFeatureReport, weak_factory_.GetWeakPtr(), report_id, buffer, callback), task_runner_)); } void HidConnectionLinux::PlatformSendFeatureReport( scoped_refptr buffer, size_t size, const WriteCallback& callback) { // Linux expects the first byte of the buffer to always be a report ID so the // buffer can be used directly. file_task_runner_->PostTask( FROM_HERE, base::Bind(&HidConnectionLinux::BlockingIoctl, device_file_.GetPlatformFile(), HIDIOCSFEATURE(size), buffer, base::Bind(&HidConnectionLinux::FinishSendFeatureReport, weak_factory_.GetWeakPtr(), callback), task_runner_)); } void HidConnectionLinux::FinishWrite(size_t expected_size, const WriteCallback& callback, ssize_t result) { if (result < 0) { HID_PLOG(EVENT) << "Write failed"; callback.Run(false); } else { if (static_cast(result) != expected_size) { HID_LOG(EVENT) << "Incomplete HID write: " << result << " != " << expected_size; } callback.Run(true); } } void HidConnectionLinux::FinishGetFeatureReport( uint8_t report_id, scoped_refptr buffer, const ReadCallback& callback, int result) { if (result < 0) { HID_PLOG(EVENT) << "Failed to get feature report"; callback.Run(false, NULL, 0); } else if (result == 0) { HID_LOG(EVENT) << "Get feature result too short."; callback.Run(false, NULL, 0); } else if (report_id == 0) { // Linux adds a 0 to the beginning of the data received from the device. scoped_refptr copied_buffer(new net::IOBuffer(result - 1)); memcpy(copied_buffer->data(), buffer->data() + 1, result - 1); callback.Run(true, copied_buffer, result - 1); } else { callback.Run(true, buffer, result); } } void HidConnectionLinux::FinishSendFeatureReport(const WriteCallback& callback, int result) { if (result < 0) { HID_PLOG(EVENT) << "Failed to send feature report"; callback.Run(false); } else { callback.Run(true); } } // static void HidConnectionLinux::BlockingWrite( base::PlatformFile platform_file, scoped_refptr buffer, size_t size, const InternalWriteCallback& callback, scoped_refptr task_runner) { base::ThreadRestrictions::AssertIOAllowed(); ssize_t result = HANDLE_EINTR(write(platform_file, buffer->data(), size)); task_runner->PostTask(FROM_HERE, base::Bind(callback, result)); } // static void HidConnectionLinux::BlockingIoctl( base::PlatformFile platform_file, int request, scoped_refptr buffer, const IoctlCallback& callback, scoped_refptr task_runner) { base::ThreadRestrictions::AssertIOAllowed(); int result = ioctl(platform_file, request, buffer->data()); task_runner->PostTask(FROM_HERE, base::Bind(callback, result)); } // static void HidConnectionLinux::CloseDevice(base::File device_file) { device_file.Close(); } void HidConnectionLinux::ProcessInputReport(scoped_refptr buffer, size_t size) { DCHECK(thread_checker().CalledOnValidThread()); PendingHidReport report; report.buffer = buffer; report.size = size; pending_reports_.push(report); ProcessReadQueue(); } void HidConnectionLinux::ProcessReadQueue() { DCHECK(thread_checker().CalledOnValidThread()); while (pending_reads_.size() && pending_reports_.size()) { PendingHidRead read = pending_reads_.front(); PendingHidReport report = pending_reports_.front(); pending_reports_.pop(); if (CompleteRead(report.buffer, report.size, read.callback)) { pending_reads_.pop(); } } } } // namespace device