[mutter] kms: Add atomic MetaKmsImplDevice backend



commit fb38c451b5bdf9db9c89530a366a6841c4b70ac4
Author: Jonas Ã…dahl <jadahl gmail com>
Date:   Sat Oct 10 01:25:06 2020 +0200

    kms: Add atomic MetaKmsImplDevice backend
    
    This adds a MetaKmsImplDevice backend using atomic drmMode* API in constrast to
    non-atomic legacy drmMode* API used in MetaKmsImplDeviceSimple.
    
    This has various behavioral differences worth noting, compared to the
    simple backend:
    
     * We can only commit once per CRTC per page flip.
    
    This means that we can only update the cursor plane once. If a primary
    plane composition missed a dead line, we cannot commit only a cursor
    update that would be presented earlier.
    
     * Partial success is not possible with the atomic backend.
    
    Cursor planes may fail with the simple backend. This is not the case
    with the atomic backend. This will instead later be handled using API
    specific to the atomic backend, that will effectively translate into
    TEST_ONLY commits.
    
    For testing and debugging purposes, the environment variable
    MUTTER_DEBUG_ENABLE_ATOMIC_KMS can be set to either 1 or 0 to
    force-enable or force-disable atomic mode setting. Setting it to some
    other value will cause mutter to abort().
    
    Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/548
    Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/1488>

 src/backends/native/meta-kms-device.c             |   78 +-
 src/backends/native/meta-kms-impl-device-atomic.c | 1035 +++++++++++++++++++++
 src/backends/native/meta-kms-impl-device-atomic.h |   29 +
 src/backends/native/meta-kms-impl-device.c        |   18 +
 src/backends/native/meta-kms-impl-device.h        |    4 +
 src/backends/native/meta-renderer-native.c        |    1 +
 src/meson.build                                   |    2 +
 7 files changed, 1164 insertions(+), 3 deletions(-)
---
diff --git a/src/backends/native/meta-kms-device.c b/src/backends/native/meta-kms-device.c
index 8587c3065c..b5b50d602c 100644
--- a/src/backends/native/meta-kms-device.c
+++ b/src/backends/native/meta-kms-device.c
@@ -26,6 +26,7 @@
 #include <xf86drm.h>
 
 #include "backends/native/meta-backend-native.h"
+#include "backends/native/meta-kms-impl-device-atomic.h"
 #include "backends/native/meta-kms-impl-device-simple.h"
 #include "backends/native/meta-kms-impl-device.h"
 #include "backends/native/meta-kms-impl.h"
@@ -237,6 +238,20 @@ typedef struct _CreateImplDeviceData
   char *out_driver_description;
 } CreateImplDeviceData;
 
