drm_hwcomposer: Introduce DrmDisplayPipeline class

Create systematic way of binding DRM objects (Crtc,Encoder,Planes...)
to the pipeline using RAII. Use it to create the pipeline.

+ Allow pipeline creation to fail.

Closes: https://gitlab.freedesktop.org/drm-hwcomposer/drm-hwcomposer/-/issues/14
Signed-off-by: Roman Stratiienko <roman.o.stratiienko@globallogic.com>
diff --git a/Android.bp b/Android.bp
index b3bceaa..9236b3e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -91,6 +91,7 @@
         "drm/DrmConnector.cpp",
         "drm/DrmCrtc.cpp",
         "drm/DrmDevice.cpp",
+        "drm/DrmDisplayPipeline.cpp",
         "drm/DrmEncoder.cpp",
         "drm/DrmFbImporter.cpp",
         "drm/DrmMode.cpp",
diff --git a/drm/DrmConnector.h b/drm/DrmConnector.h
index 5f8ba88..629b3cc 100644
--- a/drm/DrmConnector.h
+++ b/drm/DrmConnector.h
@@ -32,7 +32,7 @@
 
 class DrmDevice;
 
-class DrmConnector {
+class DrmConnector : public PipelineBindable<DrmConnector> {
  public:
   static auto CreateInstance(DrmDevice &dev, uint32_t connector_id,
                              uint32_t index) -> std::unique_ptr<DrmConnector>;
diff --git a/drm/DrmCrtc.h b/drm/DrmCrtc.h
index 42fc9f9..ebf0a97 100644
--- a/drm/DrmCrtc.h
+++ b/drm/DrmCrtc.h
@@ -21,6 +21,7 @@
 
 #include <cstdint>
 
+#include "DrmDisplayPipeline.h"
 #include "DrmMode.h"
 #include "DrmProperty.h"
 #include "DrmUnique.h"
@@ -29,7 +30,7 @@
 
 class DrmDevice;
 
-class DrmCrtc {
+class DrmCrtc : public PipelineBindable<DrmCrtc> {
  public:
   static auto CreateInstance(DrmDevice &dev, uint32_t crtc_id, uint32_t index)
       -> std::unique_ptr<DrmCrtc>;
diff --git a/drm/DrmDevice.cpp b/drm/DrmDevice.cpp
index 29dd95f..ece9437 100644
--- a/drm/DrmDevice.cpp
+++ b/drm/DrmDevice.cpp
@@ -66,7 +66,6 @@
   ret = drmSetClientCap(GetFd(), DRM_CLIENT_CAP_WRITEBACK_CONNECTORS, 1);
   if (ret != 0) {
     ALOGI("Failed to set writeback cap %d", ret);
-    ret = 0;
   }
 #endif
 
@@ -125,30 +124,6 @@
     }
   }
 
-  auto add_displays = [this, &num_displays](bool internal, bool connected) {
-    for (auto &conn : connectors_) {
-      bool is_connected = conn->IsConnected();
-      if ((internal ? conn->IsInternal() : conn->IsExternal()) &&
-          (connected ? is_connected : !is_connected)) {
-        bound_connectors_[num_displays] = conn.get();
-        connectors_to_display_id_[conn.get()] = num_displays;
-        ++num_displays;
-      }
-    }
-  };
-
-  /* Put internal first to ensure Primary display will be internal
-   * in case at least 1 internal is available
-   */
-  add_displays(/*internal = */ true, /*connected = */ true);
-  add_displays(/*internal = */ false, /*connected = */ true);
-  add_displays(/*internal = */ true, /*connected = */ false);
-  add_displays(/*internal = */ false, /*connected = */ false);
-
-  // Catch-all for the above loops
-  if (ret != 0)
-    return std::make_tuple(ret, 0);
-
   auto plane_res = MakeDrmModePlaneResUnique(GetFd());
   if (!plane_res) {
     ALOGE("Failed to get plane resources");
@@ -164,91 +139,50 @@
     }
   }
 
-  for (auto &conn : connectors_) {
-    ret = CreateDisplayPipe(conn.get());
-    if (ret != 0) {
-      ALOGE("Failed CreateDisplayPipe %d with %d", conn->GetId(), ret);
-      return std::make_tuple(ret, 0);
+  auto add_displays = [this, &num_displays](bool internal, bool connected) {
+    for (auto &conn : connectors_) {
+      bool is_connected = conn->IsConnected();
+      if ((internal ? conn->IsInternal() : conn->IsExternal()) &&
+          (connected ? is_connected : !is_connected)) {
+        auto pipe = DrmDisplayPipeline::CreatePipeline(*conn);
+        if (pipe) {
+          pipelines_[num_displays] = std::move(pipe);
+          ++num_displays;
+        }
+      }
     }
-  }
-  return std::make_tuple(ret, bound_connectors_.size());
+  };
+
+  /* Put internal first to ensure Primary display will be internal
+   * in case at least 1 internal is available
+   */
+  add_displays(/*internal = */ true, /*connected = */ true);
+  add_displays(/*internal = */ false, /*connected = */ true);
+  add_displays(/*internal = */ true, /*connected = */ false);
+  add_displays(/*internal = */ false, /*connected = */ false);
+
+  return std::make_tuple(0, pipelines_.size());
 }
 
 bool DrmDevice::HandlesDisplay(int display) const {
-  return bound_connectors_.count(display) != 0;
+  return pipelines_.count(display) != 0;
 }
 
 DrmConnector *DrmDevice::GetConnectorForDisplay(int display) const {
-  return bound_connectors_.at(display);
+  return pipelines_.at(display)->connector->Get();
 }
 
 DrmCrtc *DrmDevice::GetCrtcForDisplay(int display) const {
-  return bound_crtcs_.at(display);
+  return pipelines_.at(display)->crtc->Get();
 }
 
-int DrmDevice::TryEncoderForDisplay(int display, DrmEncoder *enc) {
-  /* First try to use the currently-bound crtc */
-  auto *crtc = FindCrtcById(enc->GetCurrentCrtcId());
-  if (crtc != nullptr && bound_crtcs_.count(display) == 0) {
-    bound_crtcs_[display] = crtc;
-    bound_encoders_[crtc] = enc;
-    return 0;
-  }
-
-  /* Try to find a possible crtc which will work */
-  for (auto &crtc : crtcs_) {
-    /* Crtc not supported or we've already tried this earlier */
-    if (!enc->SupportsCrtc(*crtc) || crtc->GetId() == enc->GetCurrentCrtcId()) {
-      continue;
-    }
-
-    if (bound_crtcs_.count(display) == 0) {
-      bound_crtcs_[display] = crtc.get();
-      bound_encoders_[crtc.get()] = enc;
-      return 0;
+auto DrmDevice::GetDisplayId(DrmConnector *conn) -> int {
+  for (auto &dpipe : pipelines_) {
+    if (dpipe.second->connector->Get() == conn) {
+      return dpipe.first;
     }
   }
-
-  /* We can't use the encoder, but nothing went wrong, try another one */
-  return -EAGAIN;
-}
-
-int DrmDevice::CreateDisplayPipe(DrmConnector *connector) {
-  int display = connectors_to_display_id_.at(connector);
-  /* Try to use current setup first */
-  auto *enc0 = FindEncoderById(connector->GetCurrentEncoderId());
-  if (enc0 != nullptr && encoders_to_display_id_.count(enc0) == 0) {
-    int ret = TryEncoderForDisplay(display, enc0);
-    if (ret == 0) {
-      encoders_to_display_id_[enc0] = display;
-      return 0;
-    }
-
-    if (ret != -EAGAIN) {
-      ALOGE("Could not set mode %d/%d", display, ret);
-      return ret;
-    }
-  }
-
-  for (auto &enc : encoders_) {
-    if (!connector->SupportsEncoder(*enc) ||
-        encoders_to_display_id_.count(enc.get()) != 0) {
-      continue;
-    }
-
-    int ret = TryEncoderForDisplay(display, enc.get());
-    if (ret == 0) {
-      encoders_to_display_id_[enc.get()] = display;
-      return 0;
-    }
-
-    if (ret != -EAGAIN) {
-      ALOGE("Could not set mode %d/%d", display, ret);
-      return ret;
-    }
-  }
-  ALOGE("Could not find a suitable encoder/crtc for display %d", display);
-  return -ENODEV;
+  return -1;
 }
 
 auto DrmDevice::RegisterUserPropertyBlob(void *data, size_t length) const
diff --git a/drm/DrmDevice.h b/drm/DrmDevice.h
index 6d792c2..8d9a34c 100644
--- a/drm/DrmDevice.h
+++ b/drm/DrmDevice.h
@@ -96,9 +96,7 @@
     return nullptr;
   }
 
-  auto GetDisplayId(DrmConnector *conn) {
-    return connectors_to_display_id_.at(conn);
-  }
+  auto GetDisplayId(DrmConnector *conn) -> int;
 
   int GetProperty(uint32_t obj_id, uint32_t obj_type, const char *prop_name,
                   DrmProperty *property) const;
@@ -119,11 +117,7 @@
   std::pair<uint32_t, uint32_t> min_resolution_;
   std::pair<uint32_t, uint32_t> max_resolution_;
 
-  std::map<int /*display*/, DrmCrtc *> bound_crtcs_;
-  std::map<int /*display*/, DrmConnector *> bound_connectors_;
-  std::map<DrmConnector *, int /*display*/> connectors_to_display_id_;
-  std::map<DrmEncoder *, int /*display*/> encoders_to_display_id_;
-  std::map<DrmCrtc *, DrmEncoder *> bound_encoders_;
+  std::map<int /*display*/, std::unique_ptr<DrmDisplayPipeline>> pipelines_;
 
   bool HasAddFb2ModifiersSupport_{};
 
diff --git a/drm/DrmDisplayPipeline.cpp b/drm/DrmDisplayPipeline.cpp
new file mode 100644
index 0000000..8a490f8
--- /dev/null
+++ b/drm/DrmDisplayPipeline.cpp
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2022 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-display-pipeline"
+
+#include "DrmDisplayPipeline.h"
+
+#include "DrmConnector.h"
+#include "DrmCrtc.h"
+#include "DrmDevice.h"
+#include "DrmEncoder.h"
+#include "DrmPlane.h"
+#include "compositor/DrmDisplayCompositor.h"
+#include "utils/log.h"
+
+namespace android {
+
+template <class O>
+auto PipelineBindable<O>::BindPipeline(DrmDisplayPipeline *pipeline,
+                                       bool return_object_if_bound)
+    -> std::shared_ptr<BindingOwner<O>> {
+  auto owner_object = owner_object_.lock();
+  if (owner_object) {
+    if (bound_pipeline_ == pipeline && return_object_if_bound) {
+      return owner_object;
+    }
+
+    return {};
+  }
+  owner_object = std::make_shared<BindingOwner<O>>(static_cast<O *>(this));
+
+  bound_pipeline_ = pipeline;
+  return owner_object;
+}
+
+static auto TryCreatePipeline(DrmDevice &dev, DrmConnector &connector,
+                              DrmEncoder &enc, DrmCrtc &crtc)
+    -> std::unique_ptr<DrmDisplayPipeline> {
+  /* Check if resources are available */
+
+  auto pipe = std::make_unique<DrmDisplayPipeline>();
+  pipe->device = &dev;
+
+  pipe->connector = connector.BindPipeline(pipe.get());
+  pipe->encoder = enc.BindPipeline(pipe.get());
+  pipe->crtc = crtc.BindPipeline(pipe.get());
+
+  if (!pipe->connector || !pipe->encoder || !pipe->crtc) {
+    return {};
+  }
+
+  std::vector<DrmPlane *> primary_planes;
+  std::vector<DrmPlane *> overlay_planes;
+
+  /* Attach necessary resources */
+  auto display_planes = std::vector<DrmPlane *>();
+  for (const auto &plane : dev.GetPlanes()) {
+    if (plane->IsCrtcSupported(crtc)) {
+      if (plane->GetType() == DRM_PLANE_TYPE_PRIMARY) {
+        primary_planes.emplace_back(plane.get());
+      } else if (plane->GetType() == DRM_PLANE_TYPE_OVERLAY) {
+        overlay_planes.emplace_back(plane.get());
+      } else {
+        ALOGI("Ignoring cursor plane %d", plane->GetId());
+      }
+    }
+  }
+
+  if (primary_planes.empty()) {
+    ALOGE("Primary plane for CRTC %d not found", crtc.GetId());
+    return {};
+  }
+
+  if (primary_planes.size() > 1) {
+    ALOGE("Found more than 1 primary plane for CRTC %d", crtc.GetId());
+    return {};
+  }
+
+  pipe->primary_plane = primary_planes[0]->BindPipeline(pipe.get());
+  if (!pipe->primary_plane) {
+    ALOGE("Primary plane %d is already owned. Internal error.",
+          primary_planes[0]->GetId());
+    return {};
+  }
+
+  bool use_overlay_planes = true;  // TODO(rsglobal): restore
+                                   // strtol(use_overlay_planes_prop, nullptr,
+                                   // 10);
+  if (use_overlay_planes) {
+    for (auto *plane : overlay_planes) {
+      auto op = plane->BindPipeline(pipe.get());
+      if (op) {
+        pipe->overlay_planes.emplace_back(op);
+      }
+    }
+  }
+
+  return pipe;
+}
+
+static auto TryCreatePipelineUsingEncoder(DrmDevice &dev, DrmConnector &conn,
+                                          DrmEncoder &enc)
+    -> std::unique_ptr<DrmDisplayPipeline> {
+  /* First try to use the currently-bound crtc */
+  auto *crtc = dev.FindCrtcById(enc.GetCurrentCrtcId());
+  if (crtc != nullptr) {
+    auto pipeline = TryCreatePipeline(dev, conn, enc, *crtc);
+    if (pipeline) {
+      return pipeline;
+    }
+  }
+
+  /* Try to find a possible crtc which will work */
+  for (const auto &crtc : dev.GetCrtcs()) {
+    if (enc.SupportsCrtc(*crtc)) {
+      auto pipeline = TryCreatePipeline(dev, conn, enc, *crtc);
+      if (pipeline) {
+        return pipeline;
+      }
+    }
+  }
+
+  /* We can't use this encoder, but nothing went wrong, try another one */
+  return {};
+}
+
+auto DrmDisplayPipeline::CreatePipeline(DrmConnector &connector)
+    -> std::unique_ptr<DrmDisplayPipeline> {
+  auto &dev = connector.GetDev();
+  /* Try to use current setup first */
+  auto *encoder = dev.FindEncoderById(connector.GetCurrentEncoderId());
+
+  if (encoder != nullptr) {
+    auto pipeline = TryCreatePipelineUsingEncoder(dev, connector, *encoder);
+    if (pipeline) {
+      return pipeline;
+    }
+  }
+
+  for (const auto &enc : dev.GetEncoders()) {
+    if (connector.SupportsEncoder(*enc)) {
+      auto pipeline = TryCreatePipelineUsingEncoder(dev, connector, *enc);
+      if (pipeline) {
+        return pipeline;
+      }
+    }
+  }
+
+  ALOGE("Could not find a suitable encoder/crtc for connector %s",
+        connector.GetName().c_str());
+
+  return {};
+}
+
+}  // namespace android
diff --git a/drm/DrmDisplayPipeline.h b/drm/DrmDisplayPipeline.h
new file mode 100644
index 0000000..cb5b9e2
--- /dev/null
+++ b/drm/DrmDisplayPipeline.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 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_DRMDISPLAYPIPELINE_H_
+#define ANDROID_DRMDISPLAYPIPELINE_H_
+
+#include <memory>
+#include <vector>
+
+namespace android {
+
+class DrmConnector;
+class DrmDevice;
+class DrmPlane;
+class DrmCrtc;
+class DrmEncoder;
+class DrmDisplayCompositor;
+
+struct DrmDisplayPipeline;
+
+template <class O>
+class BindingOwner;
+
+template <class O>
+class PipelineBindable {
+  friend class BindingOwner<O>;
+
+ public:
+  auto *GetPipeline() {
+    return bound_pipeline_;
+  }
+
+  auto BindPipeline(DrmDisplayPipeline *pipeline,
+                    bool return_object_if_bound = false)
+      -> std::shared_ptr<BindingOwner<O>>;
+
+ private:
+  DrmDisplayPipeline *bound_pipeline_;
+  std::weak_ptr<BindingOwner<O>> owner_object_;
+};
+
+template <class B>
+class BindingOwner {
+ public:
+  explicit BindingOwner(B *pb) : bindable_(pb){};
+  ~BindingOwner() {
+    bindable_->bound_pipeline_ = nullptr;
+  }
+
+  B *Get() {
+    return bindable_;
+  }
+
+ private:
+  B *const bindable_;
+};
+
+struct DrmDisplayPipeline {
+  static auto CreatePipeline(DrmConnector &connector)
+      -> std::unique_ptr<DrmDisplayPipeline>;
+
+  DrmDevice *device;
+
+  std::shared_ptr<BindingOwner<DrmConnector>> connector;
+  std::shared_ptr<BindingOwner<DrmEncoder>> encoder;
+  std::shared_ptr<BindingOwner<DrmCrtc>> crtc;
+  std::shared_ptr<BindingOwner<DrmPlane>> primary_plane;
+  std::vector<std::shared_ptr<BindingOwner<DrmPlane>>> overlay_planes;
+};
+
+}  // namespace android
+
+#endif
diff --git a/drm/DrmEncoder.h b/drm/DrmEncoder.h
index e66d6f1..39a695c 100644
--- a/drm/DrmEncoder.h
+++ b/drm/DrmEncoder.h
@@ -24,10 +24,11 @@
 #include <vector>
 
 #include "DrmCrtc.h"
+#include "DrmDisplayPipeline.h"
 
 namespace android {
 
-class DrmEncoder {
+class DrmEncoder : public PipelineBindable<DrmEncoder> {
  public:
   static auto CreateInstance(DrmDevice &dev, uint32_t encoder_id,
                              uint32_t index) -> std::unique_ptr<DrmEncoder>;
diff --git a/drm/DrmPlane.h b/drm/DrmPlane.h
index 9826f67..65f458f 100644
--- a/drm/DrmPlane.h
+++ b/drm/DrmPlane.h
@@ -31,7 +31,7 @@
 class DrmDevice;
 struct DrmHwcLayer;
 
-class DrmPlane {
+class DrmPlane : public PipelineBindable<DrmPlane> {
  public:
   DrmPlane(const DrmPlane &) = delete;
   DrmPlane &operator=(const DrmPlane &) = delete;