[PATCH 14/16] drm/client: Add display abstraction

Noralf Trønnes noralf at tronnes.org
Tue Mar 26 17:55:44 UTC 2019


Add display abstraction and helpers to probe for displays and commit
modesets.

TODO:
If the bootsplash client doesn't need to subclass drm_client_display,
the callbacks can be removed.

Signed-off-by: Noralf Trønnes <noralf at tronnes.org>
---
 drivers/gpu/drm/drm_client.c | 415 +++++++++++++++++++++++++++++++++++
 include/drm/drm_client.h     |  80 +++++++
 2 files changed, 495 insertions(+)

diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c
index 3bc96b0b30ec..ef01a31a9dbe 100644
--- a/drivers/gpu/drm/drm_client.c
+++ b/drivers/gpu/drm/drm_client.c
@@ -4,6 +4,7 @@
  */
 
 #include <linux/list.h>
+#include <linux/list_sort.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
 #include <linux/seq_file.h>
@@ -106,6 +107,9 @@ int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
 
 	drm_dev_get(dev);
 
+	mutex_init(&client->displaylist_mutex);
+	INIT_LIST_HEAD(&client->displaylist);
+
 	return 0;
 
 err_put_module:
@@ -156,6 +160,9 @@ void drm_client_release(struct drm_client_dev *client)
 
 	DRM_DEV_DEBUG_KMS(dev->dev, "%s\n", client->name);
 
+	drm_client_release_displays(client);
+	mutex_destroy(&client->displaylist_mutex);
+
 	drm_client_close(client);
 	drm_dev_put(dev);
 	if (client->funcs)
@@ -1419,6 +1426,414 @@ void drm_client_modesets_dpms(struct drm_device *dev, struct drm_mode_set *modes
 }
 EXPORT_SYMBOL(drm_client_modesets_dpms);
 
