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*/) {