This adds an API for writing in-kernel clients.

TODO:
- Flesh out and complete documentation.
- Cloned displays is not tested.
- Complete tiled display support and test it.
- Test plug/unplug different monitors.
- A runtime knob to prevent clients from attaching for debugging purposes.
- Maybe a way to unbind individual client instances.
- Maybe take the sysrq support in drm_fb_helper and move it here somehow.
- Add suspend/resume callbacks.
  Does anyone know why fbdev requires suspend/resume?

Signed-off-by: Noralf Trønnes <nor...@tronnes.org>
---
 drivers/gpu/drm/Kconfig             |    2 +
 drivers/gpu/drm/Makefile            |    3 +-
 drivers/gpu/drm/client/Kconfig      |    4 +
 drivers/gpu/drm/client/Makefile     |    1 +
 drivers/gpu/drm/client/drm_client.c | 1612 +++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/drm_drv.c           |    6 +
 drivers/gpu/drm/drm_file.c          |    3 +
 drivers/gpu/drm/drm_probe_helper.c  |    3 +
 include/drm/drm_client.h            |  192 +++++
 include/drm/drm_device.h            |    1 +
 include/drm/drm_file.h              |    7 +
 11 files changed, 1833 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/client/Kconfig
 create mode 100644 drivers/gpu/drm/client/Makefile
 create mode 100644 drivers/gpu/drm/client/drm_client.c
 create mode 100644 include/drm/drm_client.h

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index deeefa7a1773..d4ae15f9ee9f 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -154,6 +154,8 @@ config DRM_SCHED
        tristate
        depends on DRM
 
+source "drivers/gpu/drm/client/Kconfig"
+
 source "drivers/gpu/drm/i2c/Kconfig"
 
 source "drivers/gpu/drm/arm/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 50093ff4479b..8e06dc7eeca1 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -18,7 +18,7 @@ drm-y       :=        drm_auth.o drm_bufs.o drm_cache.o \
                drm_encoder.o drm_mode_object.o drm_property.o \
                drm_plane.o drm_color_mgmt.o drm_print.o \
                drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
-               drm_syncobj.o drm_lease.o
+               drm_syncobj.o drm_lease.o client/drm_client.o
 
 drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
 drm-$(CONFIG_DRM_VM) += drm_vm.o
@@ -103,3 +103,4 @@ obj-$(CONFIG_DRM_MXSFB)     += mxsfb/
 obj-$(CONFIG_DRM_TINYDRM) += tinydrm/
 obj-$(CONFIG_DRM_PL111) += pl111/
 obj-$(CONFIG_DRM_TVE200) += tve200/
