summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/drm_client.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/drm_client.c')
-rw-r--r--drivers/gpu/drm/drm_client.c443
1 files changed, 243 insertions, 200 deletions
diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c
index 9b2bd28dde0a..a82d741e6630 100644
--- a/drivers/gpu/drm/drm_client.c
+++ b/drivers/gpu/drm/drm_client.c
@@ -1,24 +1,26 @@
-// SPDX-License-Identifier: GPL-2.0
+// SPDX-License-Identifier: GPL-2.0 or MIT
/*
* Copyright 2018 Noralf Trønnes
*/
+#include <linux/export.h>
+#include <linux/iosys-map.h>
#include <linux/list.h>
-#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <drm/drm_client.h>
-#include <drm/drm_debugfs.h>
+#include <drm/drm_client_event.h>
#include <drm/drm_device.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
#include <drm/drm_gem.h>
+#include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_mode.h>
#include <drm/drm_print.h>
-#include <drm/drmP.h>
#include "drm_crtc_internal.h"
#include "drm_internal.h"
@@ -27,7 +29,6 @@
* DOC: overview
*
* This library provides support for clients running in the kernel like fbdev and bootsplash.
- * Currently it's only partially implemented, just enough to support fbdev.
*
* GEM drivers which provide a GEM based dumb buffer with a virtual address are supported.
*/
@@ -60,7 +61,6 @@ static void drm_client_close(struct drm_client_dev *client)
drm_file_free(client->file);
}
-EXPORT_SYMBOL(drm_client_close);
/**
* drm_client_init - Initialise a DRM client
@@ -69,7 +69,8 @@ EXPORT_SYMBOL(drm_client_close);
* @name: Client name
* @funcs: DRM client functions (optional)
*
- * This initialises the client and opens a &drm_file. Use drm_client_add() to complete the process.
+ * This initialises the client and opens a &drm_file.
+ * Use drm_client_register() to complete the process.
* The caller needs to hold a reference on @dev before calling this function.
* The client is freed when the &drm_device is unregistered. See drm_client_release().
*
@@ -84,48 +85,68 @@ int drm_client_init(struct drm_device *dev, struct drm_client_dev *client,
if (!drm_core_check_feature(dev, DRIVER_MODESET) || !dev->driver->dumb_create)
return -EOPNOTSUPP;
- if (funcs && !try_module_get(funcs->owner))
- return -ENODEV;
-
client->dev = dev;
client->name = name;
client->funcs = funcs;
+ ret = drm_client_modeset_create(client);
+ if (ret)
+ return ret;
+
ret = drm_client_open(client);
if (ret)
- goto err_put_module;
+ goto err_free;
drm_dev_get(dev);
return 0;
-err_put_module:
- if (funcs)
- module_put(funcs->owner);
-
+err_free:
+ drm_client_modeset_free(client);
return ret;
}
EXPORT_SYMBOL(drm_client_init);
/**
- * drm_client_add - Add client to the device list
+ * drm_client_register - Register client
* @client: DRM client
*
* Add the client to the &drm_device client list to activate its callbacks.
* @client must be initialized by a call to drm_client_init(). After
- * drm_client_add() it is no longer permissible to call drm_client_release()
+ * drm_client_register() it is no longer permissible to call drm_client_release()
* directly (outside the unregister callback), instead cleanup will happen
* automatically on driver unload.
+ *
+ * Registering a client generates a hotplug event that allows the client
+ * to set up its display from pre-existing outputs. The client must have
+ * initialized its state to able to handle the hotplug event successfully.
*/
-void drm_client_add(struct drm_client_dev *client)
+void drm_client_register(struct drm_client_dev *client)
{
struct drm_device *dev = client->dev;
+ int ret;
mutex_lock(&dev->clientlist_mutex);
list_add(&client->list, &dev->clientlist);
+
+ if (client->funcs && client->funcs->hotplug) {
+ /*
+ * Perform an initial hotplug event to pick up the
+ * display configuration for the client. This step
+ * has to be performed *after* registering the client
+ * in the list of clients, or a concurrent hotplug
+ * event might be lost; leaving the display off.
+ *
+ * Hold the clientlist_mutex as for a regular hotplug
+ * event.
+ */
+ ret = client->funcs->hotplug(client);
+ if (ret)
+ drm_dbg_kms(dev, "client hotplug ret=%d\n", ret);
+ }
mutex_unlock(&dev->clientlist_mutex);
}
-EXPORT_SYMBOL(drm_client_add);
+EXPORT_SYMBOL(drm_client_register);
/**
* drm_client_release - Release DRM client resources
@@ -145,108 +166,63 @@ void drm_client_release(struct drm_client_dev *client)
{
struct drm_device *dev = client->dev;
- DRM_DEV_DEBUG_KMS(dev->dev, "%s\n", client->name);
+ drm_dbg_kms(dev, "%s\n", client->name);
+ drm_client_modeset_free(client);
drm_client_close(client);
- drm_dev_put(dev);
- if (client->funcs)
- module_put(client->funcs->owner);
-}
-EXPORT_SYMBOL(drm_client_release);
-void drm_client_dev_unregister(struct drm_device *dev)
-{
- struct drm_client_dev *client, *tmp;
-
- if (!drm_core_check_feature(dev, DRIVER_MODESET))
- return;
+ if (client->funcs && client->funcs->free)
+ client->funcs->free(client);
- mutex_lock(&dev->clientlist_mutex);
- list_for_each_entry_safe(client, tmp, &dev->clientlist, list) {
- list_del(&client->list);
- if (client->funcs && client->funcs->unregister) {
- client->funcs->unregister(client);
- } else {
- drm_client_release(client);
- kfree(client);
- }
- }
- mutex_unlock(&dev->clientlist_mutex);
+ drm_dev_put(dev);
}
+EXPORT_SYMBOL(drm_client_release);
/**
- * drm_client_dev_hotplug - Send hotplug event to clients
- * @dev: DRM device
- *
- * This function calls the &drm_client_funcs.hotplug callback on the attached clients.
- *
- * drm_kms_helper_hotplug_event() calls this function, so drivers that use it
- * don't need to call this function themselves.
+ * drm_client_buffer_delete - Delete a client buffer
+ * @buffer: DRM client buffer
*/
-void drm_client_dev_hotplug(struct drm_device *dev)
+void drm_client_buffer_delete(struct drm_client_buffer *buffer)
{
- struct drm_client_dev *client;
+ struct drm_gem_object *gem;
int ret;
- if (!drm_core_check_feature(dev, DRIVER_MODESET))
- return;
-
- mutex_lock(&dev->clientlist_mutex);
- list_for_each_entry(client, &dev->clientlist, list) {
- if (!client->funcs || !client->funcs->hotplug)
- continue;
-
- ret = client->funcs->hotplug(client);
- DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", client->name, ret);
- }
- mutex_unlock(&dev->clientlist_mutex);
-}
-EXPORT_SYMBOL(drm_client_dev_hotplug);
-
-void drm_client_dev_restore(struct drm_device *dev)
-{
- struct drm_client_dev *client;
- int ret;
-
- if (!drm_core_check_feature(dev, DRIVER_MODESET))
+ if (!buffer)
return;
- mutex_lock(&dev->clientlist_mutex);
- list_for_each_entry(client, &dev->clientlist, list) {
- if (!client->funcs || !client->funcs->restore)
- continue;
-
- ret = client->funcs->restore(client);
- DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", client->name, ret);
- if (!ret) /* The first one to return zero gets the privilege to restore */
- break;
- }
- mutex_unlock(&dev->clientlist_mutex);
-}
-
-static void drm_client_buffer_delete(struct drm_client_buffer *buffer)
-{
- struct drm_device *dev = buffer->client->dev;
+ gem = buffer->fb->obj[0];
+ drm_gem_vunmap(gem, &buffer->map);
- drm_gem_vunmap(buffer->gem, buffer->vaddr);
-
- if (buffer->gem)
- drm_gem_object_put_unlocked(buffer->gem);
+ ret = drm_mode_rmfb(buffer->client->dev, buffer->fb->base.id, buffer->client->file);
+ if (ret)
+ drm_err(buffer->client->dev,
+ "Error removing FB:%u (%d)\n", buffer->fb->base.id, ret);
- if (buffer->handle)
- drm_mode_destroy_dumb(dev, buffer->handle, buffer->client->file);
+ drm_gem_object_put(buffer->gem);
kfree(buffer);
}
+EXPORT_SYMBOL(drm_client_buffer_delete);
static struct drm_client_buffer *
-drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height, u32 format)
+drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height,
+ u32 format, u32 handle, u32 pitch)
{
- struct drm_mode_create_dumb dumb_args = { };
+ struct drm_mode_fb_cmd2 fb_req = {
+ .width = width,
+ .height = height,
+ .pixel_format = format,
+ .handles = {
+ handle,
+ },
+ .pitches = {
+ pitch,
+ },
+ };
struct drm_device *dev = client->dev;
struct drm_client_buffer *buffer;
struct drm_gem_object *obj;
- void *vaddr;
+ struct drm_framebuffer *fb;
int ret;
buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
@@ -255,97 +231,154 @@ drm_client_buffer_create(struct drm_client_dev *client, u32 width, u32 height, u
buffer->client = client;
- dumb_args.width = width;
- dumb_args.height = height;
- dumb_args.bpp = drm_format_plane_cpp(format, 0) * 8;
- ret = drm_mode_create_dumb(dev, &dumb_args, client->file);
- if (ret)
- goto err_delete;
-
- buffer->handle = dumb_args.handle;
- buffer->pitch = dumb_args.pitch;
-
- obj = drm_gem_object_lookup(client->file, dumb_args.handle);
+ obj = drm_gem_object_lookup(client->file, handle);
if (!obj) {
ret = -ENOENT;
goto err_delete;
}
- buffer->gem = obj;
+ ret = drm_mode_addfb2(dev, &fb_req, client->file);
+ if (ret)
+ goto err_drm_gem_object_put;
- /*
- * FIXME: The dependency on GEM here isn't required, we could
- * convert the driver handle to a dma-buf instead and use the
- * backend-agnostic dma-buf vmap support instead. This would
- * require that the handle2fd prime ioctl is reworked to pull the
- * fd_install step out of the driver backend hooks, to make that
- * final step optional for internal users.
- */
- vaddr = drm_gem_vmap(obj);
- if (IS_ERR(vaddr)) {
- ret = PTR_ERR(vaddr);
- goto err_delete;
+ fb = drm_framebuffer_lookup(dev, client->file, fb_req.fb_id);
+ if (drm_WARN_ON(dev, !fb)) {
+ ret = -ENOENT;
+ goto err_drm_mode_rmfb;
}
- buffer->vaddr = vaddr;
+ /* drop the reference we picked up in framebuffer lookup */
+ drm_framebuffer_put(fb);
+
+ strscpy(fb->comm, client->name, TASK_COMM_LEN);
+
+ buffer->gem = obj;
+ buffer->fb = fb;
return buffer;
+err_drm_mode_rmfb:
+ drm_mode_rmfb(dev, fb_req.fb_id, client->file);
+err_drm_gem_object_put:
+ drm_gem_object_put(obj);
err_delete:
- drm_client_buffer_delete(buffer);
-
+ kfree(buffer);
return ERR_PTR(ret);
}
-static void drm_client_buffer_rmfb(struct drm_client_buffer *buffer)
+/**
+ * drm_client_buffer_vmap_local - Map DRM client buffer into address space
+ * @buffer: DRM client buffer
+ * @map_copy: Returns the mapped memory's address
+ *
+ * This function maps a client buffer into kernel address space. If the
+ * buffer is already mapped, it returns the existing mapping's address.
+ *
+ * Client buffer mappings are not ref'counted. Each call to
+ * drm_client_buffer_vmap_local() should be closely followed by a call to
+ * drm_client_buffer_vunmap_local(). See drm_client_buffer_vmap() for
+ * long-term mappings.
+ *
+ * The returned address is a copy of the internal value. In contrast to
+ * other vmap interfaces, you don't need it for the client's vunmap
+ * function. So you can modify it at will during blit and draw operations.
+ *
+ * Returns:
+ * 0 on success, or a negative errno code otherwise.
+ */
+int drm_client_buffer_vmap_local(struct drm_client_buffer *buffer,
+ struct iosys_map *map_copy)
{
+ struct drm_gem_object *gem = buffer->fb->obj[0];
+ struct iosys_map *map = &buffer->map;
int ret;
- if (!buffer->fb)
- return;
+ drm_gem_lock(gem);
- ret = drm_mode_rmfb(buffer->client->dev, buffer->fb->base.id, buffer->client->file);
+ ret = drm_gem_vmap_locked(gem, map);
if (ret)
- DRM_DEV_ERROR(buffer->client->dev->dev,
- "Error removing FB:%u (%d)\n", buffer->fb->base.id, ret);
+ goto err_drm_gem_vmap_unlocked;
+ *map_copy = *map;
- buffer->fb = NULL;
+ return 0;
+
+err_drm_gem_vmap_unlocked:
+ drm_gem_unlock(gem);
+ return ret;
}
+EXPORT_SYMBOL(drm_client_buffer_vmap_local);
-static int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
- u32 width, u32 height, u32 format)
+/**
+ * drm_client_buffer_vunmap_local - Unmap DRM client buffer
+ * @buffer: DRM client buffer
+ *
+ * This function removes a client buffer's memory mapping established
+ * with drm_client_buffer_vunmap_local(). Calling this function is only
+ * required by clients that manage their buffer mappings by themselves.
+ */
+void drm_client_buffer_vunmap_local(struct drm_client_buffer *buffer)
{
- struct drm_client_dev *client = buffer->client;
- struct drm_mode_fb_cmd fb_req = { };
- const struct drm_format_info *info;
- int ret;
+ struct drm_gem_object *gem = buffer->fb->obj[0];
+ struct iosys_map *map = &buffer->map;
+
+ drm_gem_vunmap_locked(gem, map);
+ drm_gem_unlock(gem);
+}
+EXPORT_SYMBOL(drm_client_buffer_vunmap_local);
- info = drm_format_info(format);
- fb_req.bpp = info->cpp[0] * 8;
- fb_req.depth = info->depth;
- fb_req.width = width;
- fb_req.height = height;
- fb_req.handle = buffer->handle;
- fb_req.pitch = buffer->pitch;
+/**
+ * drm_client_buffer_vmap - Map DRM client buffer into address space
+ * @buffer: DRM client buffer
+ * @map_copy: Returns the mapped memory's address
+ *
+ * This function maps a client buffer into kernel address space. If the
+ * buffer is already mapped, it returns the existing mapping's address.
+ *
+ * Client buffer mappings are not ref'counted. Each call to
+ * drm_client_buffer_vmap() should be followed by a call to
+ * drm_client_buffer_vunmap(); or the client buffer should be mapped
+ * throughout its lifetime.
+ *
+ * The returned address is a copy of the internal value. In contrast to
+ * other vmap interfaces, you don't need it for the client's vunmap
+ * function. So you can modify it at will during blit and draw operations.
+ *
+ * Returns:
+ * 0 on success, or a negative errno code otherwise.
+ */
+int drm_client_buffer_vmap(struct drm_client_buffer *buffer,
+ struct iosys_map *map_copy)
+{
+ struct drm_gem_object *gem = buffer->fb->obj[0];
+ int ret;
- ret = drm_mode_addfb(client->dev, &fb_req, client->file);
+ ret = drm_gem_vmap(gem, &buffer->map);
if (ret)
return ret;
+ *map_copy = buffer->map;
- buffer->fb = drm_framebuffer_lookup(client->dev, buffer->client->file, fb_req.fb_id);
- if (WARN_ON(!buffer->fb))
- return -ENOENT;
-
- /* drop the reference we picked up in framebuffer lookup */
- drm_framebuffer_put(buffer->fb);
+ return 0;
+}
+EXPORT_SYMBOL(drm_client_buffer_vmap);
- strscpy(buffer->fb->comm, client->name, TASK_COMM_LEN);
+/**
+ * drm_client_buffer_vunmap - Unmap DRM client buffer
+ * @buffer: DRM client buffer
+ *
+ * This function removes a client buffer's memory mapping. Calling this
+ * function is only required by clients that manage their buffer mappings
+ * by themselves.
+ */
+void drm_client_buffer_vunmap(struct drm_client_buffer *buffer)
+{
+ struct drm_gem_object *gem = buffer->fb->obj[0];
- return 0;
+ drm_gem_vunmap(gem, &buffer->map);
}
+EXPORT_SYMBOL(drm_client_buffer_vunmap);
/**
- * drm_client_framebuffer_create - Create a client framebuffer
+ * drm_client_buffer_create_dumb - Create a client buffer backed by a dumb buffer
* @client: DRM client
* @width: Framebuffer width
* @height: Framebuffer height
@@ -353,69 +386,79 @@ static int drm_client_buffer_addfb(struct drm_client_buffer *buffer,
*
* This function creates a &drm_client_buffer which consists of a
* &drm_framebuffer backed by a dumb buffer.
- * Call drm_client_framebuffer_delete() to free the buffer.
+ * Call drm_client_buffer_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, u32 width, u32 height, u32 format)
+drm_client_buffer_create_dumb(struct drm_client_dev *client, u32 width, u32 height, u32 format)
{
+ const struct drm_format_info *info = drm_format_info(format);
+ struct drm_device *dev = client->dev;
+ struct drm_mode_create_dumb dumb_args = { };
struct drm_client_buffer *buffer;
int ret;
- buffer = drm_client_buffer_create(client, width, height, format);
- if (IS_ERR(buffer))
- return buffer;
-
- ret = drm_client_buffer_addfb(buffer, width, height, format);
- if (ret) {
- drm_client_buffer_delete(buffer);
+ dumb_args.width = width;
+ dumb_args.height = height;
+ dumb_args.bpp = drm_format_info_bpp(info, 0);
+ ret = drm_mode_create_dumb(dev, &dumb_args, client->file);
+ if (ret)
return ERR_PTR(ret);
+
+ buffer = drm_client_buffer_create(client, width, height, format,
+ dumb_args.handle, dumb_args.pitch);
+ if (IS_ERR(buffer)) {
+ ret = PTR_ERR(buffer);
+ goto err_drm_mode_destroy_dumb;
}
+ /*
+ * The handle is only needed for creating the framebuffer, destroy it
+ * again to solve a circular dependency should anybody export the GEM
+ * object as DMA-buf. The framebuffer and our buffer structure are still
+ * holding references to the GEM object to prevent its destruction.
+ */
+ drm_mode_destroy_dumb(client->dev, dumb_args.handle, client->file);
+
return buffer;
+
+err_drm_mode_destroy_dumb:
+ drm_mode_destroy_dumb(client->dev, dumb_args.handle, client->file);
+ return ERR_PTR(ret);
}
-EXPORT_SYMBOL(drm_client_framebuffer_create);
+EXPORT_SYMBOL(drm_client_buffer_create_dumb);
/**
- * drm_client_framebuffer_delete - Delete a client framebuffer
- * @buffer: DRM client buffer (can be NULL)
+ * drm_client_buffer_flush - Manually flush client buffer
+ * @buffer: DRM client buffer
+ * @rect: Damage rectangle (if NULL flushes all)
+ *
+ * This calls &drm_framebuffer_funcs->dirty (if present) to flush buffer changes
+ * for drivers that need it.
+ *
+ * Returns:
+ * Zero on success or negative error code on failure.
*/
-void drm_client_framebuffer_delete(struct drm_client_buffer *buffer)
-{
- if (!buffer)
- return;
-
- drm_client_buffer_rmfb(buffer);
- drm_client_buffer_delete(buffer);
-}
-EXPORT_SYMBOL(drm_client_framebuffer_delete);
-
-#ifdef CONFIG_DEBUG_FS
-static int drm_client_debugfs_internal_clients(struct seq_file *m, void *data)
+int drm_client_buffer_flush(struct drm_client_buffer *buffer, struct drm_rect *rect)
{
- struct drm_info_node *node = m->private;
- struct drm_device *dev = node->minor->dev;
- struct drm_printer p = drm_seq_file_printer(m);
- struct drm_client_dev *client;
-
- mutex_lock(&dev->clientlist_mutex);
- list_for_each_entry(client, &dev->clientlist, list)
- drm_printf(&p, "%s\n", client->name);
- mutex_unlock(&dev->clientlist_mutex);
-
- return 0;
-}
-
-static const struct drm_info_list drm_client_debugfs_list[] = {
- { "internal_clients", drm_client_debugfs_internal_clients, 0 },
-};
+ if (!buffer || !buffer->fb || !buffer->fb->funcs->dirty)
+ return 0;
+
+ if (rect) {
+ struct drm_clip_rect clip = {
+ .x1 = rect->x1,
+ .y1 = rect->y1,
+ .x2 = rect->x2,
+ .y2 = rect->y2,
+ };
+
+ return buffer->fb->funcs->dirty(buffer->fb, buffer->client->file,
+ 0, 0, &clip, 1);
+ }
-int drm_client_debugfs_init(struct drm_minor *minor)
-{
- return drm_debugfs_create_files(drm_client_debugfs_list,
- ARRAY_SIZE(drm_client_debugfs_list),
- minor->debugfs_root, minor);
+ return buffer->fb->funcs->dirty(buffer->fb, buffer->client->file,
+ 0, 0, NULL, 0);
}
-#endif
+EXPORT_SYMBOL(drm_client_buffer_flush);