+static gboolean
+is_atomic_allowed (const char *driver_name)
+{
+  const char *atomic_driver_deny_list[] = {
+    "qxl",
+    "vmwgfx",
+    "vboxvideo",
+    "nvidia-drm",
+    NULL,
+  };
+
+  return !g_strv_contains (atomic_driver_deny_list, driver_name);
+}
+
 static gboolean
 get_driver_info (int    fd,
                  char **name,
@@ -264,9 +279,12 @@ meta_create_kms_impl_device (MetaKmsDevice  *device,
                              const char     *path,
                              GError        **error)
 {
+  GType impl_device_type;
+  gboolean supports_atomic_mode_setting;
   int ret;
   g_autofree char *driver_name = NULL;
   g_autofree char *driver_description = NULL;
+  const char *atomic_kms_enable_env;
 
   meta_assert_in_kms_impl (meta_kms_impl_get_kms (impl));
 
@@ -285,10 +303,64 @@ meta_create_kms_impl_device (MetaKmsDevice  *device,
       driver_description = g_strdup ("Unknown");
     }
 
-  g_message ("Adding device '%s' (%s) using non-atomic mode setting.",
-             path, driver_name);
+  atomic_kms_enable_env = getenv ("MUTTER_DEBUG_ENABLE_ATOMIC_KMS");
+  if (atomic_kms_enable_env)
+    {
+      if (g_strcmp0 (atomic_kms_enable_env, "1") == 0)
+        {
+          impl_device_type = META_TYPE_KMS_IMPL_DEVICE_ATOMIC;
+          if (drmSetClientCap (fd, DRM_CLIENT_CAP_ATOMIC, 1) != 0)
+            {
+              g_error ("Failed to force atomic mode setting on '%s' (%s).",
+                       path, driver_name);
+            }
+        }
+      else if (g_strcmp0 (atomic_kms_enable_env, "0") == 0)
+        {
+          impl_device_type = META_TYPE_KMS_IMPL_DEVICE_SIMPLE;
+        }
+      else
+        {
+          g_error ("Invalid value '%s' for MUTTER_DEBUG_ENABLE_ATOMIC_KMS, "
+                   "bailing.",
+                   atomic_kms_enable_env);
+        }
+
+      g_message ("Mode setting implementation for '%s' (%s) forced (%s).",
+                 path, driver_name,
+                 impl_device_type == META_TYPE_KMS_IMPL_DEVICE_ATOMIC ?
+                   "atomic" : "non-atomic");
+    }
+  else if (!is_atomic_allowed (driver_name))
+    {
+      g_message ("Adding device '%s' (%s) using non-atomic mode setting"
+                 " (using atomic mode setting not allowed).",
+                 path, driver_name);
+      impl_device_type = META_TYPE_KMS_IMPL_DEVICE_SIMPLE;
+    }
+  else
+    {
+      ret = drmSetClientCap (fd, DRM_CLIENT_CAP_ATOMIC, 1);
+      if (ret == 0)
+        supports_atomic_mode_setting = TRUE;
+      else
+        supports_atomic_mode_setting = FALSE;
+
+      if (supports_atomic_mode_setting)
+        {
+          g_message ("Adding device '%s' (%s) using atomic mode setting.",
+                     path, driver_name);
+          impl_device_type = META_TYPE_KMS_IMPL_DEVICE_ATOMIC;
+        }
+      else
+        {
+          g_message ("Adding device '%s' (%s) using non-atomic mode setting.",
+                     path, driver_name);
+          impl_device_type = META_TYPE_KMS_IMPL_DEVICE_SIMPLE;
+        }
+    }
 
-  return g_initable_new (META_TYPE_KMS_IMPL_DEVICE_SIMPLE, NULL, error,
+  return g_initable_new (impl_device_type, NULL, error,
                          "device", device,
                          "impl", impl,
                          "fd", fd,
diff --git a/src/backends/native/meta-kms-impl-device-atomic.c 
b/src/backends/native/meta-kms-impl-device-atomic.c
new file mode 100644
index 0000000000..a00c91a319
--- /dev/null
+++ b/src/backends/native/meta-kms-impl-device-atomic.c
@@ -0,0 +1,1035 @@
+/*
+ * Copyright (C) 2019-2020 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include "backends/native/meta-kms-impl-device-atomic.h"
+
+#include "backends/native/meta-kms-connector-private.h"
+#include "backends/native/meta-kms-crtc-private.h"
+#include "backends/native/meta-kms-device-private.h"
+#include "backends/native/meta-kms-mode-private.h"
+#include "backends/native/meta-kms-plane-private.h"
+#include "backends/native/meta-kms-private.h"
+#include "backends/native/meta-kms-update-private.h"
+
+typedef gboolean (* MetaKmsAtomicProcessFunc) (MetaKmsImplDevice  *impl_device,
+                                               MetaKmsUpdate      *update,
+                                               drmModeAtomicReq   *req,
+                                               GArray             *blob_ids,
+                                               gpointer            entry_data,
+                                               gpointer            user_data,
+                                               GError            **error);
+
+struct _MetaKmsImplDeviceAtomic
+{
+  MetaKmsImplDevice parent;
+
+  GHashTable *page_flip_datas;
+};
+
+G_DEFINE_TYPE (MetaKmsImplDeviceAtomic, meta_kms_impl_device_atomic,
+               META_TYPE_KMS_IMPL_DEVICE)
+
+static uint32_t
+store_new_blob (MetaKmsImplDevice  *impl_device,
+                GArray             *blob_ids,
+                const void         *data,
+                size_t              size,
+                GError            **error)
+{
+  int fd = meta_kms_impl_device_get_fd (impl_device);
+  uint32_t blob_id;
+  int ret;
+
+  ret = drmModeCreatePropertyBlob (fd, data, size, &blob_id);
+  if (ret < 0)
+    {
+      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
+                   "drmModeCreatePropertyBlob: %s", g_strerror (-ret));
+      return 0;
+    }
+
+  g_array_append_val (blob_ids, blob_id);
+
+  return blob_id;
+}
+
+static void
+release_blob_ids (MetaKmsImplDevice *impl_device,
+                  GArray            *blob_ids)
+{
+  int fd = meta_kms_impl_device_get_fd (impl_device);
+  unsigned int i;
+
+  for (i = 0; i < blob_ids->len; i++)
+    {
+      uint32_t blob_id = g_array_index (blob_ids, uint32_t, i);
+
+      drmModeDestroyPropertyBlob (fd, blob_id);
+    }
+}
+
+static gboolean
+add_connector_property (MetaKmsImplDevice     *impl_device,
+                        MetaKmsConnector      *connector,
+                        drmModeAtomicReq      *req,
+                        MetaKmsConnectorProp   prop,
+                        uint64_t               value,
+                        GError               **error)
+{
+  int ret;
+  uint32_t prop_id;
+
+  prop_id = meta_kms_connector_get_prop_id (connector, prop);
+  if (!prop_id)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                   "Connector property '%s' not found",
+                   meta_kms_connector_get_prop_name (connector, prop));
+      return FALSE;
+    }
+
+  meta_topic (META_DEBUG_KMS,
+              "[atomic] Setting connector %u (%s) property '%s' (%u) to %"
+              G_GUINT64_FORMAT,
+              meta_kms_connector_get_id (connector),
+              meta_kms_impl_device_get_path (impl_device),
+              meta_kms_connector_get_prop_name (connector, prop),
+              meta_kms_connector_get_prop_id (connector, prop),
+              value);
+  ret = drmModeAtomicAddProperty (req,
+                                  meta_kms_connector_get_id (connector),
+                                  prop_id,
+                                  value);
+  if (ret < 0)
+    {
+      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
+                   "drmModeAtomicAddProperty, connector: %u, prop id: %u: %s",
+                   meta_kms_connector_get_id (connector),
+                   prop_id,
+                   g_strerror (-ret));
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+process_connector_update (MetaKmsImplDevice  *impl_device,
+                          MetaKmsUpdate      *update,
+                          drmModeAtomicReq   *req,
+                          GArray             *blob_ids,
+                          gpointer            update_entry,
+                          gpointer            user_data,
+                          GError            **error)
+{
+  MetaKmsConnectorUpdate *connector_update = update_entry;
+  MetaKmsConnector *connector = connector_update->connector;
+
+  if (connector_update->underscanning.has_update &&
+      connector_update->underscanning.is_active)
+    {
+      meta_topic (META_DEBUG_KMS,
+                  "[atomic] Setting underscanning on connector %u (%s) to "
+                  "%" G_GUINT64_FORMAT "x%" G_GUINT64_FORMAT,
+                  meta_kms_connector_get_id (connector),
+                  meta_kms_impl_device_get_path (impl_device),
+                  connector_update->underscanning.hborder,
+                  connector_update->underscanning.vborder);
+
+      if (!add_connector_property (impl_device,
+                                   connector, req,
+                                   META_KMS_CONNECTOR_PROP_UNDERSCAN,
+                                   1,
+                                   error))
+        return FALSE;
+      if (!add_connector_property (impl_device,
+                                   connector, req,
+                                   META_KMS_CONNECTOR_PROP_UNDERSCAN_HBORDER,
+                                   connector_update->underscanning.hborder,
+                                   error))
+        return FALSE;
+      if (!add_connector_property (impl_device,
+                                   connector, req,
+                                   META_KMS_CONNECTOR_PROP_UNDERSCAN_VBORDER,
+                                   connector_update->underscanning.vborder,
+                                   error))
+        return FALSE;
+    }
+  else if (connector_update->underscanning.has_update)
+    {
+      meta_topic (META_DEBUG_KMS,
+                  "[atomic] Unsetting underscanning on connector %u (%s)",
+                  meta_kms_connector_get_id (connector),
+                  meta_kms_impl_device_get_path (impl_device));
+
+      if (!add_connector_property (impl_device,
+                                   connector, req,
+                                   META_KMS_CONNECTOR_PROP_UNDERSCAN,
+                                   0,
+                                   error))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+add_crtc_property (MetaKmsImplDevice  *impl_device,
+                   MetaKmsCrtc        *crtc,
+                   drmModeAtomicReq   *req,
+                   MetaKmsCrtcProp     prop,
+                   uint64_t            value,
+                   GError            **error)
+{
+  int ret;
+  uint32_t prop_id;
+
+  prop_id = meta_kms_crtc_get_prop_id (crtc, prop);
+  if (!prop_id)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                   "CRTC property (%s) not found",
+                   meta_kms_crtc_get_prop_name (crtc, prop));
+      return FALSE;
+    }
+
+  meta_topic (META_DEBUG_KMS,
+              "[atomic] Setting CRTC %u (%s) property '%s' (%u) to %"
+              G_GUINT64_FORMAT,
+              meta_kms_crtc_get_id (crtc),
+              meta_kms_impl_device_get_path (impl_device),
+              meta_kms_crtc_get_prop_name (crtc, prop),
+              meta_kms_crtc_get_prop_id (crtc, prop),
+              value);
+  ret = drmModeAtomicAddProperty (req,
+                                  meta_kms_crtc_get_id (crtc),
+                                  prop_id,
+                                  value);
+  if (ret < 0)
+    {
+      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
+                   "drmModeAtomicAddProperty, crtc: %u, prop: %s (%u): %s",
+                   meta_kms_crtc_get_id (crtc),
+                   meta_kms_crtc_get_prop_name (crtc, prop),
+                   prop_id,
+                   g_strerror (-ret));
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+process_mode_set (MetaKmsImplDevice  *impl_device,
+                  MetaKmsUpdate      *update,
+                  drmModeAtomicReq   *req,
+                  GArray             *blob_ids,
+                  gpointer            update_entry,
+                  gpointer            user_data,
+                  GError            **error)
+{
+  MetaKmsModeSet *mode_set = update_entry;
+  MetaKmsCrtc *crtc = mode_set->crtc;
+  MetaKmsMode *mode;
+
+  mode = (MetaKmsMode *) mode_set->mode;
+  if (mode)
+    {
+      uint32_t mode_id;
+      GList *l;
+
+      mode_id = meta_kms_mode_ensure_blob_id (mode, error);
+      if (mode_id == 0)
+        return FALSE;
+
+      meta_topic (META_DEBUG_KMS,
+                  "[atomic] Setting mode of CRTC %u (%s) to %s",
+                  meta_kms_crtc_get_id (crtc),
+                  meta_kms_impl_device_get_path (impl_device),
+                  meta_kms_mode_get_name (mode));
+
+      if (!add_crtc_property (impl_device,
+                              crtc, req,
+                              META_KMS_CRTC_PROP_MODE_ID,
+                              mode_id,
+                              error))
+        return FALSE;
+
+      if (!add_crtc_property (impl_device,
+                              crtc, req,
+                              META_KMS_CRTC_PROP_ACTIVE,
+                              1,
+                              error))
+        return FALSE;
+
+      for (l = mode_set->connectors; l; l = l->next)
+        {
+          MetaKmsConnector *connector = l->data;
+
+          if (!add_connector_property (impl_device,
+                                       connector, req,
+                                       META_KMS_CONNECTOR_PROP_CRTC_ID,
+                                       meta_kms_crtc_get_id (crtc),
+                                       error))
+            return FALSE;
+        }
+    }
+  else
+    {
+      if (!add_crtc_property (impl_device,
+                              crtc, req,
+                              META_KMS_CRTC_PROP_MODE_ID,
+                              0,
+                              error))
+        return FALSE;
+
+      if (!add_crtc_property (impl_device,
+                              crtc, req,
+                              META_KMS_CRTC_PROP_ACTIVE,
+                              0,
+                              error))
+        return FALSE;
+
+      meta_topic (META_DEBUG_KMS,
+                  "[atomic] Unsetting mode of (%u, %s)",
+                  meta_kms_crtc_get_id (crtc),
+                  meta_kms_impl_device_get_path (impl_device));
+    }
+
+  return TRUE;
+}
+
+static gboolean
+add_plane_property (MetaKmsImplDevice  *impl_device,
+                    MetaKmsPlane       *plane,
+                    drmModeAtomicReq   *req,
+                    MetaKmsCrtcProp     prop,
+                    uint64_t            value,
+                    GError            **error)
+{
+  int ret;
+  uint32_t prop_id;
+
+  prop_id = meta_kms_plane_get_prop_id (plane, prop);
+  if (!prop_id)
+    {
+      g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+                   "Plane property (%s) not found on %u",
+                   meta_kms_plane_get_prop_name (plane, prop),
+                   meta_kms_plane_get_id (plane));
+      return FALSE;
+    }
+
+  meta_topic (META_DEBUG_KMS,
+              "[atomic] Setting plane %u (%s) property '%s' (%u) to %"
+              G_GUINT64_FORMAT,
+              meta_kms_plane_get_id (plane),
+              meta_kms_impl_device_get_path (impl_device),
+              meta_kms_plane_get_prop_name (plane, prop),
+              meta_kms_plane_get_prop_id (plane, prop),
+              value);
+  ret = drmModeAtomicAddProperty (req,
+                                  meta_kms_plane_get_id (plane),
+                                  prop_id,
+                                  value);
+  if (ret < 0)
+    {
+      g_set_error (error, G_IO_ERROR, g_io_error_from_errno (-ret),
+                   "drmModeAtomicAddProperty, plane: %u, prop: %s (%u): %s",
+                   meta_kms_plane_get_id (plane),
+                   meta_kms_plane_get_prop_name (plane, prop),
+                   prop_id,
+                   g_strerror (-ret));
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+static const char *
+get_plane_type_string (MetaKmsPlane *plane)
+{
+  switch (meta_kms_plane_get_plane_type (plane))
+    {
+    case META_KMS_PLANE_TYPE_PRIMARY:
+      return "primary";
+    case META_KMS_PLANE_TYPE_CURSOR:
+      return "cursor";
+    case META_KMS_PLANE_TYPE_OVERLAY:
+      return "overlay";
+    }
+
+  g_assert_not_reached ();
+}
+
+static gboolean
+process_plane_assignment (MetaKmsImplDevice  *impl_device,
+                          MetaKmsUpdate      *update,
+                          drmModeAtomicReq   *req,
+                          GArray             *blob_ids,
+                          gpointer            update_entry,
+                          gpointer            user_data,
+                          GError            **error)
+{
+  MetaKmsPlaneAssignment *plane_assignment = update_entry;
+  MetaKmsPlane *plane = plane_assignment->plane;
+  MetaDrmBuffer *buffer;
+
+  buffer = plane_assignment->buffer;
+
+  meta_topic (META_DEBUG_KMS,
+              "[atomic] Assigning %s plane (%u, %s) to %u, "
+              "%hdx%hd+%hd+%hd -> %dx%d+%d+%d",
+              get_plane_type_string (plane),
+              meta_kms_plane_get_id (plane),
+              meta_kms_impl_device_get_path (impl_device),
+              buffer ? meta_drm_buffer_get_fb_id (buffer) : 0,
+              meta_fixed_16_to_int (plane_assignment->src_rect.width),
+              meta_fixed_16_to_int (plane_assignment->src_rect.height),
+              meta_fixed_16_to_int (plane_assignment->src_rect.x),
+              meta_fixed_16_to_int (plane_assignment->src_rect.y),
+              plane_assignment->dst_rect.width,
+              plane_assignment->dst_rect.height,
+              plane_assignment->dst_rect.x,
+              plane_assignment->dst_rect.y);
+
+  if (buffer)
+    {
+      int i;
+      struct {
+        MetaKmsPlaneProp prop;
+        uint64_t value;
+      } props[] = {
+        {
+          .prop = META_KMS_PLANE_PROP_FB_ID,
+          .value = meta_drm_buffer_get_fb_id (buffer),
+        },
+        {
+          .prop = META_KMS_PLANE_PROP_CRTC_ID,
+          .value = meta_kms_crtc_get_id (plane_assignment->crtc),
+        },
+        {
+          .prop = META_KMS_PLANE_PROP_SRC_X,
+          .value = plane_assignment->src_rect.x,
+        },
+        {
+          .prop = META_KMS_PLANE_PROP_SRC_Y,
+          .value = plane_assignment->src_rect.y,
+        },
+        {
+          .prop = META_KMS_PLANE_PROP_SRC_W,
+          .value = plane_assignment->src_rect.width,
+        },
+        {
+          .prop = META_KMS_PLANE_PROP_SRC_H,
+          .value = plane_assignment->src_rect.height,
+        },
+        {
+          .prop = META_KMS_PLANE_PROP_CRTC_X,
+          .value = plane_assignment->dst_rect.x,
+        },
+        {
+          .prop = META_KMS_PLANE_PROP_CRTC_Y,
+          .value = plane_assignment->dst_rect.y,
+        },
+        {
+          .prop = META_KMS_PLANE_PROP_CRTC_W,
+          .value = plane_assignment->dst_rect.width,
+        },
+        {
+          .prop = META_KMS_PLANE_PROP_CRTC_H,
+          .value = plane_assignment->dst_rect.height,
+        },
+      };
+
+      for (i = 0; i < G_N_ELEMENTS (props); i++)
+        {
+          if (!add_plane_property (impl_device,
+                                   plane, req,
+                                   props[i].prop,
+                                   props[i].value,
+                                   error))
+            return FALSE;
+        }
+    }
+  else
+    {
+      int i;
+      struct {
+        MetaKmsPlaneProp prop;
+        uint64_t value;
+      } props[] = {
+        {
+          .prop = META_KMS_PLANE_PROP_FB_ID,
+          .value = 0,
+        },
+        {
+          .prop = META_KMS_PLANE_PROP_CRTC_ID,
+          .value = 0,
+        },
+      };
+
+      for (i = 0; i < G_N_ELEMENTS (props); i++)
+        {
+          if (!add_plane_property (impl_device,
+                                   plane, req,
+                                   props[i].prop,
+                                   props[i].value,
+                                   error))
+            return FALSE;
+        }
+    }
+
+  if (plane_assignment->rotation)
+    {
+      meta_topic (META_DEBUG_KMS,
+                  "[atomic] Setting plane (%u, %s) rotation to %"
+                  G_GUINT64_FORMAT,
+                  meta_kms_plane_get_id (plane),
+                  meta_kms_impl_device_get_path (impl_device),
+                  plane_assignment->rotation);
+
+      if (!add_plane_property (impl_device,
+                               plane, req,
+                               META_KMS_PLANE_PROP_ROTATION,
+                               plane_assignment->rotation,
+                               error))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+process_crtc_gamma (MetaKmsImplDevice  *impl_device,
+                    MetaKmsUpdate      *update,
+                    drmModeAtomicReq   *req,
+                    GArray             *blob_ids,
+                    gpointer            update_entry,
+                    gpointer            user_data,
+                    GError            **error)
+{
+  MetaKmsCrtcGamma *gamma = update_entry;
+  MetaKmsCrtc *crtc = gamma->crtc;
+  struct drm_color_lut drm_color_lut[gamma->size];
+  int i;
+  uint32_t color_lut_blob_id;
+
+  for (i = 0; i < gamma->size; i++)
+    {
+      drm_color_lut[i].red = gamma->red[i];
+      drm_color_lut[i].green = gamma->green[i];
+      drm_color_lut[i].blue = gamma->blue[i];
+    }
+
+  color_lut_blob_id = store_new_blob (impl_device,
+                                      blob_ids,
+                                      drm_color_lut,
+                                      sizeof drm_color_lut,
+                                      error);
+  if (!color_lut_blob_id)
+    return FALSE;
+
+  meta_topic (META_DEBUG_KMS,
+              "[atomic] Setting CRTC (%u, %s) gamma, size: %d",
+              meta_kms_crtc_get_id (crtc),
+              meta_kms_impl_device_get_path (impl_device),
+              gamma->size);
+
+  if (!add_crtc_property (impl_device,
+                          crtc, req,
+                          META_KMS_CRTC_PROP_GAMMA_LUT,
+                          color_lut_blob_id,
+                          error))
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+process_page_flip_listener (MetaKmsImplDevice  *impl_device,
+                            MetaKmsUpdate      *update,
+                            drmModeAtomicReq   *req,
+                            GArray             *blob_ids,
+                            gpointer            update_entry,
+                            gpointer            user_data,
+                            GError            **error)
+{
+  MetaKmsImplDeviceAtomic *impl_device_atomic =
+    META_KMS_IMPL_DEVICE_ATOMIC (impl_device);
+  MetaKmsPageFlipListener *listener = update_entry;
+  MetaKmsPageFlipData *page_flip_data;
+  uint32_t crtc_id;
+  gpointer listener_user_data;
+  GDestroyNotify listener_destroy_notify;
+
+  crtc_id = meta_kms_crtc_get_id (listener->crtc);
+  page_flip_data = g_hash_table_lookup (impl_device_atomic->page_flip_datas,
+                                        GUINT_TO_POINTER (crtc_id));
+  if (!page_flip_data)
+    {
+      page_flip_data = meta_kms_page_flip_data_new (impl_device,
+                                                    listener->crtc);
+      g_hash_table_insert (impl_device_atomic->page_flip_datas,
+                           GUINT_TO_POINTER (crtc_id),
+                           page_flip_data);
+
+      meta_topic (META_DEBUG_KMS,
+                  "[atomic] Adding page flip data for (%u, %s): %p",
+                  crtc_id,
+                  meta_kms_impl_device_get_path (impl_device),
+                  page_flip_data);
+    }
+
+  listener_user_data = g_steal_pointer (&listener->user_data);
+  listener_destroy_notify = g_steal_pointer (&listener->destroy_notify);
+  meta_kms_page_flip_data_add_listener (page_flip_data,
+                                        listener->vtable,
+                                        listener->flags,
+                                        listener_user_data,
+                                        listener_destroy_notify);
+
+  return TRUE;
+}
+
+static gboolean
+discard_page_flip_listener (MetaKmsImplDevice  *impl_device,
+                            MetaKmsUpdate      *update,
+                            drmModeAtomicReq   *req,
+                            GArray             *blob_ids,
+                            gpointer            update_entry,
+                            gpointer            user_data,
+                            GError            **error)
+{
+  MetaKmsPageFlipListener *listener = update_entry;
+  GError *commit_error = user_data;
+  MetaKmsPageFlipData *page_flip_data;
+  gpointer listener_user_data;
+  GDestroyNotify listener_destroy_notify;
+
+  page_flip_data = meta_kms_page_flip_data_new (impl_device,
+                                                listener->crtc);
+
+  meta_topic (META_DEBUG_KMS,
+              "[atomic] Creating transient page flip data for (%u, %s): %p",
+              meta_kms_crtc_get_id (listener->crtc),
+              meta_kms_impl_device_get_path (impl_device),
+              page_flip_data);
+
+  listener_user_data = g_steal_pointer (&listener->user_data);
+  listener_destroy_notify = g_steal_pointer (&listener->destroy_notify);
+  meta_kms_page_flip_data_add_listener (page_flip_data,
+                                        listener->vtable,
+                                        listener->flags,
+                                        listener_user_data,
+                                        listener_destroy_notify);
+
+  meta_kms_page_flip_data_discard_in_impl (page_flip_data, commit_error);
+
+  return TRUE;
+}
+
+static gboolean
+process_entries (MetaKmsImplDevice         *impl_device,
+                 MetaKmsUpdate             *update,
+                 drmModeAtomicReq          *req,
+                 GArray                    *blob_ids,
+                 GList                     *entries,
+                 gpointer                   user_data,
+                 MetaKmsAtomicProcessFunc   func,
+                 GError                   **error)
+{
+  GList *l;
+
+  for (l = entries; l; l = l->next)
+    {
+      if (!func (impl_device,
+                 update,
+                 req,
+                 blob_ids,
+                 l->data,
+                 user_data,
+                 error))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static void
+atomic_page_flip_handler (int           fd,
+                          unsigned int  sequence,
+                          unsigned int  tv_sec,
+                          unsigned int  tv_usec,
+                          unsigned int  crtc_id,
+                          void         *user_data)
+{
+  MetaKmsImplDeviceAtomic *impl_device_atomic = user_data;
+  MetaKmsImplDevice *impl_device = META_KMS_IMPL_DEVICE (impl_device_atomic);
+  MetaKmsPageFlipData *page_flip_data = NULL;
+
+  g_hash_table_steal_extended (impl_device_atomic->page_flip_datas,
+                               GUINT_TO_POINTER (crtc_id),
+                               NULL,
+                               (gpointer *) &page_flip_data);
+
+  meta_topic (META_DEBUG_KMS,
+              "[atomic] Page flip callback for CRTC (%u, %s), data: %p",
+              crtc_id, meta_kms_impl_device_get_path (impl_device),
+              page_flip_data);
+
+  if (!page_flip_data)
+    return;
+  g_return_if_fail (page_flip_data);
+
+  meta_kms_page_flip_data_set_timings_in_impl (page_flip_data,
+                                               sequence, tv_sec, tv_usec);
+  meta_kms_impl_device_handle_page_flip_callback (impl_device, page_flip_data);
+}
+
+static void
+meta_kms_impl_device_atomic_setup_drm_event_context (MetaKmsImplDevice *impl,
+                                                     drmEventContext   *drm_event_context)
+{
+  drm_event_context->version = 3;
+  drm_event_context->page_flip_handler2 = atomic_page_flip_handler;
+}
+
+static const char *
+commit_flags_string (uint32_t commit_flags)
+{
+  static char static_commit_flags_string[255];
+  const char *commit_flag_strings[4] = { NULL };
+  int i = 0;
+  g_autofree char *commit_flags_string = NULL;
+
+  if (commit_flags & DRM_MODE_ATOMIC_NONBLOCK)
+    commit_flag_strings[i++] = "ATOMIC_NONBLOCK";
+  if (commit_flags & DRM_MODE_ATOMIC_ALLOW_MODESET)
+    commit_flag_strings[i++] = "ATOMIC_ALLOW_MODESET";
+  if (commit_flags & DRM_MODE_PAGE_FLIP_EVENT)
+    commit_flag_strings[i++] = "PAGE_FLIP_EVENT";
+
+  commit_flags_string = g_strjoinv ("|", (char **) commit_flag_strings);
+  strncpy (static_commit_flags_string, commit_flags_string,
+           (sizeof static_commit_flags_string) - 1);
+
+  return static_commit_flags_string;
+}
+
+static gboolean
+disable_connectors (MetaKmsImplDevice  *impl_device,
+                    drmModeAtomicReq   *req,
+                    GError            **error)
+{
+  GList *l;
+
+  for (l = meta_kms_impl_device_peek_connectors (impl_device); l; l = l->next)
+    {
+      MetaKmsConnector *connector = l->data;
+
+      if (!add_connector_property (impl_device,
+                                   connector, req,
+                                   META_KMS_CONNECTOR_PROP_CRTC_ID,
+                                   0,
+                                   error))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+disable_planes (MetaKmsImplDevice  *impl_device,
+                drmModeAtomicReq   *req,
+                GError            **error)
+{
+  GList *l;
+
+  for (l = meta_kms_impl_device_peek_planes (impl_device); l; l = l->next)
+    {
+      MetaKmsPlane *plane = l->data;
+
+      if (!add_plane_property (impl_device,
+                               plane, req,
+                               META_KMS_PLANE_PROP_CRTC_ID,
+                               0,
+                               error))
+        return FALSE;
+
+      if (!add_plane_property (impl_device,
+                               plane, req,
+                               META_KMS_PLANE_PROP_FB_ID,
+                               0,
+                               error))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+disable_crtcs (MetaKmsImplDevice  *impl_device,
+               drmModeAtomicReq   *req,
+               GError            **error)
+{
+  GList *l;
+
+  for (l = meta_kms_impl_device_peek_crtcs (impl_device); l; l = l->next)
+    {
+      MetaKmsCrtc *crtc = l->data;
+
+      if (!add_crtc_property (impl_device,
+                              crtc, req,
+                              META_KMS_CRTC_PROP_ACTIVE,
+                              0,
+                              error))
+        return FALSE;
+
+      if (!add_crtc_property (impl_device,
+                              crtc, req,
+                              META_KMS_CRTC_PROP_MODE_ID,
+                              0,
+                              error))
+        return FALSE;
+    }
+
+  return TRUE;
+}
+
+static gboolean
+disable_planes_and_connectors (MetaKmsImplDevice  *impl_device,
+                               drmModeAtomicReq   *req,
+                               GError            **error)
+{
+  if (!disable_connectors (impl_device, req, error))
+    return FALSE;
+  if (!disable_planes (impl_device, req, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+static gboolean
+process_power_save (MetaKmsImplDevice *impl_device,
+                   drmModeAtomicReq   *req,
+                   GError            **error)
+{
+  if (!disable_connectors (impl_device, req, error))
+    return FALSE;
+  if (!disable_planes (impl_device, req, error))
+    return FALSE;
+  if (!disable_crtcs (impl_device, req, error))
+    return FALSE;
+
+  return TRUE;
+}
+
+static MetaKmsFeedback *
+meta_kms_impl_device_atomic_process_update (MetaKmsImplDevice *impl_device,
+                                            MetaKmsUpdate     *update)
+{
+  GError *error = NULL;
+  GList *failed_planes = NULL;
+  drmModeAtomicReq *req;
+  GArray *blob_ids;
+  int fd;
+  uint32_t commit_flags = 0;
+  int ret;
+
+  blob_ids = g_array_new (FALSE, TRUE, sizeof (uint32_t));
+
+  meta_topic (META_DEBUG_KMS,
+              "[atomic] Processing update %" G_GUINT64_FORMAT,
+              meta_kms_update_get_sequence_number (update));
+
+  req = drmModeAtomicAlloc ();
+  if (!req)
+    {
+      g_set_error (&error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                   "Failed to create atomic transaction request: %s",
+                   g_strerror (errno));
+      goto err;
+    }
+
+  if (meta_kms_update_get_mode_sets (update))
+    {
+      if (!disable_planes_and_connectors (impl_device, req, &error))
+        goto err;
+    }
+
+  if (meta_kms_update_is_power_save (update))
+    {
+      meta_topic (META_DEBUG_KMS,
+                  "[atomic] Entering power save mode for %s",
+                  meta_kms_impl_device_get_path (impl_device));
+
+      if (!process_power_save (impl_device, req, &error))
+        goto err;
+
+      commit_flags = DRM_MODE_ATOMIC_ALLOW_MODESET;
+      goto commit;
+    }
+
+  if (!process_entries (impl_device,
+                        update,
+                        req,
+                        blob_ids,
+                        meta_kms_update_get_connector_updates (update),
+                        NULL,
+                        process_connector_update,
+                        &error))
+    goto err;
+
+  if (!process_entries (impl_device,
+                        update,
+                        req,
+                        blob_ids,
+                        meta_kms_update_get_mode_sets (update),
+                        NULL,
+                        process_mode_set,
+                        &error))
+    goto err;
+
+  if (!process_entries (impl_device,
+                        update,
+                        req,
+                        blob_ids,
+                        meta_kms_update_get_plane_assignments (update),
+                        NULL,
+                        process_plane_assignment,
+                        &error))
+    goto err;
+
+  if (!process_entries (impl_device,
+                        update,
+                        req,
+                        blob_ids,
+                        meta_kms_update_get_crtc_gammas (update),
+                        NULL,
+                        process_crtc_gamma,
+                        &error))
+    goto err;
+
+  if (meta_kms_update_get_mode_sets (update))
+    commit_flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
+  else
+    commit_flags |= DRM_MODE_ATOMIC_NONBLOCK;
+
+  if (meta_kms_update_get_page_flip_listeners (update))
+    commit_flags |= DRM_MODE_PAGE_FLIP_EVENT;
+
+commit:
+  meta_topic (META_DEBUG_KMS,
+              "[atomic] Committing update %" G_GUINT64_FORMAT ", flags: %s",
+              meta_kms_update_get_sequence_number (update),
+              commit_flags_string (commit_flags));
+
+  fd = meta_kms_impl_device_get_fd (impl_device);
+  ret = drmModeAtomicCommit (fd, req, commit_flags, impl_device);
+  drmModeAtomicFree (req);
+  if (ret < 0)
+    {
+      g_set_error (&error, G_IO_ERROR, g_io_error_from_errno (-ret),
+                   "drmModeAtomicCommit: %s", g_strerror (-ret));
+      goto err;
+    }
+
+  process_entries (impl_device,
+                   update,
+                   req,
+                   blob_ids,
+                   meta_kms_update_get_page_flip_listeners (update),
+                   NULL,
+                   process_page_flip_listener,
+                   NULL);
+
+  release_blob_ids (impl_device, blob_ids);
+
+  return meta_kms_feedback_new_passed (NULL);
+
+err:
+  meta_topic (META_DEBUG_KMS, "[atomic] KMS update failed: %s", error->message);
+
+  process_entries (impl_device,
+                   update,
+                   req,
+                   blob_ids,
+                   meta_kms_update_get_page_flip_listeners (update),
+                   error,
+                   discard_page_flip_listener,
+                   NULL);
+
+  release_blob_ids (impl_device, blob_ids);
+
+  return meta_kms_feedback_new_failed (failed_planes, error);
+}
+
+static void
+meta_kms_impl_device_atomic_handle_page_flip_callback (MetaKmsImplDevice   *impl_device,
+                                                       MetaKmsPageFlipData *page_flip_data)
+{
+  meta_kms_page_flip_data_flipped_in_impl (page_flip_data);
+}
+
+static void
+meta_kms_impl_device_atomic_discard_pending_page_flips (MetaKmsImplDevice *impl_device)
+{
+}
+
+static void
+meta_kms_impl_device_atomic_finalize (GObject *object)
+{
+  MetaKmsImplDeviceAtomic *impl_device_atomic =
+    META_KMS_IMPL_DEVICE_ATOMIC (object);
+
+  g_hash_table_unref (impl_device_atomic->page_flip_datas);
+
+  G_OBJECT_CLASS (meta_kms_impl_device_atomic_parent_class)->finalize (object);
+}
+
+static void
+meta_kms_impl_device_atomic_init (MetaKmsImplDeviceAtomic *impl_device_atomic)
+{
+  impl_device_atomic->page_flip_datas =
+    g_hash_table_new_full (NULL, NULL,
+                           NULL,
+                           (GDestroyNotify) meta_kms_page_flip_data_unref);
+}
+
+static void
+meta_kms_impl_device_atomic_class_init (MetaKmsImplDeviceAtomicClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  MetaKmsImplDeviceClass *impl_device_class =
+    META_KMS_IMPL_DEVICE_CLASS (klass);
+
+  object_class->finalize = meta_kms_impl_device_atomic_finalize;
+
+  impl_device_class->setup_drm_event_context =
+    meta_kms_impl_device_atomic_setup_drm_event_context;
+  impl_device_class->process_update =
+    meta_kms_impl_device_atomic_process_update;
+  impl_device_class->handle_page_flip_callback =
+    meta_kms_impl_device_atomic_handle_page_flip_callback;
+  impl_device_class->discard_pending_page_flips =
+    meta_kms_impl_device_atomic_discard_pending_page_flips;
+}
diff --git a/src/backends/native/meta-kms-impl-device-atomic.h 
b/src/backends/native/meta-kms-impl-device-atomic.h
new file mode 100644
index 0000000000..74658797c6
--- /dev/null
+++ b/src/backends/native/meta-kms-impl-device-atomic.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2019 Red Hat
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ */
+
+#ifndef META_KMS_IMPL_DEVICE_ATOMIC_H
+#define META_KMS_IMPL_DEVICE_ATOMIC_H
+
+#include "backends/native/meta-kms-impl-device.h"
+
+#define META_TYPE_KMS_IMPL_DEVICE_ATOMIC (meta_kms_impl_device_atomic_get_type ())
+G_DECLARE_FINAL_TYPE (MetaKmsImplDeviceAtomic, meta_kms_impl_device_atomic,
+                      META, KMS_IMPL_DEVICE_ATOMIC, MetaKmsImplDevice)
+
+#endif /* META_KMS_IMPL_DEVICE_ATOMIC_H */
diff --git a/src/backends/native/meta-kms-impl-device.c b/src/backends/native/meta-kms-impl-device.c
index 8cc8a3b433..b2f1c3f111 100644
--- a/src/backends/native/meta-kms-impl-device.c
+++ b/src/backends/native/meta-kms-impl-device.c
@@ -129,6 +129,24 @@ meta_kms_impl_device_peek_connectors (MetaKmsImplDevice *impl_device)
   return priv->connectors;
 }
 
+GList *
+meta_kms_impl_device_peek_crtcs (MetaKmsImplDevice *impl_device)
+{
+  MetaKmsImplDevicePrivate *priv =
+    meta_kms_impl_device_get_instance_private (impl_device);
+
+  return priv->crtcs;
+}
+
+GList *
+meta_kms_impl_device_peek_planes (MetaKmsImplDevice *impl_device)
+{
+  MetaKmsImplDevicePrivate *priv =
+    meta_kms_impl_device_get_instance_private (impl_device);
+
+  return priv->planes;
+}
+
 const MetaKmsDeviceCaps *
 meta_kms_impl_device_get_caps (MetaKmsImplDevice *impl_device)
 {
diff --git a/src/backends/native/meta-kms-impl-device.h b/src/backends/native/meta-kms-impl-device.h
index 79db8a575b..a3d31daebf 100644
--- a/src/backends/native/meta-kms-impl-device.h
+++ b/src/backends/native/meta-kms-impl-device.h
@@ -80,6 +80,10 @@ GList * meta_kms_impl_device_copy_planes (MetaKmsImplDevice *impl_device);
 
 GList * meta_kms_impl_device_peek_connectors (MetaKmsImplDevice *impl_device);
 
+GList * meta_kms_impl_device_peek_crtcs (MetaKmsImplDevice *impl_device);
+
+GList * meta_kms_impl_device_peek_planes (MetaKmsImplDevice *impl_device);
+
 const MetaKmsDeviceCaps * meta_kms_impl_device_get_caps (MetaKmsImplDevice *impl_device);
 
 GList * meta_kms_impl_device_copy_fallback_modes (MetaKmsImplDevice *impl_device);
diff --git a/src/backends/native/meta-renderer-native.c b/src/backends/native/meta-renderer-native.c
index 85ba1d9982..0b05925914 100644
--- a/src/backends/native/meta-renderer-native.c
+++ b/src/backends/native/meta-renderer-native.c
@@ -3330,6 +3330,7 @@ meta_renderer_native_finish_frame (MetaRendererNative *renderer_native,
                                     CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
           break;
         case META_KMS_FEEDBACK_FAILED:
+          add_onscreen_frame_info (crtc);
           clutter_frame_set_result (frame,
                                     CLUTTER_FRAME_RESULT_PENDING_PRESENTED);
 
diff --git a/src/meson.build b/src/meson.build
index fb1d2ab565..8875fb2efb 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -679,6 +679,8 @@ if have_native_backend
     'backends/native/meta-kms-device-private.h',
     'backends/native/meta-kms-device.c',
     'backends/native/meta-kms-device.h',
+    'backends/native/meta-kms-impl-device-atomic.c',
+    'backends/native/meta-kms-impl-device-atomic.h',
     'backends/native/meta-kms-impl-device-simple.c',
     'backends/native/meta-kms-impl-device-simple.h',
     'backends/native/meta-kms-impl-device.c',


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]