+obj-y                  += client/
diff --git a/drivers/gpu/drm/client/Kconfig b/drivers/gpu/drm/client/Kconfig
new file mode 100644
index 000000000000..4bb8e4655ff7
--- /dev/null
+++ b/drivers/gpu/drm/client/Kconfig
@@ -0,0 +1,4 @@
+menu "DRM Clients"
+       depends on DRM
+
+endmenu
diff --git a/drivers/gpu/drm/client/Makefile b/drivers/gpu/drm/client/Makefile
new file mode 100644
index 000000000000..f66554cd5c45
--- /dev/null
+++ b/drivers/gpu/drm/client/Makefile
@@ -0,0 +1 @@
+# SPDX-License-Identifier: GPL-2.0
diff --git a/drivers/gpu/drm/client/drm_client.c 
b/drivers/gpu/drm/client/drm_client.c
new file mode 100644
index 000000000000..a633bf747316
--- /dev/null
+++ b/drivers/gpu/drm/client/drm_client.c
@@ -0,0 +1,1612 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2018 Noralf Trønnes
+
+#include <linux/dma-buf.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#include <drm/drm_client.h>
+#include <drm/drm_connector.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_file.h>
+#include <drm/drm_ioctl.h>
+#include <drm/drmP.h>
+
+#include "drm_crtc_internal.h"
+#include "drm_internal.h"
+
+struct drm_client_funcs_entry {
+       struct list_head list;
+       const struct drm_client_funcs *funcs;
+};
+
+static LIST_HEAD(drm_client_list);
+static LIST_HEAD(drm_client_funcs_list);
+static DEFINE_MUTEX(drm_client_list_lock);
+
+static void drm_client_new(struct drm_device *dev,
+                          const struct drm_client_funcs *funcs)
+{
+       struct drm_client_dev *client;
+       int ret;
+
+       lockdep_assert_held(&drm_client_list_lock);
+
+       client = kzalloc(sizeof(*client), GFP_KERNEL);
+       if (!client)
+               return;
+
+       mutex_init(&client->lock);
+       client->dev = dev;
+       client->funcs = funcs;
+
+       ret = funcs->new(client);
+       DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", funcs->name, ret);
+       if (ret) {
+               drm_client_free(client);
+               return;
+       }
+
+       list_add(&client->list, &drm_client_list);
+}
+
+/**
+ * drm_client_free - Free DRM client resources
+ * @client: DRM client
+ *
+ * This is called automatically on client removal unless the client returns
+ * non-zero in the &drm_client_funcs->remove callback. The fbdev client does
+ * this when it can't close &drm_file because userspace has an open fd.
+ */
+void drm_client_free(struct drm_client_dev *client)
+{
+       DRM_DEV_DEBUG_KMS(client->dev->dev, "%s\n", client->funcs->name);
+       if (WARN_ON(client->file)) {
+               client->file_ref_count = 1;
+               drm_client_put_file(client);
+       }
+       mutex_destroy(&client->lock);
+       kfree(client->crtcs);
+       kfree(client);
+}
+EXPORT_SYMBOL(drm_client_free);
+
+static void drm_client_remove(struct drm_client_dev *client)
+{
+       lockdep_assert_held(&drm_client_list_lock);
+
+       list_del(&client->list);
+
+       if (!client->funcs->remove || !client->funcs->remove(client))
+               drm_client_free(client);
+}
+
+/**
+ * drm_client_register - Register a DRM client
+ * @funcs: Client callbacks
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int drm_client_register(const struct drm_client_funcs *funcs)
+{
+       struct drm_client_funcs_entry *funcs_entry;
+       struct drm_device_list_iter iter;
+       struct drm_device *dev;
+
+       funcs_entry = kzalloc(sizeof(*funcs_entry), GFP_KERNEL);
+       if (!funcs_entry)
+               return -ENOMEM;
+
+       funcs_entry->funcs = funcs;
+
+       mutex_lock(&drm_global_mutex);
+       mutex_lock(&drm_client_list_lock);
+
+       drm_device_list_iter_begin(&iter);
+       drm_for_each_device_iter(dev, &iter)
+               if (drm_core_check_feature(dev, DRIVER_MODESET))
+                       drm_client_new(dev, funcs);
+       drm_device_list_iter_end(&iter);
+
+       list_add(&funcs_entry->list, &drm_client_funcs_list);
+
+       mutex_unlock(&drm_client_list_lock);
+       mutex_unlock(&drm_global_mutex);
+
+       DRM_DEBUG_KMS("%s\n", funcs->name);
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_client_register);
+
+/**
+ * drm_client_unregister - Unregister a DRM client
+ * @funcs: Client callbacks
+ */
+void drm_client_unregister(const struct drm_client_funcs *funcs)
+{
+       struct drm_client_funcs_entry *funcs_entry;
+       struct drm_client_dev *client, *tmp;
+
+       mutex_lock(&drm_client_list_lock);
+
+       list_for_each_entry_safe(client, tmp, &drm_client_list, list) {
+               if (client->funcs == funcs)
+                       drm_client_remove(client);
+       }
+
+       list_for_each_entry(funcs_entry, &drm_client_funcs_list, list) {
+               if (funcs_entry->funcs == funcs) {
+                       list_del(&funcs_entry->list);
+                       kfree(funcs_entry);
+                       break;
+               }
+       }
+
+       mutex_unlock(&drm_client_list_lock);
+
+       DRM_DEBUG_KMS("%s\n", funcs->name);
+}
+EXPORT_SYMBOL(drm_client_unregister);
+
+void drm_client_dev_register(struct drm_device *dev)
+{
+       struct drm_client_funcs_entry *funcs_entry;
+
+       /*
+        * Minors are created at the beginning of drm_dev_register(), but can
+        * be removed again if the function fails. Since we iterate DRM devices
+        * by walking DRM minors, we need to stay under this lock.
+        */
+       lockdep_assert_held(&drm_global_mutex);
+
+       if (!drm_core_check_feature(dev, DRIVER_MODESET))
+               return;
+
+       mutex_lock(&drm_client_list_lock);
+       list_for_each_entry(funcs_entry, &drm_client_funcs_list, list)
+               drm_client_new(dev, funcs_entry->funcs);
+       mutex_unlock(&drm_client_list_lock);
+}
+
+void drm_client_dev_unregister(struct drm_device *dev)
+{
+       struct drm_client_dev *client, *tmp;
+
+       if (!drm_core_check_feature(dev, DRIVER_MODESET))
+               return;
+
+       mutex_lock(&drm_client_list_lock);
+       list_for_each_entry_safe(client, tmp, &drm_client_list, list)
+               if (client->dev == dev)
+                       drm_client_remove(client);
+       mutex_unlock(&drm_client_list_lock);
+}
+
+void drm_client_dev_hotplug(struct drm_device *dev)
+{
+       struct drm_client_dev *client;
+       int ret;
+
+       if (!drm_core_check_feature(dev, DRIVER_MODESET))
+               return;
+
+       mutex_lock(&drm_client_list_lock);
+       list_for_each_entry(client, &drm_client_list, list)
+               if (client->dev == dev && client->funcs->hotplug) {
+                       ret = client->funcs->hotplug(client);
+                       DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
+                                         client->funcs->name, ret);
+               }
+       mutex_unlock(&drm_client_list_lock);
+}
+
+void drm_client_dev_lastclose(struct drm_device *dev)
+{
+       struct drm_client_dev *client;
+       int ret;
+
+       if (!drm_core_check_feature(dev, DRIVER_MODESET))
+               return;
+
+       mutex_lock(&drm_client_list_lock);
+       list_for_each_entry(client, &drm_client_list, list)
+               if (client->dev == dev && client->funcs->lastclose) {
+                       ret = client->funcs->lastclose(client);
+                       DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n",
+                                         client->funcs->name, ret);
+               }
+       mutex_unlock(&drm_client_list_lock);
+}
+
+/* Get static info */
+static int drm_client_init(struct drm_client_dev *client, struct drm_file 
*file)
+{
+       struct drm_mode_card_res card_res = {};
+       struct drm_device *dev = client->dev;
+       u32 *crtcs;
+       int ret;
+
+       ret = drm_mode_getresources(dev, &card_res, file, false);
+       if (ret)
+               return ret;
+       if (!card_res.count_crtcs)
+               return -ENOENT;
+
+       crtcs = kmalloc_array(card_res.count_crtcs, sizeof(*crtcs), GFP_KERNEL);
+       if (!crtcs)
+               return -ENOMEM;
+
+       card_res.count_fbs = 0;
+       card_res.count_connectors = 0;
+       card_res.count_encoders = 0;
+       card_res.crtc_id_ptr = (unsigned long)crtcs;
+
+       ret = drm_mode_getresources(dev, &card_res, file, false);
+       if (ret) {
+               kfree(crtcs);
+               return ret;
+       }
+
+       client->crtcs = crtcs;
+       client->num_crtcs = card_res.count_crtcs;
+       client->min_width = card_res.min_width;
+       client->max_width = card_res.max_width;
+       client->min_height = card_res.min_height;
+       client->max_height = card_res.max_height;
+
+       return 0;
+}
+
+/**
+ * drm_client_get_file - Get a DRM file
+ * @client: DRM client
+ *
+ * This function makes sure the client has a &drm_file available. The client
+ * doesn't normally need to call this, since all client functions that depends
+ * on a DRM file will call it. A matching call to drm_client_put_file() is
+ * necessary.
+ *
+ * The reason for not opening a DRM file when a @client is created is because
+ * we have to take a ref on the driver module due to &drm_driver->postclose
+ * being called in drm_file_free(). Having a DRM file open for the lifetime of
+ * the client instance would block driver module unload.
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int drm_client_get_file(struct drm_client_dev *client)
+{
+       struct drm_device *dev = client->dev;
+       struct drm_file *file;
+       int ret = 0;
+
+       mutex_lock(&client->lock);
+
+       if (client->file_ref_count++) {
+               mutex_unlock(&client->lock);
+               return 0;
+       }
+
+       if (!try_module_get(dev->driver->fops->owner)) {
+               ret = -ENODEV;
+               goto err_unlock;
+       }
+
+       drm_dev_get(dev);
+
+       file = drm_file_alloc(dev->primary);
+       if (IS_ERR(file)) {
+               ret = PTR_ERR(file);
+               goto err_put;
+       }
+
+       if (!client->crtcs) {
+               ret = drm_client_init(client, file);
+               if (ret)
+                       goto err_free;
+       }
+
+       mutex_lock(&dev->filelist_mutex);
+       list_add(&file->lhead, &dev->filelist_internal);
+       mutex_unlock(&dev->filelist_mutex);
+
+       client->file = file;
+
+       mutex_unlock(&client->lock);
+
+       return 0;
+
+err_free:
+       drm_file_free(file);
+err_put:
+       drm_dev_put(dev);
+       module_put(dev->driver->fops->owner);
+err_unlock:
+       client->file_ref_count = 0;
+       mutex_unlock(&client->lock);
+
+       return ret;
+}
+EXPORT_SYMBOL(drm_client_get_file);
+
+void drm_client_put_file(struct drm_client_dev *client)
+{
+       struct drm_device *dev = client->dev;
+
+       if (!client)
+               return;
+
+       mutex_lock(&client->lock);
+
+       if (WARN_ON(!client->file_ref_count))
+               goto out_unlock;
+
+       if (--client->file_ref_count)
+               goto out_unlock;
+
+       mutex_lock(&dev->filelist_mutex);
+       list_del(&client->file->lhead);
+       mutex_unlock(&dev->filelist_mutex);
+
+       drm_file_free(client->file);
+       client->file = NULL;
+       drm_dev_put(dev);
+       module_put(dev->driver->fops->owner);
+out_unlock:
+       mutex_unlock(&client->lock);
+}
+EXPORT_SYMBOL(drm_client_put_file);
+
+static struct drm_pending_event *
+drm_client_read_get_pending_event(struct drm_device *dev, struct drm_file 
*file)
+{
+       struct drm_pending_event *e = NULL;
+       int ret;
+
+       ret = mutex_lock_interruptible(&file->event_read_lock);
+       if (ret)
+               return ERR_PTR(ret);
+
+       spin_lock_irq(&dev->event_lock);
+       if (!list_empty(&file->event_list)) {
+               e = list_first_entry(&file->event_list,
+                                    struct drm_pending_event, link);
+               file->event_space += e->event->length;
+               list_del(&e->link);
+       }
+       spin_unlock_irq(&dev->event_lock);
+
+       mutex_unlock(&file->event_read_lock);
+
+       return e;
+}
+
+struct drm_event *
+drm_client_read_event(struct drm_client_dev *client, bool block)
+{
+       struct drm_file *file = client->file;
+       struct drm_device *dev = client->dev;
+       struct drm_pending_event *e;
+       struct drm_event *event;
+       int ret;
+
+       /* Allocate so it fits all events, there's a sanity check later */
+       event = kzalloc(128, GFP_KERNEL);
+       if (!event)
+               return ERR_PTR(-ENOMEM);
+
+       e = drm_client_read_get_pending_event(dev, file);
+       if (IS_ERR(e)) {
+               ret = PTR_ERR(e);
+               goto err_free;
+       }
+
+       if (!e && !block) {
+               ret = 0;
+               goto err_free;
+       }
+
+       ret = wait_event_interruptible_timeout(file->event_wait,
+                                              !list_empty(&file->event_list),
+                                              5 * HZ);
+       if (!ret)
+               ret = -ETIMEDOUT;
+       if (ret < 0)
+               goto err_free;
+
+       e = drm_client_read_get_pending_event(dev, file);
+       if (IS_ERR_OR_NULL(e)) {
+               ret = PTR_ERR_OR_ZERO(e);
+               goto err_free;
+       }
+
+       if (WARN_ON(e->event->length > 128)) {
+               /* Increase buffer if this happens */
+               ret = -ENOMEM;
+               goto err_free;
+       }
+
+       memcpy(event, e->event, e->event->length);
+       kfree(e);
+
+       return event;
+
+err_free:
+       kfree(event);
+
+       return ret ? ERR_PTR(ret) : NULL;
+}
+EXPORT_SYMBOL(drm_client_read_event);
+
+static void drm_client_connector_free(struct drm_client_connector *connector)
+{
+       if (!connector)
+               return;
+       kfree(connector->modes);
+       kfree(connector);
+}
+
+static struct drm_client_connector *
+drm_client_get_connector(struct drm_client_dev *client, unsigned int id)
+{
+       struct drm_mode_get_connector req = {
+               .connector_id = id,
+       };
+       struct drm_client_connector *connector;
+       struct drm_mode_modeinfo *modes = NULL;
+       struct drm_device *dev = client->dev;
+       struct drm_connector *conn;
+       bool non_desktop;
+       int ret;
+
+       connector = kzalloc(sizeof(*connector), GFP_KERNEL);
+       if (!connector)
+               return ERR_PTR(-ENOMEM);
+
+       ret = drm_mode_getconnector(dev, &req, client->file, false);
+       if (ret)
+               goto err_free;
+
+       connector->conn_id = id;
+       connector->status = req.connection;
+
+       conn = drm_connector_lookup(dev, client->file, id);
+       if (!conn) {
+               ret = -ENOENT;
+               goto err_free;
+       }
+
+       non_desktop = conn->display_info.non_desktop;
+
+       connector->has_tile = conn->has_tile;
+       connector->tile_h_loc = conn->tile_h_loc;
+       connector->tile_v_loc = conn->tile_v_loc;
+       if (conn->tile_group)
+               connector->tile_group = conn->tile_group->id;
+
+       drm_connector_put(conn);
+
+       if (non_desktop) {
+               kfree(connector);
+               return NULL;
+       }
+
+       if (!req.count_modes)
+               return connector;
+
+       modes = kcalloc(req.count_modes, sizeof(*modes), GFP_KERNEL);
+       if (!modes) {
+               ret = -ENOMEM;
+               goto err_free;
+       }
+
+       connector->modes = modes;
+       connector->num_modes = req.count_modes;
+
+       req.count_props = 0;
+       req.count_encoders = 0;
+       req.modes_ptr = (unsigned long)modes;
+
+       ret = drm_mode_getconnector(dev, &req, client->file, false);
+       if (ret)
+               goto err_free;
+
+       return connector;
+
+err_free:
+       kfree(modes);
+       kfree(connector);
+
+       return ERR_PTR(ret);
+}
+
+static int drm_client_get_connectors(struct drm_client_dev *client,
+                                    struct drm_client_connector ***connectors)
+{
+       struct drm_mode_card_res card_res = {};
+       struct drm_device *dev = client->dev;
+       int ret, num_connectors;
+       u32 *connector_ids;
+       unsigned int i;
+
+       ret = drm_mode_getresources(dev, &card_res, client->file, false);
+       if (ret)
+               return ret;
+       if (!card_res.count_connectors)
+               return 0;
+
+       num_connectors = card_res.count_connectors;
+       connector_ids = kcalloc(num_connectors,
+                               sizeof(*connector_ids), GFP_KERNEL);
+       if (!connector_ids)
+               return -ENOMEM;
+
+       card_res.count_fbs = 0;
+       card_res.count_crtcs = 0;
+       card_res.count_encoders = 0;
+       card_res.connector_id_ptr = (unsigned long)connector_ids;
+
+       ret = drm_mode_getresources(dev, &card_res, client->file, false);
+       if (ret)
+               goto err_free;
+
+       *connectors = kcalloc(num_connectors, sizeof(**connectors), GFP_KERNEL);
+       if (!(*connectors)) {
+               ret = -ENOMEM;
+               goto err_free;
+       }
+
+       for (i = 0; i < num_connectors; i++) {
+               struct drm_client_connector *connector;
+
+               connector = drm_client_get_connector(client, connector_ids[i]);
+               if (IS_ERR(connector)) {
+                       ret = PTR_ERR(connector);
+                       goto err_free;
+               }
+               if (connector)
+                       (*connectors)[i] = connector;
+               else
+                       num_connectors--;
+       }
+
+       if (!num_connectors) {
+               ret = 0;
+               goto err_free;
+       }
+
+       return num_connectors;
+
+err_free:
+       if (connectors)
+               for (i = 0; i < num_connectors; i++)
+                       drm_client_connector_free((*connectors)[i]);
+
+       kfree(connectors);
+       kfree(connector_ids);
+
+       return ret;
+}
+
+static bool
+drm_client_connector_is_enabled(struct drm_client_connector *connector,
+                               bool strict)
+{
+       if (strict)
+               return connector->status == connector_status_connected;
+       else
+               return connector->status != connector_status_disconnected;
+}
+
+struct drm_mode_modeinfo *
+drm_client_display_first_mode(struct drm_client_display *display)
+{
+       if (!display->num_modes)
+               return NULL;
+       return display->modes;
+}
+EXPORT_SYMBOL(drm_client_display_first_mode);
+
+struct drm_mode_modeinfo *
+drm_client_display_next_mode(struct drm_client_display *display,
+                            struct drm_mode_modeinfo *mode)
+{
+       struct drm_mode_modeinfo *modes = display->modes;
+
+       if (++mode < &modes[display->num_modes])
+               return mode;
+
+       return NULL;
+}
+EXPORT_SYMBOL(drm_client_display_next_mode);
+
+static void
+drm_client_display_fill_tile_modes(struct drm_client_display *display,
+                                  struct drm_mode_modeinfo *tile_modes)
+{
+       unsigned int i, j, num_modes = display->connectors[0]->num_modes;
+       struct drm_mode_modeinfo *tile_mode, *conn_mode;
+
+       if (!num_modes) {
+               kfree(tile_modes);
+               kfree(display->modes);
+               display->modes = NULL;
+               display->num_modes = 0;
+               return;
+       }
+
+       for (i = 0; i < num_modes; i++) {
+               tile_mode = &tile_modes[i];
+
+               conn_mode = &display->connectors[0]->modes[i];
+               tile_mode->clock = conn_mode->clock;
+               tile_mode->vscan = conn_mode->vscan;
+               tile_mode->vrefresh = conn_mode->vrefresh;
+               tile_mode->flags = conn_mode->flags;
+               tile_mode->type = conn_mode->type;
+
+               for (j = 0; j < display->num_connectors; j++) {
+                       conn_mode = &display->connectors[j]->modes[i];
+
+                       if (!display->connectors[j]->tile_h_loc) {
+                               tile_mode->hdisplay += conn_mode->hdisplay;
+                               tile_mode->hsync_start += 
conn_mode->hsync_start;
+                               tile_mode->hsync_end += conn_mode->hsync_end;
+                               tile_mode->htotal += conn_mode->htotal;
+                       }
+
+                       if (!display->connectors[j]->tile_v_loc) {
+                               tile_mode->vdisplay += conn_mode->vdisplay;
+                               tile_mode->vsync_start += 
conn_mode->vsync_start;
+                               tile_mode->vsync_end += conn_mode->vsync_end;
+                               tile_mode->vtotal += conn_mode->vtotal;
+                       }
+               }
+       }
+
+       kfree(display->modes);
+       display->modes = tile_modes;
+       display->num_modes = num_modes;
+}
+
+/**
+ * drm_client_display_update_modes - Fetch display modes
+ * @display: Client display
+ * @mode_changed: Optional pointer to boolen which return whether the modes
+ *                have changed or not.
+ *
+ * This function can be used in the client hotplug callback to check if the
+ * video modes have changed and get them up-to-date.
+ *
+ * Returns:
+ * Number of modes on success, negative error code on failure.
+ */
+int drm_client_display_update_modes(struct drm_client_display *display,
+                                   bool *mode_changed)
+{
+       unsigned int num_connectors = display->num_connectors;
+       struct drm_client_dev *client = display->client;
+       struct drm_mode_modeinfo *display_tile_modes;
+       struct drm_client_connector **connectors;
+       unsigned int i, num_modes = 0;
+       bool dummy_changed = false;
+       int ret;
+
+       if (mode_changed)
+               *mode_changed = false;
+       else
+               mode_changed = &dummy_changed;
+
+       if (display->cloned)
+               return 2;
+
+       ret = drm_client_get_file(client);
+       if (ret)
+               return ret;
+
+       connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
+       if (!connectors) {
+               ret = -ENOMEM;
+               goto out_put_file;
+       }
+
+       /* Get a new set for comparison */
+       for (i = 0; i < num_connectors; i++) {
+               connectors[i] = drm_client_get_connector(client, 
display->connectors[i]->conn_id);
+               if (IS_ERR_OR_NULL(connectors[i])) {
+                       ret = PTR_ERR_OR_ZERO(connectors[i]);
+                       if (!ret)
+                               ret = -ENOENT;
+                       goto out_cleanup;
+               }
+       }
+
+       /* All connectors should have the same number of modes */
+       num_modes = connectors[0]->num_modes;
+       for (i = 0; i < num_connectors; i++) {
+               if (num_modes != connectors[i]->num_modes) {
+                       ret = -EINVAL;
+                       goto out_cleanup;
+               }
+       }
+
+       if (num_connectors > 1) {
+               display_tile_modes = kcalloc(num_modes, 
sizeof(*display_tile_modes), GFP_KERNEL);
+               if (!display_tile_modes) {
+                       ret = -ENOMEM;
+                       goto out_cleanup;
+               }
+       }
+
+       mutex_lock(&display->modes_lock);
+
+       for (i = 0; i < num_connectors; i++) {
+               display->connectors[i]->status = connectors[i]->status;
+               if (display->connectors[i]->num_modes != 
connectors[i]->num_modes) {
+                       display->connectors[i]->num_modes = 
connectors[i]->num_modes;
+                       kfree(display->connectors[i]->modes);
+                       display->connectors[i]->modes = connectors[i]->modes;
+                       connectors[i]->modes = NULL;
+                       *mode_changed = true;
+               }
+       }
+
+       if (num_connectors > 1)
+               drm_client_display_fill_tile_modes(display, display_tile_modes);
+       else
+               display->modes = display->connectors[0]->modes;
+
+       mutex_unlock(&display->modes_lock);
+
+out_cleanup:
+       for (i = 0; i < num_connectors; i++)
+               drm_client_connector_free(connectors[i]);
+       kfree(connectors);
+out_put_file:
+       drm_client_put_file(client);
+
+       return ret ? ret : num_modes;
+}
+EXPORT_SYMBOL(drm_client_display_update_modes);
+
+void drm_client_display_free(struct drm_client_display *display)
+{
+       unsigned int i;
+
+       if (!display)
+               return;
+
+       /* tile modes? */
+       if (display->modes != display->connectors[0]->modes)
+               kfree(display->modes);
+
+       for (i = 0; i < display->num_connectors; i++)
+               drm_client_connector_free(display->connectors[i]);
+
+       kfree(display->connectors);
+       mutex_destroy(&display->modes_lock);
+       kfree(display);
+}
+EXPORT_SYMBOL(drm_client_display_free);
+
+static struct drm_client_display *
+drm_client_display_alloc(struct drm_client_dev *client,
+                        unsigned int num_connectors)
+{
+       struct drm_client_display *display;
+       struct drm_client_connector **connectors;
+
+       display = kzalloc(sizeof(*display), GFP_KERNEL);
+       connectors = kcalloc(num_connectors, sizeof(*connectors), GFP_KERNEL);
+       if (!display || !connectors) {
+               kfree(display);
+               kfree(connectors);
+               return NULL;
+       }
+
+       mutex_init(&display->modes_lock);
+       display->client = client;
+       display->connectors = connectors;
+       display->num_connectors = num_connectors;
+
+       return display;
+}
+
+/* Logic is from drm_fb_helper */
+static struct drm_client_display *
+drm_client_connector_pick_cloned(struct drm_client_dev *client,
+                                struct drm_client_connector **connectors,
+                                unsigned int num_connectors)
+{
+       struct drm_mode_modeinfo modes[2], udmt_mode, *mode, *tmp;
+       struct drm_display_mode *dmt_display_mode = NULL;
+       unsigned int i, j, conns[2], num_conns = 0;
+       struct drm_client_connector *connector;
+       struct drm_device *dev = client->dev;
+       struct drm_client_display *display;
+
+       /* only contemplate cloning in the single crtc case */
+       if (dev->mode_config.num_crtc > 1)
+               return NULL;
+retry:
+       for (i = 0; i < num_connectors; i++) {
+               connector = connectors[i];
+               if (!connector || connector->has_tile || !connector->num_modes)
+                       continue;
+
+               for (j = 0; j < connector->num_modes; j++) {
+                       mode = &connector->modes[j];
+                       if (dmt_display_mode) {
+                               if (drm_umode_equal(&udmt_mode, mode)) {
+                                       conns[num_conns] = i;
+                                       modes[num_conns++] = *mode;
+                                       break;
+                               }
+                       } else {
+                               if (mode->type & DRM_MODE_TYPE_USERDEF) {
+                                       conns[num_conns] = i;
+                                       modes[num_conns++] = *mode;
+                                       break;
+                               }
+                       }
+               }
+               if (num_conns == 2)
+                       break;
+       }
+
+       if (num_conns == 2)
+               goto found;
+
+       if (dmt_display_mode)
+               return NULL;
+
+       dmt_display_mode = drm_mode_find_dmt(dev, 1024, 768, 60, false);
+       drm_mode_convert_to_umode(&udmt_mode, dmt_display_mode);
+       drm_mode_destroy(dev, dmt_display_mode);
+
+       goto retry;
+
+found:
+       tmp = kcalloc(2, sizeof(*tmp), GFP_KERNEL);
+       if (!tmp)
+               return ERR_PTR(-ENOMEM);
+
+       display = drm_client_display_alloc(client, 2);
+       if (!display) {
+               kfree(tmp);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       for (i = 0; i < 2; i++) {
+               connector = connectors[conns[i]];
+               display->connectors[i] = connector;
+               connectors[conns[i]] = NULL;
+               kfree(connector->modes);
+               tmp[i] = modes[i];
+               connector->modes = &tmp[i];
+               connector->num_modes = 1;
+       }
+
+       display->cloned = true;
+       display->modes = &tmp[0];
+       display->num_modes = 1;
+
+       return display;
+}
+
+static struct drm_client_display *
+drm_client_connector_pick_tile(struct drm_client_dev *client,
+                              struct drm_client_connector **connectors,
+                              unsigned int num_connectors)
+{
+       unsigned int i, num_conns, num_modes, tile_group = 0;
+       struct drm_mode_modeinfo *tile_modes = NULL;
+       struct drm_client_connector *connector;
+       struct drm_client_display *display;
+       u16 conns[32];
+
+       for (i = 0; i < num_connectors; i++) {
+               connector = connectors[i];
+               if (!connector || !connector->tile_group)
+                       continue;
+
+               if (!tile_group) {
+                       tile_group = connector->tile_group;
+                       num_modes = connector->num_modes;
+               }
+
+               if (connector->tile_group != tile_group)
+                       continue;
+
+               if (num_modes != connector->num_modes) {
+                       DRM_ERROR("Tile connectors must have the same number of 
modes\n");
+                       return ERR_PTR(-EINVAL);
+               }
+
+               conns[num_conns++] = i;
+               if (WARN_ON(num_conns == 33))
+                       return ERR_PTR(-EINVAL);
+       }
+
+       if (!num_conns)
+               return NULL;
+
+       if (num_modes) {
+               tile_modes = kcalloc(num_modes, sizeof(*tile_modes), 
GFP_KERNEL);
+               if (!tile_modes)
+                       return ERR_PTR(-ENOMEM);
+       }
+
+       display = drm_client_display_alloc(client, num_conns);
+       if (!display) {
+               kfree(tile_modes);
+               return ERR_PTR(-ENOMEM);
+       }
+
+       if (num_modes)
+               drm_client_display_fill_tile_modes(display, tile_modes);
+
+       return display;
+}
+
+static struct drm_client_display *
+drm_client_connector_pick_not_tile(struct drm_client_dev *client,
+                                  struct drm_client_connector **connectors,
+                                  unsigned int num_connectors)
+{
+       struct drm_client_display *display;
+       unsigned int i;
+
+       for (i = 0; i < num_connectors; i++) {
+               if (!connectors[i] || connectors[i]->has_tile)
+                       continue;
+               break;
+       }
+
+       if (i == num_connectors)
+               return NULL;
+
+       display = drm_client_display_alloc(client, 1);
+       if (!display)
+               return ERR_PTR(-ENOMEM);
+
+       display->connectors[0] = connectors[i];
+       connectors[i] = NULL;
+       display->modes = display->connectors[0]->modes;
+       display->num_modes = display->connectors[0]->num_modes;
+
+       return display;
+}
+
+/* Get connectors and bundle them up into displays */
+static int drm_client_get_displays(struct drm_client_dev *client,
+                                  struct drm_client_display ***displays)
+{
+       int ret, num_connectors, num_displays = 0;
+       struct drm_client_connector **connectors;
+       struct drm_client_display *display;
+       unsigned int i;
+
+       ret = drm_client_get_file(client);
+       if (ret)
+               return ret;
+
+       num_connectors = drm_client_get_connectors(client, &connectors);
+       if (num_connectors <= 0) {
+               ret = num_connectors;
+               goto err_put_file;
+       }
+
+       *displays = kcalloc(num_connectors, sizeof(*displays), GFP_KERNEL);
+       if (!(*displays)) {
+               ret = -ENOMEM;
+               goto err_free;
+       }
+
+       display = drm_client_connector_pick_cloned(client, connectors,
+                                                  num_connectors);
+       if (IS_ERR(display)) {
+               ret = PTR_ERR(display);
+               goto err_free;
+       }
+       if (display)
+               (*displays)[num_displays++] = display;
+
+       for (i = 0; i < num_connectors; i++) {
+               display = drm_client_connector_pick_tile(client, connectors,
+                                                        num_connectors);
+               if (IS_ERR(display)) {
+                       ret = PTR_ERR(display);
+                       goto err_free;
+               }
+               if (!display)
+                       break;
+               (*displays)[num_displays++] = display;
+       }
+
+       for (i = 0; i < num_connectors; i++) {
+               display = drm_client_connector_pick_not_tile(client, connectors,
+                                                            num_connectors);
+               if (IS_ERR(display)) {
+                       ret = PTR_ERR(display);
+                       goto err_free;
+               }
+               if (!display)
+                       break;
+               (*displays)[num_displays++] = display;
+       }
+
+       for (i = 0; i < num_connectors; i++) {
+               if (connectors[i]) {
+                       DRM_INFO("Connector %u fell through the cracks.\n",
+                                connectors[i]->conn_id);
+                       drm_client_connector_free(connectors[i]);
+               }
+       }
+
+       drm_client_put_file(client);
+       kfree(connectors);
+
+       return num_displays;
+
+err_free:
+       for (i = 0; i < num_displays; i++)
+               drm_client_display_free((*displays)[i]);
+       kfree(*displays);
+       *displays = NULL;
+       for (i = 0; i < num_connectors; i++)
+               drm_client_connector_free(connectors[i]);
+       kfree(connectors);
+err_put_file:
+       drm_client_put_file(client);
+
+       return ret;
+}
+
+static bool
+drm_client_display_is_enabled(struct drm_client_display *display, bool strict)
+{
+       unsigned int i;
+
+       if (!display->num_modes)
+               return false;
+
+       for (i = 0; i < display->num_connectors; i++)
+               if (!drm_client_connector_is_enabled(display->connectors[i], 
strict))
+                       return false;
+
+       return true;
+}
+
+/**
+ * drm_client_display_get_first_enabled - Get first enabled display
+ * @client: DRM client
+ * @strict: If true the connector(s) have to be connected, if false they can
+ *          also have unknown status.
+ *
+ * This function gets all connectors and bundles them into displays
+ * (tiled/cloned). It then picks the first one with connectors that is enabled
+ * according to @strict.
+ *
+ * Returns:
+ * Pointer to a client display if such a display was found, NULL if not found
+ * or an error pointer on failure.
+ */
+struct drm_client_display *
+drm_client_display_get_first_enabled(struct drm_client_dev *client, bool 
strict)
+{
+       struct drm_client_display **displays, *display = NULL;
+       int num_displays;
+       unsigned int i;
+
+       num_displays = drm_client_get_displays(client, &displays);
+       if (num_displays < 0)
+               return ERR_PTR(num_displays);
+       if (!num_displays)
+               return NULL;
+
+       for (i = 0; i < num_displays; i++) {
+               if (!display &&
+                   drm_client_display_is_enabled(displays[i], strict)) {
+                       display = displays[i];
+                       continue;
+               }
+               drm_client_display_free(displays[i]);
+       }
+
+       kfree(displays);
+
+       return display;
+}
+EXPORT_SYMBOL(drm_client_display_get_first_enabled);
+
+unsigned int
+drm_client_display_preferred_depth(struct drm_client_display *display)
+{
+       struct drm_connector *conn;
+       unsigned int ret;
+
+       conn = drm_connector_lookup(display->client->dev, NULL,
+                                   display->connectors[0]->conn_id);
+       if (!conn)
+               return 0;
+
+       if (conn->cmdline_mode.bpp_specified)
+               ret = conn->cmdline_mode.bpp;
+       else
+               ret = display->client->dev->mode_config.preferred_depth;
+
+       drm_connector_put(conn);
+
+       return ret;
+}
+EXPORT_SYMBOL(drm_client_display_preferred_depth);
+
+int drm_client_display_dpms(struct drm_client_display *display, int mode)
+{
+       struct drm_mode_obj_set_property prop;
+
+       prop.value = mode;
+       prop.prop_id = display->client->dev->mode_config.dpms_property->base.id;
+       prop.obj_id = display->connectors[0]->conn_id;
+       prop.obj_type = DRM_MODE_OBJECT_CONNECTOR;
+
+       return drm_mode_obj_set_property(display->client->dev, &prop,
+                                        display->client->file);
+}
+EXPORT_SYMBOL(drm_client_display_dpms);
+
+int drm_client_display_wait_vblank(struct drm_client_display *display)
+{
+       struct drm_crtc *crtc;
+       union drm_wait_vblank vblank_req = {
+               .request = {
+                       .type = _DRM_VBLANK_RELATIVE,
+                       .sequence = 1,
+               },
+       };
+
+       crtc = drm_crtc_find(display->client->dev, display->client->file,
+                            display->connectors[0]->crtc_id);
+       if (!crtc)
+               return -ENOENT;
+
+       vblank_req.request.type |= drm_crtc_index(crtc) << 
_DRM_VBLANK_HIGH_CRTC_SHIFT;
+
+       return drm_wait_vblank(display->client->dev, &vblank_req,
+                              display->client->file);
+}
+EXPORT_SYMBOL(drm_client_display_wait_vblank);
+
+static int drm_client_get_crtc_index(struct drm_client_dev *client, u32 id)
+{
+       int i;
+
+       for (i = 0; i < client->num_crtcs; i++)
+               if (client->crtcs[i] == id)
+                       return i;
+
+       return -ENOENT;
+}
+
+static int drm_client_display_find_crtcs(struct drm_client_display *display)
+{
+       struct drm_client_dev *client = display->client;
+       struct drm_device *dev = client->dev;
+       struct drm_file *file = client->file;
+       u32 encoder_ids[DRM_CONNECTOR_MAX_ENCODER];
+       unsigned int i, j, available_crtcs = ~0;
+       struct drm_mode_get_connector conn_req;
+       struct drm_mode_get_encoder enc_req;
+       int ret;
+
+       /* Already done? */
+       if (display->connectors[0]->crtc_id)
+               return 0;
+
+       for (i = 0; i < display->num_connectors; i++) {
+               u32 active_crtcs = 0, crtcs_for_connector = 0;
+               int crtc_idx;
+
+               memset(&conn_req, 0, sizeof(conn_req));
+               conn_req.connector_id = display->connectors[i]->conn_id;
+               conn_req.encoders_ptr = (unsigned long)(encoder_ids);
+               conn_req.count_encoders = DRM_CONNECTOR_MAX_ENCODER;
+               ret = drm_mode_getconnector(dev, &conn_req, file, false);
+               if (ret)
+                       return ret;
+
+               if (conn_req.encoder_id) {
+                       memset(&enc_req, 0, sizeof(enc_req));
+                       enc_req.encoder_id = conn_req.encoder_id;
+                       ret = drm_mode_getencoder(dev, &enc_req, file);
+                       if (ret)
+                               return ret;
+                       crtcs_for_connector |= enc_req.possible_crtcs;
+                       if (crtcs_for_connector & available_crtcs)
+                               goto found;
+               }
+
+               for (j = 0; j < conn_req.count_encoders; j++) {
+                       memset(&enc_req, 0, sizeof(enc_req));
+                       enc_req.encoder_id = encoder_ids[j];
+                       ret = drm_mode_getencoder(dev, &enc_req, file);
+                       if (ret)
+                               return ret;
+
+                       crtcs_for_connector |= enc_req.possible_crtcs;
+
+                       if (enc_req.crtc_id) {
+                               crtc_idx = drm_client_get_crtc_index(client, 
enc_req.crtc_id);
+                               if (crtc_idx >= 0)
+                                       active_crtcs |= 1 << crtc_idx;
+                       }
+               }
+
+found:
+               crtcs_for_connector &= available_crtcs;
+               active_crtcs &= available_crtcs;
+
+               if (!crtcs_for_connector)
+                       return -ENOENT;
+
+               if (active_crtcs)
+                       crtc_idx = ffs(active_crtcs) - 1;
+               else
+                       crtc_idx = ffs(crtcs_for_connector) - 1;
+
+               if (crtc_idx >= client->num_crtcs)
+                       return -EINVAL;
+
+               display->connectors[i]->crtc_id = client->crtcs[crtc_idx];
+               available_crtcs &= ~BIT(crtc_idx);
+       }
+
+       return 0;
+}
+
+/**
+ * drm_client_display_commit_mode - Commit a mode to the crtc(s)
+ * @display: Client display
+ * @fb_id: Framebuffer id
+ * @mode: Video mode
+ *
+ * Returns:
+ * Zero on success, negative error code on failure.
+ */
+int drm_client_display_commit_mode(struct drm_client_display *display,
+                                  u32 fb_id, struct drm_mode_modeinfo *mode)
+{
+       struct drm_client_dev *client = display->client;
+       struct drm_device *dev = client->dev;
+       unsigned int num_crtcs = client->num_crtcs;
+       struct drm_file *file = client->file;
+       unsigned int *xoffsets = NULL, *yoffsets = NULL;
+       struct drm_mode_crtc *crtc_reqs, *req;
+       u32 cloned_conn_ids[2];
+       unsigned int i;
+       int idx, ret;
+
+       ret = drm_client_display_find_crtcs(display);
+       if (ret)
+               return ret;
+
+       crtc_reqs = kcalloc(num_crtcs, sizeof(*crtc_reqs), GFP_KERNEL);
+       if (!crtc_reqs)
+               return -ENOMEM;
+
+       for (i = 0; i < num_crtcs; i++)
+               crtc_reqs[i].crtc_id = client->crtcs[i];
+
+       if (drm_client_display_is_tiled(display)) {
+               /* TODO calculate tile crtc offsets */
+       }
+
+       for (i = 0; i < display->num_connectors; i++) {
+               idx = drm_client_get_crtc_index(client, 
display->connectors[i]->crtc_id);
+               if (idx < 0)
+                       return -ENOENT;
+
+               req = &crtc_reqs[idx];
+
+               req->fb_id = fb_id;
+               if (xoffsets) {
+                       req->x = xoffsets[i];
+                       req->y = yoffsets[i];
+               }
+               req->mode_valid = 1;
+               req->mode = *mode;
+
+               if (display->cloned) {
+                       cloned_conn_ids[0] = display->connectors[0]->conn_id;
+                       cloned_conn_ids[1] = display->connectors[1]->conn_id;
+                       req->set_connectors_ptr = (unsigned 
long)(cloned_conn_ids);
+                       req->count_connectors = 2;
+                       break;
+               }
+
+               req->set_connectors_ptr = (unsigned 
long)(&display->connectors[i]->conn_id);
+               req->count_connectors = 1;
+       }
+
+       for (i = 0; i < num_crtcs; i++) {
+               ret = drm_mode_setcrtc(dev, &crtc_reqs[i], file, false);
+               if (ret)
+                       break;
+       }
+
+       kfree(xoffsets);
+       kfree(yoffsets);
+       kfree(crtc_reqs);
+
+       return ret;
+}
+EXPORT_SYMBOL(drm_client_display_commit_mode);
+
+unsigned int drm_client_display_current_fb(struct drm_client_display *display)
+{
+       struct drm_client_dev *client = display->client;
+       int ret;
+       struct drm_mode_crtc crtc_req = {
+               .crtc_id = display->connectors[0]->crtc_id,
+       };
+
+       ret = drm_mode_getcrtc(client->dev, &crtc_req, client->file);
+       if (ret)
+               return 0;
+
+       return crtc_req.fb_id;
+}
+EXPORT_SYMBOL(drm_client_display_current_fb);
+
+int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
+                            struct drm_clip_rect *clips, unsigned int 
num_clips)
+{
+       struct drm_client_dev *client = display->client;
+       struct drm_mode_fb_dirty_cmd dirty_req = {
+               .fb_id = fb_id,
+               .clips_ptr = (unsigned long)clips,
+               .num_clips = num_clips,
+       };
+       int ret;
+
+       if (display->no_flushing)
+               return 0;
+
+       ret = drm_mode_dirtyfb(client->dev, &dirty_req, client->file, false);
+       if (ret == -ENOSYS) {
+               ret = 0;
+               display->no_flushing = true;
+       }
+
+       return ret;
+}
+EXPORT_SYMBOL(drm_client_display_flush);
+
+int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
+                                bool event)
+{
+       struct drm_client_dev *client = display->client;
+       struct drm_mode_crtc_page_flip_target page_flip_req = {
+               .crtc_id = display->connectors[0]->crtc_id,
+               .fb_id = fb_id,
+       };
+
+       if (event)
+               page_flip_req.flags = DRM_MODE_PAGE_FLIP_EVENT;
+
+       return drm_mode_page_flip(client->dev, &page_flip_req, client->file);
+       /*
+        * TODO:
+        * Where do we flush on page flip? Should the driver handle that?
+        */
+}
+EXPORT_SYMBOL(drm_client_display_page_flip);
+
+/**
+ * drm_client_framebuffer_create - Create a client framebuffer
+ * @client: DRM client
+ * @mode: Display mode to create a buffer for
+ * @format: Buffer format
+ *
+ * This function creates a &drm_client_buffer which consists of a
+ * &drm_framebuffer backed by a dumb buffer. The dumb buffer is &dma_buf
+ * exported to aquire a virtual address which is stored in
+ * &drm_client_buffer->vaddr.
+ * Call drm_client_framebuffer_delete() to free the buffer.
+ *
+ * Returns:
+ * Pointer to a client buffer or an error pointer on failure.
+ */
+struct drm_client_buffer *
+drm_client_framebuffer_create(struct drm_client_dev *client,
+                             struct drm_mode_modeinfo *mode, u32 format)
+{
+       struct drm_client_buffer *buffer;
+       int ret;
+
+       buffer = drm_client_buffer_create(client, mode->hdisplay,
+                                         mode->vdisplay, format);
+       if (IS_ERR(buffer))
+               return buffer;
+
+       ret = drm_client_buffer_addfb(buffer, mode);
+       if (ret) {
+               drm_client_buffer_delete(buffer);
+               return ERR_PTR(ret);
+       }
+
+       return buffer;
+}
+EXPORT_SYMBOL(drm_client_framebuffer_create);
+
+void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
+{
+       drm_client_buffer_rmfb(buffer);
+       drm_client_buffer_delete(buffer);
+}
+EXPORT_SYMBOL(drm_client_framebuffer_delete);
+
+struct drm_client_buffer *
+drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
+                        u32 format)
+{
+       struct drm_mode_create_dumb dumb_args = { 0 };
+       struct drm_prime_handle prime_args = { 0 };
+       struct drm_client_buffer *buffer;
+       struct dma_buf *dma_buf;
+       void *vaddr;
+       int ret;
+
+       buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
+       if (!buffer)
+               return ERR_PTR(-ENOMEM);
+
+       ret = drm_client_get_file(client);
+       if (ret)
+               goto err_free;
+
+       buffer->client = client;
+       buffer->width = width;
+       buffer->height = height;
+       buffer->format = format;
+
+       dumb_args.width = buffer->width;
+       dumb_args.height = buffer->height;
+       dumb_args.bpp = drm_format_plane_cpp(format, 0) * 8;
+       ret = drm_mode_create_dumb(client->dev, &dumb_args, client->file);
+       if (ret)
+               goto err_put_file;
+
+       buffer->handle = dumb_args.handle;
+       buffer->pitch = dumb_args.pitch;
+       buffer->size = dumb_args.size;
+
+       prime_args.handle = dumb_args.handle;
+       ret = drm_prime_handle_to_fd(client->dev, &prime_args, client->file);
+       if (ret)
+               goto err_delete;
+
+       dma_buf = dma_buf_get(prime_args.fd);
+       if (IS_ERR(dma_buf)) {
+               ret = PTR_ERR(dma_buf);
+               goto err_delete;
+       }
+
+       buffer->dma_buf = dma_buf;
+
+       vaddr = dma_buf_vmap(dma_buf);
+       if (!vaddr) {
+               ret = -ENOMEM;
+               goto err_delete;
+       }
+
+       buffer->vaddr = vaddr;
+
+       return buffer;
+
+err_delete:
+       drm_client_buffer_delete(buffer);
+err_put_file:
+       drm_client_put_file(client);
+err_free:
+       kfree(buffer);
+
+       return ERR_PTR(ret);
+}
+EXPORT_SYMBOL(drm_client_buffer_create);
+
+void drm_client_buffer_delete(struct drm_client_buffer *buffer)
+{
+       if (!buffer)
+               return;
+
+       if (buffer->vaddr)
+               dma_buf_vunmap(buffer->dma_buf, buffer->vaddr);
+
+       if (buffer->dma_buf)
+               dma_buf_put(buffer->dma_buf);
+
+       drm_mode_destroy_dumb(buffer->client->dev, buffer->handle,
+                             buffer->client->file);
+       drm_client_put_file(buffer->client);
+       kfree(buffer);
+}
+EXPORT_SYMBOL(drm_client_buffer_delete);
+
+int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
+                           struct drm_mode_modeinfo *mode)
+{
+       struct drm_client_dev *client = buffer->client;
+       struct drm_mode_fb_cmd2 fb_req = { };
+       unsigned int num_fbs, *fb_ids;
+       int i, ret;
+
+       if (buffer->num_fbs)
+               return -EINVAL;
+
+       if (mode->hdisplay > buffer->width || mode->vdisplay > buffer->height)
+               return -EINVAL;
+
+       num_fbs = buffer->height / mode->vdisplay;
+       fb_ids = kcalloc(num_fbs, sizeof(*fb_ids), GFP_KERNEL);
+       if (!fb_ids)
+               return -ENOMEM;
+
+       fb_req.width = mode->hdisplay;
+       fb_req.height = mode->vdisplay;
+       fb_req.pixel_format = buffer->format;
+       fb_req.handles[0] = buffer->handle;
+       fb_req.pitches[0] = buffer->pitch;
+
+       for (i = 0; i < num_fbs; i++) {
+               fb_req.offsets[0] = i * mode->vdisplay * buffer->pitch;
+               ret = drm_mode_addfb2(client->dev, &fb_req, client->file,
+                                     client->funcs->name);
+               if (ret)
+                       goto err_remove;
+               fb_ids[i] = fb_req.fb_id;
+       }
+
+       buffer->fb_ids = fb_ids;
+       buffer->num_fbs = num_fbs;
+
+       return 0;
+
+err_remove:
+       for (i--; i >= 0; i--)
+               drm_mode_rmfb(client->dev, fb_ids[i], client->file);
+       kfree(fb_ids);
+
+       return ret;
+}
+EXPORT_SYMBOL(drm_client_buffer_addfb);
+
+int drm_client_buffer_rmfb(struct drm_client_buffer *buffer)
+{
+       unsigned int i;
+       int ret;
+
+       if (!buffer || !buffer->num_fbs)
+               return 0;
+
+       for (i = 0; i < buffer->num_fbs; i++) {
+               ret = drm_mode_rmfb(buffer->client->dev, buffer->fb_ids[i],
+                                   buffer->client->file);
+               if (ret)
+                       DRM_DEV_ERROR(buffer->client->dev->dev,
+                                     "Error removing FB:%u (%d)\n",
+                                     buffer->fb_ids[i], ret);
+       }
+
+       kfree(buffer->fb_ids);
+       buffer->fb_ids = NULL;
+       buffer->num_fbs = 0;
+
+       return 0;
+}
+EXPORT_SYMBOL(drm_client_buffer_rmfb);
diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c
index f869de185986..db161337d87c 100644
--- a/drivers/gpu/drm/drm_drv.c
+++ b/drivers/gpu/drm/drm_drv.c
@@ -33,6 +33,7 @@
 #include <linux/mount.h>
 #include <linux/slab.h>
 
