| // Copyright 2018 The Chromium OS 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 "biod/uinput_device.h" |
| |
| #include <fcntl.h> |
| #include <linux/uinput.h> |
| #include <sys/ioctl.h> |
| |
| #include <algorithm> |
| |
| #include <base/logging.h> |
| |
| namespace biod { |
| |
| namespace { |
| // When creating a new uinput device, you must specify these parameters like |
| // with an actual, physical device. These are sane, safe values that we use |
| // when creating a uinput device. Note that powerd uses this name to identify |
| // Chrome OS fingerprint devices. |
| constexpr char kFpInputDeviceName[] = "cros_fp_input"; |
| // This is the file handle on disk that you use to control the uinput module. |
| constexpr char kUinputControlPath[] = "/dev/uinput"; |
| |
| constexpr int kDummyProductID = 0xffff; |
| constexpr int kGoogleVendorID = 0x18d1; |
| constexpr int kVersionNumber = 1; |
| } // namespace |
| |
| UinputDevice::UinputDevice() { |
| uinput_fd_ = base::ScopedFD(-1); |
| } |
| |
| UinputDevice::~UinputDevice() { |
| // Tell the OS to destroy the uinput device as this object is destructed. |
| if (uinput_fd_.is_valid()) { |
| int error = TEMP_FAILURE_RETRY(ioctl(uinput_fd_.get(), UI_DEV_DESTROY)); |
| if (error == -1) { |
| PLOG(ERROR) << "Unable to destroy uinput device."; |
| } |
| } |
| } |
| |
| bool UinputDevice::Init() { |
| // Open a control file descriptor for creating a new uinput device. |
| // This file descriptor is used with ioctls to configure the device and |
| // receive the outgoing event information. |
| if (uinput_fd_.is_valid()) { |
| LOG(ERROR) << "Control FD already opened! (" << uinput_fd_.get() << ")."; |
| return false; |
| } |
| |
| uinput_fd_ = base::ScopedFD( |
| TEMP_FAILURE_RETRY(open(kUinputControlPath, O_WRONLY | O_NONBLOCK))); |
| if (!uinput_fd_.is_valid()) { |
| PLOG(ERROR) << "Unable to open " << kUinputControlPath << "."; |
| return false; |
| } |
| LOG(INFO) << "Uinput control file descriptor opened (" << uinput_fd_.get() |
| << ")."; |
| |
| // Tell the kernel that this uinput device will report events of a |
| // type |EV_KEY|. Individual event codes must still be |
| // enabled individually, but their overarching types need to be enabled |
| // first, which is done here. |
| int error = TEMP_FAILURE_RETRY(ioctl(uinput_fd_.get(), UI_SET_EVBIT, EV_KEY)); |
| if (error == -1) { |
| PLOG(ERROR) << "Unable to enable event type 0x" << std::hex << EV_KEY |
| << "."; |
| return false; |
| } |
| LOG(INFO) << "Enabled events of type 0x" << std::hex << EV_KEY << "."; |
| |
| // Tell the kernel that this uinput device will report |KEY_WAKEUP| |
| // key event. |
| error = |
| TEMP_FAILURE_RETRY(ioctl(uinput_fd_.get(), UI_SET_KEYBIT, KEY_WAKEUP)); |
| if (error == -1) { |
| PLOG(ERROR) << "Unable to enable EV_KEY 0x" << std::hex << KEY_WAKEUP |
| << " events."; |
| return false; |
| } |
| LOG(INFO) << "Enabled EV_KEY 0x" << std::hex << KEY_WAKEUP << " events."; |
| |
| if (!FinalizeUinputCreation()) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool UinputDevice::SendEvent(int value) const { |
| // Send an input event to the kernel through this uinput device. |
| struct input_event ev; |
| ev.type = EV_KEY; |
| ev.code = KEY_WAKEUP; |
| ev.value = value; |
| |
| int bytes_written = |
| TEMP_FAILURE_RETRY(write(uinput_fd_.get(), &ev, sizeof(ev))); |
| if (bytes_written != sizeof(ev)) { |
| LOG(ERROR) << "Failed to write() when sending an event. (" << bytes_written |
| << ")."; |
| return false; |
| } |
| return true; |
| } |
| |
| bool UinputDevice::FinalizeUinputCreation() const { |
| struct uinput_setup device_info = {}; |
| DCHECK(strlen(kFpInputDeviceName) < UINPUT_MAX_NAME_SIZE); |
| std::copy(kFpInputDeviceName, kFpInputDeviceName + strlen(kFpInputDeviceName), |
| device_info.name); |
| device_info.id = {.bustype = BUS_USB, |
| .vendor = kGoogleVendorID, |
| .product = kDummyProductID, |
| .version = kVersionNumber}; |
| int error = |
| TEMP_FAILURE_RETRY(ioctl(uinput_fd_.get(), UI_DEV_SETUP, &device_info)); |
| if (error == -1) { |
| PLOG(ERROR) << "uinput device setup ioctl failed."; |
| return false; |
| } |
| |
| // Finally request that a new uinput device is created to those specs. |
| // After this step the device should be fully functional and ready to |
| // send events. |
| error = TEMP_FAILURE_RETRY(ioctl(uinput_fd_.get(), UI_DEV_CREATE)); |
| if (error == -1) { |
| PLOG(ERROR) << "uinput device creation ioctl failed."; |
| return false; |
| } |
| |
| LOG(INFO) << "Successfully finalized uinput device creation."; |
| return true; |
| } |
| |
| } // namespace biod |