From patchwork Tue Mar 26 17:55:44 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Noralf_Tr=C3=B8nnes?= X-Patchwork-Id: 10871865 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 7350117E0 for ; Tue, 26 Mar 2019 18:04:47 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 60EE6212D9 for ; Tue, 26 Mar 2019 18:04:47 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 547E928BFE; Tue, 26 Mar 2019 18:04:47 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-5.2 required=2.0 tests=BAYES_00,MAILING_LIST_MULTI, RCVD_IN_DNSWL_MED autolearn=unavailable version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 88656212D9 for ; Tue, 26 Mar 2019 18:04:46 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id AB72189D66; Tue, 26 Mar 2019 18:04:45 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from smtp.domeneshop.no (smtp.domeneshop.no [IPv6:2a01:5b40:0:3005::1]) by gabe.freedesktop.org (Postfix) with ESMTPS id 1940C88CBF; Tue, 26 Mar 2019 18:04:37 +0000 (UTC) Received: from 211.81-166-168.customer.lyse.net ([81.166.168.211]:46322 helo=localhost.localdomain) by smtp.domeneshop.no with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_CBC_SHA1:128) (Exim 4.84_2) (envelope-from ) id 1h8qIy-0006Mj-09; Tue, 26 Mar 2019 18:56:08 +0100 From: =?utf-8?q?Noralf_Tr=C3=B8nnes?= To: dri-devel@lists.freedesktop.org Subject: [PATCH 14/16] drm/client: Add display abstraction Date: Tue, 26 Mar 2019 18:55:44 +0100 Message-Id: <20190326175546.18126-15-noralf@tronnes.org> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190326175546.18126-1-noralf@tronnes.org> References: <20190326175546.18126-1-noralf@tronnes.org> MIME-Version: 1.0 X-Mailman-Original-DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=tronnes.org; s=ds201810; h=Content-Transfer-Encoding:Content-Type:MIME-Version:References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From; bh=9i04bfSOphlWJW+XwsuWaWw9/UaM8Di2rLDwlctN6tE=; b=paVYCfEU77MHvvzZKWVAdYdlr3J9c5+f/cYpKMlDETRJe4vHlwQl0F4Hx4CyR3lVoQ7K37uSN7M5qtpAv2xu32UcI/pSbbJS70qYDu+xneXE084lCmuFPTxda2WszPBTe1Cg6Jds3um/CnRcpAQAIV2N9Nmar5+bgfJ6M/U4OniJYRA8ayl1oYJo0d1QxM6tjxwt4gV1N4VFUM71+iZAra9H2udTTzyc73EZFKylqpSGmKuBLnlfeO++ub/AcKKPPASlBA4jYFhyast92XMCL2Dgr9hgISvBlAN5BuLyKCOZW0dDBVU4CjxOCd7x7HBJuyhNxBN+NVf6j1yYtCtznQ==; X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: intel-gfx@lists.freedesktop.org, mstaudt@suse.de Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Virus-Scanned: ClamAV using ClamSMTP 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 --- 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 +#include #include #include #include @@ -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 +#include #include #include #include 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