+#include <drm/drm_client.h>
 #include <drm/drm_drv.h>
 #include <drm/drmP.h>
 
@@ -463,6 +464,7 @@ int drm_dev_init(struct drm_device *dev,
        dev->driver = driver;
 
        INIT_LIST_HEAD(&dev->filelist);
+       INIT_LIST_HEAD(&dev->filelist_internal);
        INIT_LIST_HEAD(&dev->ctxlist);
        INIT_LIST_HEAD(&dev->vmalist);
        INIT_LIST_HEAD(&dev->maplist);
@@ -787,6 +789,8 @@ int drm_dev_register(struct drm_device *dev, unsigned long 
flags)
                 dev->dev ? dev_name(dev->dev) : "virtual device",
                 dev->primary->index);
 
+       drm_client_dev_register(dev);
+
        goto out_unlock;
 
 err_minors:
@@ -839,6 +843,8 @@ void drm_dev_unregister(struct drm_device *dev)
        drm_minor_unregister(dev, DRM_MINOR_PRIMARY);
        drm_minor_unregister(dev, DRM_MINOR_RENDER);
        drm_minor_unregister(dev, DRM_MINOR_CONTROL);
+
+       drm_client_dev_unregister(dev);
 }
 EXPORT_SYMBOL(drm_dev_unregister);
 
