drm_hwcomposer: Add DrmEventListener worker

This patch adds a worker which listens to drm events. If the
drm event has a handler associated with it, the listener will
call the handler.

BUG=chrome-os-partner:41682
TEST=Tested on ryu with DP

Change-Id: I5d691d191425604766a00be3e72111095d025d06
Signed-off-by: Sean Paul <seanpaul@chromium.org>
diff --git a/Android.mk b/Android.mk
index f9add2f..07f1e32 100644
--- a/Android.mk
+++ b/Android.mk
@@ -47,6 +47,7 @@
 	drmdisplaycomposition.cpp \
 	drmdisplaycompositor.cpp \
 	drmencoder.cpp \
+	drmeventlistener.cpp \
 	drmmode.cpp \
 	drmplane.cpp \
 	drmproperty.cpp \
diff --git a/drmeventlistener.cpp b/drmeventlistener.cpp
new file mode 100644
index 0000000..b3e2328
--- /dev/null
+++ b/drmeventlistener.cpp
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016 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 "hwc-drm-event-listener"
+
+#include "drmeventlistener.h"
+#include "drmresources.h"
+
+#include <linux/netlink.h>
+#include <sys/socket.h>
+
+#include <cutils/log.h>
+#include <xf86drm.h>
+
+namespace android {
+
+DrmEventListener::DrmEventListener(DrmResources *drm)
+    : Worker("drm-event-listener", HAL_PRIORITY_URGENT_DISPLAY),
+      drm_(drm) {
+}
+
+int DrmEventListener::Init() {
+  uevent_fd_.Set(socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT));
+  if (uevent_fd_.get() < 0) {
+    ALOGE("Failed to open uevent socket %d", uevent_fd_.get());
+    return uevent_fd_.get();
+  }
+
+  struct sockaddr_nl addr;
+  memset(&addr, 0, sizeof(addr));
+  addr.nl_family = AF_NETLINK;
+  addr.nl_pid = getpid();
+  addr.nl_groups = 0xFFFFFFFF;
+
+  int ret = bind(uevent_fd_.get(), (struct sockaddr *)&addr, sizeof(addr));
+  if (ret) {
+    ALOGE("Failed to bind uevent socket %d", -errno);
+    return -errno;
+  }
+
+  FD_ZERO(&fds_);
+  FD_SET(drm_->fd(), &fds_);
+  FD_SET(uevent_fd_.get(), &fds_);
+  max_fd_ = std::max(drm_->fd(), uevent_fd_.get());
+
+  return InitWorker();
+}
+
+void DrmEventListener::RegisterHotplugHandler(DrmEventHandler *handler) {
+  assert(!hotplug_handler_);
+  hotplug_handler_ = handler;
+}
+
+void DrmEventListener::FlipHandler(int /* fd */, unsigned int /* sequence */,
+                                   unsigned int tv_sec, unsigned int tv_usec,
+                                   void *user_data) {
+  DrmEventHandler *handler = (DrmEventHandler *)user_data;
+  if (!handler)
+    return;
+
+  handler->HandleEvent((uint64_t)tv_sec * 1000 * 1000 + tv_usec);
+  delete handler;
+}
+
+void DrmEventListener::UEventHandler() {
+  char buffer[1024];
+  int ret;
+
+  struct timespec ts;
+  uint64_t timestamp = 0;
+  ret = clock_gettime(CLOCK_MONOTONIC, &ts);
+  if (!ret)
+    timestamp = ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec;
+  else
+    ALOGE("Failed to get monotonic clock on hotplug %d", ret);
+
+  while (true) {
+    ret = read(uevent_fd_.get(), &buffer, sizeof(buffer));
+    if (ret == 0) {
+      return;
+    } else if (ret < 0) {
+      ALOGE("Got error reading uevent %d", ret);
+      return;
+    }
+
+    if (!hotplug_handler_)
+      continue;
+
+    bool drm_event = false, hotplug_event = false;
+    for (int i = 0; i < ret;) {
+      char *event = buffer + i;
+      if (strcmp(event, "DEVTYPE=drm_minor"))
+        drm_event = true;
+      else if (strcmp(event, "HOTPLUG=1"))
+        hotplug_event = true;
+
+      i += strlen(event) + 1;
+    }
+
+    if (drm_event && hotplug_event)
+      hotplug_handler_->HandleEvent(timestamp);
+  }
+}
+
+void DrmEventListener::Routine() {
+  int ret;
+  do {
+    ret = select(max_fd_ + 1, &fds_, NULL, NULL, NULL);
+  } while (ret == -1 && errno == EINTR);
+
+  if (FD_ISSET(drm_->fd(), &fds_)) {
+    drmEventContext event_context = {
+        .version = DRM_EVENT_CONTEXT_VERSION,
+        .vblank_handler = NULL,
+        .page_flip_handler = DrmEventListener::FlipHandler};
+    drmHandleEvent(drm_->fd(), &event_context);
+  }
+
+  if (FD_ISSET(uevent_fd_.get(), &fds_))
+    UEventHandler();
+}
+}
diff --git a/drmeventlistener.h b/drmeventlistener.h
new file mode 100644
index 0000000..61eefe8
--- /dev/null
+++ b/drmeventlistener.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_DRM_EVENT_LISTENER_H_
+#define ANDROID_DRM_EVENT_LISTENER_H_
+
+#include "autofd.h"
+#include "worker.h"
+
+namespace android {
+
+class DrmResources;
+
+class DrmEventHandler {
+ public:
+  DrmEventHandler() {
+  }
+  virtual ~DrmEventHandler() {
+  }
+
+  virtual void HandleEvent(uint64_t timestamp_us) = 0;
+};
+
+class DrmEventListener : public Worker {
+ public:
+  DrmEventListener(DrmResources *drm);
+  virtual ~DrmEventListener() {
+  }
+
+  int Init();
+
+  void RegisterHotplugHandler(DrmEventHandler *handler);
+
+  static void FlipHandler(int fd, unsigned int sequence, unsigned int tv_sec,
+                          unsigned int tv_usec, void *user_data);
+
+ protected:
+  virtual void Routine();
+
+ private:
+  void UEventHandler();
+
+  fd_set fds_;
+  UniqueFd uevent_fd_;
+  int max_fd_ = -1;
+
+  DrmResources *drm_;
+  DrmEventHandler *hotplug_handler_ = NULL;
+};
+}
+
+#endif
diff --git a/drmresources.cpp b/drmresources.cpp
index 80e61ec..81381c3 100644
--- a/drmresources.cpp
+++ b/drmresources.cpp
@@ -19,6 +19,7 @@
 #include "drmconnector.h"
 #include "drmcrtc.h"
 #include "drmencoder.h"
