libhealthloop: Only wake up for power supply events
healthd receives power supply information as uevents and holds a
wakelock while receiving these uevents. Without uevent filter, suspend
is postponed indefinitely if a uevent is generated during suspend. Fix
this by attaching a BPF program to the uevent socket that filters out
all events that are not power supply events.
This CL replaces the following CLs:
* Lianwei Wang, healthd: Don't set all eventpoll wakeup-able,
2015-07-09
(https://android-review.googlesource.com/c/platform/system/core/+/158851).
* Stephane Lee, Add BPF filter to filter uevents for
SUBSYSTEM=powersupply, 2022-05-06.
Multiple ideas and some code in this CL have been borrowed from
Stephane Lee's CL.
Bug: 139203596
Bug: 140330870
Bug: 203131934
Bug: 203229817
Bug: 203462310
Bug: 221725014
Test: Verified that a Pixel 2024 still wakes up if a USB cable is connected or disconnected.
Test: Verified that suspend and resume still works for a Pixel 2024 device.
Test: Verified that the following text appears in the Cuttlefish logcat output: "HealthLoop: Successfully attached BPF program to uevent socket"
Test: Verified as follows that recovery mode works fine with Cuttlefish: adb reboot recovery && adb root && adb shell dmesg | grep -E 'F DEBUG|HealthLoop'
Change-Id: I64446b103d660d220880461bdef7ef0f531e1734
Signed-off-by: Bart Van Assche <bvanassche@google.com>
diff --git a/health/utils/libhealthloop/HealthLoop.cpp b/health/utils/libhealthloop/HealthLoop.cpp
index f8c3490..70b7745 100644
--- a/health/utils/libhealthloop/HealthLoop.cpp
+++ b/health/utils/libhealthloop/HealthLoop.cpp
@@ -30,8 +30,12 @@
#include <cutils/uevent.h>
#include <healthd/healthd.h>
+#include <BpfSyscallWrappers.h>
#include <health/utils.h>
+using android::base::ErrnoError;
+using android::base::Result;
+using android::base::unique_fd;
using namespace android;
using namespace std::chrono_literals;
@@ -116,7 +120,6 @@
ScheduleBatteryUpdate();
}
-// TODO(b/140330870): Use BPF instead.
#define UEVENT_MSG_LEN 2048
void HealthLoop::UeventEvent(uint32_t epevents) {
// No need to lock because uevent_fd_ is guaranteed to be initialized.
@@ -152,8 +155,26 @@
}
}
+// Attach a BPF filter to the @uevent_fd file descriptor. This fails in recovery mode because BPF is
+// not supported in recovery mode. This fails for kernel versions 5.4 and before because the BPF
+// program is rejected by the BPF verifier of older kernels.
+Result<void> HealthLoop::AttachFilter(int uevent_fd) {
+ static const char prg[] =
+ "/sys/fs/bpf/vendor/prog_filterPowerSupplyEvents_skfilter_power_supply";
+ int filter_fd(bpf::retrieveProgram(prg));
+ if (filter_fd < 0) {
+ return ErrnoError() << "failed to load BPF program " << prg;
+ }
+ if (setsockopt(uevent_fd, SOL_SOCKET, SO_ATTACH_BPF, &filter_fd, sizeof(filter_fd)) < 0) {
+ close(filter_fd);
+ return ErrnoError() << "failed to attach BPF program";
+ }
+ close(filter_fd);
+ return {};
+}
+
void HealthLoop::UeventInit(void) {
- uevent_fd_.reset(uevent_open_socket(64 * 1024, true));
+ uevent_fd_.reset(uevent_create_socket(64 * 1024, true));
if (uevent_fd_ < 0) {
KLOG_ERROR(LOG_TAG, "uevent_init: uevent_open_socket failed\n");
@@ -161,8 +182,25 @@
}
fcntl(uevent_fd_, F_SETFL, O_NONBLOCK);
+
+ Result<void> attach_result = AttachFilter(uevent_fd_);
+ if (!attach_result.ok()) {
+ std::string error_msg = attach_result.error().message();
+ error_msg +=
+ ". This is expected in recovery mode and also for kernel versions before 5.10.";
+ KLOG_WARNING(LOG_TAG, "%s", error_msg.c_str());
+ } else {
+ KLOG_INFO(LOG_TAG, "Successfully attached the BPF filter to the uevent socket");
+ }
+
if (RegisterEvent(uevent_fd_, &HealthLoop::UeventEvent, EVENT_WAKEUP_FD))
KLOG_ERROR(LOG_TAG, "register for uevent events failed\n");
+
+ if (uevent_bind(uevent_fd_.get()) < 0) {
+ uevent_fd_.reset();
+ KLOG_ERROR(LOG_TAG, "uevent_init: binding socket failed\n");
+ return;
+ }
}
void HealthLoop::WakeAlarmEvent(uint32_t /*epevents*/) {