diff --git a/drivers/gpu/drm/drm_file.c b/drivers/gpu/drm/drm_file.c
index 55505378df47..bcc688e58776 100644
--- a/drivers/gpu/drm/drm_file.c
+++ b/drivers/gpu/drm/drm_file.c
@@ -35,6 +35,7 @@
 #include <linux/slab.h>
 #include <linux/module.h>
 
+#include <drm/drm_client.h>
 #include <drm/drm_file.h>
 #include <drm/drmP.h>
 
@@ -443,6 +444,8 @@ void drm_lastclose(struct drm_device * dev)
 
        if (drm_core_check_feature(dev, DRIVER_LEGACY))
                drm_legacy_dev_reinit(dev);
+
+       drm_client_dev_lastclose(dev);
 }
 
 /**
diff --git a/drivers/gpu/drm/drm_probe_helper.c 
b/drivers/gpu/drm/drm_probe_helper.c
index 2d1643bdae78..5d2a6c6717f5 100644
--- a/drivers/gpu/drm/drm_probe_helper.c
+++ b/drivers/gpu/drm/drm_probe_helper.c
@@ -33,6 +33,7 @@
 #include <linux/moduleparam.h>
 
 #include <drm/drmP.h>
+#include <drm/drm_client.h>
 #include <drm/drm_crtc.h>
 #include <drm/drm_fourcc.h>
 #include <drm/drm_crtc_helper.h>
@@ -563,6 +564,8 @@ void drm_kms_helper_hotplug_event(struct drm_device *dev)
        drm_sysfs_hotplug_event(dev);
        if (dev->mode_config.funcs->output_poll_changed)
                dev->mode_config.funcs->output_poll_changed(dev);
+
+       drm_client_dev_hotplug(dev);
 }
 EXPORT_SYMBOL(drm_kms_helper_hotplug_event);
 
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
new file mode 100644
index 000000000000..88f6f87919c5
--- /dev/null
+++ b/include/drm/drm_client.h
@@ -0,0 +1,192 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/mutex.h>
+
+struct dma_buf;
+struct drm_clip_rect;
+struct drm_device;
+struct drm_file;
+struct drm_mode_modeinfo;
+
+struct drm_client_dev;
+
+/**
+ * struct drm_client_funcs - DRM client  callbacks
+ */
+struct drm_client_funcs {
+       /**
+        * @name:
+        *
+        * Name of the client.
+        */
+       const char *name;
+
+       /**
+        * @new:
+        *
+        * Called when a client or a &drm_device is registered.
+        * If the callback returns anything but zero, then this client instance
+        * is dropped.
+        *
+        * This callback is mandatory.
+        */
+       int (*new)(struct drm_client_dev *client);
+
+       /**
+        * @remove:
+        *
+        * Called when a &drm_device is unregistered or the client is
+        * unregistered. If zero is returned drm_client_free() is called
+        * automatically. If the client can't drop it's resources it should
+        * return non-zero and call drm_client_free() later.
+        *
+        * This callback is optional.
+        */
+       int (*remove)(struct drm_client_dev *client);
+
+       /**
+        * @lastclose:
+        *
+        * Called on drm_lastclose(). The first client instance in the list
+        * that returns zero gets the privilege to restore and no more clients
+        * are called.
+        *
+        * This callback is optional.
+        */
+       int (*lastclose)(struct drm_client_dev *client);
+
+       /**
+        * @hotplug:
+        *
+        * Called on drm_kms_helper_hotplug_event().
+        *
+        * This callback is optional.
+        */
+       int (*hotplug)(struct drm_client_dev *client);
+
+// TODO
+//     void (*suspend)(struct drm_client_dev *client);
+//     void (*resume)(struct drm_client_dev *client);
+};
+
+/**
+ * struct drm_client_dev - DRM client instance
+ */
+struct drm_client_dev {
+       struct list_head list;
+       struct drm_device *dev;
+       const struct drm_client_funcs *funcs;
+       struct mutex lock;
+       struct drm_file *file;
+       unsigned int file_ref_count;
+       u32 *crtcs;
+       unsigned int num_crtcs;
+       u32 min_width;
+       u32 max_width;
+       u32 min_height;
+       u32 max_height;
+       void *private;
+};
+
+void drm_client_free(struct drm_client_dev *client);
+int drm_client_register(const struct drm_client_funcs *funcs);
+void drm_client_unregister(const struct drm_client_funcs *funcs);
+
+void drm_client_dev_register(struct drm_device *dev);
+void drm_client_dev_unregister(struct drm_device *dev);
+void drm_client_dev_hotplug(struct drm_device *dev);
+void drm_client_dev_lastclose(struct drm_device *dev);
+
+int drm_client_get_file(struct drm_client_dev *client);
+void drm_client_put_file(struct drm_client_dev *client);
+struct drm_event *
+drm_client_read_event(struct drm_client_dev *client, bool block);
+
+struct drm_client_connector {
+       unsigned int conn_id;
+       unsigned int status;
+       unsigned int crtc_id;
+       struct drm_mode_modeinfo *modes;
+       unsigned int num_modes;
+       bool has_tile;
+       int tile_group;
+       u8 tile_h_loc, tile_v_loc;
+};
+
+struct drm_client_display {
+       struct drm_client_dev *client;
+
+       struct drm_client_connector **connectors;
+       unsigned int num_connectors;
+
+       struct mutex modes_lock;
+       struct drm_mode_modeinfo *modes;
+       unsigned int num_modes;
+
+       bool cloned;
+       bool no_flushing;
+};
+
+void drm_client_display_free(struct drm_client_display *display);
+struct drm_client_display *
+drm_client_display_get_first_enabled(struct drm_client_dev *client, bool 
strict);
+
+int drm_client_display_update_modes(struct drm_client_display *display,
+                                   bool *mode_changed);
+
+static inline bool
+drm_client_display_is_tiled(struct drm_client_display *display)
+{
+       return !display->cloned && display->num_connectors > 1;
+}
+
+int drm_client_display_dpms(struct drm_client_display *display, int mode);
+int drm_client_display_wait_vblank(struct drm_client_display *display);
+
+struct drm_mode_modeinfo *
+drm_client_display_first_mode(struct drm_client_display *display);
+struct drm_mode_modeinfo *
+drm_client_display_next_mode(struct drm_client_display *display,
+                            struct drm_mode_modeinfo *mode);
+
+#define drm_client_display_for_each_mode(display, mode) \
+       for (mode = drm_client_display_first_mode(display); mode; \
+            mode = drm_client_display_next_mode(display, mode))
+
+unsigned int
+drm_client_display_preferred_depth(struct drm_client_display *display);
+
+int drm_client_display_commit_mode(struct drm_client_display *display,
+                                  u32 fb_id, struct drm_mode_modeinfo *mode);
+unsigned int drm_client_display_current_fb(struct drm_client_display *display);
+int drm_client_display_flush(struct drm_client_display *display, u32 fb_id,
+                            struct drm_clip_rect *clips, unsigned int 
num_clips);
+int drm_client_display_page_flip(struct drm_client_display *display, u32 fb_id,
+                                bool event);
+
+struct drm_client_buffer {
+       struct drm_client_dev *client;
+       u32 width;
+       u32 height;
+       u32 format;
+       u32 handle;
+       u32 pitch;
+       u64 size;
+       struct dma_buf *dma_buf;
+       void *vaddr;
+
+       unsigned int *fb_ids;
+       unsigned int num_fbs;
+};
+
+struct drm_client_buffer *
+drm_client_framebuffer_create(struct drm_client_dev *client,
+                             struct drm_mode_modeinfo *mode, u32 format);
+void drm_client_framebuffer_delete(struct drm_client_buffer *buffer);
+struct drm_client_buffer *
+drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
+                        u32 format);
+void drm_client_buffer_delete(struct drm_client_buffer *buffer);
+int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
+                           struct drm_mode_modeinfo *mode);
+int drm_client_buffer_rmfb(struct drm_client_buffer *buffer);
diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h
index 7c4fa32f3fc6..32dfed3d5a86 100644
--- a/include/drm/drm_device.h
+++ b/include/drm/drm_device.h
@@ -67,6 +67,7 @@ struct drm_device {
 
        struct mutex filelist_mutex;
        struct list_head filelist;
+       struct list_head filelist_internal;
 
        /** \name Memory management */
        /*@{ */
diff --git a/include/drm/drm_file.h b/include/drm/drm_file.h
index 5176c3797680..39af8a4be7b3 100644
--- a/include/drm/drm_file.h
+++ b/include/drm/drm_file.h
@@ -248,6 +248,13 @@ struct drm_file {
         */
        void *driver_priv;
 
+       /**
+        * @user_priv:
+        *
+        * Optional pointer for user private data. Useful for in-kernel clients.
+        */
+       void *user_priv;
+
        /**
         * @fbs:
         *
-- 
2.15.1

_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

Reply via email to