+static struct drm_client_display *
+drm_client_display_alloc(struct drm_client_dev *client, unsigned int num_modesets)
+{
+	struct drm_client_display *display;
+	struct drm_mode_set *modesets;
+
+	modesets = kcalloc(num_modesets + 1, sizeof(*modesets), GFP_KERNEL);
+	if (!modesets)
+		return ERR_PTR(-ENOMEM);
+
+	if (client->funcs && client->funcs->display_alloc)
+		display = client->funcs->display_alloc(client);
+	else
+		display = kzalloc(sizeof(*display), GFP_KERNEL);
+	if (!display)
+		display = ERR_PTR(-ENOMEM);
+
+	if (IS_ERR(display)) {
+		kfree(modesets);
+		return display;
+	}
+
+	display->client = client;
+	display->modesets = modesets;
+
+	return display;
+}
+
+static void drm_client_display_release(struct drm_client_display *display)
+{
+	unsigned int i;
+
+	if (!display)
+		return;
+
+	for (i = 0; i < display->num_buffers; i++)
+		drm_client_framebuffer_delete(display->buffers[i]);
+	kfree(display->buffers);
+
+	drm_mode_destroy(display->client->dev, display->mode);
+
+	drm_client_modesets_release(display->modesets);
+
+	if (display->client->funcs && display->client->funcs->display_free)
+		display->client->funcs->display_free(display);
+	else
+		kfree(display);
+}
+
+static void drm_client_display_debugprint(struct drm_client_display *display)
+{
+	struct drm_display_mode *mode = display->mode;
+	struct drm_mode_set *modeset;
+	unsigned int i;
+
+	DRM_DEBUG_KMS("  %dx%d %dHz\n", mode->hdisplay, mode->vdisplay, mode->vrefresh);
+
+	drm_client_for_each_modeset(modeset, display->modesets) {
+		DRM_DEBUG_KMS("    crtc=%d, connectors:", modeset->crtc->base.id);
+		for (i = 0; i < modeset->num_connectors; i++)
+			DRM_DEBUG_KMS("      %s\n", modeset->connectors[i]->name);
+	}
+}
+
+static bool drm_client_modeset_equal(struct drm_mode_set *modeset1, struct drm_mode_set *modeset2)
+{
+	unsigned int i;
+
+	if (modeset1->crtc != modeset2->crtc ||
+	    !drm_mode_equal(modeset1->mode, modeset2->mode) ||
+	    modeset1->x != modeset2->x || modeset1->y != modeset2->y ||
+	    modeset1->num_connectors != modeset2->num_connectors)
+		return false;
+
+	for (i = 0; i < modeset1->num_connectors; i++) {
+		if (modeset1->connectors[i] != modeset2->connectors[i])
+			return false;
+	}
+
+	return true;
+}
+
+static bool drm_client_display_equal(struct drm_client_display *display1,
+				     struct drm_client_display *display2)
+{
+	struct drm_mode_set *modeset1, *modeset2;
+
+	if (!display1 || !display2)
+		return false;
+
+	if (display1 == display2)
+		return true;
+
+	if (!drm_mode_equal(display1->mode, display2->mode))
+		return false;
+
+	for (modeset1 = display1->modesets, modeset2 = display2->modesets;
+	     modeset1->crtc && modeset2->crtc; modeset1++, modeset2++)
+		if (!drm_client_modeset_equal(modeset1, modeset2))
+			return false;
+
+	return !modeset1->crtc && !modeset2->crtc;
+}
+
+static int drm_client_display_framebuffer_create(struct drm_client_display *display,
+						 unsigned int num_buffers, u32 format)
+{
+	struct drm_client_buffer **buffers;
+	struct drm_mode_set *modeset;
+	unsigned int i;
+	int ret;
+
+	if (!display || !display->mode)
+		return -EINVAL;
+
+	if (display->num_buffers && display->num_buffers != num_buffers)
+		return -EINVAL;
+
+	if (display->num_buffers == num_buffers)
+		return 0;
+
+	buffers = kcalloc(num_buffers, sizeof(*buffers), GFP_KERNEL);
+	if (!buffers)
+		return -ENOMEM;
+
+	for (i = 0; i < num_buffers; i++) {
+		buffers[i] = drm_client_framebuffer_create(display->client, display->mode->hdisplay,
+							   display->mode->vdisplay, format);
+		if (IS_ERR(buffers[i])) {
+			ret = PTR_ERR(buffers[i]);
+			goto err_free;
+		}
+	}
+
+	display->buffers = buffers;
+	display->num_buffers = num_buffers;
+
+	drm_client_for_each_modeset(modeset, display->modesets)
+		modeset->fb = display->buffers[0]->fb;
+
+	return 0;
+
+err_free:
+	for (i = 0; i < num_buffers; i++) {
+		if (!IS_ERR_OR_NULL(buffers[i]))
+			drm_client_framebuffer_delete(buffers[i]);
+	}
+	kfree(buffers);
+
+	return ret;
+}
+
+static int drm_client_find_displays(struct drm_client_dev *client, struct list_head *displaylist)
+{
+	struct drm_mode_set *modeset, *modesets;
+	struct drm_device *dev = client->dev;
+	struct drm_client_display *display;
+	unsigned int num_modesets = 0;
+	int ret = 0;
+
+	modesets = drm_client_modesets_probe(dev, 0, 0);
+	if (IS_ERR_OR_NULL(modesets))
+		return PTR_ERR_OR_ZERO(modesets);
+
+	/* TODO: Support more than one tiled monitor? */
+	display = NULL;
+	drm_client_for_each_modeset(modeset, modesets) {
+		if (!modeset->mode || !modeset->connectors[0]->has_tile)
+			continue;
+
+		if (!display) {
+			display = drm_client_display_alloc(client, dev->mode_config.num_crtc);
+			if (IS_ERR(display)) {
+				ret = PTR_ERR(display);
+				goto err_free;
+			}
+
+			list_add(&display->list, displaylist);
+		}
+
+		display->modesets[num_modesets++] = *modeset;
+		modeset->num_connectors = 0;
+		modeset->connectors = NULL;
+		modeset->mode = NULL;
+	}
+
+	/* Contruct a mode for the tiled monitor */
+	if (display) {
+		int hdisplay = 0, vdisplay = 0, vrefresh = 0;
+
+		drm_client_for_each_modeset(modeset, display->modesets) {
+			if (!modeset->y)
+				hdisplay += modeset->mode->hdisplay;
+			if (!modeset->x)
+				vdisplay += modeset->mode->vdisplay;
+			vrefresh = modeset->mode->vrefresh;
+		}
+
+		display->mode = drm_cvt_mode(dev, hdisplay, vdisplay, vrefresh, false, false, false);
+		if (!display->mode) {
+			ret = -ENOMEM;
+			goto err_free;
+		}
+	}
+
+	/* The rest have one display per modeset */
+	drm_client_for_each_modeset(modeset, modesets) {
+		if (!modeset->mode || modeset->connectors[0]->has_tile)
+			continue;
+
+		display = drm_client_display_alloc(client, 1);
+		if (IS_ERR(display)) {
+			ret = PTR_ERR(display);
+			goto err_free;
+		}
+
+		list_add(&display->list, displaylist);
+		display->modesets[0] = *modeset;
+		modeset->num_connectors = 0;
+		modeset->connectors = NULL;
+		modeset->mode = NULL;
+
+		display->mode = drm_mode_duplicate(dev, display->modesets[0].mode);
+		if (!display->mode) {
+			ret = -ENOMEM;
+			goto err_free;
+		}
+	}
+
+	goto out;
+
+err_free:
+	list_for_each_entry(display, displaylist, list)
+		drm_client_display_release(display);
+out:
+	drm_client_modesets_release(modesets);
+
+	return ret;
+}
+
+static int drm_client_displays_compare(void *priv, struct list_head *lh_a, struct list_head *lh_b)
+{
+	struct drm_client_display *a = list_entry(lh_a, struct drm_client_display, list);
+	struct drm_client_display *b = list_entry(lh_b, struct drm_client_display, list);
+
+	return b->mode->hdisplay * b->mode->vdisplay - a->mode->hdisplay * a->mode->vdisplay;
+}
+
+static void drm_client_displays_sort(struct list_head *displaylist)
+{
+	list_sort(NULL, displaylist, drm_client_displays_compare);
+}
+
+/**
+ * drm_client_probe_displays() - Probe for displays
+ * @client: DRM client
+ * @num_buffers: Number of buffers to attach (optional)
+ * @format: Buffer format
+ *
+ * This function probes for connected displays and updates the clients list of displays.
+ * The list is sorted from largest to smallest.
+ *
+ * Returns:
+ * Number of displays or negative error code on failure.
+ */
+int drm_client_probe_displays(struct drm_client_dev *client, unsigned int num_buffers, u32 format)
+{
+	struct drm_client_display *display, *tmp;
+	LIST_HEAD(displaylist);
+	bool changed = false;
+	int ret;
+
+	ret = drm_client_find_displays(client, &displaylist);
+	if (ret < 0)
+		return ret;
+
+	if (list_empty(&displaylist)) {
+		drm_client_release_displays(client);
+		return 0;
+	}
+
+	mutex_lock(&client->displaylist_mutex);
+
+	/* If a display hasn't changed, keep it to avoid reallocating buffers */
+	list_for_each_entry_safe(display, tmp, &client->displaylist, list) {
+		struct drm_client_display *display2, *tmp2;
+		bool found = false;
+
+		list_for_each_entry_safe(display2, tmp2, &displaylist, list) {
+			if (drm_client_display_equal(display, display2)) {
+				list_del(&display2->list);
+				drm_client_display_release(display2);
+				found = true;
+				break;
+			}
+		}
+
+		if (!found) {
+			list_del(&display->list);
+			drm_client_display_release(display);
+			changed = true;
+		}
+	}
+
+	if (!list_empty(&displaylist))
+		changed = true;
+
+	list_splice(&displaylist, &client->displaylist);
+
+	/* Sort from largest to smallest */
+	drm_client_displays_sort(&client->displaylist);
+
+	if (changed) {
+		DRM_DEBUG_KMS("Displays:\n");
+		drm_client_for_each_display(display, client)
+			drm_client_display_debugprint(display);
+	}
+
+	if (num_buffers) {
+		drm_client_for_each_display(display, client) {
+			ret = drm_client_display_framebuffer_create(display, num_buffers, format);
+			if (ret)
+				goto out_unlock;
+		}
+	}
+
+	ret = 0;
+	drm_client_for_each_display(display, client)
+		ret++;
+
+out_unlock:
+	mutex_unlock(&client->displaylist_mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(drm_client_probe_displays);
+
+/**
+ * drm_client_release_displays() - Release displays
+ * @client: DRM client
+ *
+ * This function releases all the clients displays.
+ */
+void drm_client_release_displays(struct drm_client_dev *client)
+{
+	struct drm_client_display *display, *tmp;
+
+	mutex_lock(&client->displaylist_mutex);
+	list_for_each_entry_safe(display, tmp, &client->displaylist, list) {
+		list_del(&display->list);
+		drm_client_display_release(display);
+	}
+	mutex_unlock(&client->displaylist_mutex);
+}
+EXPORT_SYMBOL(drm_client_release_displays);
+
+static int drm_client_display_set_buffer(struct drm_client_display *display, unsigned int buffer)
+{
+	struct drm_mode_set *modeset;
+
+	if (!display || buffer >= display->num_buffers)
+		return -EINVAL;
+
+	drm_client_for_each_modeset(modeset, display->modesets)
+		modeset->fb = display->buffers[buffer]->fb;
+
+	return 0;
+}
+
+static int drm_client_display_commit(struct drm_client_display *display)
+{
+	int ret;
+
+	if (!display)
+		return -EINVAL;
+
+	if (!drm_master_internal_acquire(display->client->dev))
+		return -EBUSY;
+
+	ret = drm_client_modesets_commit(display->client->dev, display->modesets);
+
+	drm_master_internal_release(display->client->dev);
+
+	return ret;
+}
+
+/**
+ * drm_client_display_commit_buffer() - Commit display modeset(s) with buffer
+ * @display: Client display
+ * @buffer: Buffer index
+ *
+ * This function sets the framebuffer to @buffer and commits the modeset(s).
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
+ */
+int drm_client_display_commit_buffer(struct drm_client_display *display, unsigned int buffer)
+{
+	int ret;
+
+	ret = drm_client_display_set_buffer(display, buffer);
+	if (ret)
+		return ret;
+
+	return drm_client_display_commit(display);
+}
+EXPORT_SYMBOL(drm_client_display_commit_buffer);
+
 #ifdef CONFIG_DEBUG_FS
 static int drm_client_debugfs_internal_clients(struct seq_file *m, void *data)
 {
diff --git a/include/drm/drm_client.h b/include/drm/drm_client.h
index 78fb82bd8371..ef7a9bd07b3c 100644
--- a/include/drm/drm_client.h
+++ b/include/drm/drm_client.h
@@ -3,12 +3,15 @@
 #ifndef _DRM_CLIENT_H_
 #define _DRM_CLIENT_H_
 
+#include <linux/list.h>
+#include <linux/mutex.h>
 #include <linux/types.h>
 
 #include <drm/drm_connector.h>
 #include <drm/drm_crtc.h>
 
 struct drm_client_dev;
+struct drm_client_display;
 struct drm_device;
 struct drm_file;
 struct drm_framebuffer;
@@ -55,6 +58,25 @@ struct drm_client_funcs {
 	 * This callback is optional.
 	 */
 	int (*hotplug)(struct drm_client_dev *client);
+
+	/**
+	 * @display_alloc:
+	 *
+	 * Called when allocating a &drm_client_display. It can be use to
+	 * subclass the structure.
+	 *
+	 * This callback is optional.
+	 */
+	struct drm_client_display *(*display_alloc)(struct drm_client_dev *client);
+
+	/**
+	 * @display_free:
+	 *
+	 * Called when releasing a &drm_client_display.
+	 *
+	 * This callback is optional.
+	 */
+	void (*display_free)(struct drm_client_display *display);
 };
 
 /**
@@ -88,6 +110,19 @@ struct drm_client_dev {
 	 * @file: DRM file
 	 */
 	struct drm_file *file;
+
+	/**
+	 * @displaylist_mutex: Protects @displaylist.
+	 */
+	struct mutex displaylist_mutex;
+
+	/**
+	 * @displaylist:
+	 *
+	 * List of displays, linked through &drm_client_display.list.
+	 */
+	struct list_head displaylist;
+
 };
 
 int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
@@ -169,6 +204,51 @@ void drm_client_modesets_dpms(struct drm_device *dev, struct drm_mode_set *modes
 #define drm_client_for_each_modeset(modeset, modesets) \
 	for (modeset = modesets; modeset->crtc; modeset++)
 
+/**
+ * struct drm_client_display - DRM client display
+ */
+struct drm_client_display {
+	/**
+	 * @client: DRM client.
+	 */
+	struct drm_client_dev *client;
+
+	/**
+	 * @list:
+	 *
+	 * List of all displays for a client, linked into
+	 * &drm_client_dev.displaylist. Protected by &drm_client_dev.displaylist_mutex.
+	 */
+	struct list_head list;
+
+	/**
+	 * @mode: Current display mode.
+	 */
+	struct drm_display_mode *mode;
+
+	/**
+	 * @modesets: Per CRTC array of modeset configurations.
+	 */
+	struct drm_mode_set *modesets;
+
+	/**
+	 * @buffers: Display buffers (optional).
+	 */
+	struct drm_client_buffer **buffers;
+
+	/**
+	 * @num_buffers: Number of backing buffers.
+	 */
+	unsigned int num_buffers;
+};
+
+int drm_client_probe_displays(struct drm_client_dev *client, unsigned int num_buffers, u32 format);
+void drm_client_release_displays(struct drm_client_dev *client);
+int drm_client_display_commit_buffer(struct drm_client_display *display, unsigned int buffer);
+
+#define drm_client_for_each_display(display, client) \
+	list_for_each_entry(display, &(client)->displaylist, list)
+
 int drm_client_debugfs_init(struct drm_minor *minor);
 
 #endif
-- 
2.20.1



More information about the dri-devel mailing list