From 49fa5048df0b912bf6c448246080157a57bdb2d4 Mon Sep 17 00:00:00 2001 From: roger2015 Date: Thu, 20 Feb 2025 11:46:44 +0800 Subject: [PATCH] deal with CAP --- .../radio/rild/rild_goldfish.legacy.rc | 5 + .../goldfish/radio/rild/rild_goldfish.rc | 5 + .../av/media/audioserver/audioserver.rc | 60 + ...droid_server_alarm_AlarmManagerService.cpp | 313 ++ .../native/cmds/installd/installd.rc | 105 + .../services/inputflinger/reader/EventHub.cpp | 2933 +++++++++++++++++ ...oid.hardware.audio.service-aidl.example.rc | 23 + ...roid.hardware.audio@7.0-service.example.rc | 6 + .../service/android.hardware.audio.service.rc | 11 + .../android.hardware.bluetooth@1.0-service.rc | 8 + .../android.hardware.bluetooth@1.1-service.rc | 9 + .../aidl/default/bluetooth-service-default.rc | 6 + .../bluetooth-finder-service-default.rc | 6 + .../aidl/default/lmp_event-default.rc | 6 + .../bluetooth-ranging-service-default.rc | 6 + .../android.hardware.health@2.1-service.rc | 5 + ...android.hardware.health-service.example.rc | 16 + ...ardware.health-service.example_recovery.rc | 6 + .../android.hardware.sensors@1.0-service.rc | 6 + ...d.hardware.sensors@2.0-service-multihal.rc | 6 + ...d.hardware.sensors@2.1-service-multihal.rc | 6 + ...droid.hardware.sensors-service-multihal.rc | 6 + .../android.hardware.wifi-service-lazy.rc | 8 + .../default/android.hardware.wifi-service.rc | 6 + aosp/hardware/ril/rild/rild.legacy.rc | 5 + aosp/hardware/ril/rild/rild.rc | 5 + .../modules/Bluetooth/system/osi/src/alarm.cc | 764 +++++ .../modules/Virtualization/microdroid/init.rc | 177 + .../microdroid_manager/microdroid_manager.rc | 15 + aosp/system/apex/apexd/apexd.rc | 30 + aosp/system/core/llkd/llkd-debuggable.rc | 19 + aosp/system/core/llkd/llkd.rc | 45 + aosp/system/core/storaged/storaged.rc | 7 + aosp/system/logging/logd/logd.rc | 37 + aosp/system/memory/lmkd/lmkd.rc | 51 + aosp/system/netd/server/netd.rc | 2 +- 36 files changed, 4723 insertions(+), 1 deletion(-) create mode 100644 aosp/device/generic/goldfish/radio/rild/rild_goldfish.legacy.rc create mode 100644 aosp/device/generic/goldfish/radio/rild/rild_goldfish.rc create mode 100644 aosp/frameworks/av/media/audioserver/audioserver.rc create mode 100644 aosp/frameworks/base/apex/jobscheduler/service/jni/com_android_server_alarm_AlarmManagerService.cpp create mode 100644 aosp/frameworks/native/cmds/installd/installd.rc create mode 100644 aosp/frameworks/native/services/inputflinger/reader/EventHub.cpp create mode 100644 aosp/hardware/interfaces/audio/aidl/default/android.hardware.audio.service-aidl.example.rc create mode 100644 aosp/hardware/interfaces/audio/common/7.0/example/android.hardware.audio@7.0-service.example.rc create mode 100644 aosp/hardware/interfaces/audio/common/all-versions/default/service/android.hardware.audio.service.rc create mode 100644 aosp/hardware/interfaces/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc create mode 100644 aosp/hardware/interfaces/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc create mode 100644 aosp/hardware/interfaces/bluetooth/aidl/default/bluetooth-service-default.rc create mode 100644 aosp/hardware/interfaces/bluetooth/finder/aidl/default/bluetooth-finder-service-default.rc create mode 100644 aosp/hardware/interfaces/bluetooth/lmp_event/aidl/default/lmp_event-default.rc create mode 100644 aosp/hardware/interfaces/bluetooth/ranging/aidl/default/bluetooth-ranging-service-default.rc create mode 100644 aosp/hardware/interfaces/health/2.1/default/android.hardware.health@2.1-service.rc create mode 100644 aosp/hardware/interfaces/health/aidl/default/android.hardware.health-service.example.rc create mode 100644 aosp/hardware/interfaces/health/aidl/default/android.hardware.health-service.example_recovery.rc create mode 100644 aosp/hardware/interfaces/sensors/1.0/default/android.hardware.sensors@1.0-service.rc create mode 100644 aosp/hardware/interfaces/sensors/2.0/multihal/android.hardware.sensors@2.0-service-multihal.rc create mode 100644 aosp/hardware/interfaces/sensors/2.1/multihal/android.hardware.sensors@2.1-service-multihal.rc create mode 100644 aosp/hardware/interfaces/sensors/aidl/multihal/android.hardware.sensors-service-multihal.rc create mode 100644 aosp/hardware/interfaces/wifi/aidl/default/android.hardware.wifi-service-lazy.rc create mode 100644 aosp/hardware/interfaces/wifi/aidl/default/android.hardware.wifi-service.rc create mode 100644 aosp/hardware/ril/rild/rild.legacy.rc create mode 100644 aosp/hardware/ril/rild/rild.rc create mode 100644 aosp/packages/modules/Bluetooth/system/osi/src/alarm.cc create mode 100644 aosp/packages/modules/Virtualization/microdroid/init.rc create mode 100644 aosp/packages/modules/Virtualization/microdroid_manager/microdroid_manager.rc create mode 100644 aosp/system/apex/apexd/apexd.rc create mode 100644 aosp/system/core/llkd/llkd-debuggable.rc create mode 100644 aosp/system/core/llkd/llkd.rc create mode 100644 aosp/system/core/storaged/storaged.rc create mode 100644 aosp/system/logging/logd/logd.rc create mode 100644 aosp/system/memory/lmkd/lmkd.rc diff --git a/aosp/device/generic/goldfish/radio/rild/rild_goldfish.legacy.rc b/aosp/device/generic/goldfish/radio/rild/rild_goldfish.legacy.rc new file mode 100644 index 000000000..fcaabc887 --- /dev/null +++ b/aosp/device/generic/goldfish/radio/rild/rild_goldfish.legacy.rc @@ -0,0 +1,5 @@ +service ril-daemon /vendor/bin/hw/libgoldfish-rild + class main + user radio + group radio cache inet misc audio log readproc wakelock + capabilities NET_ADMIN NET_RAW diff --git a/aosp/device/generic/goldfish/radio/rild/rild_goldfish.rc b/aosp/device/generic/goldfish/radio/rild/rild_goldfish.rc new file mode 100644 index 000000000..fa5916864 --- /dev/null +++ b/aosp/device/generic/goldfish/radio/rild/rild_goldfish.rc @@ -0,0 +1,5 @@ +service vendor.ril-daemon /vendor/bin/hw/libgoldfish-rild + class main + user radio + group radio cache inet misc audio log readproc wakelock + capabilities NET_ADMIN NET_RAW diff --git a/aosp/frameworks/av/media/audioserver/audioserver.rc b/aosp/frameworks/av/media/audioserver/audioserver.rc new file mode 100644 index 000000000..4bef34460 --- /dev/null +++ b/aosp/frameworks/av/media/audioserver/audioserver.rc @@ -0,0 +1,60 @@ +service audioserver /system/bin/audioserver + class core + user audioserver + # media gid needed for /dev/fm (radio) and for /data/misc/media (tee) + group audio camera drmrpc media mediadrm net_bt net_bt_admin net_bw_acct wakelock + # match rtprio cur / max with sensor service as we handle AR/VR HID sensor data. + rlimit rtprio 10 10 + ioprio rt 4 + task_profiles ProcessCapacityHigh HighPerformance + onrestart restart vendor.audio-hal + onrestart restart vendor.audio-hal-aidl + onrestart restart vendor.audio-effect-hal-aidl + onrestart restart vendor.audio-hal-4-0-msd + onrestart restart audio_proxy_service + +on property:vts.native_server.on=1 + stop audioserver +on property:vts.native_server.on=0 + start audioserver + +on property:init.svc.audioserver=stopped + stop vendor.audio-hal + stop vendor.audio-hal-aidl + stop vendor.audio-effect-hal-aidl + stop vendor.audio-hal-4-0-msd + stop audio_proxy_service + # See b/155364397. Need to have HAL service running for VTS. + # Can't use 'restart' because then HAL service would restart + # audioserver bringing it back into running state. + start vendor.audio-hal + start vendor.audio-hal-aidl + start vendor.audio-effect-hal-aidl + start vendor.audio-hal-4-0-msd + start audio_proxy_service + +on property:init.svc.audioserver=running + start vendor.audio-hal + start vendor.audio-hal-aidl + start vendor.audio-effect-hal-aidl + start vendor.audio-hal-4-0-msd + start audio_proxy_service + +on property:sys.audio.restart.hal=1 + # See b/159966243. Avoid restart loop between audioserver and HAL. + # Keep the original service names for backward compatibility + stop vendor.audio-hal + stop vendor.audio-hal-aidl + stop vendor.audio-effect-hal-aidl + stop vendor.audio-hal-4-0-msd + stop audio_proxy_service + start vendor.audio-hal + start vendor.audio-hal-aidl + start vendor.audio-effect-hal-aidl + start vendor.audio-hal-4-0-msd + start audio_proxy_service + # reset the property + setprop sys.audio.restart.hal 0 + +on init + mkdir /dev/socket/audioserver 0775 audioserver audioserver diff --git a/aosp/frameworks/base/apex/jobscheduler/service/jni/com_android_server_alarm_AlarmManagerService.cpp b/aosp/frameworks/base/apex/jobscheduler/service/jni/com_android_server_alarm_AlarmManagerService.cpp new file mode 100644 index 000000000..c939b9dcc --- /dev/null +++ b/aosp/frameworks/base/apex/jobscheduler/service/jni/com_android_server_alarm_AlarmManagerService.cpp @@ -0,0 +1,313 @@ +/* +** Copyright 2006, 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 "AlarmManagerService" + +#include +#include +#include +#include +#include +#include +#include "jni.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace android { + +static constexpr int ANDROID_ALARM_TIME_CHANGE_MASK = 1 << 16; + +/** + * The AlarmManager alarm constants: + * + * RTC_WAKEUP + * RTC + * REALTIME_WAKEUP + * REALTIME + * SYSTEMTIME (only defined in old alarm driver header, possibly unused?) + * + * We also need an extra CLOCK_REALTIME fd which exists specifically to be + * canceled on RTC changes. + */ +static const size_t ANDROID_ALARM_TYPE_COUNT = 5; +static const size_t N_ANDROID_TIMERFDS = ANDROID_ALARM_TYPE_COUNT + 1; +static const clockid_t android_alarm_to_clockid[N_ANDROID_TIMERFDS] = { + CLOCK_REALTIME, + CLOCK_REALTIME, + CLOCK_BOOTTIME, + CLOCK_BOOTTIME, + CLOCK_MONOTONIC, + CLOCK_REALTIME, +}; + +typedef std::array TimerFds; + +class AlarmImpl +{ +public: + AlarmImpl(const TimerFds &fds, int epollfd) + : fds{fds}, epollfd{epollfd} {} + ~AlarmImpl(); + + int set(int type, struct timespec *ts); + int waitForAlarm(); + int getTime(int type, struct itimerspec *spec); + +private: + const TimerFds fds; + const int epollfd; +}; + +AlarmImpl::~AlarmImpl() +{ + for (auto fd : fds) { + epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, nullptr); + close(fd); + } + + close(epollfd); +} + +int AlarmImpl::set(int type, struct timespec *ts) +{ + if (static_cast(type) > ANDROID_ALARM_TYPE_COUNT) { + errno = EINVAL; + return -1; + } + + if (!ts->tv_nsec && !ts->tv_sec) { + ts->tv_nsec = 1; + } + /* timerfd interprets 0 = disarm, so replace with a practically + equivalent deadline of 1 ns */ + + struct itimerspec spec; + memset(&spec, 0, sizeof(spec)); + memcpy(&spec.it_value, ts, sizeof(spec.it_value)); + + return timerfd_settime(fds[type], TFD_TIMER_ABSTIME, &spec, NULL); +} + +int AlarmImpl::getTime(int type, struct itimerspec *spec) +{ + if (static_cast(type) > ANDROID_ALARM_TYPE_COUNT) { + errno = EINVAL; + return -1; + } + + return timerfd_gettime(fds[type], spec); +} + +int AlarmImpl::waitForAlarm() +{ + epoll_event events[N_ANDROID_TIMERFDS]; + + int nevents = epoll_wait(epollfd, events, N_ANDROID_TIMERFDS, -1); + if (nevents < 0) { + return nevents; + } + + int result = 0; + for (int i = 0; i < nevents; i++) { + uint32_t alarm_idx = events[i].data.u32; + uint64_t unused; + ssize_t err = read(fds[alarm_idx], &unused, sizeof(unused)); + // Worth evaluating even if read fails with EAGAIN, since epoll_wait + // returned. (see b/78560047#comment34) + if (err < 0 && errno != EAGAIN) { + if (alarm_idx == ANDROID_ALARM_TYPE_COUNT && errno == ECANCELED) { + result |= ANDROID_ALARM_TIME_CHANGE_MASK; + } else { + return err; + } + } else { + result |= (1 << alarm_idx); + } + } + + return result; +} + +static void log_timerfd_create_error(clockid_t id) +{ + if (errno == EINVAL) { + switch (id) { + case CLOCK_REALTIME_ALARM: + case CLOCK_BOOTTIME_ALARM: + ALOGE("kernel missing required commits:"); + ALOGE("https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=6cffe00f7d4e24679eae6b7aae4caaf915288256"); + ALOGE("https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=11ffa9d6065f344a9bd769a2452f26f2f671e5f8"); + LOG_ALWAYS_FATAL("kernel does not support timerfd_create() with alarm timers"); + break; + + case CLOCK_BOOTTIME: + ALOGE("kernel missing required commit:"); + ALOGE("https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=4a2378a943f09907fb1ae35c15de917f60289c14"); + LOG_ALWAYS_FATAL("kernel does not support timerfd_create(CLOCK_BOOTTIME)"); + break; + + default: + break; + } + } + + ALOGE("timerfd_create(%u) failed: %s", id, strerror(errno)); +} + +static jlong android_server_alarm_AlarmManagerService_init(JNIEnv*, jobject) +{ + int epollfd; + TimerFds fds; + + epollfd = epoll_create(fds.size()); + if (epollfd < 0) { + ALOGE("epoll_create(%zu) failed: %s", fds.size(), strerror(errno)); + return 0; + } + + for (size_t i = 0; i < fds.size(); i++) { + fds[i] = timerfd_create(android_alarm_to_clockid[i], TFD_NONBLOCK); + if (fds[i] < 0) { + log_timerfd_create_error(android_alarm_to_clockid[i]); + close(epollfd); + for (size_t j = 0; j < i; j++) { + close(fds[j]); + } + return 0; + } + } + + std::unique_ptr alarm{new AlarmImpl(fds, epollfd)}; + + for (size_t i = 0; i < fds.size(); i++) { + epoll_event event; + event.events = EPOLLIN | EPOLLWAKEUP; + event.data.u32 = i; + + int err = epoll_ctl(epollfd, EPOLL_CTL_ADD, fds[i], &event); + if (err < 0) { + ALOGE("epoll_ctl(EPOLL_CTL_ADD) failed: %s", strerror(errno)); + return 0; + } + } + + struct itimerspec spec = {}; + /* 0 = disarmed; the timerfd doesn't need to be armed to get + RTC change notifications, just set up as cancelable */ + + int err = timerfd_settime(fds[ANDROID_ALARM_TYPE_COUNT], + TFD_TIMER_ABSTIME | TFD_TIMER_CANCEL_ON_SET, &spec, NULL); + if (err < 0) { + ALOGE("timerfd_settime() failed: %s", strerror(errno)); + return 0; + } + + return reinterpret_cast(alarm.release()); +} + +static jlong android_server_alarm_AlarmManagerService_getNextAlarm(JNIEnv*, jobject, jlong nativeData, jint type) +{ + AlarmImpl *impl = reinterpret_cast(nativeData); + struct itimerspec spec; + memset(&spec, 0, sizeof(spec)); + const int result = impl->getTime(type, &spec); + if (result < 0) + { + ALOGE("timerfd_gettime() failed for fd %d: %s\n", static_cast(type), strerror(errno)); + return result; + } + struct timespec nextTimespec = spec.it_value; + long long millis = nextTimespec.tv_sec * 1000LL; + millis += (nextTimespec.tv_nsec / 1000000LL); + return static_cast(millis); +} + +static void android_server_alarm_AlarmManagerService_close(JNIEnv*, jobject, jlong nativeData) +{ + AlarmImpl *impl = reinterpret_cast(nativeData); + delete impl; +} + +static jint android_server_alarm_AlarmManagerService_set(JNIEnv*, jobject, jlong nativeData, jint type, jlong seconds, jlong nanoseconds) +{ + AlarmImpl *impl = reinterpret_cast(nativeData); + struct timespec ts; + ts.tv_sec = seconds; + ts.tv_nsec = nanoseconds; + + const int result = impl->set(type, &ts); + if (result < 0) + { + ALOGE("Unable to set alarm to %lld.%09lld: %s\n", + static_cast(seconds), + static_cast(nanoseconds), strerror(errno)); + } + return result >= 0 ? 0 : errno; +} + +static jint android_server_alarm_AlarmManagerService_waitForAlarm(JNIEnv*, jobject, jlong nativeData) +{ + AlarmImpl *impl = reinterpret_cast(nativeData); + int result = 0; + + do + { + result = impl->waitForAlarm(); + } while (result < 0 && errno == EINTR); + + if (result < 0) + { + ALOGE("Unable to wait on alarm: %s\n", strerror(errno)); + return 0; + } + + return result; +} + +static const JNINativeMethod sMethods[] = { + /* name, signature, funcPtr */ + {"init", "()J", (void*)android_server_alarm_AlarmManagerService_init}, + {"close", "(J)V", (void*)android_server_alarm_AlarmManagerService_close}, + {"set", "(JIJJ)I", (void*)android_server_alarm_AlarmManagerService_set}, + {"waitForAlarm", "(J)I", (void*)android_server_alarm_AlarmManagerService_waitForAlarm}, + {"getNextAlarm", "(JI)J", (void*)android_server_alarm_AlarmManagerService_getNextAlarm}, +}; + +int register_android_server_alarm_AlarmManagerService(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, "com/android/server/alarm/AlarmManagerService", + sMethods, NELEM(sMethods)); +} + +} /* namespace android */ diff --git a/aosp/frameworks/native/cmds/installd/installd.rc b/aosp/frameworks/native/cmds/installd/installd.rc new file mode 100644 index 000000000..371a26994 --- /dev/null +++ b/aosp/frameworks/native/cmds/installd/installd.rc @@ -0,0 +1,105 @@ + +service installd /system/bin/installd + class main + user root + capabilities CHOWN DAC_OVERRIDE FOWNER FSETID KILL SETGID SETUID SYS_ADMIN + +on early-boot + mkdir /config/sdcardfs/extensions/1055 + mkdir /config/sdcardfs/extensions/1056 + mkdir /config/sdcardfs/extensions/1057 + mkdir /config/sdcardfs/extensions/1056/3gpp + mkdir /config/sdcardfs/extensions/1056/3gp + mkdir /config/sdcardfs/extensions/1056/3gpp2 + mkdir /config/sdcardfs/extensions/1056/3g2 + mkdir /config/sdcardfs/extensions/1056/avi + mkdir /config/sdcardfs/extensions/1056/dl + mkdir /config/sdcardfs/extensions/1056/dif + mkdir /config/sdcardfs/extensions/1056/dv + mkdir /config/sdcardfs/extensions/1056/fli + mkdir /config/sdcardfs/extensions/1056/m4v + mkdir /config/sdcardfs/extensions/1056/ts + mkdir /config/sdcardfs/extensions/1056/mpeg + mkdir /config/sdcardfs/extensions/1056/mpg + mkdir /config/sdcardfs/extensions/1056/mpe + mkdir /config/sdcardfs/extensions/1056/mp4 + mkdir /config/sdcardfs/extensions/1056/vob + mkdir /config/sdcardfs/extensions/1056/qt + mkdir /config/sdcardfs/extensions/1056/mov + mkdir /config/sdcardfs/extensions/1056/mxu + mkdir /config/sdcardfs/extensions/1056/webm + mkdir /config/sdcardfs/extensions/1056/lsf + mkdir /config/sdcardfs/extensions/1056/lsx + mkdir /config/sdcardfs/extensions/1056/mkv + mkdir /config/sdcardfs/extensions/1056/mng + mkdir /config/sdcardfs/extensions/1056/asf + mkdir /config/sdcardfs/extensions/1056/asx + mkdir /config/sdcardfs/extensions/1056/wm + mkdir /config/sdcardfs/extensions/1056/wmv + mkdir /config/sdcardfs/extensions/1056/wmx + mkdir /config/sdcardfs/extensions/1056/wvx + mkdir /config/sdcardfs/extensions/1056/movie + mkdir /config/sdcardfs/extensions/1056/wrf + mkdir /config/sdcardfs/extensions/1057/bmp + mkdir /config/sdcardfs/extensions/1057/gif + mkdir /config/sdcardfs/extensions/1057/jpg + mkdir /config/sdcardfs/extensions/1057/jpeg + mkdir /config/sdcardfs/extensions/1057/jpe + mkdir /config/sdcardfs/extensions/1057/pcx + mkdir /config/sdcardfs/extensions/1057/png + mkdir /config/sdcardfs/extensions/1057/svg + mkdir /config/sdcardfs/extensions/1057/svgz + mkdir /config/sdcardfs/extensions/1057/tiff + mkdir /config/sdcardfs/extensions/1057/tif + mkdir /config/sdcardfs/extensions/1057/wbmp + mkdir /config/sdcardfs/extensions/1057/webp + mkdir /config/sdcardfs/extensions/1057/dng + mkdir /config/sdcardfs/extensions/1057/cr2 + mkdir /config/sdcardfs/extensions/1057/ras + mkdir /config/sdcardfs/extensions/1057/art + mkdir /config/sdcardfs/extensions/1057/jng + mkdir /config/sdcardfs/extensions/1057/nef + mkdir /config/sdcardfs/extensions/1057/nrw + mkdir /config/sdcardfs/extensions/1057/orf + mkdir /config/sdcardfs/extensions/1057/rw2 + mkdir /config/sdcardfs/extensions/1057/pef + mkdir /config/sdcardfs/extensions/1057/psd + mkdir /config/sdcardfs/extensions/1057/pnm + mkdir /config/sdcardfs/extensions/1057/pbm + mkdir /config/sdcardfs/extensions/1057/pgm + mkdir /config/sdcardfs/extensions/1057/ppm + mkdir /config/sdcardfs/extensions/1057/srw + mkdir /config/sdcardfs/extensions/1057/arw + mkdir /config/sdcardfs/extensions/1057/rgb + mkdir /config/sdcardfs/extensions/1057/xbm + mkdir /config/sdcardfs/extensions/1057/xpm + mkdir /config/sdcardfs/extensions/1057/xwd + mkdir /config/sdcardfs/extensions/1055/aac + mkdir /config/sdcardfs/extensions/1055/aac + mkdir /config/sdcardfs/extensions/1055/amr + mkdir /config/sdcardfs/extensions/1055/awb + mkdir /config/sdcardfs/extensions/1055/snd + mkdir /config/sdcardfs/extensions/1055/flac + mkdir /config/sdcardfs/extensions/1055/flac + mkdir /config/sdcardfs/extensions/1055/mp3 + mkdir /config/sdcardfs/extensions/1055/mpga + mkdir /config/sdcardfs/extensions/1055/mpega + mkdir /config/sdcardfs/extensions/1055/mp2 + mkdir /config/sdcardfs/extensions/1055/m4a + mkdir /config/sdcardfs/extensions/1055/aif + mkdir /config/sdcardfs/extensions/1055/aiff + mkdir /config/sdcardfs/extensions/1055/aifc + mkdir /config/sdcardfs/extensions/1055/gsm + mkdir /config/sdcardfs/extensions/1055/mka + mkdir /config/sdcardfs/extensions/1055/m3u + mkdir /config/sdcardfs/extensions/1055/wma + mkdir /config/sdcardfs/extensions/1055/wax + mkdir /config/sdcardfs/extensions/1055/ra + mkdir /config/sdcardfs/extensions/1055/rm + mkdir /config/sdcardfs/extensions/1055/ram + mkdir /config/sdcardfs/extensions/1055/ra + mkdir /config/sdcardfs/extensions/1055/pls + mkdir /config/sdcardfs/extensions/1055/sd2 + mkdir /config/sdcardfs/extensions/1055/wav + mkdir /config/sdcardfs/extensions/1055/ogg + mkdir /config/sdcardfs/extensions/1055/oga diff --git a/aosp/frameworks/native/services/inputflinger/reader/EventHub.cpp b/aosp/frameworks/native/services/inputflinger/reader/EventHub.cpp new file mode 100644 index 000000000..763c9628a --- /dev/null +++ b/aosp/frameworks/native/services/inputflinger/reader/EventHub.cpp @@ -0,0 +1,2933 @@ +/* + * Copyright (C) 2005 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_TAG "EventHub" + +// #define LOG_NDEBUG 0 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "EventHub.h" + +#include "KeyCodeClassifications.h" + +#define INDENT " " +#define INDENT2 " " +#define INDENT3 " " + +using android::base::StringPrintf; + +namespace android { + +using namespace ftl::flag_operators; + +static const char* DEVICE_INPUT_PATH = "/dev/input"; +// v4l2 devices go directly into /dev +static const char* DEVICE_PATH = "/dev"; + +static constexpr size_t OBFUSCATED_LENGTH = 8; + +static constexpr int32_t FF_STRONG_MAGNITUDE_CHANNEL_IDX = 0; +static constexpr int32_t FF_WEAK_MAGNITUDE_CHANNEL_IDX = 1; + +static constexpr size_t EVENT_BUFFER_SIZE = 256; + +// Mapping for input battery class node IDs lookup. +// https://www.kernel.org/doc/Documentation/power/power_supply_class.txt +static const std::unordered_map BATTERY_CLASSES = + {{"capacity", InputBatteryClass::CAPACITY}, + {"capacity_level", InputBatteryClass::CAPACITY_LEVEL}, + {"status", InputBatteryClass::STATUS}}; + +// Mapping for input battery class node names lookup. +// https://www.kernel.org/doc/Documentation/power/power_supply_class.txt +static const std::unordered_map BATTERY_NODES = + {{InputBatteryClass::CAPACITY, "capacity"}, + {InputBatteryClass::CAPACITY_LEVEL, "capacity_level"}, + {InputBatteryClass::STATUS, "status"}}; + +// must be kept in sync with definitions in kernel /drivers/power/supply/power_supply_sysfs.c +static const std::unordered_map BATTERY_STATUS = + {{"Unknown", BATTERY_STATUS_UNKNOWN}, + {"Charging", BATTERY_STATUS_CHARGING}, + {"Discharging", BATTERY_STATUS_DISCHARGING}, + {"Not charging", BATTERY_STATUS_NOT_CHARGING}, + {"Full", BATTERY_STATUS_FULL}}; + +// Mapping taken from +// https://gitlab.freedesktop.org/upower/upower/-/blob/master/src/linux/up-device-supply.c#L484 +static const std::unordered_map BATTERY_LEVEL = {{"Critical", 5}, + {"Low", 10}, + {"Normal", 55}, + {"High", 70}, + {"Full", 100}, + {"Unknown", 50}}; + +// Mapping for input led class node names lookup. +// https://www.kernel.org/doc/html/latest/leds/leds-class.html +static const std::unordered_map LIGHT_CLASSES = + {{"red", InputLightClass::RED}, + {"green", InputLightClass::GREEN}, + {"blue", InputLightClass::BLUE}, + {"global", InputLightClass::GLOBAL}, + {"brightness", InputLightClass::BRIGHTNESS}, + {"multi_index", InputLightClass::MULTI_INDEX}, + {"multi_intensity", InputLightClass::MULTI_INTENSITY}, + {"max_brightness", InputLightClass::MAX_BRIGHTNESS}, + {"kbd_backlight", InputLightClass::KEYBOARD_BACKLIGHT}}; + +// Mapping for input multicolor led class node names. +// https://www.kernel.org/doc/html/latest/leds/leds-class-multicolor.html +static const std::unordered_map LIGHT_NODES = + {{InputLightClass::BRIGHTNESS, "brightness"}, + {InputLightClass::MULTI_INDEX, "multi_index"}, + {InputLightClass::MULTI_INTENSITY, "multi_intensity"}}; + +// Mapping for light color name and the light color +const std::unordered_map LIGHT_COLORS = {{"red", LightColor::RED}, + {"green", LightColor::GREEN}, + {"blue", LightColor::BLUE}}; + +// Mapping for country code to Layout info. +// See bCountryCode in 6.2.1 of https://usb.org/sites/default/files/hid1_11.pdf. +const std::unordered_map LAYOUT_INFOS = + {{0, RawLayoutInfo{.languageTag = "", .layoutType = ""}}, // NOT_SUPPORTED + {1, RawLayoutInfo{.languageTag = "ar-Arab", .layoutType = ""}}, // ARABIC + {2, RawLayoutInfo{.languageTag = "fr-BE", .layoutType = ""}}, // BELGIAN + {3, RawLayoutInfo{.languageTag = "fr-CA", .layoutType = ""}}, // CANADIAN_BILINGUAL + {4, RawLayoutInfo{.languageTag = "fr-CA", .layoutType = ""}}, // CANADIAN_FRENCH + {5, RawLayoutInfo{.languageTag = "cs", .layoutType = ""}}, // CZECH_REPUBLIC + {6, RawLayoutInfo{.languageTag = "da", .layoutType = ""}}, // DANISH + {7, RawLayoutInfo{.languageTag = "fi", .layoutType = ""}}, // FINNISH + {8, RawLayoutInfo{.languageTag = "fr-FR", .layoutType = ""}}, // FRENCH + {9, RawLayoutInfo{.languageTag = "de", .layoutType = ""}}, // GERMAN + {10, RawLayoutInfo{.languageTag = "el", .layoutType = ""}}, // GREEK + {11, RawLayoutInfo{.languageTag = "iw", .layoutType = ""}}, // HEBREW + {12, RawLayoutInfo{.languageTag = "hu", .layoutType = ""}}, // HUNGARY + {13, RawLayoutInfo{.languageTag = "en", .layoutType = "extended"}}, // INTERNATIONAL (ISO) + {14, RawLayoutInfo{.languageTag = "it", .layoutType = ""}}, // ITALIAN + {15, RawLayoutInfo{.languageTag = "ja", .layoutType = ""}}, // JAPAN + {16, RawLayoutInfo{.languageTag = "ko", .layoutType = ""}}, // KOREAN + {17, RawLayoutInfo{.languageTag = "es-419", .layoutType = ""}}, // LATIN_AMERICA + {18, RawLayoutInfo{.languageTag = "nl", .layoutType = ""}}, // DUTCH + {19, RawLayoutInfo{.languageTag = "nb", .layoutType = ""}}, // NORWEGIAN + {20, RawLayoutInfo{.languageTag = "fa", .layoutType = ""}}, // PERSIAN + {21, RawLayoutInfo{.languageTag = "pl", .layoutType = ""}}, // POLAND + {22, RawLayoutInfo{.languageTag = "pt", .layoutType = ""}}, // PORTUGUESE + {23, RawLayoutInfo{.languageTag = "ru", .layoutType = ""}}, // RUSSIA + {24, RawLayoutInfo{.languageTag = "sk", .layoutType = ""}}, // SLOVAKIA + {25, RawLayoutInfo{.languageTag = "es-ES", .layoutType = ""}}, // SPANISH + {26, RawLayoutInfo{.languageTag = "sv", .layoutType = ""}}, // SWEDISH + {27, RawLayoutInfo{.languageTag = "fr-CH", .layoutType = ""}}, // SWISS_FRENCH + {28, RawLayoutInfo{.languageTag = "de-CH", .layoutType = ""}}, // SWISS_GERMAN + {29, RawLayoutInfo{.languageTag = "de-CH", .layoutType = ""}}, // SWITZERLAND + {30, RawLayoutInfo{.languageTag = "zh-TW", .layoutType = ""}}, // TAIWAN + {31, RawLayoutInfo{.languageTag = "tr", .layoutType = "turkish_q"}}, // TURKISH_Q + {32, RawLayoutInfo{.languageTag = "en-GB", .layoutType = ""}}, // UK + {33, RawLayoutInfo{.languageTag = "en-US", .layoutType = ""}}, // US + {34, RawLayoutInfo{.languageTag = "", .layoutType = ""}}, // YUGOSLAVIA + {35, RawLayoutInfo{.languageTag = "tr", .layoutType = "turkish_f"}}}; // TURKISH_F + +static std::string sha1(const std::string& in) { + SHA_CTX ctx; + SHA1_Init(&ctx); + SHA1_Update(&ctx, reinterpret_cast(in.c_str()), in.size()); + u_char digest[SHA_DIGEST_LENGTH]; + SHA1_Final(digest, &ctx); + + std::string out; + for (size_t i = 0; i < SHA_DIGEST_LENGTH; i++) { + out += StringPrintf("%02x", digest[i]); + } + return out; +} + +/** + * Return true if name matches "v4l-touch*" + */ +static bool isV4lTouchNode(std::string name) { + return name.find("v4l-touch") != std::string::npos; +} + +/** + * Returns true if V4L devices should be scanned. + * + * The system property ro.input.video_enabled can be used to control whether + * EventHub scans and opens V4L devices. As V4L does not support multiple + * clients, EventHub effectively blocks access to these devices when it opens + * them. + * + * Setting this to "false" would prevent any video devices from being discovered and + * associated with input devices. + * + * This property can be used as follows: + * 1. To turn off features that are dependent on video device presence. + * 2. During testing and development, to allow other clients to read video devices + * directly from /dev. + */ +static bool isV4lScanningEnabled() { + return property_get_bool("ro.input.video_enabled", /*default_value=*/true); +} + +static nsecs_t processEventTimestamp(const struct input_event& event) { + // Use the time specified in the event instead of the current time + // so that downstream code can get more accurate estimates of + // event dispatch latency from the time the event is enqueued onto + // the evdev client buffer. + // + // The event's timestamp fortuitously uses the same monotonic clock + // time base as the rest of Android. The kernel event device driver + // (drivers/input/evdev.c) obtains timestamps using ktime_get_ts(). + // The systemTime(SYSTEM_TIME_MONOTONIC) function we use everywhere + // calls clock_gettime(CLOCK_MONOTONIC) which is implemented as a + // system call that also queries ktime_get_ts(). + + const nsecs_t inputEventTime = seconds_to_nanoseconds(event.input_event_sec) + + microseconds_to_nanoseconds(event.input_event_usec); + return inputEventTime; +} + +/** + * Returns the sysfs root path of the input device. + */ +static std::optional getSysfsRootPath(const char* devicePath) { + std::error_code errorCode; + + // Stat the device path to get the major and minor number of the character file + struct stat statbuf; + if (stat(devicePath, &statbuf) == -1) { + ALOGE("Could not stat device %s due to error: %s.", devicePath, std::strerror(errno)); + return std::nullopt; + } + + unsigned int major_num = major(statbuf.st_rdev); + unsigned int minor_num = minor(statbuf.st_rdev); + + // Realpath "/sys/dev/char/{major}:{minor}" to get the sysfs path to the input event + auto sysfsPath = std::filesystem::path("/sys/dev/char/"); + sysfsPath /= std::to_string(major_num) + ":" + std::to_string(minor_num); + sysfsPath = std::filesystem::canonical(sysfsPath, errorCode); + + // Make sure nothing went wrong in call to canonical() + if (errorCode) { + ALOGW("Could not run filesystem::canonical() due to error %d : %s.", errorCode.value(), + errorCode.message().c_str()); + return std::nullopt; + } + + // Continue to go up a directory until we reach a directory named "input" + while (sysfsPath != "/" && sysfsPath.filename() != "input") { + sysfsPath = sysfsPath.parent_path(); + } + + // Then go up one more and you will be at the sysfs root of the device + sysfsPath = sysfsPath.parent_path(); + + // Make sure we didn't reach root path and that directory actually exists + if (sysfsPath == "/" || !std::filesystem::exists(sysfsPath, errorCode)) { + if (errorCode) { + ALOGW("Could not run filesystem::exists() due to error %d : %s.", errorCode.value(), + errorCode.message().c_str()); + } + + // Not found + return std::nullopt; + } + + return sysfsPath; +} + +/** + * Returns the list of files under a specified path. + */ +static std::vector allFilesInPath(const std::filesystem::path& path) { + std::vector nodes; + std::error_code errorCode; + auto iter = std::filesystem::directory_iterator(path, errorCode); + while (!errorCode && iter != std::filesystem::directory_iterator()) { + nodes.push_back(iter->path()); + iter++; + } + return nodes; +} + +/** + * Returns the list of files under a specified directory in a sysfs path. + * Example: + * findSysfsNodes(sysfsRootPath, SysfsClass::LEDS) will return all led nodes under "leds" directory + * in the sysfs path. + */ +static std::vector findSysfsNodes(const std::filesystem::path& sysfsRoot, + SysfsClass clazz) { + std::string nodeStr = ftl::enum_string(clazz); + std::for_each(nodeStr.begin(), nodeStr.end(), + [](char& c) { c = std::tolower(static_cast(c)); }); + std::vector nodes; + for (auto path = sysfsRoot; path != "/" && nodes.empty(); path = path.parent_path()) { + nodes = allFilesInPath(path / nodeStr); + } + return nodes; +} + +static std::optional> getColorIndexArray( + std::filesystem::path path) { + std::string indexStr; + if (!base::ReadFileToString(path, &indexStr)) { + return std::nullopt; + } + + // Parse the multi color LED index file, refer to kernel docs + // leds/leds-class-multicolor.html + std::regex indexPattern("(red|green|blue)\\s(red|green|blue)\\s(red|green|blue)[\\n]"); + std::smatch results; + std::array colors; + if (!std::regex_match(indexStr, results, indexPattern)) { + return std::nullopt; + } + + for (size_t i = 1; i < results.size(); i++) { + const auto it = LIGHT_COLORS.find(results[i].str()); + if (it != LIGHT_COLORS.end()) { + // intensities.emplace(it->second, 0); + colors[i - 1] = it->second; + } + } + return colors; +} + +/** + * Read country code information exposed through the sysfs path and convert it to Layout info. + */ +static std::optional readLayoutConfiguration( + const std::filesystem::path& sysfsRootPath) { + // Check the sysfs root path + int32_t hidCountryCode = -1; + std::string str; + if (base::ReadFileToString(sysfsRootPath / "country", &str)) { + hidCountryCode = std::stoi(str, nullptr, 16); + // Update this condition if new supported country codes are added to HID spec. + if (hidCountryCode > 35 || hidCountryCode < 0) { + ALOGE("HID country code should be in range [0, 35], but for sysfs path %s it was %d", + sysfsRootPath.c_str(), hidCountryCode); + } + } + const auto it = LAYOUT_INFOS.find(hidCountryCode); + if (it != LAYOUT_INFOS.end()) { + return it->second; + } + + return std::nullopt; +} + +/** + * Read information about batteries exposed through the sysfs path. + */ +static std::unordered_map readBatteryConfiguration( + const std::filesystem::path& sysfsRootPath) { + std::unordered_map batteryInfos; + int32_t nextBatteryId = 0; + // Check if device has any battery. + const auto& paths = findSysfsNodes(sysfsRootPath, SysfsClass::POWER_SUPPLY); + for (const auto& nodePath : paths) { + RawBatteryInfo info; + info.id = ++nextBatteryId; + info.path = nodePath; + info.name = nodePath.filename(); + + // Scan the path for all the files + // Refer to https://www.kernel.org/doc/Documentation/leds/leds-class.txt + const auto& files = allFilesInPath(nodePath); + for (const auto& file : files) { + const auto it = BATTERY_CLASSES.find(file.filename().string()); + if (it != BATTERY_CLASSES.end()) { + info.flags |= it->second; + } + } + batteryInfos.insert_or_assign(info.id, info); + ALOGD("configureBatteryLocked rawBatteryId %d name %s", info.id, info.name.c_str()); + } + return batteryInfos; +} + +/** + * Read information about lights exposed through the sysfs path. + */ +static std::unordered_map readLightsConfiguration( + const std::filesystem::path& sysfsRootPath) { + std::unordered_map lightInfos; + int32_t nextLightId = 0; + // Check if device has any lights. + const auto& paths = findSysfsNodes(sysfsRootPath, SysfsClass::LEDS); + for (const auto& nodePath : paths) { + RawLightInfo info; + info.id = ++nextLightId; + info.path = nodePath; + info.name = nodePath.filename(); + info.maxBrightness = std::nullopt; + + // Light name should follow the naming pattern :: + // Refer kernel docs /leds/leds-class.html for valid supported LED names. + std::regex indexPattern("([a-zA-Z0-9_.:]*:)?([a-zA-Z0-9_.]*):([a-zA-Z0-9_.]*)"); + std::smatch results; + + if (std::regex_match(info.name, results, indexPattern)) { + // regex_match will return full match at index 0 and at index 1. For RawLightInfo + // we only care about sections and which will be at index 2 and 3. + for (int i = 2; i <= 3; i++) { + const auto it = LIGHT_CLASSES.find(results.str(i)); + if (it != LIGHT_CLASSES.end()) { + info.flags |= it->second; + } + } + + // Set name of the raw light to which represents playerIDs for LEDs that + // turn on/off based on the current player ID (Refer to PeripheralController.cpp for + // player ID logic) + info.name = results.str(3); + } + // Scan the path for all the files + // Refer to https://www.kernel.org/doc/Documentation/leds/leds-class.txt + const auto& files = allFilesInPath(nodePath); + for (const auto& file : files) { + const auto it = LIGHT_CLASSES.find(file.filename().string()); + if (it != LIGHT_CLASSES.end()) { + info.flags |= it->second; + // If the node has maximum brightness, read it + if (it->second == InputLightClass::MAX_BRIGHTNESS) { + std::string str; + if (base::ReadFileToString(file, &str)) { + info.maxBrightness = std::stoi(str); + } + } + } + } + lightInfos.insert_or_assign(info.id, info); + ALOGD("configureLightsLocked rawLightId %d name %s", info.id, info.name.c_str()); + } + return lightInfos; +} + +// --- Global Functions --- + +ftl::Flags getAbsAxisUsage(int32_t axis, + ftl::Flags deviceClasses) { + // Touch devices get dibs on touch-related axes. + if (deviceClasses.test(InputDeviceClass::TOUCH)) { + switch (axis) { + case ABS_X: + case ABS_Y: + case ABS_PRESSURE: + case ABS_TOOL_WIDTH: + case ABS_DISTANCE: + case ABS_TILT_X: + case ABS_TILT_Y: + case ABS_MT_SLOT: + case ABS_MT_TOUCH_MAJOR: + case ABS_MT_TOUCH_MINOR: + case ABS_MT_WIDTH_MAJOR: + case ABS_MT_WIDTH_MINOR: + case ABS_MT_ORIENTATION: + case ABS_MT_POSITION_X: + case ABS_MT_POSITION_Y: + case ABS_MT_TOOL_TYPE: + case ABS_MT_BLOB_ID: + case ABS_MT_TRACKING_ID: + case ABS_MT_PRESSURE: + case ABS_MT_DISTANCE: + return InputDeviceClass::TOUCH; + } + } + + if (deviceClasses.test(InputDeviceClass::SENSOR)) { + switch (axis) { + case ABS_X: + case ABS_Y: + case ABS_Z: + case ABS_RX: + case ABS_RY: + case ABS_RZ: + return InputDeviceClass::SENSOR; + } + } + + // External stylus gets the pressure axis + if (deviceClasses.test(InputDeviceClass::EXTERNAL_STYLUS)) { + if (axis == ABS_PRESSURE) { + return InputDeviceClass::EXTERNAL_STYLUS; + } + } + + // Joystick devices get the rest. + return deviceClasses & InputDeviceClass::JOYSTICK; +} + +// --- RawAbsoluteAxisInfo --- + +std::ostream& operator<<(std::ostream& out, const RawAbsoluteAxisInfo& info) { + if (info.valid) { + out << "min=" << info.minValue << ", max=" << info.maxValue << ", flat=" << info.flat + << ", fuzz=" << info.fuzz << ", resolution=" << info.resolution; + } else { + out << "unknown range"; + } + return out; +} + +// --- EventHub::Device --- + +EventHub::Device::Device(int fd, int32_t id, std::string path, InputDeviceIdentifier identifier, + std::shared_ptr assocDev) + : fd(fd), + id(id), + path(std::move(path)), + identifier(std::move(identifier)), + classes(0), + configuration(nullptr), + virtualKeyMap(nullptr), + ffEffectPlaying(false), + ffEffectId(-1), + associatedDevice(std::move(assocDev)), + controllerNumber(0), + enabled(true), + isVirtual(fd < 0), + currentFrameDropped(false) {} + +EventHub::Device::~Device() { + close(); +} + +void EventHub::Device::close() { + if (fd >= 0) { + ::close(fd); + fd = -1; + } +} + +status_t EventHub::Device::enable() { + fd = open(path.c_str(), O_RDWR | O_CLOEXEC | O_NONBLOCK); + if (fd < 0) { + ALOGE("could not open %s, %s\n", path.c_str(), strerror(errno)); + return -errno; + } + enabled = true; + return OK; +} + +status_t EventHub::Device::disable() { + close(); + enabled = false; + return OK; +} + +bool EventHub::Device::hasValidFd() const { + return !isVirtual && enabled; +} + +const std::shared_ptr EventHub::Device::getKeyCharacterMap() const { + return keyMap.keyCharacterMap; +} + +template +status_t EventHub::Device::readDeviceBitMask(unsigned long ioctlCode, BitArray& bitArray) { + if (!hasValidFd()) { + return BAD_VALUE; + } + if ((_IOC_SIZE(ioctlCode) == 0)) { + ioctlCode |= _IOC(0, 0, 0, bitArray.bytes()); + } + + typename BitArray::Buffer buffer; + status_t ret = ioctl(fd, ioctlCode, buffer.data()); + bitArray.loadFromBuffer(buffer); + return ret; +} + +void EventHub::Device::configureFd() { + // Set fd parameters with ioctl, such as key repeat, suspend block, and clock type + if (classes.test(InputDeviceClass::KEYBOARD)) { + // Disable kernel key repeat since we handle it ourselves + unsigned int repeatRate[] = {0, 0}; + if (ioctl(fd, EVIOCSREP, repeatRate)) { + ALOGW("Unable to disable kernel key repeat for %s: %s", path.c_str(), strerror(errno)); + } + } + + // 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. + int clockId = CLOCK_MONOTONIC; + if (classes.test(InputDeviceClass::SENSOR)) { + // Each new sensor event should use the same time base as + // SystemClock.elapsedRealtimeNanos(). + clockId = CLOCK_BOOTTIME; + } + bool usingClockIoctl = !ioctl(fd, EVIOCSCLOCKID, &clockId); + ALOGI("usingClockIoctl=%s", toString(usingClockIoctl)); + + // Query the initial state of keys and switches, which is tracked by EventHub. + readDeviceState(); +} + +void EventHub::Device::readDeviceState() { + if (readDeviceBitMask(EVIOCGKEY(0), keyState) < 0) { + ALOGD("Unable to query the global key state for %s: %s", path.c_str(), strerror(errno)); + } + if (readDeviceBitMask(EVIOCGSW(0), swState) < 0) { + ALOGD("Unable to query the global switch state for %s: %s", path.c_str(), strerror(errno)); + } + + // Read absolute axis info and values for all available axes for the device. + populateAbsoluteAxisStates(); +} + +void EventHub::Device::populateAbsoluteAxisStates() { + absState.clear(); + + for (int axis = 0; axis <= ABS_MAX; axis++) { + if (!absBitmask.test(axis)) { + continue; + } + struct input_absinfo info {}; + if (ioctl(fd, EVIOCGABS(axis), &info)) { + ALOGE("Error reading absolute controller %d for device %s fd %d: %s", axis, + identifier.name.c_str(), fd, strerror(errno)); + continue; + } + auto& [axisInfo, value] = absState[axis]; + axisInfo.valid = true; + axisInfo.minValue = info.minimum; + axisInfo.maxValue = info.maximum; + axisInfo.flat = info.flat; + axisInfo.fuzz = info.fuzz; + axisInfo.resolution = info.resolution; + value = info.value; + } +} + +bool EventHub::Device::hasKeycodeLocked(int keycode) const { + if (!keyMap.haveKeyLayout()) { + return false; + } + + std::vector scanCodes = keyMap.keyLayoutMap->findScanCodesForKey(keycode); + const size_t N = scanCodes.size(); + for (size_t i = 0; i < N && i <= KEY_MAX; i++) { + int32_t sc = scanCodes[i]; + if (sc >= 0 && sc <= KEY_MAX && keyBitmask.test(sc)) { + return true; + } + } + + std::vector usageCodes = keyMap.keyLayoutMap->findUsageCodesForKey(keycode); + if (usageCodes.size() > 0 && mscBitmask.test(MSC_SCAN)) { + return true; + } + + return false; +} + +void EventHub::Device::loadConfigurationLocked() { + configurationFile = + getInputDeviceConfigurationFilePathByDeviceIdentifier(identifier, + InputDeviceConfigurationFileType:: + CONFIGURATION); + if (configurationFile.empty()) { + ALOGD("No input device configuration file found for device '%s'.", identifier.name.c_str()); + } else { + android::base::Result> propertyMap = + PropertyMap::load(configurationFile.c_str()); + if (!propertyMap.ok()) { + ALOGE("Error loading input device configuration file for device '%s'. " + "Using default configuration.", + identifier.name.c_str()); + } else { + configuration = std::move(*propertyMap); + } + } +} + +bool EventHub::Device::loadVirtualKeyMapLocked() { + // The virtual key map is supplied by the kernel as a system board property file. + std::string propPath = "/sys/board_properties/virtualkeys."; + propPath += identifier.getCanonicalName(); + if (access(propPath.c_str(), R_OK)) { + return false; + } + virtualKeyMap = VirtualKeyMap::load(propPath); + return virtualKeyMap != nullptr; +} + +status_t EventHub::Device::loadKeyMapLocked() { + return keyMap.load(identifier, configuration.get()); +} + +bool EventHub::Device::isExternalDeviceLocked() { + if (configuration) { + std::optional isInternal = configuration->getBool("device.internal"); + if (isInternal.has_value()) { + return !isInternal.value(); + } + } + return identifier.bus == BUS_USB || identifier.bus == BUS_BLUETOOTH; +} + +bool EventHub::Device::deviceHasMicLocked() { + if (configuration) { + std::optional hasMic = configuration->getBool("audio.mic"); + if (hasMic.has_value()) { + return hasMic.value(); + } + } + return false; +} + +void EventHub::Device::setLedStateLocked(int32_t led, bool on) { + int32_t sc; + if (hasValidFd() && mapLed(led, &sc) != NAME_NOT_FOUND) { + struct input_event ev; + ev.input_event_sec = 0; + ev.input_event_usec = 0; + ev.type = EV_LED; + ev.code = sc; + ev.value = on ? 1 : 0; + + ssize_t nWrite; + do { + nWrite = write(fd, &ev, sizeof(struct input_event)); + } while (nWrite == -1 && errno == EINTR); + } +} + +void EventHub::Device::setLedForControllerLocked() { + for (int i = 0; i < MAX_CONTROLLER_LEDS; i++) { + setLedStateLocked(ALED_CONTROLLER_1 + i, controllerNumber == i + 1); + } +} + +status_t EventHub::Device::mapLed(int32_t led, int32_t* outScanCode) const { + if (!keyMap.haveKeyLayout()) { + return NAME_NOT_FOUND; + } + + std::optional scanCode = keyMap.keyLayoutMap->findScanCodeForLed(led); + if (scanCode.has_value()) { + if (*scanCode >= 0 && *scanCode <= LED_MAX && ledBitmask.test(*scanCode)) { + *outScanCode = *scanCode; + return NO_ERROR; + } + } + return NAME_NOT_FOUND; +} + +void EventHub::Device::trackInputEvent(const struct input_event& event) { + switch (event.type) { + case EV_KEY: { + LOG_ALWAYS_FATAL_IF(!currentFrameDropped && + !keyState.set(static_cast(event.code), + event.value != 0), + "%s: device '%s' received invalid EV_KEY event code: %s value: %d", + __func__, identifier.name.c_str(), + InputEventLookup::getLinuxEvdevLabel(EV_KEY, event.code, 1) + .code.c_str(), + event.value); + break; + } + case EV_SW: { + LOG_ALWAYS_FATAL_IF(!currentFrameDropped && + !swState.set(static_cast(event.code), + event.value != 0), + "%s: device '%s' received invalid EV_SW event code: %s value: %d", + __func__, identifier.name.c_str(), + InputEventLookup::getLinuxEvdevLabel(EV_SW, event.code, 1) + .code.c_str(), + event.value); + break; + } + case EV_ABS: { + if (currentFrameDropped) { + break; + } + auto it = absState.find(event.code); + LOG_ALWAYS_FATAL_IF(it == absState.end(), + "%s: device '%s' received invalid EV_ABS event code: %s value: %d", + __func__, identifier.name.c_str(), + InputEventLookup::getLinuxEvdevLabel(EV_ABS, event.code, 0) + .code.c_str(), + event.value); + it->second.value = event.value; + break; + } + case EV_SYN: { + switch (event.code) { + case SYN_REPORT: + if (currentFrameDropped) { + // To recover after a SYN_DROPPED, we need to query the state of the device + // to synchronize our device state with the kernel's to account for the + // dropped events on receiving the next SYN_REPORT. + // Note we don't drop the SYN_REPORT at this point but it is used by the + // InputDevice to reset and repopulate mapper state + readDeviceState(); + currentFrameDropped = false; + } + break; + case SYN_DROPPED: + // When we receive SYN_DROPPED, all events in the current frame should be + // dropped up to and including next SYN_REPORT + currentFrameDropped = true; + break; + default: + break; + } + break; + } + default: + break; + } +} + +/** + * Get the capabilities for the current process. + * Crashes the system if unable to create / check / destroy the capabilities object. + */ +class Capabilities final { +public: + explicit Capabilities() { + mCaps = cap_get_proc(); + LOG_ALWAYS_FATAL_IF(mCaps == nullptr, "Could not get capabilities of the current process"); + } + + /** + * Check whether the current process has a specific capability + * in the set of effective capabilities. + * Return CAP_SET if the process has the requested capability + * Return CAP_CLEAR otherwise. + */ + cap_flag_value_t checkEffectiveCapability(cap_value_t capability) { + cap_flag_value_t value; + const int result = cap_get_flag(mCaps, capability, CAP_EFFECTIVE, &value); + LOG_ALWAYS_FATAL_IF(result == -1, "Could not obtain the requested capability"); + return value; + } + + ~Capabilities() { + const int result = cap_free(mCaps); + LOG_ALWAYS_FATAL_IF(result == -1, "Could not release the capabilities structure"); + } + +private: + cap_t mCaps; +}; + +// --- EventHub --- + +const int EventHub::EPOLL_MAX_EVENTS; + +EventHub::EventHub(void) + : mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), + mNextDeviceId(1), + mControllerNumbers(), + mNeedToSendFinishedDeviceScan(false), + mNeedToReopenDevices(false), + mNeedToScanDevices(true), + mPendingEventCount(0), + mPendingEventIndex(0), + mPendingINotify(false) { + + mEpollFd = epoll_create1(EPOLL_CLOEXEC); + LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno)); + + mINotifyFd = inotify_init1(IN_CLOEXEC); + LOG_ALWAYS_FATAL_IF(mINotifyFd < 0, "Could not create inotify instance: %s", strerror(errno)); + + std::error_code errorCode; + bool isDeviceInotifyAdded = false; + if (std::filesystem::exists(DEVICE_INPUT_PATH, errorCode)) { + addDeviceInputInotify(); + } else { + addDeviceInotify(); + isDeviceInotifyAdded = true; + if (errorCode) { + ALOGW("Could not run filesystem::exists() due to error %d : %s.", errorCode.value(), + errorCode.message().c_str()); + } + } + + if (isV4lScanningEnabled() && !isDeviceInotifyAdded) { + addDeviceInotify(); + } else { + ALOGI("Video device scanning disabled"); + } + + struct epoll_event eventItem = {}; + eventItem.events = EPOLLIN | EPOLLWAKEUP; + eventItem.data.fd = 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 = pipe2(wakeFds, O_CLOEXEC); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe. errno=%d", errno); + + mWakeReadPipeFd = wakeFds[0]; + mWakeWritePipeFd = wakeFds[1]; + + result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d", + errno); + + result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d", + errno); + + eventItem.data.fd = mWakeReadPipeFd; + result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem); + LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance. errno=%d", + errno); +} + +EventHub::~EventHub(void) { + closeAllDevicesLocked(); + + ::close(mEpollFd); + ::close(mINotifyFd); + ::close(mWakeReadPipeFd); + ::close(mWakeWritePipeFd); +} + +/** + * On devices that don't have any input devices (like some development boards), the /dev/input + * directory will be absent. However, the user may still plug in an input device at a later time. + * Add watch for contents of /dev/input only when /dev/input appears. + */ +void EventHub::addDeviceInputInotify() { + mDeviceInputWd = inotify_add_watch(mINotifyFd, DEVICE_INPUT_PATH, IN_DELETE | IN_CREATE); + LOG_ALWAYS_FATAL_IF(mDeviceInputWd < 0, "Could not register INotify for %s: %s", + DEVICE_INPUT_PATH, strerror(errno)); +} + +void EventHub::addDeviceInotify() { + mDeviceWd = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE); + LOG_ALWAYS_FATAL_IF(mDeviceWd < 0, "Could not register INotify for %s: %s", DEVICE_PATH, + strerror(errno)); +} + +InputDeviceIdentifier EventHub::getDeviceIdentifier(int32_t deviceId) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + return device != nullptr ? device->identifier : InputDeviceIdentifier(); +} + +ftl::Flags EventHub::getDeviceClasses(int32_t deviceId) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + return device != nullptr ? device->classes : ftl::Flags(0); +} + +int32_t EventHub::getDeviceControllerNumber(int32_t deviceId) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + return device != nullptr ? device->controllerNumber : 0; +} + +std::optional EventHub::getConfiguration(int32_t deviceId) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == nullptr || device->configuration == nullptr) { + return {}; + } + return *device->configuration; +} + +status_t EventHub::getAbsoluteAxisInfo(int32_t deviceId, int axis, + RawAbsoluteAxisInfo* outAxisInfo) const { + outAxisInfo->clear(); + if (axis < 0 || axis > ABS_MAX) { + return NAME_NOT_FOUND; + } + std::scoped_lock _l(mLock); + const Device* device = getDeviceLocked(deviceId); + if (device == nullptr) { + return NAME_NOT_FOUND; + } + // We can read the RawAbsoluteAxisInfo even if the device is disabled and doesn't have a valid + // fd, because the info is populated once when the device is first opened, and it doesn't change + // throughout the device lifecycle. + auto it = device->absState.find(axis); + if (it == device->absState.end()) { + return NAME_NOT_FOUND; + } + *outAxisInfo = it->second.info; + return OK; +} + +bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const { + if (axis >= 0 && axis <= REL_MAX) { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + return device != nullptr ? device->relBitmask.test(axis) : false; + } + return false; +} + +bool EventHub::hasInputProperty(int32_t deviceId, int property) const { + std::scoped_lock _l(mLock); + + Device* device = getDeviceLocked(deviceId); + return property >= 0 && property <= INPUT_PROP_MAX && device != nullptr + ? device->propBitmask.test(property) + : false; +} + +bool EventHub::hasMscEvent(int32_t deviceId, int mscEvent) const { + std::scoped_lock _l(mLock); + + Device* device = getDeviceLocked(deviceId); + return mscEvent >= 0 && mscEvent <= MSC_MAX && device != nullptr + ? device->mscBitmask.test(mscEvent) + : false; +} + +int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const { + if (scanCode < 0 || scanCode > KEY_MAX) { + return AKEY_STATE_UNKNOWN; + } + std::scoped_lock _l(mLock); + const Device* device = getDeviceLocked(deviceId); + if (device == nullptr || !device->hasValidFd() || !device->keyBitmask.test(scanCode)) { + return AKEY_STATE_UNKNOWN; + } + return device->keyState.test(scanCode) ? AKEY_STATE_DOWN : AKEY_STATE_UP; +} + +int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const { + std::scoped_lock _l(mLock); + const Device* device = getDeviceLocked(deviceId); + if (device == nullptr || !device->hasValidFd() || !device->keyMap.haveKeyLayout()) { + return AKEY_STATE_UNKNOWN; + } + const std::vector scanCodes = + device->keyMap.keyLayoutMap->findScanCodesForKey(keyCode); + if (scanCodes.empty()) { + return AKEY_STATE_UNKNOWN; + } + return std::any_of(scanCodes.begin(), scanCodes.end(), + [&device](const int32_t sc) { + return sc >= 0 && sc <= KEY_MAX && device->keyState.test(sc); + }) + ? AKEY_STATE_DOWN + : AKEY_STATE_UP; +} + +int32_t EventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const { + std::scoped_lock _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device == nullptr || !device->hasValidFd() || device->keyMap.keyCharacterMap == nullptr || + device->keyMap.keyLayoutMap == nullptr) { + return AKEYCODE_UNKNOWN; + } + std::vector scanCodes = + device->keyMap.keyLayoutMap->findScanCodesForKey(locationKeyCode); + if (scanCodes.empty()) { + ALOGW("Failed to get key code for key location: no scan code maps to key code %d for input" + "device %d", + locationKeyCode, deviceId); + return AKEYCODE_UNKNOWN; + } + if (scanCodes.size() > 1) { + ALOGW("Multiple scan codes map to the same key code %d, returning only the first match", + locationKeyCode); + } + int32_t outKeyCode; + status_t mapKeyRes = + device->getKeyCharacterMap()->mapKey(scanCodes[0], /*usageCode=*/0, &outKeyCode); + switch (mapKeyRes) { + case OK: + break; + case NAME_NOT_FOUND: + // key character map doesn't re-map this scanCode, hence the keyCode remains the same + outKeyCode = locationKeyCode; + break; + default: + ALOGW("Failed to get key code for key location: Key character map returned error %s", + statusToString(mapKeyRes).c_str()); + outKeyCode = AKEYCODE_UNKNOWN; + break; + } + // Remap if there is a Key remapping added to the KCM and return the remapped key + return device->getKeyCharacterMap()->applyKeyRemapping(outKeyCode); +} + +int32_t EventHub::getSwitchState(int32_t deviceId, int32_t sw) const { + if (sw < 0 || sw > SW_MAX) { + return AKEY_STATE_UNKNOWN; + } + std::scoped_lock _l(mLock); + const Device* device = getDeviceLocked(deviceId); + if (device == nullptr || !device->hasValidFd() || !device->swBitmask.test(sw)) { + return AKEY_STATE_UNKNOWN; + } + return device->swState.test(sw) ? AKEY_STATE_DOWN : AKEY_STATE_UP; +} + +status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const { + *outValue = 0; + if (axis < 0 || axis > ABS_MAX) { + return NAME_NOT_FOUND; + } + std::scoped_lock _l(mLock); + const Device* device = getDeviceLocked(deviceId); + if (device == nullptr || !device->hasValidFd()) { + return NAME_NOT_FOUND; + } + const auto it = device->absState.find(axis); + if (it == device->absState.end()) { + return NAME_NOT_FOUND; + } + *outValue = it->second.value; + return OK; +} + +base::Result> EventHub::getMtSlotValues(int32_t deviceId, int32_t axis, + size_t slotCount) const { + std::scoped_lock _l(mLock); + const Device* device = getDeviceLocked(deviceId); + if (device == nullptr || !device->hasValidFd() || !device->absBitmask.test(axis)) { + return base::ResultError("device problem or axis not supported", NAME_NOT_FOUND); + } + std::vector outValues(slotCount + 1); + outValues[0] = axis; + const size_t bufferSize = outValues.size() * sizeof(int32_t); + if (ioctl(device->fd, EVIOCGMTSLOTS(bufferSize), outValues.data()) != OK) { + return base::ErrnoError(); + } + return std::move(outValues); +} + +bool EventHub::markSupportedKeyCodes(int32_t deviceId, const std::vector& keyCodes, + uint8_t* outFlags) const { + std::scoped_lock _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device != nullptr && device->keyMap.haveKeyLayout()) { + for (size_t codeIndex = 0; codeIndex < keyCodes.size(); codeIndex++) { + if (device->hasKeycodeLocked(keyCodes[codeIndex])) { + outFlags[codeIndex] = 1; + } + } + return true; + } + return false; +} + +void EventHub::addKeyRemapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == nullptr) { + return; + } + const std::shared_ptr kcm = device->getKeyCharacterMap(); + if (kcm) { + kcm->addKeyRemapping(fromKeyCode, toKeyCode); + } +} + +status_t EventHub::mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState, + int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + status_t status = NAME_NOT_FOUND; + + if (device != nullptr) { + // Check the key character map first. + const std::shared_ptr kcm = device->getKeyCharacterMap(); + if (kcm) { + if (!kcm->mapKey(scanCode, usageCode, outKeycode)) { + *outFlags = 0; + status = NO_ERROR; + } + } + + // Check the key layout next. + if (status != NO_ERROR && device->keyMap.haveKeyLayout()) { + if (!device->keyMap.keyLayoutMap->mapKey(scanCode, usageCode, outKeycode, outFlags)) { + status = NO_ERROR; + } + } + + if (status == NO_ERROR) { + if (kcm) { + // Remap keys based on user-defined key remappings and key behavior defined in the + // corresponding kcm file + *outKeycode = kcm->applyKeyRemapping(*outKeycode); + + // Remap keys based on Key behavior defined in KCM file + std::tie(*outKeycode, *outMetaState) = + kcm->applyKeyBehavior(*outKeycode, metaState); + } else { + *outMetaState = metaState; + } + } + } + + if (status != NO_ERROR) { + *outKeycode = 0; + *outFlags = 0; + *outMetaState = metaState; + } + + return status; +} + +status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + + if (device == nullptr || !device->keyMap.haveKeyLayout()) { + return NAME_NOT_FOUND; + } + std::optional info = device->keyMap.keyLayoutMap->mapAxis(scanCode); + if (!info.has_value()) { + return NAME_NOT_FOUND; + } + *outAxisInfo = *info; + return NO_ERROR; +} + +base::Result> EventHub::mapSensor(int32_t deviceId, + int32_t absCode) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + + if (device != nullptr && device->keyMap.haveKeyLayout()) { + return device->keyMap.keyLayoutMap->mapSensor(absCode); + } + return Errorf("Device not found or device has no key layout."); +} + +// Gets the battery info map from battery ID to RawBatteryInfo of the miscellaneous device +// associated with the device ID. Returns an empty map if no miscellaneous device found. +const std::unordered_map& EventHub::getBatteryInfoLocked( + int32_t deviceId) const { + static const std::unordered_map EMPTY_BATTERY_INFO = {}; + Device* device = getDeviceLocked(deviceId); + if (device == nullptr || !device->associatedDevice) { + return EMPTY_BATTERY_INFO; + } + return device->associatedDevice->batteryInfos; +} + +std::vector EventHub::getRawBatteryIds(int32_t deviceId) const { + std::scoped_lock _l(mLock); + std::vector batteryIds; + + for (const auto& [id, info] : getBatteryInfoLocked(deviceId)) { + batteryIds.push_back(id); + } + + return batteryIds; +} + +std::optional EventHub::getRawBatteryInfo(int32_t deviceId, + int32_t batteryId) const { + std::scoped_lock _l(mLock); + + const auto infos = getBatteryInfoLocked(deviceId); + + auto it = infos.find(batteryId); + if (it != infos.end()) { + return it->second; + } + + return std::nullopt; +} + +// Gets the light info map from light ID to RawLightInfo of the miscellaneous device associated +// with the device ID. Returns an empty map if no miscellaneous device found. +const std::unordered_map& EventHub::getLightInfoLocked( + int32_t deviceId) const { + static const std::unordered_map EMPTY_LIGHT_INFO = {}; + Device* device = getDeviceLocked(deviceId); + if (device == nullptr || !device->associatedDevice) { + return EMPTY_LIGHT_INFO; + } + return device->associatedDevice->lightInfos; +} + +std::vector EventHub::getRawLightIds(int32_t deviceId) const { + std::scoped_lock _l(mLock); + std::vector lightIds; + + for (const auto& [id, info] : getLightInfoLocked(deviceId)) { + lightIds.push_back(id); + } + + return lightIds; +} + +std::optional EventHub::getRawLightInfo(int32_t deviceId, int32_t lightId) const { + std::scoped_lock _l(mLock); + + const auto infos = getLightInfoLocked(deviceId); + + auto it = infos.find(lightId); + if (it != infos.end()) { + return it->second; + } + + return std::nullopt; +} + +std::optional EventHub::getLightBrightness(int32_t deviceId, int32_t lightId) const { + std::scoped_lock _l(mLock); + + const auto infos = getLightInfoLocked(deviceId); + auto it = infos.find(lightId); + if (it == infos.end()) { + return std::nullopt; + } + std::string buffer; + if (!base::ReadFileToString(it->second.path / LIGHT_NODES.at(InputLightClass::BRIGHTNESS), + &buffer)) { + return std::nullopt; + } + return std::stoi(buffer); +} + +std::optional> EventHub::getLightIntensities( + int32_t deviceId, int32_t lightId) const { + std::scoped_lock _l(mLock); + + const auto infos = getLightInfoLocked(deviceId); + auto lightIt = infos.find(lightId); + if (lightIt == infos.end()) { + return std::nullopt; + } + + auto ret = + getColorIndexArray(lightIt->second.path / LIGHT_NODES.at(InputLightClass::MULTI_INDEX)); + + if (!ret.has_value()) { + return std::nullopt; + } + std::array colors = ret.value(); + + std::string intensityStr; + if (!base::ReadFileToString(lightIt->second.path / + LIGHT_NODES.at(InputLightClass::MULTI_INTENSITY), + &intensityStr)) { + return std::nullopt; + } + + // Intensity node outputs 3 color values + std::regex intensityPattern("([0-9]+)\\s([0-9]+)\\s([0-9]+)[\\n]"); + std::smatch results; + + if (!std::regex_match(intensityStr, results, intensityPattern)) { + return std::nullopt; + } + std::unordered_map intensities; + for (size_t i = 1; i < results.size(); i++) { + int value = std::stoi(results[i].str()); + intensities.emplace(colors[i - 1], value); + } + return intensities; +} + +void EventHub::setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) { + std::scoped_lock _l(mLock); + + const auto infos = getLightInfoLocked(deviceId); + auto lightIt = infos.find(lightId); + if (lightIt == infos.end()) { + ALOGE("%s lightId %d not found ", __func__, lightId); + return; + } + + if (!base::WriteStringToFile(std::to_string(brightness), + lightIt->second.path / + LIGHT_NODES.at(InputLightClass::BRIGHTNESS))) { + ALOGE("Can not write to file, error: %s", strerror(errno)); + } +} + +void EventHub::setLightIntensities(int32_t deviceId, int32_t lightId, + std::unordered_map intensities) { + std::scoped_lock _l(mLock); + + const auto infos = getLightInfoLocked(deviceId); + auto lightIt = infos.find(lightId); + if (lightIt == infos.end()) { + ALOGE("Light Id %d does not exist.", lightId); + return; + } + + auto ret = + getColorIndexArray(lightIt->second.path / LIGHT_NODES.at(InputLightClass::MULTI_INDEX)); + + if (!ret.has_value()) { + return; + } + std::array colors = ret.value(); + + std::string rgbStr; + for (size_t i = 0; i < COLOR_NUM; i++) { + auto it = intensities.find(colors[i]); + if (it != intensities.end()) { + rgbStr += std::to_string(it->second); + // Insert space between colors + if (i < COLOR_NUM - 1) { + rgbStr += " "; + } + } + } + // Append new line + rgbStr += "\n"; + + if (!base::WriteStringToFile(rgbStr, + lightIt->second.path / + LIGHT_NODES.at(InputLightClass::MULTI_INTENSITY))) { + ALOGE("Can not write to file, error: %s", strerror(errno)); + } +} + +std::optional EventHub::getRawLayoutInfo(int32_t deviceId) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == nullptr || !device->associatedDevice) { + return std::nullopt; + } + return device->associatedDevice->layoutInfo; +} + +void EventHub::setExcludedDevices(const std::vector& devices) { + std::scoped_lock _l(mLock); + + mExcludedDevices = devices; +} + +bool EventHub::hasScanCode(int32_t deviceId, int32_t scanCode) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device != nullptr && scanCode >= 0 && scanCode <= KEY_MAX) { + return device->keyBitmask.test(scanCode); + } + return false; +} + +bool EventHub::hasKeyCode(int32_t deviceId, int32_t keyCode) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device != nullptr) { + return device->hasKeycodeLocked(keyCode); + } + return false; +} + +bool EventHub::hasLed(int32_t deviceId, int32_t led) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + int32_t sc; + if (device != nullptr && device->mapLed(led, &sc) == NO_ERROR) { + return device->ledBitmask.test(sc); + } + return false; +} + +void EventHub::setLedState(int32_t deviceId, int32_t led, bool on) { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device != nullptr && device->hasValidFd()) { + device->setLedStateLocked(led, on); + } +} + +void EventHub::getVirtualKeyDefinitions(int32_t deviceId, + std::vector& outVirtualKeys) const { + outVirtualKeys.clear(); + + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device != nullptr && device->virtualKeyMap) { + const std::vector virtualKeys = + device->virtualKeyMap->getVirtualKeys(); + outVirtualKeys.insert(outVirtualKeys.end(), virtualKeys.begin(), virtualKeys.end()); + } +} + +const std::shared_ptr EventHub::getKeyCharacterMap(int32_t deviceId) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device != nullptr) { + return device->getKeyCharacterMap(); + } + return nullptr; +} + +// If provided map is null, it will reset key character map to default KCM. +bool EventHub::setKeyboardLayoutOverlay(int32_t deviceId, std::shared_ptr map) { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == nullptr || device->keyMap.keyCharacterMap == nullptr) { + return false; + } + if (map == nullptr) { + device->keyMap.keyCharacterMap->clearLayoutOverlay(); + return true; + } + device->keyMap.keyCharacterMap->combine(*map); + return true; +} + +static std::string generateDescriptor(InputDeviceIdentifier& identifier) { + std::string rawDescriptor; + rawDescriptor += StringPrintf(":%04x:%04x:", identifier.vendor, identifier.product); + // TODO add handling for USB devices to not uniqueify kbs that show up twice + if (!identifier.uniqueId.empty()) { + rawDescriptor += "uniqueId:"; + rawDescriptor += identifier.uniqueId; + } + if (identifier.nonce != 0) { + rawDescriptor += StringPrintf("nonce:%04x", identifier.nonce); + } + + if (identifier.vendor == 0 && identifier.product == 0) { + // If we don't know the vendor and product id, then the device is probably + // built-in so we need to rely on other information to uniquely identify + // the input device. Usually we try to avoid relying on the device name or + // location but for built-in input device, they are unlikely to ever change. + if (!identifier.name.empty()) { + rawDescriptor += "name:"; + rawDescriptor += identifier.name; + } else if (!identifier.location.empty()) { + rawDescriptor += "location:"; + rawDescriptor += identifier.location; + } + } + identifier.descriptor = sha1(rawDescriptor); + return rawDescriptor; +} + +void EventHub::assignDescriptorLocked(InputDeviceIdentifier& identifier) { + // Compute a device descriptor that uniquely identifies the device. + // The descriptor is assumed to be a stable identifier. Its value should not + // change between reboots, reconnections, firmware updates or new releases + // of Android. In practice we sometimes get devices that cannot be uniquely + // identified. In this case we enforce uniqueness between connected devices. + // Ideally, we also want the descriptor to be short and relatively opaque. + // Note that we explicitly do not use the path or location for external devices + // as their path or location will change as they are plugged/unplugged or moved + // to different ports. We do fallback to using name and location in the case of + // internal devices which are detected by the vendor and product being 0 in + // generateDescriptor. If two identical descriptors are detected we will fallback + // to using a 'nonce' and incrementing it until the new descriptor no longer has + // a match with any existing descriptors. + + identifier.nonce = 0; + std::string rawDescriptor = generateDescriptor(identifier); + // Enforce that the generated descriptor is unique. + while (hasDeviceWithDescriptorLocked(identifier.descriptor)) { + identifier.nonce++; + rawDescriptor = generateDescriptor(identifier); + } + ALOGV("Created descriptor: raw=%s, cooked=%s", rawDescriptor.c_str(), + identifier.descriptor.c_str()); +} + +std::shared_ptr EventHub::obtainAssociatedDeviceLocked( + const std::filesystem::path& devicePath) const { + const std::optional sysfsRootPathOpt = + getSysfsRootPath(devicePath.c_str()); + if (!sysfsRootPathOpt) { + return nullptr; + } + + const auto& path = *sysfsRootPathOpt; + + std::shared_ptr associatedDevice = std::make_shared( + AssociatedDevice{.sysfsRootPath = path, + .batteryInfos = readBatteryConfiguration(path), + .lightInfos = readLightsConfiguration(path), + .layoutInfo = readLayoutConfiguration(path)}); + + bool associatedDeviceChanged = false; + for (const auto& [id, dev] : mDevices) { + if (dev->associatedDevice && dev->associatedDevice->sysfsRootPath == path) { + if (*associatedDevice != *dev->associatedDevice) { + associatedDeviceChanged = true; + dev->associatedDevice = associatedDevice; + } + associatedDevice = dev->associatedDevice; + } + } + ALOGI_IF(associatedDeviceChanged, + "The AssociatedDevice changed for path '%s'. Using new AssociatedDevice: %s", + path.c_str(), associatedDevice->dump().c_str()); + + return associatedDevice; +} + +bool EventHub::AssociatedDevice::isChanged() const { + std::unordered_map newBatteryInfos = + readBatteryConfiguration(sysfsRootPath); + std::unordered_map newLightInfos = + readLightsConfiguration(sysfsRootPath); + std::optional newLayoutInfo = readLayoutConfiguration(sysfsRootPath); + + if (newBatteryInfos == batteryInfos && newLightInfos == lightInfos && + newLayoutInfo == layoutInfo) { + return false; + } + return true; +} + +void EventHub::vibrate(int32_t deviceId, const VibrationElement& element) { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device != nullptr && device->hasValidFd()) { + ff_effect effect; + memset(&effect, 0, sizeof(effect)); + effect.type = FF_RUMBLE; + effect.id = device->ffEffectId; + // evdev FF_RUMBLE effect only supports two channels of vibration. + effect.u.rumble.strong_magnitude = element.getMagnitude(FF_STRONG_MAGNITUDE_CHANNEL_IDX); + effect.u.rumble.weak_magnitude = element.getMagnitude(FF_WEAK_MAGNITUDE_CHANNEL_IDX); + effect.replay.length = element.duration.count(); + effect.replay.delay = 0; + if (ioctl(device->fd, EVIOCSFF, &effect)) { + ALOGW("Could not upload force feedback effect to device %s due to error %d.", + device->identifier.name.c_str(), errno); + return; + } + device->ffEffectId = effect.id; + + struct input_event ev; + ev.input_event_sec = 0; + ev.input_event_usec = 0; + ev.type = EV_FF; + ev.code = device->ffEffectId; + ev.value = 1; + if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) { + ALOGW("Could not start force feedback effect on device %s due to error %d.", + device->identifier.name.c_str(), errno); + return; + } + device->ffEffectPlaying = true; + } +} + +void EventHub::cancelVibrate(int32_t deviceId) { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device != nullptr && device->hasValidFd()) { + if (device->ffEffectPlaying) { + device->ffEffectPlaying = false; + + struct input_event ev; + ev.input_event_sec = 0; + ev.input_event_usec = 0; + ev.type = EV_FF; + ev.code = device->ffEffectId; + ev.value = 0; + if (write(device->fd, &ev, sizeof(ev)) != sizeof(ev)) { + ALOGW("Could not stop force feedback effect on device %s due to error %d.", + device->identifier.name.c_str(), errno); + return; + } + } + } +} + +std::vector EventHub::getVibratorIds(int32_t deviceId) const { + std::scoped_lock _l(mLock); + std::vector vibrators; + Device* device = getDeviceLocked(deviceId); + if (device != nullptr && device->hasValidFd() && + device->classes.test(InputDeviceClass::VIBRATOR)) { + vibrators.push_back(FF_STRONG_MAGNITUDE_CHANNEL_IDX); + vibrators.push_back(FF_WEAK_MAGNITUDE_CHANNEL_IDX); + } + return vibrators; +} + +/** + * Checks both mDevices and mOpeningDevices for a device with the descriptor passed. + */ +bool EventHub::hasDeviceWithDescriptorLocked(const std::string& descriptor) const { + for (const auto& device : mOpeningDevices) { + if (descriptor == device->identifier.descriptor) { + return true; + } + } + + for (const auto& [id, device] : mDevices) { + if (descriptor == device->identifier.descriptor) { + return true; + } + } + return false; +} + +EventHub::Device* EventHub::getDeviceLocked(int32_t deviceId) const { + if (deviceId == ReservedInputDeviceId::BUILT_IN_KEYBOARD_ID) { + deviceId = mBuiltInKeyboardId; + } + const auto& it = mDevices.find(deviceId); + return it != mDevices.end() ? it->second.get() : nullptr; +} + +EventHub::Device* EventHub::getDeviceByPathLocked(const std::string& devicePath) const { + for (const auto& [id, device] : mDevices) { + if (device->path == devicePath) { + return device.get(); + } + } + return nullptr; +} + +/** + * The file descriptor could be either input device, or a video device (associated with a + * specific input device). Check both cases here, and return the device that this event + * belongs to. Caller can compare the fd's once more to determine event type. + * Looks through all input devices, and only attached video devices. Unattached video + * devices are ignored. + */ +EventHub::Device* EventHub::getDeviceByFdLocked(int fd) const { + for (const auto& [id, device] : mDevices) { + if (device->fd == fd) { + // This is an input device event + return device.get(); + } + if (device->videoDevice && device->videoDevice->getFd() == fd) { + // This is a video device event + return device.get(); + } + } + // We do not check mUnattachedVideoDevices here because they should not participate in epoll, + // and therefore should never be looked up by fd. + return nullptr; +} + +std::optional EventHub::getBatteryCapacity(int32_t deviceId, int32_t batteryId) const { + std::filesystem::path batteryPath; + { + // Do not read the sysfs node to get the battery state while holding + // the EventHub lock. For some peripheral devices, reading battery state + // can be broken and take 5+ seconds. Holding the lock in this case would + // block all other event processing during this time. For now, we assume this + // call never happens on the InputReader thread and read the sysfs node outside + // the lock to prevent event processing from being blocked by this call. + std::scoped_lock _l(mLock); + + const auto& infos = getBatteryInfoLocked(deviceId); + auto it = infos.find(batteryId); + if (it == infos.end()) { + return std::nullopt; + } + batteryPath = it->second.path; + } // release lock + + std::string buffer; + + // Some devices report battery capacity as an integer through the "capacity" file + if (base::ReadFileToString(batteryPath / BATTERY_NODES.at(InputBatteryClass::CAPACITY), + &buffer)) { + return std::stoi(base::Trim(buffer)); + } + + // Other devices report capacity as an enum value POWER_SUPPLY_CAPACITY_LEVEL_XXX + // These values are taken from kernel source code include/linux/power_supply.h + if (base::ReadFileToString(batteryPath / BATTERY_NODES.at(InputBatteryClass::CAPACITY_LEVEL), + &buffer)) { + // Remove any white space such as trailing new line + const auto levelIt = BATTERY_LEVEL.find(base::Trim(buffer)); + if (levelIt != BATTERY_LEVEL.end()) { + return levelIt->second; + } + } + + return std::nullopt; +} + +std::optional EventHub::getBatteryStatus(int32_t deviceId, int32_t batteryId) const { + std::filesystem::path batteryPath; + { + // Do not read the sysfs node to get the battery state while holding + // the EventHub lock. For some peripheral devices, reading battery state + // can be broken and take 5+ seconds. Holding the lock in this case would + // block all other event processing during this time. For now, we assume this + // call never happens on the InputReader thread and read the sysfs node outside + // the lock to prevent event processing from being blocked by this call. + std::scoped_lock _l(mLock); + + const auto& infos = getBatteryInfoLocked(deviceId); + auto it = infos.find(batteryId); + if (it == infos.end()) { + return std::nullopt; + } + batteryPath = it->second.path; + } // release lock + + std::string buffer; + + if (!base::ReadFileToString(batteryPath / BATTERY_NODES.at(InputBatteryClass::STATUS), + &buffer)) { + ALOGE("Failed to read sysfs battery info: %s", strerror(errno)); + return std::nullopt; + } + + // Remove white space like trailing new line + const auto statusIt = BATTERY_STATUS.find(base::Trim(buffer)); + if (statusIt != BATTERY_STATUS.end()) { + return statusIt->second; + } + + return std::nullopt; +} + +std::vector EventHub::getEvents(int timeoutMillis) { + std::scoped_lock _l(mLock); + + std::array readBuffer; + + std::vector events; + bool awoken = false; + for (;;) { + nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); + + // Reopen input devices if needed. + if (mNeedToReopenDevices) { + mNeedToReopenDevices = false; + + ALOGI("Reopening all input devices due to a configuration change."); + + closeAllDevicesLocked(); + mNeedToScanDevices = true; + break; // return to the caller before we actually rescan + } + + // Report any devices that had last been added/removed. + for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) { + std::unique_ptr device = std::move(*it); + ALOGV("Reporting device closed: id=%d, name=%s\n", device->id, device->path.c_str()); + const int32_t deviceId = (device->id == mBuiltInKeyboardId) + ? ReservedInputDeviceId::BUILT_IN_KEYBOARD_ID + : device->id; + events.push_back({ + .when = now, + .deviceId = deviceId, + .type = DEVICE_REMOVED, + }); + it = mClosingDevices.erase(it); + mNeedToSendFinishedDeviceScan = true; + if (events.size() == EVENT_BUFFER_SIZE) { + break; + } + } + + if (mNeedToScanDevices) { + mNeedToScanDevices = false; + scanDevicesLocked(); + mNeedToSendFinishedDeviceScan = true; + } + + while (!mOpeningDevices.empty()) { + std::unique_ptr device = std::move(*mOpeningDevices.rbegin()); + mOpeningDevices.pop_back(); + ALOGV("Reporting device opened: id=%d, name=%s\n", device->id, device->path.c_str()); + const int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; + events.push_back({ + .when = now, + .deviceId = deviceId, + .type = DEVICE_ADDED, + }); + + // Try to find a matching video device by comparing device names + for (auto it = mUnattachedVideoDevices.begin(); it != mUnattachedVideoDevices.end(); + it++) { + std::unique_ptr& videoDevice = *it; + if (tryAddVideoDeviceLocked(*device, videoDevice)) { + // videoDevice was transferred to 'device' + it = mUnattachedVideoDevices.erase(it); + break; + } + } + + auto [dev_it, inserted] = mDevices.insert_or_assign(device->id, std::move(device)); + if (!inserted) { + ALOGW("Device id %d exists, replaced.", device->id); + } + mNeedToSendFinishedDeviceScan = true; + if (events.size() == EVENT_BUFFER_SIZE) { + break; + } + } + + if (mNeedToSendFinishedDeviceScan) { + mNeedToSendFinishedDeviceScan = false; + events.push_back({ + .when = now, + .type = FINISHED_DEVICE_SCAN, + }); + if (events.size() == EVENT_BUFFER_SIZE) { + break; + } + } + + // Grab the next input event. + bool deviceChanged = false; + while (mPendingEventIndex < mPendingEventCount) { + const struct epoll_event& eventItem = mPendingEventItems[mPendingEventIndex++]; + if (eventItem.data.fd == mINotifyFd) { + if (eventItem.events & EPOLLIN) { + mPendingINotify = true; + } else { + ALOGW("Received unexpected epoll event 0x%08x for INotify.", eventItem.events); + } + continue; + } + + if (eventItem.data.fd == mWakeReadPipeFd) { + if (eventItem.events & EPOLLIN) { + ALOGV("awoken after wake()"); + awoken = true; + char wakeReadBuffer[16]; + ssize_t nRead; + do { + nRead = read(mWakeReadPipeFd, wakeReadBuffer, sizeof(wakeReadBuffer)); + } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(wakeReadBuffer)); + } else { + ALOGW("Received unexpected epoll event 0x%08x for wake read pipe.", + eventItem.events); + } + continue; + } + + Device* device = getDeviceByFdLocked(eventItem.data.fd); + if (device == nullptr) { + ALOGE("Received unexpected epoll event 0x%08x for unknown fd %d.", eventItem.events, + eventItem.data.fd); + ALOG_ASSERT(!DEBUG); + continue; + } + if (device->videoDevice && eventItem.data.fd == device->videoDevice->getFd()) { + if (eventItem.events & EPOLLIN) { + size_t numFrames = device->videoDevice->readAndQueueFrames(); + if (numFrames == 0) { + ALOGE("Received epoll event for video device %s, but could not read frame", + device->videoDevice->getName().c_str()); + } + } else if (eventItem.events & EPOLLHUP) { + // TODO(b/121395353) - consider adding EPOLLRDHUP + ALOGI("Removing video device %s due to epoll hang-up event.", + device->videoDevice->getName().c_str()); + unregisterVideoDeviceFromEpollLocked(*device->videoDevice); + device->videoDevice = nullptr; + } else { + ALOGW("Received unexpected epoll event 0x%08x for device %s.", eventItem.events, + device->videoDevice->getName().c_str()); + ALOG_ASSERT(!DEBUG); + } + continue; + } + // This must be an input event + if (eventItem.events & EPOLLIN) { + int32_t readSize = + read(device->fd, readBuffer.data(), + sizeof(decltype(readBuffer)::value_type) * readBuffer.size()); + if (readSize == 0 || (readSize < 0 && errno == ENODEV)) { + // Device was removed before INotify noticed. + ALOGW("could not get event, removed? (fd: %d size: %" PRId32 + " capacity: %zu errno: %d)\n", + device->fd, readSize, readBuffer.size(), errno); + deviceChanged = true; + closeDeviceLocked(*device); + } else if (readSize < 0) { + if (errno != EAGAIN && errno != EINTR) { + ALOGW("could not get event (errno=%d)", errno); + } + } else if ((readSize % sizeof(struct input_event)) != 0) { + ALOGE("could not get event (wrong size: %d)", readSize); + } else { + const int32_t deviceId = device->id == mBuiltInKeyboardId ? 0 : device->id; + + const size_t count = size_t(readSize) / sizeof(struct input_event); + for (size_t i = 0; i < count; i++) { + struct input_event& iev = readBuffer[i]; + device->trackInputEvent(iev); + events.push_back({ + .when = processEventTimestamp(iev), + .readTime = systemTime(SYSTEM_TIME_MONOTONIC), + .deviceId = deviceId, + .type = iev.type, + .code = iev.code, + .value = iev.value, + }); + } + if (events.size() >= EVENT_BUFFER_SIZE) { + // The result buffer is full. Reset the pending event index + // so we will try to read the device again on the next iteration. + mPendingEventIndex -= 1; + break; + } + } + } else if (eventItem.events & EPOLLHUP) { + ALOGI("Removing device %s due to epoll hang-up event.", + device->identifier.name.c_str()); + deviceChanged = true; + closeDeviceLocked(*device); + } else { + ALOGW("Received unexpected epoll event 0x%08x for device %s.", eventItem.events, + device->identifier.name.c_str()); + } + } + + // readNotify() will modify the list of devices so this must be done after + // processing all other events to ensure that we read all remaining events + // before closing the devices. + if (mPendingINotify && mPendingEventIndex >= mPendingEventCount) { + mPendingINotify = false; + const auto res = readNotifyLocked(); + if (!res.ok()) { + ALOGW("Failed to read from inotify: %s", res.error().message().c_str()); + } + deviceChanged = true; + } + + // Report added or removed devices immediately. + if (deviceChanged) { + continue; + } + + // Return now if we have collected any events or if we were explicitly awoken. + if (!events.empty() || awoken) { + break; + } + + // Poll for events. + // When a device driver has pending (unread) events, it acquires + // a kernel wake lock. Once the last pending event has been read, the device + // driver will release the kernel wake lock, but the epoll will hold the wakelock, + // since we are using EPOLLWAKEUP. The wakelock is released by the epoll when epoll_wait + // is called again for the same fd that produced the event. + // Thus the system can only sleep if there are no events pending or + // currently being processed. + // + // The timeout is advisory only. If the device is asleep, it will not wake just to + // service the timeout. + mPendingEventIndex = 0; + + mLock.unlock(); // release lock before poll + + int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, timeoutMillis); + + mLock.lock(); // reacquire lock after poll + + if (pollResult == 0) { + // Timed out. + mPendingEventCount = 0; + break; + } + + if (pollResult < 0) { + // An error occurred. + mPendingEventCount = 0; + + // Sleep after errors to avoid locking up the system. + // Hopefully the error is transient. + if (errno != EINTR) { + ALOGW("poll failed (errno=%d)\n", errno); + usleep(100000); + } + } else { + // Some events occurred. + mPendingEventCount = size_t(pollResult); + } + } + + // All done, return the number of events we read. + return events; +} + +std::vector EventHub::getVideoFrames(int32_t deviceId) { + std::scoped_lock _l(mLock); + + Device* device = getDeviceLocked(deviceId); + if (device == nullptr || !device->videoDevice) { + return {}; + } + return device->videoDevice->consumeFrames(); +} + +void EventHub::wake() { + ALOGV("wake() called"); + + ssize_t nWrite; + do { + nWrite = write(mWakeWritePipeFd, "W", 1); + } while (nWrite == -1 && errno == EINTR); + + if (nWrite != 1 && errno != EAGAIN) { + ALOGW("Could not write wake signal: %s", strerror(errno)); + } +} + +void EventHub::scanDevicesLocked() { + status_t result; + std::error_code errorCode; + + if (std::filesystem::exists(DEVICE_INPUT_PATH, errorCode)) { + result = scanDirLocked(DEVICE_INPUT_PATH); + if (result < 0) { + ALOGE("scan dir failed for %s", DEVICE_INPUT_PATH); + } + } else { + if (errorCode) { + ALOGW("Could not run filesystem::exists() due to error %d : %s.", errorCode.value(), + errorCode.message().c_str()); + } + } + if (isV4lScanningEnabled()) { + result = scanVideoDirLocked(DEVICE_PATH); + if (result != OK) { + ALOGE("scan video dir failed for %s", DEVICE_PATH); + } + } + if (mDevices.find(ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID) == mDevices.end()) { + createVirtualKeyboardLocked(); + } +} + +// ---------------------------------------------------------------------------- + +status_t EventHub::registerFdForEpoll(int fd) { + // TODO(b/121395353) - consider adding EPOLLRDHUP + struct epoll_event eventItem = {}; + eventItem.events = EPOLLIN | EPOLLWAKEUP; + eventItem.data.fd = fd; + if (epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem)) { + ALOGE("Could not add fd to epoll instance: %s", strerror(errno)); + return -errno; + } + return OK; +} + +status_t EventHub::unregisterFdFromEpoll(int fd) { + if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, fd, nullptr)) { + ALOGW("Could not remove fd from epoll instance: %s", strerror(errno)); + return -errno; + } + return OK; +} + +status_t EventHub::registerDeviceForEpollLocked(Device& device) { + status_t result = registerFdForEpoll(device.fd); + if (result != OK) { + ALOGE("Could not add input device fd to epoll for device %" PRId32, device.id); + return result; + } + if (device.videoDevice) { + registerVideoDeviceForEpollLocked(*device.videoDevice); + } + return result; +} + +void EventHub::registerVideoDeviceForEpollLocked(const TouchVideoDevice& videoDevice) { + status_t result = registerFdForEpoll(videoDevice.getFd()); + if (result != OK) { + ALOGE("Could not add video device %s to epoll", videoDevice.getName().c_str()); + } +} + +status_t EventHub::unregisterDeviceFromEpollLocked(Device& device) { + if (device.hasValidFd()) { + status_t result = unregisterFdFromEpoll(device.fd); + if (result != OK) { + ALOGW("Could not remove input device fd from epoll for device %" PRId32, device.id); + return result; + } + } + if (device.videoDevice) { + unregisterVideoDeviceFromEpollLocked(*device.videoDevice); + } + return OK; +} + +void EventHub::unregisterVideoDeviceFromEpollLocked(const TouchVideoDevice& videoDevice) { + if (videoDevice.hasValidFd()) { + status_t result = unregisterFdFromEpoll(videoDevice.getFd()); + if (result != OK) { + ALOGW("Could not remove video device fd from epoll for device: %s", + videoDevice.getName().c_str()); + } + } +} + +void EventHub::reportDeviceAddedForStatisticsLocked(const InputDeviceIdentifier& identifier, + ftl::Flags classes) { + SHA256_CTX ctx; + SHA256_Init(&ctx); + SHA256_Update(&ctx, reinterpret_cast(identifier.uniqueId.c_str()), + identifier.uniqueId.size()); + std::array digest; + SHA256_Final(digest.data(), &ctx); + + std::string obfuscatedId; + for (size_t i = 0; i < OBFUSCATED_LENGTH; i++) { + obfuscatedId += StringPrintf("%02x", digest[i]); + } + + android::util::stats_write(android::util::INPUTDEVICE_REGISTERED, identifier.name.c_str(), + identifier.vendor, identifier.product, identifier.version, + identifier.bus, obfuscatedId.c_str(), classes.get()); +} + +void EventHub::openDeviceLocked(const std::string& devicePath) { + // If an input device happens to register around the time when EventHub's constructor runs, it + // is possible that the same input event node (for example, /dev/input/event3) will be noticed + // in both 'inotify' callback and also in the 'scanDirLocked' pass. To prevent duplicate devices + // from getting registered, ensure that this path is not already covered by an existing device. + for (const auto& [deviceId, device] : mDevices) { + if (device->path == devicePath) { + return; // device was already registered + } + } + + char buffer[80]; + + ALOGV("Opening device: %s", devicePath.c_str()); + + int fd = open(devicePath.c_str(), O_RDWR | O_CLOEXEC | O_NONBLOCK); + if (fd < 0) { + ALOGE("could not open %s, %s\n", devicePath.c_str(), strerror(errno)); + return; + } + + InputDeviceIdentifier identifier; + + // Get device name. + if (ioctl(fd, EVIOCGNAME(sizeof(buffer) - 1), &buffer) < 1) { + ALOGE("Could not get device name for %s: %s", devicePath.c_str(), strerror(errno)); + } else { + buffer[sizeof(buffer) - 1] = '\0'; + identifier.name = buffer; + } + + // Check to see if the device is on our excluded list + for (size_t i = 0; i < mExcludedDevices.size(); i++) { + const std::string& item = mExcludedDevices[i]; + if (identifier.name == item) { + ALOGI("ignoring event id %s driver %s\n", devicePath.c_str(), item.c_str()); + close(fd); + return; + } + } + + // Get device driver version. + int driverVersion; + if (ioctl(fd, EVIOCGVERSION, &driverVersion)) { + ALOGE("could not get driver version for %s, %s\n", devicePath.c_str(), strerror(errno)); + close(fd); + return; + } + + // Get device identifier. + struct input_id inputId; + if (ioctl(fd, EVIOCGID, &inputId)) { + ALOGE("could not get device input id for %s, %s\n", devicePath.c_str(), strerror(errno)); + close(fd); + return; + } + identifier.bus = inputId.bustype; + identifier.product = inputId.product; + identifier.vendor = inputId.vendor; + identifier.version = inputId.version; + + // Get device physical location. + if (ioctl(fd, EVIOCGPHYS(sizeof(buffer) - 1), &buffer) < 1) { + // fprintf(stderr, "could not get location for %s, %s\n", devicePath, strerror(errno)); + } else { + buffer[sizeof(buffer) - 1] = '\0'; + identifier.location = buffer; + } + + // Get device unique id. + if (ioctl(fd, EVIOCGUNIQ(sizeof(buffer) - 1), &buffer) < 1) { + // fprintf(stderr, "could not get idstring for %s, %s\n", devicePath, strerror(errno)); + } else { + buffer[sizeof(buffer) - 1] = '\0'; + identifier.uniqueId = buffer; + } + + // Attempt to get the bluetooth address of an input device from the uniqueId. + if (identifier.bus == BUS_BLUETOOTH && + std::regex_match(identifier.uniqueId, + std::regex("^[A-Fa-f0-9]{2}(?::[A-Fa-f0-9]{2}){5}$"))) { + identifier.bluetoothAddress = identifier.uniqueId; + // The Bluetooth stack requires alphabetic characters to be uppercase in a valid address. + for (auto& c : *identifier.bluetoothAddress) { + c = ::toupper(c); + } + } + + // Fill in the descriptor. + assignDescriptorLocked(identifier); + + // Allocate device. (The device object takes ownership of the fd at this point.) + int32_t deviceId = mNextDeviceId++; + std::unique_ptr device = + std::make_unique(fd, deviceId, devicePath, identifier, + obtainAssociatedDeviceLocked(devicePath)); + + ALOGV("add device %d: %s\n", deviceId, devicePath.c_str()); + ALOGV(" bus: %04x\n" + " vendor %04x\n" + " product %04x\n" + " version %04x\n", + identifier.bus, identifier.vendor, identifier.product, identifier.version); + ALOGV(" name: \"%s\"\n", identifier.name.c_str()); + ALOGV(" location: \"%s\"\n", identifier.location.c_str()); + ALOGV(" unique id: \"%s\"\n", identifier.uniqueId.c_str()); + ALOGV(" descriptor: \"%s\"\n", identifier.descriptor.c_str()); + ALOGV(" driver: v%d.%d.%d\n", driverVersion >> 16, (driverVersion >> 8) & 0xff, + driverVersion & 0xff); + + // Load the configuration file for the device. + device->loadConfigurationLocked(); + + // Figure out the kinds of events the device reports. + device->readDeviceBitMask(EVIOCGBIT(EV_KEY, 0), device->keyBitmask); + device->readDeviceBitMask(EVIOCGBIT(EV_ABS, 0), device->absBitmask); + device->readDeviceBitMask(EVIOCGBIT(EV_REL, 0), device->relBitmask); + device->readDeviceBitMask(EVIOCGBIT(EV_SW, 0), device->swBitmask); + device->readDeviceBitMask(EVIOCGBIT(EV_LED, 0), device->ledBitmask); + device->readDeviceBitMask(EVIOCGBIT(EV_FF, 0), device->ffBitmask); + device->readDeviceBitMask(EVIOCGBIT(EV_MSC, 0), device->mscBitmask); + device->readDeviceBitMask(EVIOCGPROP(0), device->propBitmask); + + // See if this is a device with keys. This could be full keyboard, or other devices like + // gamepads, joysticks, and styluses with buttons that should generate key presses. + bool haveKeyboardKeys = + device->keyBitmask.any(0, BTN_MISC) || device->keyBitmask.any(BTN_WHEEL, KEY_MAX + 1); + bool haveGamepadButtons = device->keyBitmask.any(BTN_MISC, BTN_MOUSE) || + device->keyBitmask.any(BTN_JOYSTICK, BTN_DIGI); + bool haveStylusButtons = device->keyBitmask.test(BTN_STYLUS) || + device->keyBitmask.test(BTN_STYLUS2) || device->keyBitmask.test(BTN_STYLUS3); + if (haveKeyboardKeys || haveGamepadButtons || haveStylusButtons) { + device->classes |= InputDeviceClass::KEYBOARD; + } + + // See if this is a cursor device such as a trackball or mouse. + if (device->keyBitmask.test(BTN_MOUSE) && device->relBitmask.test(REL_X) && + device->relBitmask.test(REL_Y)) { + device->classes |= InputDeviceClass::CURSOR; + } + + // See if the device is specially configured to be of a certain type. + if (device->configuration) { + std::string deviceType = device->configuration->getString("device.type").value_or(""); + if (deviceType == "rotaryEncoder") { + device->classes |= InputDeviceClass::ROTARY_ENCODER; + } else if (deviceType == "externalStylus") { + device->classes |= InputDeviceClass::EXTERNAL_STYLUS; + } + } + + // See if this is a touch pad. + // Is this a new modern multi-touch driver? + if (device->absBitmask.test(ABS_MT_POSITION_X) && device->absBitmask.test(ABS_MT_POSITION_Y)) { + // Some joysticks such as the PS3 controller report axes that conflict + // with the ABS_MT range. Try to confirm that the device really is + // a touch screen. + if (device->keyBitmask.test(BTN_TOUCH) || !haveGamepadButtons) { + device->classes |= (InputDeviceClass::TOUCH | InputDeviceClass::TOUCH_MT); + if (device->propBitmask.test(INPUT_PROP_POINTER) && + !device->keyBitmask.any(BTN_TOOL_PEN, BTN_TOOL_FINGER) && !haveStylusButtons) { + device->classes |= InputDeviceClass::TOUCHPAD; + } + } + // Is this an old style single-touch driver? + } else if (device->keyBitmask.test(BTN_TOUCH) && device->absBitmask.test(ABS_X) && + device->absBitmask.test(ABS_Y)) { + device->classes |= InputDeviceClass::TOUCH; + // Is this a stylus that reports contact/pressure independently of touch coordinates? + } else if ((device->absBitmask.test(ABS_PRESSURE) || device->keyBitmask.test(BTN_TOUCH)) && + !device->absBitmask.test(ABS_X) && !device->absBitmask.test(ABS_Y)) { + device->classes |= InputDeviceClass::EXTERNAL_STYLUS; + } + + // See if this device is a joystick. + // Assumes that joysticks always have gamepad buttons in order to distinguish them + // from other devices such as accelerometers that also have absolute axes. + if (haveGamepadButtons) { + auto assumedClasses = device->classes | InputDeviceClass::JOYSTICK; + for (int i = 0; i <= ABS_MAX; i++) { + if (device->absBitmask.test(i) && + (getAbsAxisUsage(i, assumedClasses).test(InputDeviceClass::JOYSTICK))) { + device->classes = assumedClasses; + break; + } + } + } + + // Check whether this device is an accelerometer. + if (device->propBitmask.test(INPUT_PROP_ACCELEROMETER)) { + device->classes |= InputDeviceClass::SENSOR; + } + + // Check whether this device has switches. + for (int i = 0; i <= SW_MAX; i++) { + if (device->swBitmask.test(i)) { + device->classes |= InputDeviceClass::SWITCH; + break; + } + } + + // Check whether this device supports the vibrator. + if (device->ffBitmask.test(FF_RUMBLE)) { + device->classes |= InputDeviceClass::VIBRATOR; + } + + // Configure virtual keys. + if ((device->classes.test(InputDeviceClass::TOUCH))) { + // Load the virtual keys for the touch screen, if any. + // We do this now so that we can make sure to load the keymap if necessary. + bool success = device->loadVirtualKeyMapLocked(); + if (success) { + device->classes |= InputDeviceClass::KEYBOARD; + } + } + + // Load the key map. + // We need to do this for joysticks too because the key layout may specify axes, and for + // sensor as well because the key layout may specify the axes to sensor data mapping. + status_t keyMapStatus = NAME_NOT_FOUND; + if (device->classes.any(InputDeviceClass::KEYBOARD | InputDeviceClass::JOYSTICK | + InputDeviceClass::SENSOR)) { + // Load the keymap for the device. + keyMapStatus = device->loadKeyMapLocked(); + } + + // Configure the keyboard, gamepad or virtual keyboard. + if (device->classes.test(InputDeviceClass::KEYBOARD)) { + // Register the keyboard as a built-in keyboard if it is eligible. + if (!keyMapStatus && mBuiltInKeyboardId == NO_BUILT_IN_KEYBOARD && + isEligibleBuiltInKeyboard(device->identifier, device->configuration.get(), + &device->keyMap)) { + mBuiltInKeyboardId = device->id; + } + + // 'Q' key support = cheap test of whether this is an alpha-capable kbd + if (device->hasKeycodeLocked(AKEYCODE_Q)) { + device->classes |= InputDeviceClass::ALPHAKEY; + } + + // See if this device has a D-pad. + if (std::all_of(DPAD_REQUIRED_KEYCODES.begin(), DPAD_REQUIRED_KEYCODES.end(), + [&](int32_t keycode) { return device->hasKeycodeLocked(keycode); })) { + device->classes |= InputDeviceClass::DPAD; + } + + // See if this device has a gamepad. + if (std::any_of(GAMEPAD_KEYCODES.begin(), GAMEPAD_KEYCODES.end(), + [&](int32_t keycode) { return device->hasKeycodeLocked(keycode); })) { + device->classes |= InputDeviceClass::GAMEPAD; + } + + // See if this device has any stylus buttons that we would want to fuse with touch data. + if (!device->classes.any(InputDeviceClass::TOUCH | InputDeviceClass::TOUCH_MT) && + !device->classes.any(InputDeviceClass::ALPHAKEY) && + std::any_of(STYLUS_BUTTON_KEYCODES.begin(), STYLUS_BUTTON_KEYCODES.end(), + [&](int32_t keycode) { return device->hasKeycodeLocked(keycode); })) { + device->classes |= InputDeviceClass::EXTERNAL_STYLUS; + } + } + + // If the device isn't recognized as something we handle, don't monitor it. + if (device->classes == ftl::Flags(0)) { + ALOGV("Dropping device: id=%d, path='%s', name='%s'", deviceId, devicePath.c_str(), + device->identifier.name.c_str()); + return; + } + + // Classify InputDeviceClass::BATTERY. + if (device->associatedDevice && !device->associatedDevice->batteryInfos.empty()) { + device->classes |= InputDeviceClass::BATTERY; + } + + // Classify InputDeviceClass::LIGHT. + if (device->associatedDevice && !device->associatedDevice->lightInfos.empty()) { + device->classes |= InputDeviceClass::LIGHT; + } + + // Determine whether the device has a mic. + if (device->deviceHasMicLocked()) { + device->classes |= InputDeviceClass::MIC; + } + + // Determine whether the device is external or internal. + if (device->isExternalDeviceLocked()) { + device->classes |= InputDeviceClass::EXTERNAL; + } + + if (device->classes.any(InputDeviceClass::JOYSTICK | InputDeviceClass::DPAD) && + device->classes.test(InputDeviceClass::GAMEPAD)) { + device->controllerNumber = getNextControllerNumberLocked(device->identifier.name); + device->setLedForControllerLocked(); + } + + if (registerDeviceForEpollLocked(*device) != OK) { + return; + } + + device->configureFd(); + + ALOGI("New device: id=%d, fd=%d, path='%s', name='%s', classes=%s, " + "configuration='%s', keyLayout='%s', keyCharacterMap='%s', builtinKeyboard=%s, ", + deviceId, fd, devicePath.c_str(), device->identifier.name.c_str(), + device->classes.string().c_str(), device->configurationFile.c_str(), + device->keyMap.keyLayoutFile.c_str(), device->keyMap.keyCharacterMapFile.c_str(), + toString(mBuiltInKeyboardId == deviceId)); + + addDeviceLocked(std::move(device)); +} + +void EventHub::openVideoDeviceLocked(const std::string& devicePath) { + std::unique_ptr videoDevice = TouchVideoDevice::create(devicePath); + if (!videoDevice) { + ALOGE("Could not create touch video device for %s. Ignoring", devicePath.c_str()); + return; + } + // Transfer ownership of this video device to a matching input device + for (const auto& [id, device] : mDevices) { + if (tryAddVideoDeviceLocked(*device, videoDevice)) { + return; // 'device' now owns 'videoDevice' + } + } + + // Couldn't find a matching input device, so just add it to a temporary holding queue. + // A matching input device may appear later. + ALOGI("Adding video device %s to list of unattached video devices", + videoDevice->getName().c_str()); + mUnattachedVideoDevices.push_back(std::move(videoDevice)); +} + +bool EventHub::tryAddVideoDeviceLocked(EventHub::Device& device, + std::unique_ptr& videoDevice) { + if (videoDevice->getName() != device.identifier.name) { + return false; + } + device.videoDevice = std::move(videoDevice); + if (device.enabled) { + registerVideoDeviceForEpollLocked(*device.videoDevice); + } + return true; +} + +bool EventHub::isDeviceEnabled(int32_t deviceId) const { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == nullptr) { + ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__); + return false; + } + return device->enabled; +} + +status_t EventHub::enableDevice(int32_t deviceId) { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == nullptr) { + ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__); + return BAD_VALUE; + } + if (device->enabled) { + ALOGW("Duplicate call to %s, input device %" PRId32 " already enabled", __func__, deviceId); + return OK; + } + status_t result = device->enable(); + if (result != OK) { + ALOGE("Failed to enable device %" PRId32, deviceId); + return result; + } + + device->configureFd(); + + return registerDeviceForEpollLocked(*device); +} + +status_t EventHub::disableDevice(int32_t deviceId) { + std::scoped_lock _l(mLock); + Device* device = getDeviceLocked(deviceId); + if (device == nullptr) { + ALOGE("Invalid device id=%" PRId32 " provided to %s", deviceId, __func__); + return BAD_VALUE; + } + if (!device->enabled) { + ALOGW("Duplicate call to %s, input device already disabled", __func__); + return OK; + } + unregisterDeviceFromEpollLocked(*device); + return device->disable(); +} + +// TODO(b/274755573): Shift to uevent handling on native side and remove this method +// Currently using Java UEventObserver to trigger this which uses UEvent infrastructure that uses a +// NETLINK socket to observe UEvents. We can create similar infrastructure on Eventhub side to +// directly observe UEvents instead of triggering from Java side. +void EventHub::sysfsNodeChanged(const std::string& sysfsNodePath) { + std::scoped_lock _l(mLock); + + // Check in opening devices + for (auto it = mOpeningDevices.begin(); it != mOpeningDevices.end(); it++) { + std::unique_ptr& device = *it; + if (device->associatedDevice && + sysfsNodePath.find(device->associatedDevice->sysfsRootPath.string()) != + std::string::npos && + device->associatedDevice->isChanged()) { + it = mOpeningDevices.erase(it); + openDeviceLocked(device->path); + } + } + + // Check in already added device + std::vector devicesToReopen; + for (const auto& [id, device] : mDevices) { + if (device->associatedDevice && + sysfsNodePath.find(device->associatedDevice->sysfsRootPath.string()) != + std::string::npos && + device->associatedDevice->isChanged()) { + devicesToReopen.push_back(device.get()); + } + } + for (const auto& device : devicesToReopen) { + closeDeviceLocked(*device); + openDeviceLocked(device->path); + } + devicesToReopen.clear(); +} + +void EventHub::createVirtualKeyboardLocked() { + InputDeviceIdentifier identifier; + identifier.name = "Virtual"; + identifier.uniqueId = ""; + assignDescriptorLocked(identifier); + + std::unique_ptr device = + std::make_unique(-1, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, "", + identifier, /*associatedDevice=*/nullptr); + device->classes = InputDeviceClass::KEYBOARD | InputDeviceClass::ALPHAKEY | + InputDeviceClass::DPAD | InputDeviceClass::VIRTUAL; + device->loadKeyMapLocked(); + addDeviceLocked(std::move(device)); +} + +void EventHub::addDeviceLocked(std::unique_ptr device) { + reportDeviceAddedForStatisticsLocked(device->identifier, device->classes); + mOpeningDevices.push_back(std::move(device)); +} + +int32_t EventHub::getNextControllerNumberLocked(const std::string& name) { + if (mControllerNumbers.isFull()) { + ALOGI("Maximum number of controllers reached, assigning controller number 0 to device %s", + name.c_str()); + return 0; + } + // Since the controller number 0 is reserved for non-controllers, translate all numbers up by + // one + return static_cast(mControllerNumbers.markFirstUnmarkedBit() + 1); +} + +void EventHub::releaseControllerNumberLocked(int32_t num) { + if (num > 0) { + mControllerNumbers.clearBit(static_cast(num - 1)); + } +} + +void EventHub::closeDeviceByPathLocked(const std::string& devicePath) { + Device* device = getDeviceByPathLocked(devicePath); + if (device != nullptr) { + closeDeviceLocked(*device); + return; + } + ALOGV("Remove device: %s not found, device may already have been removed.", devicePath.c_str()); +} + +/** + * Find the video device by filename, and close it. + * The video device is closed by path during an inotify event, where we don't have the + * additional context about the video device fd, or the associated input device. + */ +void EventHub::closeVideoDeviceByPathLocked(const std::string& devicePath) { + // A video device may be owned by an existing input device, or it may be stored in + // the mUnattachedVideoDevices queue. Check both locations. + for (const auto& [id, device] : mDevices) { + if (device->videoDevice && device->videoDevice->getPath() == devicePath) { + unregisterVideoDeviceFromEpollLocked(*device->videoDevice); + device->videoDevice = nullptr; + return; + } + } + std::erase_if(mUnattachedVideoDevices, + [&devicePath](const std::unique_ptr& videoDevice) { + return videoDevice->getPath() == devicePath; + }); +} + +void EventHub::closeAllDevicesLocked() { + mUnattachedVideoDevices.clear(); + while (!mDevices.empty()) { + closeDeviceLocked(*(mDevices.begin()->second)); + } +} + +void EventHub::closeDeviceLocked(Device& device) { + ALOGI("Removed device: path=%s name=%s id=%d fd=%d classes=%s", device.path.c_str(), + device.identifier.name.c_str(), device.id, device.fd, device.classes.string().c_str()); + + if (device.id == mBuiltInKeyboardId) { + ALOGW("built-in keyboard device %s (id=%d) is closing! the apps will not like this", + device.path.c_str(), mBuiltInKeyboardId); + mBuiltInKeyboardId = NO_BUILT_IN_KEYBOARD; + } + + unregisterDeviceFromEpollLocked(device); + if (device.videoDevice) { + // This must be done after the video device is removed from epoll + mUnattachedVideoDevices.push_back(std::move(device.videoDevice)); + } + + releaseControllerNumberLocked(device.controllerNumber); + device.controllerNumber = 0; + device.close(); + mClosingDevices.push_back(std::move(mDevices[device.id])); + + mDevices.erase(device.id); +} + +base::Result EventHub::readNotifyLocked() { + static constexpr auto EVENT_SIZE = static_cast(sizeof(inotify_event)); + uint8_t eventBuffer[512]; + ssize_t sizeRead; + + ALOGV("EventHub::readNotify nfd: %d\n", mINotifyFd); + do { + sizeRead = read(mINotifyFd, eventBuffer, sizeof(eventBuffer)); + } while (sizeRead < 0 && errno == EINTR); + + if (sizeRead < EVENT_SIZE) return Errorf("could not get event, %s", strerror(errno)); + + for (ssize_t eventPos = 0; sizeRead >= EVENT_SIZE;) { + const inotify_event* event; + event = (const inotify_event*)(eventBuffer + eventPos); + if (event->len == 0) continue; + + handleNotifyEventLocked(*event); + + const ssize_t eventSize = EVENT_SIZE + event->len; + sizeRead -= eventSize; + eventPos += eventSize; + } + return {}; +} + +void EventHub::handleNotifyEventLocked(const inotify_event& event) { + if (event.wd == mDeviceInputWd) { + std::string filename = std::string(DEVICE_INPUT_PATH) + "/" + event.name; + if (event.mask & IN_CREATE) { + openDeviceLocked(filename); + } else { + ALOGI("Removing device '%s' due to inotify event\n", filename.c_str()); + closeDeviceByPathLocked(filename); + } + } else if (event.wd == mDeviceWd) { + if (isV4lTouchNode(event.name)) { + std::string filename = std::string(DEVICE_PATH) + "/" + event.name; + if (event.mask & IN_CREATE) { + openVideoDeviceLocked(filename); + } else { + ALOGI("Removing video device '%s' due to inotify event", filename.c_str()); + closeVideoDeviceByPathLocked(filename); + } + } else if (strcmp(event.name, "input") == 0 && event.mask & IN_CREATE) { + addDeviceInputInotify(); + } + } else { + LOG_ALWAYS_FATAL("Unexpected inotify event, wd = %i", event.wd); + } +} + +status_t EventHub::scanDirLocked(const std::string& dirname) { + for (const auto& entry : std::filesystem::directory_iterator(dirname)) { + openDeviceLocked(entry.path()); + } + return 0; +} + +/** + * Look for all dirname/v4l-touch* devices, and open them. + */ +status_t EventHub::scanVideoDirLocked(const std::string& dirname) { + for (const auto& entry : std::filesystem::directory_iterator(dirname)) { + if (isV4lTouchNode(entry.path())) { + ALOGI("Found touch video device %s", entry.path().c_str()); + openVideoDeviceLocked(entry.path()); + } + } + return OK; +} + +void EventHub::requestReopenDevices() { + ALOGV("requestReopenDevices() called"); + + std::scoped_lock _l(mLock); + mNeedToReopenDevices = true; +} + +void EventHub::dump(std::string& dump) const { + dump += "Event Hub State:\n"; + + { // acquire lock + std::scoped_lock _l(mLock); + + dump += StringPrintf(INDENT "BuiltInKeyboardId: %d\n", mBuiltInKeyboardId); + + dump += INDENT "Devices:\n"; + + for (const auto& [id, device] : mDevices) { + if (mBuiltInKeyboardId == device->id) { + dump += StringPrintf(INDENT2 "%d: %s (aka device 0 - built-in keyboard)\n", + device->id, device->identifier.name.c_str()); + } else { + dump += StringPrintf(INDENT2 "%d: %s\n", device->id, + device->identifier.name.c_str()); + } + dump += StringPrintf(INDENT3 "Classes: %s\n", device->classes.string().c_str()); + dump += StringPrintf(INDENT3 "Path: %s\n", device->path.c_str()); + dump += StringPrintf(INDENT3 "Enabled: %s\n", toString(device->enabled)); + dump += StringPrintf(INDENT3 "Descriptor: %s\n", device->identifier.descriptor.c_str()); + dump += StringPrintf(INDENT3 "Location: %s\n", device->identifier.location.c_str()); + dump += StringPrintf(INDENT3 "ControllerNumber: %d\n", device->controllerNumber); + dump += StringPrintf(INDENT3 "UniqueId: %s\n", device->identifier.uniqueId.c_str()); + dump += StringPrintf(INDENT3 "Identifier: bus=0x%04x, vendor=0x%04x, " + "product=0x%04x, version=0x%04x, bluetoothAddress=%s\n", + device->identifier.bus, device->identifier.vendor, + device->identifier.product, device->identifier.version, + toString(device->identifier.bluetoothAddress).c_str()); + dump += StringPrintf(INDENT3 "KeyLayoutFile: %s\n", + device->keyMap.keyLayoutFile.c_str()); + dump += StringPrintf(INDENT3 "KeyCharacterMapFile: %s\n", + device->keyMap.keyCharacterMapFile.c_str()); + if (device->associatedDevice && device->associatedDevice->layoutInfo) { + dump += StringPrintf(INDENT3 "LanguageTag: %s\n", + device->associatedDevice->layoutInfo->languageTag.c_str()); + dump += StringPrintf(INDENT3 "LayoutType: %s\n", + device->associatedDevice->layoutInfo->layoutType.c_str()); + } + dump += StringPrintf(INDENT3 "ConfigurationFile: %s\n", + device->configurationFile.c_str()); + dump += StringPrintf(INDENT3 "VideoDevice: %s\n", + device->videoDevice ? device->videoDevice->dump().c_str() + : ""); + dump += StringPrintf(INDENT3 "SysfsDevicePath: %s\n", + device->associatedDevice + ? device->associatedDevice->sysfsRootPath.c_str() + : ""); + if (device->keyBitmask.any(0, KEY_MAX + 1)) { + const auto pressedKeys = device->keyState.dumpSetIndices(", ", [](int i) { + return InputEventLookup::getLinuxEvdevLabel(EV_KEY, i, 1).code; + }); + dump += StringPrintf(INDENT3 "KeyState (pressed): %s\n", pressedKeys.c_str()); + } + if (device->swBitmask.any(0, SW_MAX + 1)) { + const auto pressedSwitches = device->swState.dumpSetIndices(", ", [](int i) { + return InputEventLookup::getLinuxEvdevLabel(EV_SW, i, 1).code; + }); + dump += StringPrintf(INDENT3 "SwState (pressed): %s\n", pressedSwitches.c_str()); + } + if (!device->absState.empty()) { + std::string axisValues; + for (const auto& [axis, state] : device->absState) { + if (!axisValues.empty()) { + axisValues += ", "; + } + axisValues += StringPrintf("%s=%d", + InputEventLookup::getLinuxEvdevLabel(EV_ABS, axis, 0) + .code.c_str(), + state.value); + } + dump += INDENT3 "AbsState: " + axisValues + "\n"; + } + } + + dump += INDENT "Unattached video devices:\n"; + for (const std::unique_ptr& videoDevice : mUnattachedVideoDevices) { + dump += INDENT2 + videoDevice->dump() + "\n"; + } + if (mUnattachedVideoDevices.empty()) { + dump += INDENT2 "\n"; + } + } // release lock +} + +void EventHub::monitor() const { + // Acquire and release the lock to ensure that the event hub has not deadlocked. + std::unique_lock lock(mLock); +} + +std::string EventHub::AssociatedDevice::dump() const { + return StringPrintf("path=%s, numBatteries=%zu, numLight=%zu", sysfsRootPath.c_str(), + batteryInfos.size(), lightInfos.size()); +} + +} // namespace android diff --git a/aosp/hardware/interfaces/audio/aidl/default/android.hardware.audio.service-aidl.example.rc b/aosp/hardware/interfaces/audio/aidl/default/android.hardware.audio.service-aidl.example.rc new file mode 100644 index 000000000..5b886de40 --- /dev/null +++ b/aosp/hardware/interfaces/audio/aidl/default/android.hardware.audio.service-aidl.example.rc @@ -0,0 +1,23 @@ + +service vendor.audio-hal-aidl /apex/com.android.hardware.audio/bin/hw/android.hardware.audio.service-aidl.example + class hal + user audioserver + # media gid needed for /dev/fm (radio) and for /data/misc/media (tee) + group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub + capabilities SYS_NICE + # setting RLIMIT_RTPRIO allows binder RT priority inheritance + rlimit rtprio 10 10 + ioprio rt 4 + task_profiles ProcessCapacityHigh HighPerformance + onrestart restart audioserver + +service vendor.audio-effect-hal-aidl /apex/com.android.hardware.audio/bin/hw/android.hardware.audio.effect.service-aidl.example + class hal + user audioserver + # media gid needed for /dev/fm (radio) and for /data/misc/media (tee) + group audio media + # setting RLIMIT_RTPRIO allows binder RT priority inheritance + rlimit rtprio 10 10 + ioprio rt 4 + task_profiles ProcessCapacityHigh HighPerformance + onrestart restart audioserver \ No newline at end of file diff --git a/aosp/hardware/interfaces/audio/common/7.0/example/android.hardware.audio@7.0-service.example.rc b/aosp/hardware/interfaces/audio/common/7.0/example/android.hardware.audio@7.0-service.example.rc new file mode 100644 index 000000000..5931f5265 --- /dev/null +++ b/aosp/hardware/interfaces/audio/common/7.0/example/android.hardware.audio@7.0-service.example.rc @@ -0,0 +1,6 @@ +service vendor.audio-hal-7-0 /vendor/bin/hw/android.hardware.audio@7.0-service.example + class hal + user audioserver + group audio + ioprio rt 4 + task_profiles ProcessCapacityHigh HighPerformance diff --git a/aosp/hardware/interfaces/audio/common/all-versions/default/service/android.hardware.audio.service.rc b/aosp/hardware/interfaces/audio/common/all-versions/default/service/android.hardware.audio.service.rc new file mode 100644 index 000000000..a63c2cf6b --- /dev/null +++ b/aosp/hardware/interfaces/audio/common/all-versions/default/service/android.hardware.audio.service.rc @@ -0,0 +1,11 @@ +service vendor.audio-hal /vendor/bin/hw/android.hardware.audio.service + class hal + user audioserver + # media gid needed for /dev/fm (radio) and for /data/misc/media (tee) + group audio camera drmrpc inet media mediadrm net_bt net_bt_admin net_bw_acct wakelock context_hub system + capabilities SYS_NICE + # setting RLIMIT_RTPRIO allows binder RT priority inheritance + rlimit rtprio 10 10 + ioprio rt 4 + task_profiles ProcessCapacityHigh HighPerformance + onrestart restart audioserver diff --git a/aosp/hardware/interfaces/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc b/aosp/hardware/interfaces/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc new file mode 100644 index 000000000..60321d05d --- /dev/null +++ b/aosp/hardware/interfaces/bluetooth/1.0/default/android.hardware.bluetooth@1.0-service.rc @@ -0,0 +1,8 @@ +service vendor.bluetooth-1-0 /vendor/bin/hw/android.hardware.bluetooth@1.0-service + interface android.hardware.bluetooth@1.0::IBluetoothHci default + class hal + capabilities NET_ADMIN SYS_NICE + user bluetooth + group bluetooth + task_profiles HighPerformance + diff --git a/aosp/hardware/interfaces/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc b/aosp/hardware/interfaces/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc new file mode 100644 index 000000000..3fb5c4ace --- /dev/null +++ b/aosp/hardware/interfaces/bluetooth/1.1/default/android.hardware.bluetooth@1.1-service.rc @@ -0,0 +1,9 @@ +service vendor.bluetooth-1-1 /vendor/bin/hw/android.hardware.bluetooth@1.1-service + interface android.hardware.bluetooth@1.1::IBluetoothHci default + interface android.hardware.bluetooth@1.0::IBluetoothHci default + class hal + capabilities NET_ADMIN SYS_NICE + user bluetooth + group bluetooth + task_profiles HighPerformance + diff --git a/aosp/hardware/interfaces/bluetooth/aidl/default/bluetooth-service-default.rc b/aosp/hardware/interfaces/bluetooth/aidl/default/bluetooth-service-default.rc new file mode 100644 index 000000000..ede4108fd --- /dev/null +++ b/aosp/hardware/interfaces/bluetooth/aidl/default/bluetooth-service-default.rc @@ -0,0 +1,6 @@ +service vendor.bluetooth-default /vendor/bin/hw/android.hardware.bluetooth-service.default + class hal + capabilities NET_ADMIN SYS_NICE + user bluetooth + group bluetooth + task_profiles HighPerformance diff --git a/aosp/hardware/interfaces/bluetooth/finder/aidl/default/bluetooth-finder-service-default.rc b/aosp/hardware/interfaces/bluetooth/finder/aidl/default/bluetooth-finder-service-default.rc new file mode 100644 index 000000000..e98c37fcb --- /dev/null +++ b/aosp/hardware/interfaces/bluetooth/finder/aidl/default/bluetooth-finder-service-default.rc @@ -0,0 +1,6 @@ +service vendor.bluetooth.finder-default /vendor/bin/hw/android.hardware.bluetooth.finder-service.default + class hal + capabilities NET_ADMIN SYS_NICE + user bluetooth + group bluetooth + task_profiles ServicePerformance diff --git a/aosp/hardware/interfaces/bluetooth/lmp_event/aidl/default/lmp_event-default.rc b/aosp/hardware/interfaces/bluetooth/lmp_event/aidl/default/lmp_event-default.rc new file mode 100644 index 000000000..0c1e2c3bd --- /dev/null +++ b/aosp/hardware/interfaces/bluetooth/lmp_event/aidl/default/lmp_event-default.rc @@ -0,0 +1,6 @@ +service vendor.bluetooth.lmp_event-default /vendor/bin/hw/android.hardware.bluetooth.lmp_event-service.default + class hal + capabilities NET_ADMIN SYS_NICE + user bluetooth + group bluetooth + task_profiles HighPerformance diff --git a/aosp/hardware/interfaces/bluetooth/ranging/aidl/default/bluetooth-ranging-service-default.rc b/aosp/hardware/interfaces/bluetooth/ranging/aidl/default/bluetooth-ranging-service-default.rc new file mode 100644 index 000000000..e82791cbc --- /dev/null +++ b/aosp/hardware/interfaces/bluetooth/ranging/aidl/default/bluetooth-ranging-service-default.rc @@ -0,0 +1,6 @@ +service vendor.bluetooth.ranging-default /vendor/bin/hw/android.hardware.bluetooth.ranging-service.default + class hal + capabilities NET_ADMIN SYS_NICE + user bluetooth + group bluetooth + task_profiles HighPerformance diff --git a/aosp/hardware/interfaces/health/2.1/default/android.hardware.health@2.1-service.rc b/aosp/hardware/interfaces/health/2.1/default/android.hardware.health@2.1-service.rc new file mode 100644 index 000000000..6f1201e06 --- /dev/null +++ b/aosp/hardware/interfaces/health/2.1/default/android.hardware.health@2.1-service.rc @@ -0,0 +1,5 @@ +service health-hal-2-1 /vendor/bin/hw/android.hardware.health@2.1-service + class hal charger + user system + group system + file /dev/kmsg w diff --git a/aosp/hardware/interfaces/health/aidl/default/android.hardware.health-service.example.rc b/aosp/hardware/interfaces/health/aidl/default/android.hardware.health-service.example.rc new file mode 100644 index 000000000..eeb87a15c --- /dev/null +++ b/aosp/hardware/interfaces/health/aidl/default/android.hardware.health-service.example.rc @@ -0,0 +1,16 @@ +service vendor.health-default /vendor/bin/hw/android.hardware.health-service.example + class hal + user system + group system + file /dev/kmsg w + +service vendor.charger /vendor/bin/hw/android.hardware.health-service.example --charger + class charger + seclabel u:r:charger_vendor:s0 + user system + group system wakelock input + capabilities SYS_BOOT + file /dev/kmsg w + file /sys/fs/pstore/console-ramoops-0 r + file /sys/fs/pstore/console-ramoops r + file /proc/last_kmsg r diff --git a/aosp/hardware/interfaces/health/aidl/default/android.hardware.health-service.example_recovery.rc b/aosp/hardware/interfaces/health/aidl/default/android.hardware.health-service.example_recovery.rc new file mode 100644 index 000000000..2e5155d4e --- /dev/null +++ b/aosp/hardware/interfaces/health/aidl/default/android.hardware.health-service.example_recovery.rc @@ -0,0 +1,6 @@ +service vendor.health-default /system/bin/hw/android.hardware.health-service.example_recovery + class hal + seclabel u:r:hal_health_default:s0 + user system + group system + file /dev/kmsg w diff --git a/aosp/hardware/interfaces/sensors/1.0/default/android.hardware.sensors@1.0-service.rc b/aosp/hardware/interfaces/sensors/1.0/default/android.hardware.sensors@1.0-service.rc new file mode 100644 index 000000000..e4553c2ee --- /dev/null +++ b/aosp/hardware/interfaces/sensors/1.0/default/android.hardware.sensors@1.0-service.rc @@ -0,0 +1,6 @@ +service vendor.sensors-hal-1-0 /vendor/bin/hw/android.hardware.sensors@1.0-service + interface android.hardware.sensors@1.0::ISensors default + class hal + user system + group system wakelock uhid context_hub + rlimit rtprio 10 10 diff --git a/aosp/hardware/interfaces/sensors/2.0/multihal/android.hardware.sensors@2.0-service-multihal.rc b/aosp/hardware/interfaces/sensors/2.0/multihal/android.hardware.sensors@2.0-service-multihal.rc new file mode 100644 index 000000000..2f4dcf5e9 --- /dev/null +++ b/aosp/hardware/interfaces/sensors/2.0/multihal/android.hardware.sensors@2.0-service-multihal.rc @@ -0,0 +1,6 @@ +service vendor.sensors-hal-2-0-multihal /vendor/bin/hw/android.hardware.sensors@2.0-service.multihal + class hal + user system + group system wakelock context_hub input + task_profiles ServiceCapacityLow + rlimit rtprio 10 10 diff --git a/aosp/hardware/interfaces/sensors/2.1/multihal/android.hardware.sensors@2.1-service-multihal.rc b/aosp/hardware/interfaces/sensors/2.1/multihal/android.hardware.sensors@2.1-service-multihal.rc new file mode 100644 index 000000000..7889d962c --- /dev/null +++ b/aosp/hardware/interfaces/sensors/2.1/multihal/android.hardware.sensors@2.1-service-multihal.rc @@ -0,0 +1,6 @@ +service vendor.sensors-hal-2-1-multihal /vendor/bin/hw/android.hardware.sensors@2.1-service.multihal + class hal + user system + group system wakelock context_hub input + task_profiles ServiceCapacityLow + rlimit rtprio 10 10 diff --git a/aosp/hardware/interfaces/sensors/aidl/multihal/android.hardware.sensors-service-multihal.rc b/aosp/hardware/interfaces/sensors/aidl/multihal/android.hardware.sensors-service-multihal.rc new file mode 100644 index 000000000..1051ac395 --- /dev/null +++ b/aosp/hardware/interfaces/sensors/aidl/multihal/android.hardware.sensors-service-multihal.rc @@ -0,0 +1,6 @@ +service vendor.sensors-hal-multihal /vendor/bin/hw/android.hardware.sensors-service.multihal + class hal + user system + group system wakelock context_hub input uhid + task_profiles ServiceCapacityLow + rlimit rtprio 10 10 diff --git a/aosp/hardware/interfaces/wifi/aidl/default/android.hardware.wifi-service-lazy.rc b/aosp/hardware/interfaces/wifi/aidl/default/android.hardware.wifi-service-lazy.rc new file mode 100644 index 000000000..8bb810a0a --- /dev/null +++ b/aosp/hardware/interfaces/wifi/aidl/default/android.hardware.wifi-service-lazy.rc @@ -0,0 +1,8 @@ +service vendor.wifi_hal_legacy /vendor/bin/hw/android.hardware.wifi-service-lazy + interface aidl android.hardware.wifi.IWifi/default + oneshot + disabled + class hal + capabilities NET_ADMIN NET_RAW + user wifi + group wifi gps diff --git a/aosp/hardware/interfaces/wifi/aidl/default/android.hardware.wifi-service.rc b/aosp/hardware/interfaces/wifi/aidl/default/android.hardware.wifi-service.rc new file mode 100644 index 000000000..7ea93d914 --- /dev/null +++ b/aosp/hardware/interfaces/wifi/aidl/default/android.hardware.wifi-service.rc @@ -0,0 +1,6 @@ +service vendor.wifi_hal_legacy /vendor/bin/hw/android.hardware.wifi-service + interface aidl android.hardware.wifi.IWifi/default + class hal + capabilities NET_ADMIN NET_RAW + user wifi + group wifi gps diff --git a/aosp/hardware/ril/rild/rild.legacy.rc b/aosp/hardware/ril/rild/rild.legacy.rc new file mode 100644 index 000000000..bcde687d4 --- /dev/null +++ b/aosp/hardware/ril/rild/rild.legacy.rc @@ -0,0 +1,5 @@ +service ril-daemon /vendor/bin/hw/rild + class main + user radio + group radio cache inet misc audio log readproc wakelock + capabilities NET_ADMIN NET_RAW diff --git a/aosp/hardware/ril/rild/rild.rc b/aosp/hardware/ril/rild/rild.rc new file mode 100644 index 000000000..e4e72e141 --- /dev/null +++ b/aosp/hardware/ril/rild/rild.rc @@ -0,0 +1,5 @@ +service vendor.ril-daemon /vendor/bin/hw/rild + class main + user radio + group radio cache inet misc audio log readproc wakelock + capabilities NET_ADMIN NET_RAW diff --git a/aosp/packages/modules/Bluetooth/system/osi/src/alarm.cc b/aosp/packages/modules/Bluetooth/system/osi/src/alarm.cc new file mode 100644 index 000000000..3094cb06f --- /dev/null +++ b/aosp/packages/modules/Bluetooth/system/osi/src/alarm.cc @@ -0,0 +1,764 @@ +/****************************************************************************** + * + * Copyright 2014 Google, Inc. + * + * 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 "bt_osi_alarm" + +#include "osi/include/alarm.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "check.h" +#include "os/log.h" +#include "osi/include/allocator.h" +#include "osi/include/fixed_queue.h" +#include "osi/include/list.h" +#include "osi/include/osi.h" +#include "osi/include/thread.h" +#include "osi/include/wakelock.h" +#include "osi/semaphore.h" +#include "stack/include/main_thread.h" + +using base::Bind; +using base::CancelableClosure; + +// Callback and timer threads should run at RT priority in order to ensure they +// meet audio deadlines. Use this priority for all audio/timer related thread. +static const int THREAD_RT_PRIORITY = 1; + +typedef struct { + size_t count; + uint64_t total_ms; + uint64_t max_ms; +} stat_t; + +// Alarm-related information and statistics +typedef struct { + const char* name; + size_t scheduled_count; + size_t canceled_count; + size_t rescheduled_count; + size_t total_updates; + uint64_t last_update_ms; + stat_t overdue_scheduling; + stat_t premature_scheduling; +} alarm_stats_t; + +/* Wrapper around CancellableClosure that let it be embedded in structs, without + * need to define copy operator. */ +struct CancelableClosureInStruct { + base::CancelableClosure i; + + CancelableClosureInStruct& operator=(const CancelableClosureInStruct& in) { + if (!in.i.callback().is_null()) i.Reset(in.i.callback()); + return *this; + } +}; + +struct alarm_t { + // The mutex is held while the callback for this alarm is being executed. + // It allows us to release the coarse-grained monitor lock while a + // potentially long-running callback is executing. |alarm_cancel| uses this + // mutex to provide a guarantee to its caller that the callback will not be + // in progress when it returns. + std::shared_ptr callback_mutex; + uint64_t creation_time_ms; + uint64_t period_ms; + uint64_t deadline_ms; + uint64_t prev_deadline_ms; // Previous deadline - used for accounting of + // periodic timers + bool is_periodic; + fixed_queue_t* queue; // The processing queue to add this alarm to + alarm_callback_t callback; + void* data; + alarm_stats_t stats; + + bool for_msg_loop; // True, if the alarm should be processed on message loop + CancelableClosureInStruct closure; // posted to message loop for processing +}; + +// If the next wakeup time is less than this threshold, we should acquire +// a wakelock instead of setting a wake alarm so we're not bouncing in +// and out of suspend frequently. This value is externally visible to allow +// unit tests to run faster. It should not be modified by production code. +int64_t TIMER_INTERVAL_FOR_WAKELOCK_IN_MS = 3000; +static const clockid_t CLOCK_ID = CLOCK_BOOTTIME; + +// This mutex ensures that the |alarm_set|, |alarm_cancel|, and alarm callback +// functions execute serially and not concurrently. As a result, this mutex +// also protects the |alarms| list. +static std::mutex alarms_mutex; +static list_t* alarms; +static timer_t timer; +static timer_t wakeup_timer; +static bool timer_set; + +// All alarm callbacks are dispatched from |dispatcher_thread| +static thread_t* dispatcher_thread; +static bool dispatcher_thread_active; +static semaphore_t* alarm_expired; + +// Default alarm callback thread and queue +static thread_t* default_callback_thread; +static fixed_queue_t* default_callback_queue; + +static alarm_t* alarm_new_internal(const char* name, bool is_periodic); +static bool lazy_initialize(void); +static uint64_t now_ms(void); +static void alarm_set_internal(alarm_t* alarm, uint64_t period_ms, + alarm_callback_t cb, void* data, + fixed_queue_t* queue, bool for_msg_loop); +static void alarm_cancel_internal(alarm_t* alarm); +static void remove_pending_alarm(alarm_t* alarm); +static void schedule_next_instance(alarm_t* alarm); +static void reschedule_root_alarm(void); +static void alarm_queue_ready(fixed_queue_t* queue, void* context); +static void timer_callback(void* data); +static void callback_dispatch(void* context); +static bool timer_create_internal(const clockid_t clock_id, timer_t* timer); +static void update_scheduling_stats(alarm_stats_t* stats, uint64_t now_ms, + uint64_t deadline_ms); +// Registers |queue| for processing alarm callbacks on |thread|. +// |queue| may not be NULL. |thread| may not be NULL. +static void alarm_register_processing_queue(fixed_queue_t* queue, + thread_t* thread); + +static void update_stat(stat_t* stat, uint64_t delta_ms) { + if (stat->max_ms < delta_ms) stat->max_ms = delta_ms; + stat->total_ms += delta_ms; + stat->count++; +} + +alarm_t* alarm_new(const char* name) { return alarm_new_internal(name, false); } + +alarm_t* alarm_new_periodic(const char* name) { + return alarm_new_internal(name, true); +} + +static alarm_t* alarm_new_internal(const char* name, bool is_periodic) { + // Make sure we have a list we can insert alarms into. + if (!alarms && !lazy_initialize()) { + CHECK(false); // if initialization failed, we should not continue + return NULL; + } + + alarm_t* ret = static_cast(osi_calloc(sizeof(alarm_t))); + + std::shared_ptr ptr(new std::recursive_mutex()); + ret->callback_mutex = ptr; + ret->is_periodic = is_periodic; + ret->stats.name = osi_strdup(name); + + ret->for_msg_loop = false; + // placement new + new (&ret->closure) CancelableClosureInStruct(); + + // NOTE: The stats were reset by osi_calloc() above + + return ret; +} + +void alarm_free(alarm_t* alarm) { + if (!alarm) return; + + alarm_cancel(alarm); + + osi_free((void*)alarm->stats.name); + alarm->closure.~CancelableClosureInStruct(); + alarm->callback_mutex.reset(); + osi_free(alarm); +} + +uint64_t alarm_get_remaining_ms(const alarm_t* alarm) { + CHECK(alarm != NULL); + uint64_t remaining_ms = 0; + uint64_t just_now_ms = now_ms(); + + std::lock_guard lock(alarms_mutex); + if (alarm->deadline_ms > just_now_ms) + remaining_ms = alarm->deadline_ms - just_now_ms; + + return remaining_ms; +} + +void alarm_set(alarm_t* alarm, uint64_t interval_ms, alarm_callback_t cb, + void* data) { + alarm_set_internal(alarm, interval_ms, cb, data, default_callback_queue, + false); +} + +void alarm_set_on_mloop(alarm_t* alarm, uint64_t interval_ms, + alarm_callback_t cb, void* data) { + alarm_set_internal(alarm, interval_ms, cb, data, NULL, true); +} + +// Runs in exclusion with alarm_cancel and timer_callback. +static void alarm_set_internal(alarm_t* alarm, uint64_t period_ms, + alarm_callback_t cb, void* data, + fixed_queue_t* queue, bool for_msg_loop) { + CHECK(alarms != NULL); + CHECK(alarm != NULL); + CHECK(cb != NULL); + + std::lock_guard lock(alarms_mutex); + + alarm->creation_time_ms = now_ms(); + alarm->period_ms = period_ms; + alarm->queue = queue; + alarm->callback = cb; + alarm->data = data; + alarm->for_msg_loop = for_msg_loop; + + schedule_next_instance(alarm); + alarm->stats.scheduled_count++; +} + +void alarm_cancel(alarm_t* alarm) { + CHECK(alarms != NULL); + if (!alarm) return; + + std::shared_ptr local_mutex_ref; + { + std::lock_guard lock(alarms_mutex); + local_mutex_ref = alarm->callback_mutex; + alarm_cancel_internal(alarm); + } + + // If the callback for |alarm| is in progress, wait here until it completes. + std::lock_guard lock(*local_mutex_ref); +} + +// Internal implementation of canceling an alarm. +// The caller must hold the |alarms_mutex| +static void alarm_cancel_internal(alarm_t* alarm) { + bool needs_reschedule = + (!list_is_empty(alarms) && list_front(alarms) == alarm); + + remove_pending_alarm(alarm); + + alarm->deadline_ms = 0; + alarm->prev_deadline_ms = 0; + alarm->callback = NULL; + alarm->data = NULL; + alarm->stats.canceled_count++; + alarm->queue = NULL; + + if (needs_reschedule) reschedule_root_alarm(); +} + +bool alarm_is_scheduled(const alarm_t* alarm) { + if ((alarms == NULL) || (alarm == NULL)) return false; + return (alarm->callback != NULL); +} + +void alarm_cleanup(void) { + // If lazy_initialize never ran there is nothing else to do + if (!alarms) return; + + dispatcher_thread_active = false; + semaphore_post(alarm_expired); + thread_free(dispatcher_thread); + dispatcher_thread = NULL; + + std::lock_guard lock(alarms_mutex); + + fixed_queue_free(default_callback_queue, NULL); + default_callback_queue = NULL; + thread_free(default_callback_thread); + default_callback_thread = NULL; + + timer_delete(wakeup_timer); + timer_delete(timer); + semaphore_free(alarm_expired); + alarm_expired = NULL; + + list_free(alarms); + alarms = NULL; +} + +static bool lazy_initialize(void) { + CHECK(alarms == NULL); + + // timer_t doesn't have an invalid value so we must track whether + // the |timer| variable is valid ourselves. + bool timer_initialized = false; + bool wakeup_timer_initialized = false; + + std::lock_guard lock(alarms_mutex); + + alarms = list_new(NULL); + if (!alarms) { + LOG_ERROR("%s unable to allocate alarm list.", __func__); + goto error; + } + + if (!timer_create_internal(CLOCK_ID, &timer)) goto error; + timer_initialized = true; + + if (!timer_create_internal(CLOCK_BOOTTIME, &wakeup_timer)) { + goto error; + } + wakeup_timer_initialized = true; + + alarm_expired = semaphore_new(0); + if (!alarm_expired) { + LOG_ERROR("%s unable to create alarm expired semaphore", __func__); + goto error; + } + + default_callback_thread = + thread_new_sized("alarm_default_callbacks", SIZE_MAX); + if (default_callback_thread == NULL) { + LOG_ERROR("%s unable to create default alarm callbacks thread.", __func__); + goto error; + } + thread_set_rt_priority(default_callback_thread, THREAD_RT_PRIORITY); + default_callback_queue = fixed_queue_new(SIZE_MAX); + if (default_callback_queue == NULL) { + LOG_ERROR("%s unable to create default alarm callbacks queue.", __func__); + goto error; + } + alarm_register_processing_queue(default_callback_queue, + default_callback_thread); + + dispatcher_thread_active = true; + dispatcher_thread = thread_new("alarm_dispatcher"); + if (!dispatcher_thread) { + LOG_ERROR("%s unable to create alarm callback thread.", __func__); + goto error; + } + thread_set_rt_priority(dispatcher_thread, THREAD_RT_PRIORITY); + thread_post(dispatcher_thread, callback_dispatch, NULL); + return true; + +error: + fixed_queue_free(default_callback_queue, NULL); + default_callback_queue = NULL; + thread_free(default_callback_thread); + default_callback_thread = NULL; + + thread_free(dispatcher_thread); + dispatcher_thread = NULL; + + dispatcher_thread_active = false; + + semaphore_free(alarm_expired); + alarm_expired = NULL; + + if (wakeup_timer_initialized) timer_delete(wakeup_timer); + + if (timer_initialized) timer_delete(timer); + + list_free(alarms); + alarms = NULL; + + return false; +} + +static uint64_t now_ms(void) { + CHECK(alarms != NULL); + + struct timespec ts; + if (clock_gettime(CLOCK_ID, &ts) == -1) { + LOG_ERROR("%s unable to get current time: %s", __func__, strerror(errno)); + return 0; + } + + return (ts.tv_sec * 1000LL) + (ts.tv_nsec / 1000000LL); +} + +// Remove alarm from internal alarm list and the processing queue +// The caller must hold the |alarms_mutex| +static void remove_pending_alarm(alarm_t* alarm) { + list_remove(alarms, alarm); + + if (alarm->for_msg_loop) { + alarm->closure.i.Cancel(); + } else { + while (fixed_queue_try_remove_from_queue(alarm->queue, alarm) != NULL) { + // Remove all repeated alarm instances from the queue. + // NOTE: We are defensive here - we shouldn't have repeated alarm + // instances + } + } +} + +// Must be called with |alarms_mutex| held +static void schedule_next_instance(alarm_t* alarm) { + // If the alarm is currently set and it's at the start of the list, + // we'll need to re-schedule since we've adjusted the earliest deadline. + bool needs_reschedule = + (!list_is_empty(alarms) && list_front(alarms) == alarm); + if (alarm->callback) remove_pending_alarm(alarm); + + // Calculate the next deadline for this alarm + uint64_t just_now_ms = now_ms(); + uint64_t ms_into_period = 0; + if ((alarm->is_periodic) && (alarm->period_ms != 0)) + ms_into_period = + ((just_now_ms - alarm->creation_time_ms) % alarm->period_ms); + alarm->deadline_ms = just_now_ms + (alarm->period_ms - ms_into_period); + + // Add it into the timer list sorted by deadline (earliest deadline first). + if (list_is_empty(alarms) || + ((alarm_t*)list_front(alarms))->deadline_ms > alarm->deadline_ms) { + list_prepend(alarms, alarm); + } else { + for (list_node_t* node = list_begin(alarms); node != list_end(alarms); + node = list_next(node)) { + list_node_t* next = list_next(node); + if (next == list_end(alarms) || + ((alarm_t*)list_node(next))->deadline_ms > alarm->deadline_ms) { + list_insert_after(alarms, node, alarm); + break; + } + } + } + + // If the new alarm has the earliest deadline, we need to re-evaluate our + // schedule. + if (needs_reschedule || + (!list_is_empty(alarms) && list_front(alarms) == alarm)) { + reschedule_root_alarm(); + } +} + +// NOTE: must be called with |alarms_mutex| held +static void reschedule_root_alarm(void) { + CHECK(alarms != NULL); + + const bool timer_was_set = timer_set; + alarm_t* next; + int64_t next_expiration; + + // If used in a zeroed state, disarms the timer. + struct itimerspec timer_time; + memset(&timer_time, 0, sizeof(timer_time)); + + if (list_is_empty(alarms)) goto done; + + next = static_cast(list_front(alarms)); + next_expiration = next->deadline_ms - now_ms(); + if (next_expiration < TIMER_INTERVAL_FOR_WAKELOCK_IN_MS) { + if (!timer_set) { + if (!wakelock_acquire()) { + LOG_ERROR("%s unable to acquire wake lock", __func__); + } + } + + timer_time.it_value.tv_sec = (next->deadline_ms / 1000); + timer_time.it_value.tv_nsec = (next->deadline_ms % 1000) * 1000000LL; + + // It is entirely unsafe to call timer_settime(2) with a zeroed timerspec + // for timers with *_ALARM clock IDs. Although the man page states that the + // timer would be canceled, the current behavior (as of Linux kernel 3.17) + // is that the callback is issued immediately. The only way to cancel an + // *_ALARM timer is to delete the timer. But unfortunately, deleting and + // re-creating a timer is rather expensive; every timer_create(2) spawns a + // new thread. So we simply set the timer to fire at the largest possible + // time. + // + // If we've reached this code path, we're going to grab a wake lock and + // wait for the next timer to fire. In that case, there's no reason to + // have a pending wakeup timer so we simply cancel it. + struct itimerspec end_of_time; + memset(&end_of_time, 0, sizeof(end_of_time)); + end_of_time.it_value.tv_sec = (time_t)(1LL << (sizeof(time_t) * 8 - 2)); + timer_settime(wakeup_timer, TIMER_ABSTIME, &end_of_time, NULL); + } else { + // WARNING: do not attempt to use relative timers with *_ALARM clock IDs + // in kernels before 3.17 unless you have the following patch: + // https://lkml.org/lkml/2014/7/7/576 + struct itimerspec wakeup_time; + memset(&wakeup_time, 0, sizeof(wakeup_time)); + + wakeup_time.it_value.tv_sec = (next->deadline_ms / 1000); + wakeup_time.it_value.tv_nsec = (next->deadline_ms % 1000) * 1000000LL; + if (timer_settime(wakeup_timer, TIMER_ABSTIME, &wakeup_time, NULL) == -1) + LOG_ERROR("%s unable to set wakeup timer: %s", __func__, strerror(errno)); + } + +done: + timer_set = + timer_time.it_value.tv_sec != 0 || timer_time.it_value.tv_nsec != 0; + if (timer_was_set && !timer_set) { + wakelock_release(); + } + + if (timer_settime(timer, TIMER_ABSTIME, &timer_time, NULL) == -1) + LOG_ERROR("%s unable to set timer: %s", __func__, strerror(errno)); + + // If next expiration was in the past (e.g. short timer that got context + // switched) then the timer might have diarmed itself. Detect this case and + // work around it by manually signalling the |alarm_expired| semaphore. + // + // It is possible that the timer was actually super short (a few + // milliseconds) and the timer expired normally before we called + // |timer_gettime|. Worst case, |alarm_expired| is signaled twice for that + // alarm. Nothing bad should happen in that case though since the callback + // dispatch function checks to make sure the timer at the head of the list + // actually expired. + if (timer_set) { + struct itimerspec time_to_expire; + timer_gettime(timer, &time_to_expire); + if (time_to_expire.it_value.tv_sec == 0 && + time_to_expire.it_value.tv_nsec == 0) { + LOG_INFO( + + "%s alarm expiration too close for posix timers, switching to guns", + __func__); + semaphore_post(alarm_expired); + } + } +} + +static void alarm_register_processing_queue(fixed_queue_t* queue, + thread_t* thread) { + CHECK(queue != NULL); + CHECK(thread != NULL); + + fixed_queue_register_dequeue(queue, thread_get_reactor(thread), + alarm_queue_ready, NULL); +} + +static void alarm_ready_generic(alarm_t* alarm, + std::unique_lock& lock) { + if (alarm == NULL) { + return; // The alarm was probably canceled + } + + // + // If the alarm is not periodic, we've fully serviced it now, and can reset + // some of its internal state. This is useful to distinguish between expired + // alarms and active ones. + // + if (!alarm->callback) { + LOG(FATAL) << __func__ + << ": timer callback is NULL! Name=" << alarm->stats.name; + } + alarm_callback_t callback = alarm->callback; + void* data = alarm->data; + uint64_t deadline_ms = alarm->deadline_ms; + if (alarm->is_periodic) { + // The periodic alarm has been rescheduled and alarm->deadline has been + // updated, hence we need to use the previous deadline. + deadline_ms = alarm->prev_deadline_ms; + } else { + alarm->deadline_ms = 0; + alarm->callback = NULL; + alarm->data = NULL; + alarm->queue = NULL; + } + + // Increment the reference count of the mutex so it doesn't get freed + // before the callback gets finished executing. + std::shared_ptr local_mutex_ref = alarm->callback_mutex; + std::lock_guard cb_lock(*local_mutex_ref); + lock.unlock(); + + // Update the statistics + update_scheduling_stats(&alarm->stats, now_ms(), deadline_ms); + + // NOTE: Do NOT access "alarm" after the callback, as a safety precaution + // in case the callback itself deleted the alarm. + callback(data); +} + +static void alarm_ready_mloop(alarm_t* alarm) { + std::unique_lock lock(alarms_mutex); + alarm_ready_generic(alarm, lock); +} + +static void alarm_queue_ready(fixed_queue_t* queue, UNUSED_ATTR void* context) { + CHECK(queue != NULL); + + std::unique_lock lock(alarms_mutex); + alarm_t* alarm = (alarm_t*)fixed_queue_try_dequeue(queue); + alarm_ready_generic(alarm, lock); +} + +// Callback function for wake alarms and our posix timer +static void timer_callback(UNUSED_ATTR void* ptr) { + semaphore_post(alarm_expired); +} + +// Function running on |dispatcher_thread| that performs the following: +// (1) Receives a signal using |alarm_exired| that the alarm has expired +// (2) Dispatches the alarm callback for processing by the corresponding +// thread for that alarm. +static void callback_dispatch(UNUSED_ATTR void* context) { + while (true) { + semaphore_wait(alarm_expired); + if (!dispatcher_thread_active) break; + + std::lock_guard lock(alarms_mutex); + alarm_t* alarm; + + // Take into account that the alarm may get cancelled before we get to it. + // We're done here if there are no alarms or the alarm at the front is in + // the future. Exit right away since there's nothing left to do. + if (list_is_empty(alarms) || + (alarm = static_cast(list_front(alarms)))->deadline_ms > + now_ms()) { + reschedule_root_alarm(); + continue; + } + + list_remove(alarms, alarm); + + if (alarm->is_periodic) { + alarm->prev_deadline_ms = alarm->deadline_ms; + schedule_next_instance(alarm); + alarm->stats.rescheduled_count++; + } + reschedule_root_alarm(); + + // Enqueue the alarm for processing + if (alarm->for_msg_loop) { + if (!get_main_thread()) { + LOG_ERROR("%s: message loop already NULL. Alarm: %s", __func__, + alarm->stats.name); + continue; + } + + alarm->closure.i.Reset(Bind(alarm_ready_mloop, alarm)); + get_main_thread()->DoInThread(FROM_HERE, alarm->closure.i.callback()); + } else { + fixed_queue_enqueue(alarm->queue, alarm); + } + } + + LOG_INFO("%s Callback thread exited", __func__); +} + +static bool timer_create_internal(const clockid_t clock_id, timer_t* timer) { + CHECK(timer != NULL); + + struct sigevent sigevent; + // create timer with RT priority thread + pthread_attr_t thread_attr; + pthread_attr_init(&thread_attr); + pthread_attr_setschedpolicy(&thread_attr, SCHED_FIFO); + struct sched_param param; + param.sched_priority = THREAD_RT_PRIORITY; + pthread_attr_setschedparam(&thread_attr, ¶m); + + memset(&sigevent, 0, sizeof(sigevent)); + sigevent.sigev_notify = SIGEV_THREAD; + sigevent.sigev_notify_function = (void (*)(union sigval))timer_callback; + sigevent.sigev_notify_attributes = &thread_attr; + if (timer_create(clock_id, &sigevent, timer) == -1) { + LOG_ERROR("%s unable to create timer with clock %d: %s", __func__, clock_id, + strerror(errno)); + if (clock_id == CLOCK_BOOTTIME_ALARM) { + LOG_ERROR( + "The kernel might not have support for " + "timer_create(CLOCK_BOOTTIME_ALARM): " + "https://lwn.net/Articles/429925/"); + LOG_ERROR( + "See following patches: " + "https://git.kernel.org/cgit/linux/kernel/git/torvalds/" + "linux.git/log/?qt=grep&q=CLOCK_BOOTTIME_ALARM"); + } + return false; + } + + return true; +} + +static void update_scheduling_stats(alarm_stats_t* stats, uint64_t now_ms, + uint64_t deadline_ms) { + stats->total_updates++; + stats->last_update_ms = now_ms; + + if (deadline_ms < now_ms) { + // Overdue scheduling + uint64_t delta_ms = now_ms - deadline_ms; + update_stat(&stats->overdue_scheduling, delta_ms); + } else if (deadline_ms > now_ms) { + // Premature scheduling + uint64_t delta_ms = deadline_ms - now_ms; + update_stat(&stats->premature_scheduling, delta_ms); + } +} + +static void dump_stat(int fd, stat_t* stat, const char* description) { + uint64_t average_time_ms = 0; + if (stat->count != 0) average_time_ms = stat->total_ms / stat->count; + + dprintf(fd, "%-51s: %llu / %llu / %llu\n", description, + (unsigned long long)stat->total_ms, (unsigned long long)stat->max_ms, + (unsigned long long)average_time_ms); +} + +void alarm_debug_dump(int fd) { + dprintf(fd, "\nBluetooth Alarms Statistics:\n"); + + std::lock_guard lock(alarms_mutex); + + if (alarms == NULL) { + dprintf(fd, " None\n"); + return; + } + + uint64_t just_now_ms = now_ms(); + + dprintf(fd, " Total Alarms: %zu\n\n", list_length(alarms)); + + // Dump info for each alarm + for (list_node_t* node = list_begin(alarms); node != list_end(alarms); + node = list_next(node)) { + alarm_t* alarm = (alarm_t*)list_node(node); + alarm_stats_t* stats = &alarm->stats; + + dprintf(fd, " Alarm : %s (%s)\n", stats->name, + (alarm->is_periodic) ? "PERIODIC" : "SINGLE"); + + dprintf(fd, "%-51s: %zu / %zu / %zu / %zu\n", + " Action counts (sched/resched/exec/cancel)", + stats->scheduled_count, stats->rescheduled_count, + stats->total_updates, stats->canceled_count); + + dprintf(fd, "%-51s: %zu / %zu\n", + " Deviation counts (overdue/premature)", + stats->overdue_scheduling.count, stats->premature_scheduling.count); + + dprintf(fd, "%-51s: %llu / %llu / %lld\n", + " Time in ms (since creation/interval/remaining)", + (unsigned long long)(just_now_ms - alarm->creation_time_ms), + (unsigned long long)alarm->period_ms, + (long long)(alarm->deadline_ms - just_now_ms)); + + dump_stat(fd, &stats->overdue_scheduling, + " Overdue scheduling time in ms (total/max/avg)"); + + dump_stat(fd, &stats->premature_scheduling, + " Premature scheduling time in ms (total/max/avg)"); + + dprintf(fd, "\n"); + } +} diff --git a/aosp/packages/modules/Virtualization/microdroid/init.rc b/aosp/packages/modules/Virtualization/microdroid/init.rc new file mode 100644 index 000000000..2fe2ec7a2 --- /dev/null +++ b/aosp/packages/modules/Virtualization/microdroid/init.rc @@ -0,0 +1,177 @@ +# Copyright (C) 2021 The Android Open Source Project +# +# init.rc for microdroid. This contains a minimal script plus basic service definitions (e.g. apexd) +# needed for microdroid to run. +# TODO(b/179340780): support APEX init scripts +# +# IMPORTANT: Do not create world writable files or directories. +# This is a common source of Android security bugs. +# + +import /init.environ.rc + +# Cgroups are mounted right before early-init using list from /etc/cgroups.json +on early-init + # Android doesn't need kernel module autoloading, and it causes SELinux + # denials. So disable it by setting modprobe to the empty string. Note: to + # explicitly set a sysctl to an empty string, a trailing newline is needed. + write /proc/sys/kernel/modprobe \n + + # set RLIMIT_NICE to allow priorities from 19 to -20 + setrlimit nice 40 40 + + start ueventd + + # Generate empty linker config to suppress warnings + write /linkerconfig/ld.config.txt \# + chmod 644 /linkerconfig/ld.config.txt + + # Applies debug policy to decide whether to enable adb, adb root, and logcat. + # We don't directly exec the binary to specify stdio_to_kmsg. + exec_start init_debug_policy + + # Wait for ueventd to have finished cold boot. + # This is needed by prng-seeder (at least). + # (In Android this happens inside apexd-bootstrap.) + wait_for_prop ro.cold_boot_done true + +on init + mkdir /mnt/apk 0755 root root + mkdir /mnt/extra-apk 0755 root root + + # Allow the payload access to the console (default is 0600) + chmod 0666 /dev/console + + # Microdroid_manager starts apkdmverity/zipfuse/apexd + start microdroid_manager + + # Wait for apexd to finish activating APEXes before starting more processes. + # Microdroid starts apexd in VM mode in which apexd doesn't wait for init after setting + # apexd.status to activated, but immediately transitions to ready. Therefore, it's not safe to + # wait for the activated status, by the time this line is reached it may be already be ready. + wait_for_prop apexd.status ready + perform_apex_config + + # Notify to microdroid_manager that perform_apex_config is done. + # Microdroid_manager shouldn't execute payload before this, because app + # payloads are not designed to run with bootstrap bionic + setprop apex_config.done true + +on property:microdroid_manager.init_done=1 + # Stop ueventd to save memory + stop ueventd + +on init && property:ro.boot.microdroid.debuggable=1 + # Mount tracefs (with GID=AID_READTRACEFS) + mount tracefs tracefs /sys/kernel/tracing gid=3012 + +on property:init_debug_policy.adbd.enabled=1 + start adbd + +# Mount filesystems and start core system services. +on late-init + trigger early-fs + + # Mount fstab in init.{$device}.rc by mount_all command. Optional parameter + # '--early' can be specified to skip entries with 'latemount'. + # /system and /vendor must be mounted by the end of the fs stage, + # while /data is optional. + trigger fs + trigger post-fs + + # Mount fstab in init.{$device}.rc by mount_all with '--late' parameter + # to only mount entries with 'latemount'. This is needed if '--early' is + # specified in the previous mount_all command on the fs stage. + # With /system mounted and properties form /system + /factory available, + # some services can be started. + trigger late-fs + + # Wait for microdroid_manager to finish setting up sysprops from the payload config. + # Some further actions in the boot sequence might depend on the sysprops from the payloag, + # e.g. microdroid.config.enable_authfs configures whether to run authfs_service after + # /data is mounted. + wait_for_prop microdroid_manager.config_done 1 + + trigger post-fs-data + + trigger early-boot + trigger boot + +on post-fs + # Once everything is setup, no need to modify /. + # The bind+remount combination allows this to work in containers. + mount rootfs rootfs / remount bind ro nodev + + # TODO(b/185767624): change the hard-coded size? + mount tmpfs tmpfs /data noatime nosuid nodev noexec rw size=128M + + # We chown/chmod /data again so because mount is run as root + defaults + chown system system /data + chmod 0771 /data + + # We restorecon /data in case the userdata partition has been reset. + restorecon /data + + # set up misc directory structure first so that we can end early boot + # and start apexd + mkdir /data/misc 01771 system misc + # work around b/183668221 + restorecon /data/misc + + mkdir /data/misc/authfs 0700 root root + +on late-fs && property:ro.debuggable=1 + # Ensure that tracefs has the correct permissions. + # This does not work correctly if it is called in post-fs. + chmod 0755 /sys/kernel/tracing + chmod 0755 /sys/kernel/debug/tracing + +on post-fs-data + mark_post_data + + mkdir /data/vendor 0771 root root + mkdir /data/vendor_ce 0771 root root + mkdir /data/vendor_de 0771 root root + mkdir /data/vendor/hardware 0771 root root + + # For security reasons, /data/local/tmp should always be empty. + # Do not place files or directories in /data/local/tmp + mkdir /data/local 0751 root root + mkdir /data/local/tmp 0771 shell shell + +on post-fs-data && property:microdroid_manager.authfs.enabled=1 + start authfs_service + +on boot + # Mark boot completed. This will notify microdroid_manager to run payload. + setprop dev.bootcomplete 1 + +service apexd-vm /system/bin/apexd --vm + user root + group system + oneshot + disabled + capabilities CHOWN DAC_OVERRIDE FOWNER SYS_ADMIN + +service ueventd /system/bin/ueventd + user root + group root + class core + critical + seclabel u:r:ueventd:s0 + capabilities CHOWN DAC_OVERRIDE FOWNER FSETID MKNOD NET_ADMIN SETGID SETUID SYS_RAWIO SYS_ADMIN + +service console /system/bin/sh + class core + console + disabled + user shell + group shell log readproc + seclabel u:r:shell:s0 + setenv HOSTNAME console + +service init_debug_policy /system/bin/init_debug_policy + user root + oneshot + disabled + stdio_to_kmsg diff --git a/aosp/packages/modules/Virtualization/microdroid_manager/microdroid_manager.rc b/aosp/packages/modules/Virtualization/microdroid_manager/microdroid_manager.rc new file mode 100644 index 000000000..a540164a7 --- /dev/null +++ b/aosp/packages/modules/Virtualization/microdroid_manager/microdroid_manager.rc @@ -0,0 +1,15 @@ +service microdroid_manager /system/bin/microdroid_manager + disabled + # print android log to kmsg + file /dev/kmsg w + # redirect stdout/stderr to kmsg_debug + stdio_to_kmsg + setenv RUST_LOG info + # TODO(jooyung) remove this when microdroid_manager becomes a daemon + oneshot + # CAP_SYS_BOOT is required to exec kexecload from microdroid_manager + # CAP_SETPCAP is required to allow microdroid_manager to drop capabilities + # before executing the payload + capabilities SYS_ADMIN SYS_BOOT SETPCAP SETUID SETGID + user root + socket vm_payload_service stream 0666 system system diff --git a/aosp/system/apex/apexd/apexd.rc b/aosp/system/apex/apexd/apexd.rc new file mode 100644 index 000000000..8d61ed020 --- /dev/null +++ b/aosp/system/apex/apexd/apexd.rc @@ -0,0 +1,30 @@ +service apexd /system/bin/apexd + interface aidl apexservice + class core + user root + group system + oneshot + disabled # does not start with the core class + reboot_on_failure reboot,apexd-failed + # CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH required for apexdata snapshot & restore + # CAP_SYS_ADMIN is required to access device-mapper and to use mount syscall + capabilities CHOWN DAC_OVERRIDE FOWNER SYS_ADMIN + +service apexd-bootstrap /system/bin/apexd --bootstrap + user root + group system + oneshot + disabled + reboot_on_failure reboot,bootloader,bootstrap-apexd-failed + # CAP_SYS_ADMIN is required to access device-mapper and to use mount syscall + # apexd-bootstrap doesn't manage apexdata snapshot & restore, hence no need for other capabilities. + capabilities SYS_ADMIN + +service apexd-snapshotde /system/bin/apexd --snapshotde + user root + group system + oneshot + disabled + # CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH required for apexdata snapshot & restore + # apexd-snapshotde doesn't mount APEXes, hence no need for SYS_ADMIN capability + capabilities CHOWN DAC_OVERRIDE FOWNER diff --git a/aosp/system/core/llkd/llkd-debuggable.rc b/aosp/system/core/llkd/llkd-debuggable.rc new file mode 100644 index 000000000..8305d549d --- /dev/null +++ b/aosp/system/core/llkd/llkd-debuggable.rc @@ -0,0 +1,19 @@ +on property:ro.debuggable=1 + setprop llk.enable ${ro.llk.enable:-0} + setprop khungtask.enable ${ro.khungtask.enable:-1} + +on property:ro.llk.enable=eng + setprop llk.enable ${ro.debuggable:-0} + +on property:ro.khungtask.enable=eng + setprop khungtask.enable ${ro.debuggable:-0} + +service llkd-1 /system/bin/llkd + class late_start + disabled + user llkd + group llkd readproc + capabilities KILL SYS_PTRACE DAC_OVERRIDE SYS_ADMIN + file /dev/kmsg w + file /proc/sysrq-trigger w + task_profiles ServiceCapacityLow diff --git a/aosp/system/core/llkd/llkd.rc b/aosp/system/core/llkd/llkd.rc new file mode 100644 index 000000000..582650b6f --- /dev/null +++ b/aosp/system/core/llkd/llkd.rc @@ -0,0 +1,45 @@ +# eng default for ro.llk.enable and ro.khungtask.enable +on property:ro.debuggable=* + setprop llk.enable ${ro.llk.enable:-0} + setprop khungtask.enable ${ro.khungtask.enable:-0} + +on property:ro.llk.enable=true + setprop llk.enable true + +on property:llk.enable=1 + setprop llk.enable true + +on property:llk.enable=0 + setprop llk.enable false + +on property:ro.khungtask.enable=true + setprop khungtask.enable true + +on property:khungtask.enable=1 + setprop khungtask.enable true + +on property:khungtask.enable=0 + setprop khungtask.enable false + +# Configure [khungtaskd] +on property:khungtask.enable=true + write /proc/sys/kernel/hung_task_timeout_secs ${ro.khungtask.timeout:-720} + write /proc/sys/kernel/hung_task_warnings 65535 + write /proc/sys/kernel/hung_task_check_count 65535 + write /proc/sys/kernel/hung_task_panic 1 + +on property:khungtask.enable=false + write /proc/sys/kernel/hung_task_panic 0 + +on property:llk.enable=true + start llkd-${ro.debuggable:-0} + +service llkd-0 /system/bin/llkd + class late_start + disabled + user llkd + group llkd readproc + capabilities KILL + file /dev/kmsg w + file /proc/sysrq-trigger w + task_profiles ServiceCapacityLow diff --git a/aosp/system/core/storaged/storaged.rc b/aosp/system/core/storaged/storaged.rc new file mode 100644 index 000000000..c44a341de --- /dev/null +++ b/aosp/system/core/storaged/storaged.rc @@ -0,0 +1,7 @@ +service storaged /system/bin/storaged + class main + priority 10 + file /d/mmc0/mmc0:0001/ext_csd r + task_profiles ServiceCapacityLow + user root + group package_info diff --git a/aosp/system/logging/logd/logd.rc b/aosp/system/logging/logd/logd.rc new file mode 100644 index 000000000..b76c02799 --- /dev/null +++ b/aosp/system/logging/logd/logd.rc @@ -0,0 +1,37 @@ +service logd /system/bin/logd + socket logd stream 0666 logd logd + socket logdr seqpacket 0666 logd logd + socket logdw dgram+passcred 0222 logd logd + file /proc/kmsg r + file /dev/kmsg w + user logd + group logd system package_info readproc + priority 10 + task_profiles ServiceCapacityLow + onrestart setprop logd.ready false + +service logd-reinit /system/bin/logd --reinit + oneshot + disabled + user logd + group logd + task_profiles ServiceCapacityLow + +# Limit SELinux denial generation, defaulting to 5/second +service logd-auditctl /system/bin/auditctl -r ${persist.logd.audit.rate:-5} + oneshot + disabled + user logd + group logd + +on fs + write /dev/event-log-tags "# content owned by logd +" + chown logd logd /dev/event-log-tags + chmod 0644 /dev/event-log-tags + +on property:sys.boot_completed=1 + start logd-auditctl + +on property:persist.logd.audit.rate=* + start logd-auditctl diff --git a/aosp/system/memory/lmkd/lmkd.rc b/aosp/system/memory/lmkd/lmkd.rc new file mode 100644 index 000000000..5f4850311 --- /dev/null +++ b/aosp/system/memory/lmkd/lmkd.rc @@ -0,0 +1,51 @@ +service lmkd /system/bin/lmkd + class core + user lmkd + group lmkd system readproc + capabilities DAC_OVERRIDE KILL SYS_NICE SYS_RESOURCE + critical + socket lmkd seqpacket+passcred 0660 system system + task_profiles ServiceCapacityLow + +on property:lmkd.reinit=1 + exec_background /system/bin/lmkd --reinit + +# reinitialize lmkd after device finished booting if experiments set any flags during boot +on property:sys.boot_completed=1 && property:lmkd.reinit=0 + setprop lmkd.reinit 1 + +# properties most likely to be used in experiments +# setting persist.device_config.* property either triggers immediate lmkd re-initialization +# if the device finished booting or sets lmkd.reinit=0 to re-initialize lmkd after boot completes +on property:persist.device_config.lmkd_native.debug=* + setprop lmkd.reinit ${sys.boot_completed:-0} + +on property:persist.device_config.lmkd_native.kill_heaviest_task=* + setprop lmkd.reinit ${sys.boot_completed:-0} + +on property:persist.device_config.lmkd_native.kill_timeout_ms=* + setprop lmkd.reinit ${sys.boot_completed:-0} + +on property:persist.device_config.lmkd_native.swap_free_low_percentage=* + setprop lmkd.reinit ${sys.boot_completed:-0} + +on property:persist.device_config.lmkd_native.psi_partial_stall_ms=* + setprop lmkd.reinit ${sys.boot_completed:-0} + +on property:persist.device_config.lmkd_native.psi_complete_stall_ms=* + setprop lmkd.reinit ${sys.boot_completed:-0} + +on property:persist.device_config.lmkd_native.thrashing_limit=* + setprop lmkd.reinit ${sys.boot_completed:-0} + +on property:persist.device_config.lmkd_native.thrashing_limit_decay=* + setprop lmkd.reinit ${sys.boot_completed:-0} + +on property:persist.device_config.lmkd_native.thrashing_limit_critical=* + setprop lmkd.reinit ${sys.boot_completed:-0} + +on property:persist.device_config.lmkd_native.swap_util_max=* + setprop lmkd.reinit ${sys.boot_completed:-0} + +on property:persist.device_config.lmkd_native.filecache_min_kb=* + setprop lmkd.reinit ${sys.boot_completed:-0} diff --git a/aosp/system/netd/server/netd.rc b/aosp/system/netd/server/netd.rc index 9b6d000e8..1eab7bf30 100644 --- a/aosp/system/netd/server/netd.rc +++ b/aosp/system/netd/server/netd.rc @@ -1,6 +1,6 @@ service netd /system/bin/netd class main - capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER IPC_LOCK KILL NET_ADMIN NET_BIND_SERVICE NET_RAW SETUID SETGID + capabilities CHOWN DAC_OVERRIDE FOWNER KILL NET_ADMIN NET_BIND_SERVICE NET_RAW SETUID SETGID user root group root net_admin socket dnsproxyd stream 0660 root inet -- Gitee