diff options
Diffstat (limited to 'drivers/gpu/drm/drm_client.c')
| -rw-r--r-- | drivers/gpu/drm/drm_client.c | 443 |
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); |
