blob: b521526b63f88a7c0ca559d3bc9c0fdfcef78a00 [file] [log] [blame]
// 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