From patchwork Thu Feb 22 20:06:50 2018 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: 10236311 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 6AC5C60209 for ; Thu, 22 Feb 2018 20:15:50 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 5D124289FD for ; Thu, 22 Feb 2018 20:15:50 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 5067428C06; Thu, 22 Feb 2018 20:15:50 +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=-4.2 required=2.0 tests=BAYES_00, 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 25E62289FD for ; Thu, 22 Feb 2018 20:15:48 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id A92EC6ED6C; Thu, 22 Feb 2018 20:15:46 +0000 (UTC) X-Original-To: intel-gfx@lists.freedesktop.org Delivered-To: intel-gfx@lists.freedesktop.org X-Greylist: delayed 516 seconds by postgrey-1.36 at gabe; Thu, 22 Feb 2018 20:15:45 UTC Received: from smtp.domeneshop.no (smtp.domeneshop.no [IPv6:2a01:5b40:0:3005::1]) by gabe.freedesktop.org (Postfix) with ESMTPS id 239F46ED6C for ; Thu, 22 Feb 2018 20:15:45 +0000 (UTC) Received: from 211.81-166-168.customer.lyse.net ([81.166.168.211]:34576 helo=localhost.localdomain) by smtp.domeneshop.no with esmtpsa (TLS1.2:ECDHE_RSA_AES_128_CBC_SHA256:128) (Exim 4.84_2) (envelope-from ) id 1eox93-0004nQ-AI; Thu, 22 Feb 2018 21:07:09 +0100 From: =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= To: dri-devel@lists.freedesktop.org Date: Thu, 22 Feb 2018 21:06:50 +0100 Message-Id: <20180222200653.19453-10-noralf@tronnes.org> X-Mailer: git-send-email 2.15.1 In-Reply-To: <20180222200653.19453-1-noralf@tronnes.org> References: <20180222200653.19453-1-noralf@tronnes.org> MIME-Version: 1.0 Subject: [Intel-gfx] [RFC v3 09/12] drm: Add API for in-kernel clients X-BeenThere: intel-gfx@lists.freedesktop.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: Intel graphics driver community testing & development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: daniel.vetter@ffwll.ch, intel-gfx@lists.freedesktop.org, =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= , laurent.pinchart@ideasonboard.com, dh.herrmann@gmail.com Errors-To: intel-gfx-bounces@lists.freedesktop.org Sender: "Intel-gfx" X-Virus-Scanned: ClamAV using ClamSMTP 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 --- 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 #include +#include #include #include @@ -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 #include +#include #include #include @@ -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 #include +#include #include #include #include @@ -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 + +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: *