+#include "drmeventlistener.h"
 #include "drmplane.h"
 #include "drmresources.h"
 
@@ -33,7 +34,11 @@
 
 namespace android {
 
-DrmResources::DrmResources() : compositor_(this) {
+DrmResources::DrmResources() : compositor_(this), event_listener_(this) {
+}
+
+DrmResources::~DrmResources() {
+  event_listener_.Exit();
 }
 
 int DrmResources::Init() {
@@ -194,6 +199,12 @@
   if (ret)
     return ret;
 
+  ret = event_listener_.Init();
+  if (ret) {
+    ALOGE("Can't initialize event listener %d", ret);
+    return ret;
+  }
+
   for (auto &conn : connectors_) {
     ret = CreateDisplayPipe(conn.get());
     if (ret) {
@@ -363,6 +374,10 @@
   return &compositor_;
 }
 
+DrmEventListener *DrmResources::event_listener() {
+  return &event_listener_;
+}
+
 int DrmResources::GetProperty(uint32_t obj_id, uint32_t obj_type,
                               const char *prop_name, DrmProperty *property) {
   drmModeObjectPropertiesPtr props;
diff --git a/drmresources.h b/drmresources.h
index 6716807..64e6b57 100644
--- a/drmresources.h
+++ b/drmresources.h
@@ -21,6 +21,7 @@
 #include "drmconnector.h"
 #include "drmcrtc.h"
 #include "drmencoder.h"
+#include "drmeventlistener.h"
 #include "drmplane.h"
 
 #include <stdint.h>
@@ -30,6 +31,7 @@
 class DrmResources {
  public:
   DrmResources();
+  ~DrmResources();
 
   int Init();
 
@@ -49,6 +51,7 @@
   DrmCrtc *GetCrtcForDisplay(int display) const;
   DrmPlane *GetPlane(uint32_t id) const;
   DrmCompositor *compositor();
+  DrmEventListener *event_listener();
 
   int GetPlaneProperty(const DrmPlane &plane, const char *prop_name,
                        DrmProperty *property);
@@ -79,6 +82,7 @@
   std::vector<std::unique_ptr<DrmCrtc>> crtcs_;
   std::vector<std::unique_ptr<DrmPlane>> planes_;
   DrmCompositor compositor_;
+  DrmEventListener event_listener_;
 };
 }