diff options
Diffstat (limited to 'modules/input/evdev/InputHub.cpp')
-rw-r--r-- | modules/input/evdev/InputHub.cpp | 802 |
1 files changed, 802 insertions, 0 deletions
diff --git a/modules/input/evdev/InputHub.cpp b/modules/input/evdev/InputHub.cpp new file mode 100644 index 0000000..e72ac2e --- /dev/null +++ b/modules/input/evdev/InputHub.cpp @@ -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 |