blob: 6fef7f4fda89817f4338e5ace187282fab08d7b9 [file] [log] [blame]
// Copyright 2014 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 "power_manager/powerd/system/event_device.h"
#include <fcntl.h>
#include <linux/input.h>
#include <base/posix/eintr_wrapper.h>
#include "power_manager/common/power_constants.h"
// Helper macros for accessing the bitfields returned by the kernel interface,
// compare with include/linux/bitops.h.
#define BITS_PER_LONG (sizeof(long) * 8) // NOLINT(runtime/int)
#define BITS_TO_LONGS(bits) (((bits)-1) / BITS_PER_LONG + 1)
#define BITMASK_GET_BIT(bitmask, bit) \
((bitmask[bit / BITS_PER_LONG] >> (bit % BITS_PER_LONG)) & 1)
#define MAX(a, b) ((a) > (b) ? (a) : (b))
namespace power_manager {
namespace system {
namespace {
// C++14's <algorithm> could do std::max(EV_MAX, KEY_MAX, SW_MAX);
static constexpr int kMaxBit = MAX(MAX(EV_MAX, KEY_MAX), SW_MAX);
} // namespace
EventDevice::EventDevice(int fd, const base::FilePath& path)
: fd_(fd), path_(path) {}
EventDevice::~EventDevice() {
fd_watcher_.reset();
// ENODEV is expected if the device was just unplugged.
if (close(fd_) != 0 && errno != ENODEV)
PLOG(ERROR) << "Unable to close FD " << fd_;
}
std::string EventDevice::GetDebugName() {
return path_.value();
}
std::string EventDevice::GetPhysPath() {
char phys[256] = "";
if (TEMP_FAILURE_RETRY(ioctl(fd_, EVIOCGPHYS(sizeof(phys)), phys)) < 0 &&
errno != ENOENT)
PLOG(ERROR) << "Could not get topo phys path of " << path_.value();
return phys;
}
std::string EventDevice::GetName() {
char name[256] = {0};
if (TEMP_FAILURE_RETRY(ioctl(fd_, EVIOCGNAME(sizeof(name) - 1), name)) < 0)
PLOG(ERROR) << "Could not get name of " << path_.value();
return name;
}
bool EventDevice::IsCrosFp() {
return GetName() == kCrosFpInputDevName;
}
bool EventDevice::IsLidSwitch() {
return HasEventBit(0, EV_SW) && HasEventBit(EV_SW, SW_LID);
}
bool EventDevice::IsTabletModeSwitch() {
return HasEventBit(0, EV_SW) && HasEventBit(EV_SW, SW_TABLET_MODE);
}
bool EventDevice::IsPowerButton() {
return HasEventBit(0, EV_KEY) && HasEventBit(EV_KEY, KEY_POWER);
}
bool EventDevice::HoverSupported() {
// Multitouch hover uses just the ABS_MT_DISTANCE event in addition to
// the normal multi-touch events.
if (HasEventBit(0, EV_ABS) && HasEventBit(EV_ABS, ABS_MT_DISTANCE))
return true;
// Simple single-touch hover presence-only detection uses 3 events:
// ABS_DISTANCE, BTN_TOUCH, and BTN_TOOL_FINGER.
if (HasEventBit(0, EV_ABS) && HasEventBit(EV_ABS, ABS_DISTANCE) &&
HasEventBit(0, EV_KEY) && HasEventBit(EV_KEY, BTN_TOUCH) &&
HasEventBit(EV_KEY, BTN_TOOL_FINGER))
return true;
return false;
}
bool EventDevice::HasLeftButton() {
return HasEventBit(0, EV_KEY) && HasEventBit(EV_KEY, BTN_LEFT);
}
LidState EventDevice::GetInitialLidState() {
CHECK(!fd_watcher_) << "GetInitialLidState called after WatchForEvents";
return GetSwitchBit(SW_LID) ? LidState::CLOSED : LidState::OPEN;
}
TabletMode EventDevice::GetInitialTabletMode() {
CHECK(!fd_watcher_) << "GetInitialTabletMode called after WatchForEvents";
return GetSwitchBit(SW_TABLET_MODE) ? TabletMode::ON : TabletMode::OFF;
}
bool EventDevice::HasEventBit(int event_type, int bit) {
DCHECK(bit <= kMaxBit);
// bitmask needs to hold kMaxBit+1 bits
unsigned long bitmask[BITS_TO_LONGS(kMaxBit + 1)]; // NOLINT(runtime/int)
memset(bitmask, 0, sizeof(bitmask));
if (TEMP_FAILURE_RETRY(
ioctl(fd_, EVIOCGBIT(event_type, sizeof(bitmask)), bitmask)) < 0) {
PLOG(ERROR) << "EVIOCGBIT failed for " << path_.value();
return false;
}
return BITMASK_GET_BIT(bitmask, bit);
}
bool EventDevice::GetSwitchBit(int bit) {
DCHECK(bit <= kMaxBit);
// bitmask needs to hold SW_MAX+1 bits
unsigned long bitmask[BITS_TO_LONGS(SW_MAX + 1)]; // NOLINT(runtime/int)
memset(bitmask, 0, sizeof(bitmask));
if (TEMP_FAILURE_RETRY(ioctl(fd_, EVIOCGSW(sizeof(bitmask)), bitmask)) < 0) {
PLOG(ERROR) << "EVIOCGBIT failed for " << path_.value();
return false;
}
return BITMASK_GET_BIT(bitmask, bit);
}
bool EventDevice::ReadEvents(std::vector<input_event>* events_out) {
DCHECK(events_out);
events_out->clear();
struct input_event events[64];
ssize_t read_size = HANDLE_EINTR(read(fd_, events, sizeof(events)));
if (read_size < 0) {
// ENODEV is expected if the device was just unplugged.
if (errno != EAGAIN && errno != EWOULDBLOCK && errno != ENODEV)
PLOG(ERROR) << "Reading events from " << path_.value() << " failed";
return false;
} else if (read_size == 0) {
LOG(ERROR) << "Read returned 0 when reading events from " << path_.value();
return false;
}
const size_t num_events = read_size / sizeof(struct input_event);
if (read_size % sizeof(struct input_event)) {
LOG(ERROR) << "Read " << read_size << " byte(s) while expecting "
<< sizeof(struct input_event) << "-byte events";
return false;
}
events_out->reserve(num_events);
for (size_t i = 0; i < num_events; ++i)
events_out->push_back(events[i]);
return true;
}
void EventDevice::WatchForEvents(base::Closure new_events_cb) {
fd_watcher_ = base::FileDescriptorWatcher::WatchReadable(fd_, new_events_cb);
}
EventDeviceFactory::EventDeviceFactory() {}
EventDeviceFactory::~EventDeviceFactory() {}
std::shared_ptr<EventDeviceInterface> EventDeviceFactory::Open(
const base::FilePath& path) {
int fd = HANDLE_EINTR(open(path.value().c_str(), O_RDONLY | O_NONBLOCK));
if (fd < 0) {
PLOG(ERROR) << "open() failed for " << path.value();
return std::shared_ptr<EventDeviceInterface>();
}
return std::shared_ptr<EventDeviceInterface>(new EventDevice(fd, path));
}
} // namespace system
} // namespace power_manager