diff options
Diffstat (limited to 'modules')
-rw-r--r-- | modules/input/evdev/Android.mk | 30 | ||||
-rw-r--r-- | modules/input/evdev/EvdevModule.cpp | 80 | ||||
-rw-r--r-- | modules/input/evdev/InputDevice.cpp | 102 | ||||
-rw-r--r-- | modules/input/evdev/InputDevice.h | 60 | ||||
-rw-r--r-- | modules/input/evdev/InputDeviceManager.cpp | 50 | ||||
-rw-r--r-- | modules/input/evdev/InputDeviceManager.h | 53 | ||||
-rw-r--r-- | modules/input/evdev/InputHost.cpp | 78 | ||||
-rw-r--r-- | modules/input/evdev/InputHost.h | 133 | ||||
-rw-r--r-- | modules/input/evdev/InputHub.cpp | 802 | ||||
-rw-r--r-- | modules/input/evdev/InputHub.h | 204 |
10 files changed, 1585 insertions, 7 deletions
diff --git a/modules/input/evdev/Android.mk b/modules/input/evdev/Android.mk index ad05af3..d3c49e7 100644 --- a/modules/input/evdev/Android.mk +++ b/modules/input/evdev/Android.mk @@ -14,6 +14,29 @@ LOCAL_PATH := $(call my-dir) +# Evdev module implementation +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := \ + InputHub.cpp \ + InputDevice.cpp \ + InputDeviceManager.cpp \ + InputHost.cpp + +LOCAL_SHARED_LIBRARIES := \ + libhardware_legacy \ + liblog \ + libutils + +LOCAL_CLANG := true +LOCAL_CPPFLAGS += -std=c++14 -Wno-unused-parameter + +LOCAL_MODULE := libinput_evdev +LOCAL_MODULE_TAGS := optional + +include $(BUILD_SHARED_LIBRARY) + +# HAL module include $(CLEAR_VARS) LOCAL_MODULE := input.evdev.default @@ -22,7 +45,12 @@ LOCAL_MODULE_RELATIVE_PATH := hw LOCAL_SRC_FILES := \ EvdevModule.cpp -LOCAL_SHARED_LIBRARIES := liblog +LOCAL_SHARED_LIBRARIES := \ + libinput_evdev \ + liblog + +LOCAL_CLANG := true +LOCAL_CPPFLAGS += -std=c++14 -Wno-unused-parameter LOCAL_MODULE_TAGS := optional diff --git a/modules/input/evdev/EvdevModule.cpp b/modules/input/evdev/EvdevModule.cpp index f56842a..e9c8222 100644 --- a/modules/input/evdev/EvdevModule.cpp +++ b/modules/input/evdev/EvdevModule.cpp @@ -17,27 +17,95 @@ #define LOG_NDEBUG 0 #define LOG_TAG "EvdevModule" +#include <memory> +#include <string> +#include <thread> + #include <assert.h> #include <hardware/hardware.h> #include <hardware/input.h> -namespace input { +#include <utils/Log.h> + +#include "InputHub.h" +#include "InputDeviceManager.h" +#include "InputHost.h" + +namespace android { + +static const char kDevInput[] = "/dev/input"; + +class EvdevModule { +public: + explicit EvdevModule(InputHost inputHost); + + void init(); + void notifyReport(input_report_t* r); + +private: + void loop(); + + InputHost mInputHost; + std::shared_ptr<InputDeviceManager> mDeviceManager; + std::shared_ptr<InputHub> mInputHub; + std::thread mPollThread; +}; + +static std::shared_ptr<EvdevModule> gEvdevModule; + +EvdevModule::EvdevModule(InputHost inputHost) : + mInputHost(inputHost), + mDeviceManager(std::make_shared<InputDeviceManager>()), + mInputHub(std::make_shared<InputHub>(mDeviceManager)) {} + +void EvdevModule::init() { + ALOGV("%s", __func__); + + mInputHub->registerDevicePath(kDevInput); + mPollThread = std::thread(&EvdevModule::loop, this); +} + +void EvdevModule::notifyReport(input_report_t* r) { + ALOGV("%s", __func__); + + // notifyReport() will be called from an arbitrary thread within the input + // host. Since InputHub is not threadsafe, this is how I expect this to + // work: + // * notifyReport() will queue up the output report in the EvdevModule and + // call wake() on the InputHub. + // * In the main loop thread, after returning from poll(), the queue will + // be processed with any pending work. +} + +void EvdevModule::loop() { + ALOGV("%s", __func__); + for (;;) { + mInputHub->poll(); + + // TODO: process any pending work, like notify reports + } +} extern "C" { static int dummy_open(const hw_module_t __unused *module, const char __unused *id, - hw_device_t __unused **device) { - assert(false); + hw_device_t __unused **device) { + ALOGW("open not implemented in the input HAL!"); return 0; } static void input_init(const input_module_t* module, input_host_t* host, input_host_callbacks_t cb) { - return; + LOG_ALWAYS_FATAL_IF(strcmp(module->common.id, INPUT_HARDWARE_MODULE_ID) != 0); + InputHost inputHost = {host, cb}; + gEvdevModule = std::make_shared<EvdevModule>(inputHost); + gEvdevModule->init(); } -static void input_notify_report(input_report_t* r) { - return; +static void input_notify_report(const input_module_t* module, input_report_t* r) { + LOG_ALWAYS_FATAL_IF(strcmp(module->common.id, INPUT_HARDWARE_MODULE_ID) != 0); + LOG_ALWAYS_FATAL_IF(gEvdevModule == nullptr); + gEvdevModule->notifyReport(r); } static struct hw_module_methods_t input_module_methods = { diff --git a/modules/input/evdev/InputDevice.cpp b/modules/input/evdev/InputDevice.cpp new file mode 100644 index 0000000..c0b59d7 --- /dev/null +++ b/modules/input/evdev/InputDevice.cpp @@ -0,0 +1,102 @@ +/* + * 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 "InputDevice" +#define LOG_NDEBUG 0 + +#include <linux/input.h> + +#define __STDC_FORMAT_MACROS +#include <cinttypes> +#include <string> + +#include <utils/Log.h> +#include <utils/Timers.h> + +#include "InputHub.h" +#include "InputDevice.h" + +#define MSC_ANDROID_TIME_SEC 0x6 +#define MSC_ANDROID_TIME_USEC 0x7 + +namespace android { + +EvdevDevice::EvdevDevice(std::shared_ptr<InputDeviceNode> node) : + mDeviceNode(node) {} + +void EvdevDevice::processInput(InputEvent& event, nsecs_t currentTime) { + std::string log; + log.append("---InputEvent for device %s---\n"); + log.append(" when: %" PRId64 "\n"); + log.append(" type: %d\n"); + log.append(" code: %d\n"); + log.append(" value: %d\n"); + ALOGV(log.c_str(), mDeviceNode->getPath().c_str(), event.when, event.type, event.code, + event.value); + + if (event.type == EV_MSC) { + if (event.code == MSC_ANDROID_TIME_SEC) { + mOverrideSec = event.value; + } else if (event.code == MSC_ANDROID_TIME_USEC) { + mOverrideUsec = event.value; + } + return; + } + + if (mOverrideSec || mOverrideUsec) { + event.when = s2ns(mOverrideSec) + us2ns(mOverrideUsec); + ALOGV("applied override time %d.%06d", mOverrideSec, mOverrideUsec); + + if (event.type == EV_SYN && event.code == SYN_REPORT) { + mOverrideSec = 0; + mOverrideUsec = 0; + } + } + + // Bug 7291243: Add a guard in case the kernel generates timestamps + // that appear to be far into the future because they were generated + // using the wrong clock source. + // + // This can happen because when the input device is initially opened + // it has a default clock source of CLOCK_REALTIME. Any input events + // enqueued right after the device is opened will have timestamps + // generated using CLOCK_REALTIME. We later set the clock source + // to CLOCK_MONOTONIC but it is already too late. + // + // Invalid input event timestamps can result in ANRs, crashes and + // and other issues that are hard to track down. We must not let them + // propagate through the system. + // + // Log a warning so that we notice the problem and recover gracefully. + if (event.when >= currentTime + s2ns(10)) { + // Double-check. Time may have moved on. + auto time = systemTime(SYSTEM_TIME_MONOTONIC); + if (event.when > time) { + ALOGW("An input event from %s has a timestamp that appears to have " + "been generated using the wrong clock source (expected " + "CLOCK_MONOTONIC): event time %" PRId64 ", current time %" PRId64 + ", call time %" PRId64 ". Using current time instead.", + mDeviceNode->getPath().c_str(), event.when, time, currentTime); + event.when = time; + } else { + ALOGV("Event time is ok but failed the fast path and required an extra " + "call to systemTime: event time %" PRId64 ", current time %" PRId64 + ", call time %" PRId64 ".", event.when, time, currentTime); + } + } +} + +} // namespace android diff --git a/modules/input/evdev/InputDevice.h b/modules/input/evdev/InputDevice.h new file mode 100644 index 0000000..3aa16cc --- /dev/null +++ b/modules/input/evdev/InputDevice.h @@ -0,0 +1,60 @@ +/* + * 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. + */ + +#ifndef ANDROID_INPUT_DEVICE_H_ +#define ANDROID_INPUT_DEVICE_H_ + +#include <memory> + +#include <utils/Timers.h> + +#include "InputHub.h" + +namespace android { + +/** + * InputDeviceInterface represents an input device in the HAL. It processes + * input events before passing them to the input host. + */ +class InputDeviceInterface { +public: + virtual void processInput(InputEvent& event, nsecs_t currentTime) = 0; + +protected: + InputDeviceInterface() = default; + virtual ~InputDeviceInterface() = default; +}; + +/** + * EvdevDevice is an input device backed by a Linux evdev node. + */ +class EvdevDevice : public InputDeviceInterface { +public: + explicit EvdevDevice(std::shared_ptr<InputDeviceNode> node); + virtual ~EvdevDevice() override = default; + + virtual void processInput(InputEvent& event, nsecs_t currentTime) override; + +private: + std::shared_ptr<InputDeviceNode> mDeviceNode; + + int32_t mOverrideSec = 0; + int32_t mOverrideUsec = 0; +}; + +} // namespace android + +#endif // ANDROID_INPUT_DEVICE_H_ diff --git a/modules/input/evdev/InputDeviceManager.cpp b/modules/input/evdev/InputDeviceManager.cpp new file mode 100644 index 0000000..ceddd90 --- /dev/null +++ b/modules/input/evdev/InputDeviceManager.cpp @@ -0,0 +1,50 @@ +/* + * 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 "InputDeviceManager" +//#define LOG_NDEBUG 0 + +#include <utils/Log.h> + +#include "InputDevice.h" +#include "InputDeviceManager.h" + +namespace android { + +void InputDeviceManager::onInputEvent(std::shared_ptr<InputDeviceNode> node, InputEvent& event, + nsecs_t event_time) { + if (mDevices[node] == nullptr) { + ALOGE("got input event for unknown node %s", node->getPath().c_str()); + return; + } + mDevices[node]->processInput(event, event_time); +} + +void InputDeviceManager::onDeviceAdded(std::shared_ptr<InputDeviceNode> node) { + mDevices[node] = std::make_shared<EvdevDevice>(node); +} + +void InputDeviceManager::onDeviceRemoved(std::shared_ptr<InputDeviceNode> node) { + if (mDevices[node] == nullptr) { + ALOGE("could not remove unknown node %s", node->getPath().c_str()); + return; + } + // TODO: tell the InputDevice and InputDeviceNode that they are being + // removed so they can run any cleanup. + mDevices.erase(node); +} + +} // namespace android diff --git a/modules/input/evdev/InputDeviceManager.h b/modules/input/evdev/InputDeviceManager.h new file mode 100644 index 0000000..b652155 --- /dev/null +++ b/modules/input/evdev/InputDeviceManager.h @@ -0,0 +1,53 @@ +/* + * 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. + */ + +#ifndef ANDROID_INPUT_DEVICE_MANAGER_H_ +#define ANDROID_INPUT_DEVICE_MANAGER_H_ + +#include <memory> +#include <unordered_map> + +#include <utils/Timers.h> + +#include "InputDevice.h" +#include "InputHub.h" + +namespace android { + +/** + * InputDeviceManager keeps the mapping of InputDeviceNodes to + * InputDeviceInterfaces and handles the callbacks from the InputHub, delegating + * them to the appropriate InputDeviceInterface. + */ +class InputDeviceManager : public InputCallbackInterface { +public: + virtual ~InputDeviceManager() override = default; + + virtual void onInputEvent(std::shared_ptr<InputDeviceNode> node, InputEvent& event, + nsecs_t event_time) override; + virtual void onDeviceAdded(std::shared_ptr<InputDeviceNode> node) override; + virtual void onDeviceRemoved(std::shared_ptr<InputDeviceNode> node) override; + +private: + template<class T, class U> + using DeviceMap = std::unordered_map<std::shared_ptr<T>, std::shared_ptr<U>>; + + DeviceMap<InputDeviceNode, InputDeviceInterface> mDevices; +}; + +} // namespace android + +#endif // ANDROID_INPUT_DEVICE_MANAGER_H_ diff --git a/modules/input/evdev/InputHost.cpp b/modules/input/evdev/InputHost.cpp new file mode 100644 index 0000000..0903f47 --- /dev/null +++ b/modules/input/evdev/InputHost.cpp @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#include "InputHost.h" + +namespace android { + +void InputReport::reportEvent(InputDeviceHandle d) { + mCallbacks.report_event(mHost, d, mReport); +} + +void InputReportDefinition::addCollection(InputCollectionId id, int32_t arity) { + mCallbacks.input_report_definition_add_collection(mHost, mReportDefinition, id, arity); +} + +void InputReportDefinition::declareUsage(InputCollectionId id, InputUsage usage, + int32_t min, int32_t max, float resolution) { + mCallbacks.input_report_definition_declare_usage_int(mHost, mReportDefinition, + id, usage, min, max, resolution); +} + +void InputReportDefinition::declareUsage(InputCollectionId id, InputUsage* usage, + size_t usageCount) { + mCallbacks.input_report_definition_declare_usages_bool(mHost, mReportDefinition, + id, usage, usageCount); +} + +InputReport InputReportDefinition::allocateReport() { + return InputReport(mHost, mCallbacks, + mCallbacks.input_allocate_report(mHost, mReportDefinition)); +} + +void InputDeviceDefinition::addReport(InputReportDefinition r) { + mCallbacks.input_device_definition_add_report(mHost, mDeviceDefinition, r); +} + +InputDeviceIdentifier InputHost::createDeviceIdentifier(const char* name, int32_t productId, + int32_t vendorId, InputBus bus, const char* uniqueId) { + return mCallbacks.create_device_identifier(mHost, name, productId, vendorId, bus, uniqueId); +} + +InputDeviceDefinition InputHost::createDeviceDefinition() { + return InputDeviceDefinition(mHost, mCallbacks, mCallbacks.create_device_definition(mHost)); +} + +InputReportDefinition InputHost::createInputReportDefinition() { + return InputReportDefinition(mHost, mCallbacks, + mCallbacks.create_input_report_definition(mHost)); +} + +InputReportDefinition InputHost::createOutputReportDefinition() { + return InputReportDefinition(mHost, mCallbacks, + mCallbacks.create_output_report_definition(mHost)); +} + +InputDeviceHandle InputHost::registerDevice(InputDeviceIdentifier id, + InputDeviceDefinition d) { + return mCallbacks.register_device(mHost, id, d); +} + +void InputHost::unregisterDevice(InputDeviceHandle handle) { + return mCallbacks.unregister_device(mHost, handle); +} + +} // namespace android diff --git a/modules/input/evdev/InputHost.h b/modules/input/evdev/InputHost.h new file mode 100644 index 0000000..129443e --- /dev/null +++ b/modules/input/evdev/InputHost.h @@ -0,0 +1,133 @@ +/* + * 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. + */ + +#ifndef ANDROID_INPUT_HOST_H_ +#define ANDROID_INPUT_HOST_H_ + +#include <hardware/input.h> + +namespace android { + +/** + * Classes in this file wrap the corresponding interfaces in the Input HAL. They + * are intended to be lightweight and passed by value. It is still important not + * to use an object after a HAL-specific method has freed the underlying + * representation. + * + * See hardware/input.h for details about each of these methods. + */ + +using InputBus = input_bus_t; +using InputCollectionId = input_collection_id_t; +using InputDeviceHandle = input_device_handle_t*; +using InputDeviceIdentifier = input_device_identifier_t*; +using InputUsage = input_usage_t; + +class InputHostBase { +protected: + InputHostBase(input_host_t* host, input_host_callbacks_t cb) : mHost(host), mCallbacks(cb) {} + virtual ~InputHostBase() = default; + + input_host_t* mHost; + input_host_callbacks_t mCallbacks; +}; + +class InputReport : private InputHostBase { +public: + virtual ~InputReport() = default; + + InputReport(const InputReport& rhs) = default; + InputReport& operator=(const InputReport& rhs) = default; + operator input_report_t*() const { return mReport; } + + void reportEvent(InputDeviceHandle d); + +private: + friend class InputReportDefinition; + + InputReport(input_host_t* host, input_host_callbacks_t cb, input_report_t* r) : + InputHostBase(host, cb), mReport(r) {} + + input_report_t* mReport; +}; + +class InputReportDefinition : private InputHostBase { +public: + virtual ~InputReportDefinition() = default; + + InputReportDefinition(const InputReportDefinition& rhs) = default; + InputReportDefinition& operator=(const InputReportDefinition& rhs) = default; + operator input_report_definition_t*() { return mReportDefinition; } + + void addCollection(InputCollectionId id, int32_t arity); + void declareUsage(InputCollectionId id, InputUsage usage, int32_t min, int32_t max, + float resolution); + void declareUsage(InputCollectionId id, InputUsage* usage, size_t usageCount); + + InputReport allocateReport(); + +private: + friend class InputHost; + + InputReportDefinition( + input_host_t* host, input_host_callbacks_t cb, input_report_definition_t* r) : + InputHostBase(host, cb), mReportDefinition(r) {} + + input_report_definition_t* mReportDefinition; +}; + +class InputDeviceDefinition : private InputHostBase { +public: + virtual ~InputDeviceDefinition() = default; + + InputDeviceDefinition(const InputDeviceDefinition& rhs) = default; + InputDeviceDefinition& operator=(const InputDeviceDefinition& rhs) = default; + operator input_device_definition_t*() { return mDeviceDefinition; } + + void addReport(InputReportDefinition r); + +private: + friend class InputHost; + + InputDeviceDefinition( + input_host_t* host, input_host_callbacks_t cb, input_device_definition_t* d) : + InputHostBase(host, cb), mDeviceDefinition(d) {} + + input_device_definition_t* mDeviceDefinition; +}; + +class InputHost : private InputHostBase { +public: + InputHost(input_host_t* host, input_host_callbacks_t cb) : InputHostBase(host, cb) {} + virtual ~InputHost() = default; + + InputHost(const InputHost& rhs) = default; + InputHost& operator=(const InputHost& rhs) = default; + + InputDeviceIdentifier createDeviceIdentifier(const char* name, int32_t productId, + int32_t vendorId, InputBus bus, const char* uniqueId); + + InputDeviceDefinition createDeviceDefinition(); + InputReportDefinition createInputReportDefinition(); + InputReportDefinition createOutputReportDefinition(); + + InputDeviceHandle registerDevice(InputDeviceIdentifier id, InputDeviceDefinition d); + void unregisterDevice(InputDeviceHandle handle); +}; + +} // namespace android + +#endif // ANDROID_INPUT_HOST_H_ 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 diff --git a/modules/input/evdev/InputHub.h b/modules/input/evdev/InputHub.h new file mode 100644 index 0000000..bec327a --- /dev/null +++ b/modules/input/evdev/InputHub.h @@ -0,0 +1,204 @@ +/* + * 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. + */ + +#ifndef ANDROID_INPUT_HUB_H_ +#define ANDROID_INPUT_HUB_H_ + +#include <memory> +#include <string> +#include <unordered_map> + +#include <utils/String8.h> +#include <utils/Timers.h> + +namespace android { + +/** + * InputEvent represents an event from the kernel. The fields largely mirror + * those found in linux/input.h. + */ +struct InputEvent { + nsecs_t when; + + int32_t type; + int32_t code; + int32_t value; +}; + +/** Describes an absolute axis. */ +struct AbsoluteAxisInfo { + int32_t minValue = 0; // minimum value + int32_t maxValue = 0; // maximum value + int32_t flat = 0; // center flat position, e.g. flat == 8 means center is between -8 and 8 + int32_t fuzz = 0; // error tolerance, e.g. fuzz == 4 means value is +/- 4 due to noise + int32_t resolution = 0; // resolution in units per mm or radians per mm +}; + +/** + * An InputDeviceNode represents a device node in the Linux system. It can be + * used to interact with the device, setting and getting property values. + * + * An InputDeviceNode should only be used on the same thread that is polling for + * input events. + */ +class InputDeviceNode { +public: + virtual const std::string& getPath() const = 0; + + virtual const std::string& getName() const = 0; + virtual const std::string& getLocation() const = 0; + virtual const std::string& getUniqueId() const = 0; + + virtual uint16_t getBusType() const = 0; + virtual uint16_t getVendorId() const = 0; + virtual uint16_t getProductId() const = 0; + virtual uint16_t getVersion() const = 0; + + virtual bool hasKey(int32_t key) const = 0; + virtual bool hasRelativeAxis(int axis) const = 0; + virtual const AbsoluteAxisInfo* getAbsoluteAxisInfo(int32_t axis) const = 0; + virtual bool hasInputProperty(int property) const = 0; + + virtual int32_t getKeyState(int32_t key) const = 0; + virtual int32_t getSwitchState(int32_t sw) const = 0; + virtual status_t getAbsoluteAxisValue(int32_t axis, int32_t* outValue) const = 0; + + virtual void vibrate(nsecs_t duration) = 0; + virtual void cancelVibrate(int32_t deviceId) = 0; + + virtual void disableDriverKeyRepeat() = 0; + +protected: + InputDeviceNode() = default; + virtual ~InputDeviceNode() = default; +}; + +/** Callback interface for receiving input events, including device changes. */ +class InputCallbackInterface { +public: + virtual void onInputEvent(std::shared_ptr<InputDeviceNode> node, InputEvent& event, + nsecs_t event_time) = 0; + virtual void onDeviceAdded(std::shared_ptr<InputDeviceNode> node) = 0; + virtual void onDeviceRemoved(std::shared_ptr<InputDeviceNode> node) = 0; + +protected: + InputCallbackInterface() = default; + virtual ~InputCallbackInterface() = default; +}; + +/** + * InputHubInterface is responsible for monitoring a set of device paths and + * executing callbacks when events occur. Before calling poll(), you should set + * the device and input callbacks, and register your device path(s). + */ +class InputHubInterface { +public: + virtual status_t registerDevicePath(const std::string& path) = 0; + virtual status_t unregisterDevicePath(const std::string& path) = 0; + + virtual status_t poll() = 0; + virtual status_t wake() = 0; + + virtual void dump(String8& dump) = 0; + +protected: + InputHubInterface() = default; + virtual ~InputHubInterface() = default; +}; + +/** + * An implementation of InputHubInterface that uses epoll to wait for events. + * + * This class is not threadsafe. Any functions called on the InputHub should be + * called on the same thread that is used to call poll(). The only exception is + * wake(), which may be used to return from poll() before an input or device + * event occurs. + */ +class InputHub : public InputHubInterface { +public: + explicit InputHub(std::shared_ptr<InputCallbackInterface> cb); + virtual ~InputHub() override; + + virtual status_t registerDevicePath(const std::string& path) override; + virtual status_t unregisterDevicePath(const std::string& path) override; + + virtual status_t poll() override; + virtual status_t wake() override; + + virtual void dump(String8& dump) override; + +private: + status_t readNotify(); + status_t scanDir(const std::string& path); + status_t openNode(const std::string& path, std::shared_ptr<InputDeviceNode>* outNode); + status_t closeNode(const std::shared_ptr<InputDeviceNode>& node); + status_t closeNodeByFd(int fd); + std::shared_ptr<InputDeviceNode> findNodeByPath(const std::string& path); + + enum class WakeMechanism { + /** + * The kernel supports the EPOLLWAKEUP flag for epoll_ctl. + * + * When using this mechanism, epoll_wait will internally acquire a wake + * lock whenever one of the FDs it is monitoring becomes ready. The wake + * lock is held automatically by the kernel until the next call to + * epoll_wait. + * + * This mechanism only exists in Linux kernel 3.5+. + */ + EPOLL_WAKEUP, + /** + * The kernel evdev driver supports the EVIOCSSUSPENDBLOCK ioctl. + * + * When using this mechanism, the InputHub asks evdev to acquire and + * hold a wake lock whenever its buffer is non-empty. We must take care + * to acquire our own userspace wake lock before draining the buffer to + * prevent actually going back into suspend before we have fully + * processed all of the events. + * + * This mechanism only exists in older Android Linux kernels. + */ + LEGACY_EVDEV_SUSPENDBLOCK_IOCTL, + /** + * The kernel doesn't seem to support any special wake mechanism. + * + * We explicitly acquire and release wake locks when processing input + * events. + */ + LEGACY_EVDEV_EXPLICIT_WAKE_LOCKS, + }; + WakeMechanism mWakeupMechanism = WakeMechanism::LEGACY_EVDEV_EXPLICIT_WAKE_LOCKS; + bool manageWakeLocks() const; + bool mNeedToCheckSuspendBlockIoctl = true; + + int mEpollFd; + int mINotifyFd; + int mWakeEventFd; + int mWakeReadPipeFd; + int mWakeWritePipeFd; + + // Callback for input events + std::shared_ptr<InputCallbackInterface> mInputCallback; + + // Map from watch descriptors to watched paths + std::unordered_map<int, std::string> mWatchedPaths; + // Map from file descriptors to InputDeviceNodes + std::unordered_map<int, std::shared_ptr<InputDeviceNode>> mDeviceNodes; +}; + +} // namespace android + +#endif // ANDROID_INPUT_HUB_H_ |