Input event hub for evdev input HAL module.
InputHub monitors paths for device changes and input events. InputDeviceManager creates InputDevices and routes input events to them. InputDevices currently just log these events during development. InputHost represents a wrapper around the HAL interface. Change-Id: Ic47d574498eb07bcdcd17812a648539fdf1c69d6
This commit is contained in:
802
modules/input/evdev/InputHub.cpp
Normal file
802
modules/input/evdev/InputHub.cpp
Normal file
@@ -0,0 +1,802 @@
|
||||
/*
|
||||
* Copyright (C) 2015 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "InputHub"
|
||||
#define LOG_NDEBUG 0
|
||||
|
||||
#include <dirent.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <sys/capability.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "InputHub.h"
|
||||
|
||||
#include <android/input.h>
|
||||
#include <hardware_legacy/power.h>
|
||||
#include <linux/input.h>
|
||||
|
||||
#include <utils/Log.h>
|
||||
|
||||
namespace android {
|
||||
|
||||
static const char WAKE_LOCK_ID[] = "KeyEvents";
|
||||
static const int NO_TIMEOUT = -1;
|
||||
static const int EPOLL_MAX_EVENTS = 16;
|
||||
static const int INPUT_MAX_EVENTS = 128;
|
||||
|
||||
static constexpr bool testBit(int bit, const uint8_t arr[]) {
|
||||
return arr[bit / 8] & (1 << (bit % 8));
|
||||
}
|
||||
|
||||
static constexpr size_t sizeofBitArray(size_t bits) {
|
||||
return (bits + 7) / 8;
|
||||
}
|
||||
|
||||
static void getLinuxRelease(int* major, int* minor) {
|
||||
struct utsname info;
|
||||
if (uname(&info) || sscanf(info.release, "%d.%d", major, minor) <= 0) {
|
||||
*major = 0, *minor = 0;
|
||||
ALOGE("Could not get linux version: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
static bool processHasCapability(int capability) {
|
||||
LOG_ALWAYS_FATAL_IF(!cap_valid(capability), "invalid linux capability: %d", capability);
|
||||
struct __user_cap_header_struct cap_header_data;
|
||||
struct __user_cap_data_struct cap_data_data[2];
|
||||
cap_user_header_t caphdr = &cap_header_data;
|
||||
cap_user_data_t capdata = cap_data_data;
|
||||
caphdr->pid = 0;
|
||||
caphdr->version = _LINUX_CAPABILITY_VERSION_3;
|
||||
LOG_ALWAYS_FATAL_IF(capget(caphdr, capdata) != 0,
|
||||
"Could not get process capabilities. errno=%d", errno);
|
||||
ALOGV("effective capabilities: %08x %08x", capdata[0].effective, capdata[1].effective);
|
||||
int idx = CAP_TO_INDEX(capability);
|
||||
return capdata[idx].effective & CAP_TO_MASK(capability);
|
||||
}
|
||||
|
||||
class EvdevDeviceNode : public InputDeviceNode {
|
||||
public:
|
||||
static EvdevDeviceNode* openDeviceNode(const std::string& path);
|
||||
|
||||
virtual ~EvdevDeviceNode() {
|
||||
ALOGV("closing %s (fd=%d)", mPath.c_str(), mFd);
|
||||
if (mFd >= 0) {
|
||||
::close(mFd);
|
||||
}
|
||||
}
|
||||
|
||||
virtual int getFd() const { return mFd; }
|
||||
virtual const std::string& getPath() const override { return mPath; }
|
||||
virtual const std::string& getName() const override { return mName; }
|
||||
virtual const std::string& getLocation() const override { return mLocation; }
|
||||
virtual const std::string& getUniqueId() const override { return mUniqueId; }
|
||||
|
||||
virtual uint16_t getBusType() const override { return mBusType; }
|
||||
virtual uint16_t getVendorId() const override { return mVendorId; }
|
||||
virtual uint16_t getProductId() const override { return mProductId; }
|
||||
virtual uint16_t getVersion() const override { return mVersion; }
|
||||
|
||||
virtual bool hasKey(int32_t key) const override;
|
||||
virtual bool hasRelativeAxis(int axis) const override;
|
||||
virtual const AbsoluteAxisInfo* getAbsoluteAxisInfo(int32_t axis) const override;
|
||||
virtual bool hasInputProperty(int property) const override;
|
||||
|
||||
virtual int32_t getKeyState(int32_t key) const override;
|
||||
virtual int32_t getSwitchState(int32_t sw) const override;
|
||||
virtual status_t getAbsoluteAxisValue(int32_t axis, int32_t* outValue) const override;
|
||||
|
||||
virtual void vibrate(nsecs_t duration) override;
|
||||
virtual void cancelVibrate(int32_t deviceId) override;
|
||||
|
||||
virtual void disableDriverKeyRepeat() override;
|
||||
|
||||
private:
|
||||
EvdevDeviceNode(const std::string& path, int fd) :
|
||||
mFd(fd), mPath(path) {}
|
||||
|
||||
status_t queryProperties();
|
||||
void queryAxisInfo();
|
||||
|
||||
int mFd;
|
||||
std::string mPath;
|
||||
|
||||
std::string mName;
|
||||
std::string mLocation;
|
||||
std::string mUniqueId;
|
||||
|
||||
uint16_t mBusType;
|
||||
uint16_t mVendorId;
|
||||
uint16_t mProductId;
|
||||
uint16_t mVersion;
|
||||
|
||||
uint8_t mKeyBitmask[KEY_CNT / 8];
|
||||
uint8_t mAbsBitmask[ABS_CNT / 8];
|
||||
uint8_t mRelBitmask[REL_CNT / 8];
|
||||
uint8_t mSwBitmask[SW_CNT / 8];
|
||||
uint8_t mLedBitmask[LED_CNT / 8];
|
||||
uint8_t mFfBitmask[FF_CNT / 8];
|
||||
uint8_t mPropBitmask[INPUT_PROP_CNT / 8];
|
||||
|
||||
std::unordered_map<uint32_t, std::unique_ptr<AbsoluteAxisInfo>> mAbsInfo;
|
||||
|
||||
bool mFfEffectPlaying = false;
|
||||
int16_t mFfEffectId = -1;
|
||||
};
|
||||
|
||||
EvdevDeviceNode* EvdevDeviceNode::openDeviceNode(const std::string& path) {
|
||||
auto fd = TEMP_FAILURE_RETRY(::open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC));
|
||||
if (fd < 0) {
|
||||
ALOGE("could not open evdev device %s. err=%d", path.c_str(), errno);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Tell the kernel that we want to use the monotonic clock for reporting
|
||||
// timestamps associated with input events. This is important because the
|
||||
// input system uses the timestamps extensively and assumes they were
|
||||
// recorded using the monotonic clock.
|
||||
//
|
||||
// The EVIOCSCLOCKID ioctl was introduced in Linux 3.4.
|
||||
int clockId = CLOCK_MONOTONIC;
|
||||
if (TEMP_FAILURE_RETRY(ioctl(fd, EVIOCSCLOCKID, &clockId)) < 0) {
|
||||
ALOGW("Could not set input clock id to CLOCK_MONOTONIC. errno=%d", errno);
|
||||
}
|
||||
|
||||
auto node = new EvdevDeviceNode(path, fd);
|
||||
status_t ret = node->queryProperties();
|
||||
if (ret != OK) {
|
||||
ALOGE("could not open evdev device %s: failed to read properties. errno=%d",
|
||||
path.c_str(), ret);
|
||||
delete node;
|
||||
return nullptr;
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
status_t EvdevDeviceNode::queryProperties() {
|
||||
char buffer[80];
|
||||
|
||||
if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGNAME(sizeof(buffer) - 1), buffer)) < 1) {
|
||||
ALOGV("could not get device name for %s.", mPath.c_str());
|
||||
} else {
|
||||
buffer[sizeof(buffer) - 1] = '\0';
|
||||
mName = buffer;
|
||||
}
|
||||
|
||||
int driverVersion;
|
||||
if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGVERSION, &driverVersion))) {
|
||||
ALOGE("could not get driver version for %s. err=%d", mPath.c_str(), errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
struct input_id inputId;
|
||||
if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGID, &inputId))) {
|
||||
ALOGE("could not get device input id for %s. err=%d", mPath.c_str(), errno);
|
||||
return -errno;
|
||||
}
|
||||
mBusType = inputId.bustype;
|
||||
mVendorId = inputId.vendor;
|
||||
mProductId = inputId.product;
|
||||
mVersion = inputId.version;
|
||||
|
||||
if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGPHYS(sizeof(buffer) - 1), buffer)) < 1) {
|
||||
ALOGV("could not get location for %s.", mPath.c_str());
|
||||
} else {
|
||||
buffer[sizeof(buffer) - 1] = '\0';
|
||||
mLocation = buffer;
|
||||
}
|
||||
|
||||
if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGUNIQ(sizeof(buffer) - 1), buffer)) < 1) {
|
||||
ALOGV("could not get unique id for %s.", mPath.c_str());
|
||||
} else {
|
||||
buffer[sizeof(buffer) - 1] = '\0';
|
||||
mUniqueId = buffer;
|
||||
}
|
||||
|
||||
ALOGV("add device %s", mPath.c_str());
|
||||
ALOGV(" bus: %04x\n"
|
||||
" vendor: %04x\n"
|
||||
" product: %04x\n"
|
||||
" version: %04x\n",
|
||||
mBusType, mVendorId, mProductId, mVersion);
|
||||
ALOGV(" name: \"%s\"\n"
|
||||
" location: \"%s\"\n"
|
||||
" unique_id: \"%s\"\n"
|
||||
" descriptor: (TODO)\n"
|
||||
" driver: v%d.%d.%d",
|
||||
mName.c_str(), mLocation.c_str(), mUniqueId.c_str(),
|
||||
driverVersion >> 16, (driverVersion >> 8) & 0xff, (driverVersion >> 16) & 0xff);
|
||||
|
||||
TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGBIT(EV_KEY, sizeof(mKeyBitmask)), mKeyBitmask));
|
||||
TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGBIT(EV_ABS, sizeof(mAbsBitmask)), mAbsBitmask));
|
||||
TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGBIT(EV_REL, sizeof(mRelBitmask)), mRelBitmask));
|
||||
TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGBIT(EV_SW, sizeof(mSwBitmask)), mSwBitmask));
|
||||
TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGBIT(EV_LED, sizeof(mLedBitmask)), mLedBitmask));
|
||||
TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGBIT(EV_FF, sizeof(mFfBitmask)), mFfBitmask));
|
||||
TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGPROP(sizeof(mPropBitmask)), mPropBitmask));
|
||||
|
||||
queryAxisInfo();
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
void EvdevDeviceNode::queryAxisInfo() {
|
||||
for (int32_t axis = 0; axis < ABS_MAX; ++axis) {
|
||||
if (testBit(axis, mAbsBitmask)) {
|
||||
struct input_absinfo info;
|
||||
if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGABS(axis), &info))) {
|
||||
ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d",
|
||||
axis, mPath.c_str(), mFd, errno);
|
||||
continue;
|
||||
}
|
||||
|
||||
mAbsInfo[axis] = std::unique_ptr<AbsoluteAxisInfo>(new AbsoluteAxisInfo{
|
||||
.minValue = info.minimum,
|
||||
.maxValue = info.maximum,
|
||||
.flat = info.flat,
|
||||
.fuzz = info.fuzz,
|
||||
.resolution = info.resolution
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool EvdevDeviceNode::hasKey(int32_t key) const {
|
||||
if (key >= 0 && key <= KEY_MAX) {
|
||||
return testBit(key, mKeyBitmask);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EvdevDeviceNode::hasRelativeAxis(int axis) const {
|
||||
if (axis >= 0 && axis <= REL_MAX) {
|
||||
return testBit(axis, mRelBitmask);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const AbsoluteAxisInfo* EvdevDeviceNode::getAbsoluteAxisInfo(int32_t axis) const {
|
||||
if (axis < 0 || axis > ABS_MAX) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto absInfo = mAbsInfo.find(axis);
|
||||
if (absInfo != mAbsInfo.end()) {
|
||||
return absInfo->second.get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool EvdevDeviceNode::hasInputProperty(int property) const {
|
||||
if (property >= 0 && property <= INPUT_PROP_MAX) {
|
||||
return testBit(property, mPropBitmask);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t EvdevDeviceNode::getKeyState(int32_t key) const {
|
||||
if (key >= 0 && key <= KEY_MAX) {
|
||||
if (testBit(key, mKeyBitmask)) {
|
||||
uint8_t keyState[sizeofBitArray(KEY_CNT)];
|
||||
memset(keyState, 0, sizeof(keyState));
|
||||
if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGKEY(sizeof(keyState)), keyState)) >= 0) {
|
||||
return testBit(key, keyState) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AKEY_STATE_UNKNOWN;
|
||||
}
|
||||
|
||||
int32_t EvdevDeviceNode::getSwitchState(int32_t sw) const {
|
||||
if (sw >= 0 && sw <= SW_MAX) {
|
||||
if (testBit(sw, mSwBitmask)) {
|
||||
uint8_t swState[sizeofBitArray(SW_CNT)];
|
||||
memset(swState, 0, sizeof(swState));
|
||||
if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGSW(sizeof(swState)), swState)) >= 0) {
|
||||
return testBit(sw, swState) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
|
||||
}
|
||||
}
|
||||
}
|
||||
return AKEY_STATE_UNKNOWN;
|
||||
}
|
||||
|
||||
status_t EvdevDeviceNode::getAbsoluteAxisValue(int32_t axis, int32_t* outValue) const {
|
||||
*outValue = 0;
|
||||
|
||||
if (axis >= 0 && axis <= ABS_MAX) {
|
||||
if (testBit(axis, mAbsBitmask)) {
|
||||
struct input_absinfo info;
|
||||
if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCGABS(axis), &info))) {
|
||||
ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d",
|
||||
axis, mPath.c_str(), mFd, errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
*outValue = info.value;
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void EvdevDeviceNode::vibrate(nsecs_t duration) {
|
||||
ff_effect effect{};
|
||||
effect.type = FF_RUMBLE;
|
||||
effect.id = mFfEffectId;
|
||||
effect.u.rumble.strong_magnitude = 0xc000;
|
||||
effect.u.rumble.weak_magnitude = 0xc000;
|
||||
effect.replay.length = (duration + 999'999LL) / 1'000'000LL;
|
||||
effect.replay.delay = 0;
|
||||
if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCSFF, &effect))) {
|
||||
ALOGW("Could not upload force feedback effect to device %s due to error %d.",
|
||||
mPath.c_str(), errno);
|
||||
return;
|
||||
}
|
||||
mFfEffectId = effect.id;
|
||||
|
||||
struct input_event ev{};
|
||||
ev.type = EV_FF;
|
||||
ev.code = mFfEffectId;
|
||||
ev.value = 1;
|
||||
size_t written = TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(ev)));
|
||||
if (written != sizeof(ev)) {
|
||||
ALOGW("Could not start force feedback effect on device %s due to error %d.",
|
||||
mPath.c_str(), errno);
|
||||
return;
|
||||
}
|
||||
mFfEffectPlaying = true;
|
||||
}
|
||||
|
||||
void EvdevDeviceNode::cancelVibrate(int32_t deviceId) {
|
||||
if (mFfEffectPlaying) {
|
||||
mFfEffectPlaying = false;
|
||||
|
||||
struct input_event ev{};
|
||||
ev.type = EV_FF;
|
||||
ev.code = mFfEffectId;
|
||||
ev.value = 0;
|
||||
size_t written = TEMP_FAILURE_RETRY(write(mFd, &ev, sizeof(ev)));
|
||||
if (written != sizeof(ev)) {
|
||||
ALOGW("Could not stop force feedback effect on device %s due to error %d.",
|
||||
mPath.c_str(), errno);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EvdevDeviceNode::disableDriverKeyRepeat() {
|
||||
unsigned int repeatRate[] = {0, 0};
|
||||
if (TEMP_FAILURE_RETRY(ioctl(mFd, EVIOCSREP, repeatRate))) {
|
||||
ALOGW("Unable to disable kernel key repeat for %s due to error %d.",
|
||||
mPath.c_str(), errno);
|
||||
}
|
||||
}
|
||||
|
||||
InputHub::InputHub(std::shared_ptr<InputCallbackInterface> cb) :
|
||||
mInputCallback(cb) {
|
||||
// Determine the type of suspend blocking we can do on this device. There
|
||||
// are 3 options, in decreasing order of preference:
|
||||
// 1) EPOLLWAKEUP: introduced in Linux kernel 3.5, this flag can be set on
|
||||
// an epoll event to indicate that a wake lock should be held from the
|
||||
// time an fd has data until the next epoll_wait (or the epoll fd is
|
||||
// closed).
|
||||
// 2) EVIOCSSUSPENDBLOCK: introduced into the Android kernel's evdev
|
||||
// driver, this ioctl blocks suspend while the event queue for the fd is
|
||||
// not empty. This was never accepted into the mainline kernel, and it was
|
||||
// replaced by EPOLLWAKEUP.
|
||||
// 3) explicit wake locks: use acquire_wake_lock to manage suspend
|
||||
// blocking explicitly in the InputHub code.
|
||||
//
|
||||
// (1) can be checked by simply observing the Linux kernel version. (2)
|
||||
// requires an fd from an evdev node, which cannot be done in the InputHub
|
||||
// constructor. So we assume (3) unless (1) is true, and we can verify
|
||||
// whether (2) is true once we have an evdev fd (and we're not in (1)).
|
||||
int major, minor;
|
||||
getLinuxRelease(&major, &minor);
|
||||
if (major > 3 || (major == 3 && minor >= 5)) {
|
||||
ALOGI("Using EPOLLWAKEUP to block suspend while processing input events.");
|
||||
mWakeupMechanism = WakeMechanism::EPOLL_WAKEUP;
|
||||
mNeedToCheckSuspendBlockIoctl = false;
|
||||
}
|
||||
if (manageWakeLocks()) {
|
||||
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
|
||||
}
|
||||
|
||||
// epoll_create argument is ignored, but it must be > 0.
|
||||
mEpollFd = epoll_create(1);
|
||||
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
|
||||
|
||||
mINotifyFd = inotify_init();
|
||||
LOG_ALWAYS_FATAL_IF(mINotifyFd < 0, "Could not create inotify instance. errno=%d", errno);
|
||||
|
||||
struct epoll_event eventItem;
|
||||
memset(&eventItem, 0, sizeof(eventItem));
|
||||
eventItem.events = EPOLLIN;
|
||||
if (mWakeupMechanism == WakeMechanism::EPOLL_WAKEUP) {
|
||||
eventItem.events |= EPOLLWAKEUP;
|
||||
}
|
||||
eventItem.data.u32 = mINotifyFd;
|
||||
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
|
||||
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add INotify to epoll instance. errno=%d", errno);
|
||||
|
||||
int wakeFds[2];
|
||||
result = pipe(wakeFds);
|
||||
LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno);
|
||||
|
||||
mWakeEventFd = eventfd(0, EFD_NONBLOCK);
|
||||
LOG_ALWAYS_FATAL_IF(mWakeEventFd == -1, "Could not create wake event fd. errno=%d", errno);
|
||||
|
||||
eventItem.data.u32 = mWakeEventFd;
|
||||
result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, &eventItem);
|
||||
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance. errno=%d", errno);
|
||||
}
|
||||
|
||||
InputHub::~InputHub() {
|
||||
::close(mEpollFd);
|
||||
::close(mINotifyFd);
|
||||
::close(mWakeEventFd);
|
||||
|
||||
if (manageWakeLocks()) {
|
||||
release_wake_lock(WAKE_LOCK_ID);
|
||||
}
|
||||
}
|
||||
|
||||
status_t InputHub::registerDevicePath(const std::string& path) {
|
||||
ALOGV("registering device path %s", path.c_str());
|
||||
int wd = inotify_add_watch(mINotifyFd, path.c_str(), IN_DELETE | IN_CREATE);
|
||||
if (wd < 0) {
|
||||
ALOGE("Could not add %s to INotify watch. errno=%d", path.c_str(), errno);
|
||||
return -errno;
|
||||
}
|
||||
mWatchedPaths[wd] = path;
|
||||
scanDir(path);
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t InputHub::unregisterDevicePath(const std::string& path) {
|
||||
int wd = -1;
|
||||
for (auto pair : mWatchedPaths) {
|
||||
if (pair.second == path) {
|
||||
wd = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (wd == -1) {
|
||||
return BAD_VALUE;
|
||||
}
|
||||
mWatchedPaths.erase(wd);
|
||||
if (inotify_rm_watch(mINotifyFd, wd) != 0) {
|
||||
return -errno;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t InputHub::poll() {
|
||||
bool deviceChange = false;
|
||||
|
||||
if (manageWakeLocks()) {
|
||||
// Mind the wake lock dance!
|
||||
// If we're relying on wake locks, we hold a wake lock at all times
|
||||
// except during epoll_wait(). This works due to some subtle
|
||||
// choreography. When a device driver has pending (unread) events, it
|
||||
// acquires a kernel wake lock. However, once the last pending event
|
||||
// has been read, the device driver will release the kernel wake lock.
|
||||
// To prevent the system from going to sleep when this happens, the
|
||||
// InputHub holds onto its own user wake lock while the client is
|
||||
// processing events. Thus the system can only sleep if there are no
|
||||
// events pending or currently being processed.
|
||||
release_wake_lock(WAKE_LOCK_ID);
|
||||
}
|
||||
|
||||
struct epoll_event pendingEventItems[EPOLL_MAX_EVENTS];
|
||||
int pollResult = epoll_wait(mEpollFd, pendingEventItems, EPOLL_MAX_EVENTS, NO_TIMEOUT);
|
||||
|
||||
if (manageWakeLocks()) {
|
||||
acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
|
||||
}
|
||||
|
||||
if (pollResult == 0) {
|
||||
ALOGW("epoll_wait should not return 0 with no timeout");
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
if (pollResult < 0) {
|
||||
// An error occurred. Return even if it's EINTR, and let the caller
|
||||
// restart the poll.
|
||||
ALOGE("epoll_wait returned with errno=%d", errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
// pollResult > 0: there are events to process
|
||||
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
|
||||
std::vector<int> removedDeviceFds;
|
||||
int inputFd = -1;
|
||||
std::shared_ptr<InputDeviceNode> deviceNode;
|
||||
for (int i = 0; i < pollResult; ++i) {
|
||||
const struct epoll_event& eventItem = pendingEventItems[i];
|
||||
|
||||
int dataFd = static_cast<int>(eventItem.data.u32);
|
||||
if (dataFd == mINotifyFd) {
|
||||
if (eventItem.events & EPOLLIN) {
|
||||
deviceChange = true;
|
||||
} else {
|
||||
ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dataFd == mWakeEventFd) {
|
||||
if (eventItem.events & EPOLLIN) {
|
||||
ALOGV("awoken after wake()");
|
||||
uint64_t u;
|
||||
ssize_t nRead = TEMP_FAILURE_RETRY(read(mWakeEventFd, &u, sizeof(uint64_t)));
|
||||
if (nRead != sizeof(uint64_t)) {
|
||||
ALOGW("Could not read event fd; waking anyway.");
|
||||
}
|
||||
} else {
|
||||
ALOGW("Received unexpected epoll event 0x%08x for wake event.",
|
||||
eventItem.events);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update the fd and device node when the fd changes. When several
|
||||
// events are read back-to-back with the same fd, this saves many reads
|
||||
// from the hash table.
|
||||
if (inputFd != dataFd) {
|
||||
inputFd = dataFd;
|
||||
deviceNode = mDeviceNodes[inputFd];
|
||||
}
|
||||
if (deviceNode == nullptr) {
|
||||
ALOGE("could not find device node for fd %d", inputFd);
|
||||
continue;
|
||||
}
|
||||
if (eventItem.events & EPOLLIN) {
|
||||
struct input_event ievs[INPUT_MAX_EVENTS];
|
||||
for (;;) {
|
||||
ssize_t readSize = TEMP_FAILURE_RETRY(read(inputFd, ievs, sizeof(ievs)));
|
||||
if (readSize == 0 || (readSize < 0 && errno == ENODEV)) {
|
||||
ALOGW("could not get event, removed? (fd: %d, size: %d errno: %d)",
|
||||
inputFd, readSize, errno);
|
||||
|
||||
removedDeviceFds.push_back(inputFd);
|
||||
break;
|
||||
} else if (readSize < 0) {
|
||||
if (errno != EAGAIN && errno != EINTR) {
|
||||
ALOGW("could not get event. errno=%d", errno);
|
||||
}
|
||||
break;
|
||||
} else if (readSize % sizeof(input_event) != 0) {
|
||||
ALOGE("could not get event. wrong size=%d", readSize);
|
||||
break;
|
||||
} else {
|
||||
size_t count = static_cast<size_t>(readSize) / sizeof(struct input_event);
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
auto& iev = ievs[i];
|
||||
auto when = s2ns(iev.time.tv_sec) + us2ns(iev.time.tv_usec);
|
||||
InputEvent inputEvent = { when, iev.type, iev.code, iev.value };
|
||||
mInputCallback->onInputEvent(deviceNode, inputEvent, now);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (eventItem.events & EPOLLHUP) {
|
||||
ALOGI("Removing device fd %d due to epoll hangup event.", inputFd);
|
||||
removedDeviceFds.push_back(inputFd);
|
||||
} else {
|
||||
ALOGW("Received unexpected epoll event 0x%08x for device fd %d",
|
||||
eventItem.events, inputFd);
|
||||
}
|
||||
}
|
||||
|
||||
if (removedDeviceFds.size()) {
|
||||
for (auto deviceFd : removedDeviceFds) {
|
||||
auto deviceNode = mDeviceNodes[deviceFd];
|
||||
if (deviceNode != nullptr) {
|
||||
status_t ret = closeNodeByFd(deviceFd);
|
||||
if (ret != OK) {
|
||||
ALOGW("Could not close device with fd %d. errno=%d", deviceFd, ret);
|
||||
} else {
|
||||
mInputCallback->onDeviceRemoved(deviceNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deviceChange) {
|
||||
readNotify();
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t InputHub::wake() {
|
||||
ALOGV("wake() called");
|
||||
|
||||
uint64_t u = 1;
|
||||
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &u, sizeof(uint64_t)));
|
||||
|
||||
if (nWrite != sizeof(uint64_t) && errno != EAGAIN) {
|
||||
ALOGW("Could not write wake signal, errno=%d", errno);
|
||||
return -errno;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
void InputHub::dump(String8& dump) {
|
||||
// TODO
|
||||
}
|
||||
|
||||
status_t InputHub::readNotify() {
|
||||
char event_buf[512];
|
||||
struct inotify_event* event;
|
||||
|
||||
ssize_t res = TEMP_FAILURE_RETRY(read(mINotifyFd, event_buf, sizeof(event_buf)));
|
||||
if (res < static_cast<int>(sizeof(*event))) {
|
||||
ALOGW("could not get inotify event, %s\n", strerror(errno));
|
||||
return -errno;
|
||||
}
|
||||
|
||||
size_t event_pos = 0;
|
||||
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
|
||||
while (res >= static_cast<int>(sizeof(*event))) {
|
||||
event = reinterpret_cast<struct inotify_event*>(event_buf + event_pos);
|
||||
if (event->len) {
|
||||
std::string path = mWatchedPaths[event->wd];
|
||||
path.append("/").append(event->name);
|
||||
ALOGV("inotify event for path %s", path.c_str());
|
||||
|
||||
if (event->mask & IN_CREATE) {
|
||||
std::shared_ptr<InputDeviceNode> deviceNode;
|
||||
status_t res = openNode(path, &deviceNode);
|
||||
if (res != OK) {
|
||||
ALOGE("could not open device node %s. err=%d", path.c_str(), res);
|
||||
} else {
|
||||
mInputCallback->onDeviceAdded(deviceNode);
|
||||
}
|
||||
} else {
|
||||
auto deviceNode = findNodeByPath(path);
|
||||
if (deviceNode != nullptr) {
|
||||
status_t ret = closeNode(deviceNode);
|
||||
if (ret != OK) {
|
||||
ALOGW("Could not close device %s. errno=%d", path.c_str(), ret);
|
||||
} else {
|
||||
mInputCallback->onDeviceRemoved(deviceNode);
|
||||
}
|
||||
} else {
|
||||
ALOGW("could not find device node for %s", path.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
int event_size = sizeof(*event) + event->len;
|
||||
res -= event_size;
|
||||
event_pos += event_size;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t InputHub::scanDir(const std::string& path) {
|
||||
auto dir = ::opendir(path.c_str());
|
||||
if (dir == nullptr) {
|
||||
ALOGE("could not open device path %s to scan for devices. err=%d", path.c_str(), errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
while (auto dirent = readdir(dir)) {
|
||||
if (strcmp(dirent->d_name, ".") == 0 ||
|
||||
strcmp(dirent->d_name, "..") == 0) {
|
||||
continue;
|
||||
}
|
||||
std::string filename = path + "/" + dirent->d_name;
|
||||
std::shared_ptr<InputDeviceNode> node;
|
||||
if (openNode(filename, &node) != OK) {
|
||||
ALOGE("could not open device node %s", filename.c_str());
|
||||
} else {
|
||||
mInputCallback->onDeviceAdded(node);
|
||||
}
|
||||
}
|
||||
::closedir(dir);
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t InputHub::openNode(const std::string& path,
|
||||
std::shared_ptr<InputDeviceNode>* outNode) {
|
||||
ALOGV("opening %s...", path.c_str());
|
||||
auto evdevNode = std::shared_ptr<EvdevDeviceNode>(EvdevDeviceNode::openDeviceNode(path));
|
||||
if (evdevNode == nullptr) {
|
||||
return UNKNOWN_ERROR;
|
||||
}
|
||||
|
||||
auto fd = evdevNode->getFd();
|
||||
ALOGV("opened %s with fd %d", path.c_str(), fd);
|
||||
*outNode = std::static_pointer_cast<InputDeviceNode>(evdevNode);
|
||||
mDeviceNodes[fd] = *outNode;
|
||||
struct epoll_event eventItem{};
|
||||
eventItem.events = EPOLLIN;
|
||||
if (mWakeupMechanism == WakeMechanism::EPOLL_WAKEUP) {
|
||||
eventItem.events |= EPOLLWAKEUP;
|
||||
}
|
||||
eventItem.data.u32 = fd;
|
||||
if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) {
|
||||
ALOGE("Could not add device fd to epoll instance. errno=%d", errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (mNeedToCheckSuspendBlockIoctl) {
|
||||
#ifndef EVIOCSSUSPENDBLOCK
|
||||
// uapi headers don't include EVIOCSSUSPENDBLOCK, and future kernels
|
||||
// will use an epoll flag instead, so as long as we want to support this
|
||||
// feature, we need to be prepared to define the ioctl ourselves.
|
||||
#define EVIOCSSUSPENDBLOCK _IOW('E', 0x91, int)
|
||||
#endif
|
||||
if (TEMP_FAILURE_RETRY(ioctl(fd, EVIOCSSUSPENDBLOCK, 1))) {
|
||||
// no wake mechanism, continue using explicit wake locks
|
||||
ALOGI("Using explicit wakelocks to block suspend while processing input events.");
|
||||
} else {
|
||||
mWakeupMechanism = WakeMechanism::LEGACY_EVDEV_SUSPENDBLOCK_IOCTL;
|
||||
// release any held wakelocks since we won't need them anymore
|
||||
release_wake_lock(WAKE_LOCK_ID);
|
||||
ALOGI("Using EVIOCSSUSPENDBLOCK to block suspend while processing input events.");
|
||||
}
|
||||
mNeedToCheckSuspendBlockIoctl = false;
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
status_t InputHub::closeNode(const std::shared_ptr<InputDeviceNode>& node) {
|
||||
for (auto pair : mDeviceNodes) {
|
||||
if (pair.second.get() == node.get()) {
|
||||
return closeNodeByFd(pair.first);
|
||||
}
|
||||
}
|
||||
return BAD_VALUE;
|
||||
}
|
||||
|
||||
status_t InputHub::closeNodeByFd(int fd) {
|
||||
status_t ret = OK;
|
||||
if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, fd, NULL)) {
|
||||
ALOGW("Could not remove device fd from epoll instance. errno=%d", errno);
|
||||
ret = -errno;
|
||||
}
|
||||
mDeviceNodes.erase(fd);
|
||||
::close(fd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::shared_ptr<InputDeviceNode> InputHub::findNodeByPath(const std::string& path) {
|
||||
for (auto pair : mDeviceNodes) {
|
||||
if (pair.second->getPath() == path) return pair.second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool InputHub::manageWakeLocks() const {
|
||||
return mWakeupMechanism != WakeMechanism::EPOLL_WAKEUP;
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
Reference in New Issue
Block a user