Add client callbacks and hook them up. Add a list of clients per drm_device. Signed-off-by: Noralf Trønnes <noralf@xxxxxxxxxxx> --- drivers/gpu/drm/drm_client.c | 246 +++++++++++++++++++++++++++++++++++- drivers/gpu/drm/drm_debugfs.c | 7 + drivers/gpu/drm/drm_drv.c | 8 ++ drivers/gpu/drm/drm_fb_cma_helper.c | 2 +- drivers/gpu/drm/drm_file.c | 3 + drivers/gpu/drm/drm_probe_helper.c | 3 + include/drm/drm_client.h | 65 +++++++++- include/drm/drm_device.h | 14 ++ 8 files changed, 345 insertions(+), 3 deletions(-) diff --git a/drivers/gpu/drm/drm_client.c b/drivers/gpu/drm/drm_client.c index 0919aea7dddd..c495fcee2058 100644 --- a/drivers/gpu/drm/drm_client.c +++ b/drivers/gpu/drm/drm_client.c @@ -66,11 +66,13 @@ static void drm_client_free_file(struct drm_client_dev *client) /** * drm_client_new - Create a DRM client * @dev: DRM device + * @funcs: DRM client functions * * Returns: * Pointer to a client or an error pointer on failure. */ -struct drm_client_dev *drm_client_new(struct drm_device *dev) +struct drm_client_dev * +drm_client_new(struct drm_device *dev, const struct drm_client_funcs *funcs) { struct drm_client_dev *client; int ret; @@ -84,6 +86,7 @@ struct drm_client_dev *drm_client_new(struct drm_device *dev) return ERR_PTR(-ENOMEM); client->dev = dev; + client->funcs = funcs; ret = drm_client_alloc_file(client); if (ret) { @@ -91,21 +94,231 @@ struct drm_client_dev *drm_client_new(struct drm_device *dev) return ERR_PTR(ret); } + /* + * TODO: + * Temporary hack until all CMA drivers have been moved over to using + * drm_fbdev_generic_setup(). + */ + if (!funcs) + return client; + + mutex_lock(&dev->clientlist_mutex); + list_add(&client->list, &dev->clientlist); + mutex_unlock(&dev->clientlist_mutex); + return client; } EXPORT_SYMBOL(drm_client_new); +/** + * drm_client_new_from_id - Create a DRM client from a minor id + * @minor_id: DRM minor id + * @funcs: DRM client functions + * + * Returns: + * Pointer to a client or an error pointer on failure. + */ +struct drm_client_dev * +drm_client_new_from_id(unsigned int minor_id, const struct drm_client_funcs *funcs) +{ + struct drm_client_dev *client; + struct drm_minor *minor; + + minor = drm_minor_acquire(minor_id); + if (IS_ERR(minor)) + return ERR_CAST(minor); + + client = drm_client_new(minor->dev, funcs); + + drm_minor_release(minor); + + return client; +} +EXPORT_SYMBOL(drm_client_new_from_id); + /** * 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. + * + * Note: + * If the client can't release it's resources on remove, it needs to hold a + * reference on the driver module to prevent the code from going away. */ void drm_client_free(struct drm_client_dev *client) { + DRM_DEV_DEBUG_KMS(client->dev->dev, "\n"); drm_client_free_file(client); kfree(client); } EXPORT_SYMBOL(drm_client_free); +static void drm_client_remove_locked(struct drm_client_dev *client) +{ + list_del(&client->list); + + if (!client->funcs->remove || !client->funcs->remove(client)) + drm_client_free(client); +} + +static void drm_client_remove_safe(struct drm_device *dev, + struct drm_client_dev *client) +{ + struct drm_client_dev *iter; + + mutex_lock(&dev->clientlist_mutex); + list_for_each_entry(iter, &dev->clientlist, list) { + if (iter == client) { + drm_client_remove_locked(client); + break; + } + } + mutex_unlock(&dev->clientlist_mutex); +} + +/** + * drm_client_remove - Remove client + * @client: Client + * + * Remove the DRM client. + */ +void drm_client_remove(struct drm_client_dev *client) +{ + struct drm_device *dev; + + if (!client) + return; + + dev = client->dev; + /* Hold a reference since the client might drop the last one */ + drm_dev_get(dev); + drm_client_remove_safe(dev, client); + drm_dev_put(dev); +} +EXPORT_SYMBOL(drm_client_remove); + +struct drm_client_remove_defer { + struct list_head list; + struct drm_device *dev; + struct drm_client_dev *client; +}; + +static LIST_HEAD(drm_client_remove_defer_list); +static DEFINE_MUTEX(drm_client_remove_defer_list_lock); + +static void drm_client_remove_defer_work_fn(struct work_struct *work) +{ + struct drm_client_remove_defer *defer, *tmp; + + mutex_lock(&drm_client_remove_defer_list_lock); + list_for_each_entry_safe(defer, tmp, &drm_client_remove_defer_list, list) { + drm_client_remove_safe(defer->dev, defer->client); + drm_dev_put(defer->dev); + list_del(&defer->list); + kfree(defer); + } + mutex_unlock(&drm_client_remove_defer_list_lock); +} + +static DECLARE_WORK(drm_client_remove_defer_work, drm_client_remove_defer_work_fn); + +/** + * drm_client_remove_defer - Deferred client removal + * @client: Client + * + * Defer client removal to a worker. This makes it possible for a client running + * in a worker to remove itself. + * + * Returns: + * Zero on success, or -ENOMEM on allocation failure. + */ +int drm_client_remove_defer(struct drm_client_dev *client) +{ + struct drm_client_remove_defer *defer; + + if (!client) + return 0; + + defer = kzalloc(sizeof(*defer), GFP_KERNEL); + if (!defer) + return -ENOMEM; + + defer->dev = client->dev; + defer->client = client; + drm_dev_get(client->dev); + + mutex_lock(&drm_client_remove_defer_list_lock); + list_add(&defer->list, &drm_client_remove_defer_list); + mutex_unlock(&drm_client_remove_defer_list_lock); + + schedule_work(&drm_client_remove_defer_work); + + return 0; +} +EXPORT_SYMBOL(drm_client_remove_defer); + +void drm_client_exit(void) +{ + flush_work(&drm_client_remove_defer_work); +} + +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(&dev->clientlist_mutex); + list_for_each_entry_safe(client, tmp, &dev->clientlist, list) + drm_client_remove_locked(client); + mutex_unlock(&dev->clientlist_mutex); +} + +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(&dev->clientlist_mutex); + list_for_each_entry(client, &dev->clientlist, list) { + if (!client->funcs->hotplug) + continue; + + ret = client->funcs->hotplug(client); + DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", client->funcs->name, ret); + } + mutex_unlock(&dev->clientlist_mutex); +} +EXPORT_SYMBOL(drm_client_dev_hotplug); + +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(&dev->clientlist_mutex); + list_for_each_entry(client, &dev->clientlist, list) { + if (!client->funcs->lastclose) + continue; + + ret = client->funcs->lastclose(client); + DRM_DEV_DEBUG_KMS(dev->dev, "%s: ret=%d\n", client->funcs->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; @@ -218,6 +431,9 @@ static int drm_client_buffer_addfb(struct drm_client_buffer *buffer, /* drop the reference we picked up in framebuffer lookup */ drm_framebuffer_put(buffer->fb); + if (client->funcs) + strscpy(buffer->fb->comm, client->funcs->name, TASK_COMM_LEN); + return 0; } @@ -265,3 +481,31 @@ void drm_client_framebuffer_delete(struct drm_client_buffer *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) +{ + 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->funcs->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 }, +}; + +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); +} +#endif diff --git a/drivers/gpu/drm/drm_debugfs.c b/drivers/gpu/drm/drm_debugfs.c index b2482818fee8..50a20bfc07ea 100644 --- a/drivers/gpu/drm/drm_debugfs.c +++ b/drivers/gpu/drm/drm_debugfs.c @@ -28,6 +28,7 @@ #include <linux/slab.h> #include <linux/export.h> +#include <drm/drm_client.h> #include <drm/drm_debugfs.h> #include <drm/drm_edid.h> #include <drm/drm_atomic.h> @@ -164,6 +165,12 @@ int drm_debugfs_init(struct drm_minor *minor, int minor_id, DRM_ERROR("Failed to create framebuffer debugfs file\n"); return ret; } + + ret = drm_client_debugfs_init(minor); + if (ret) { + DRM_ERROR("Failed to create client debugfs file\n"); + return ret; + } } if (dev->driver->debugfs_init) { diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 67ac793a7108..d7c1ceebd0de 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -34,6 +34,7 @@ #include <linux/slab.h> #include <linux/srcu.h> +#include <drm/drm_client.h> #include <drm/drm_drv.h> #include <drm/drmP.h> @@ -506,6 +507,7 @@ int drm_dev_init(struct drm_device *dev, INIT_LIST_HEAD(&dev->filelist); INIT_LIST_HEAD(&dev->filelist_internal); + INIT_LIST_HEAD(&dev->clientlist); INIT_LIST_HEAD(&dev->ctxlist); INIT_LIST_HEAD(&dev->vmalist); INIT_LIST_HEAD(&dev->maplist); @@ -515,6 +517,7 @@ int drm_dev_init(struct drm_device *dev, spin_lock_init(&dev->event_lock); mutex_init(&dev->struct_mutex); mutex_init(&dev->filelist_mutex); + mutex_init(&dev->clientlist_mutex); mutex_init(&dev->ctxlist_mutex); mutex_init(&dev->master_mutex); @@ -570,6 +573,7 @@ int drm_dev_init(struct drm_device *dev, err_free: mutex_destroy(&dev->master_mutex); mutex_destroy(&dev->ctxlist_mutex); + mutex_destroy(&dev->clientlist_mutex); mutex_destroy(&dev->filelist_mutex); mutex_destroy(&dev->struct_mutex); return ret; @@ -604,6 +608,7 @@ void drm_dev_fini(struct drm_device *dev) mutex_destroy(&dev->master_mutex); mutex_destroy(&dev->ctxlist_mutex); + mutex_destroy(&dev->clientlist_mutex); mutex_destroy(&dev->filelist_mutex); mutex_destroy(&dev->struct_mutex); kfree(dev->unique); @@ -854,6 +859,8 @@ void drm_dev_unregister(struct drm_device *dev) { struct drm_map_list *r_list, *list_temp; + drm_client_dev_unregister(dev); + if (drm_core_check_feature(dev, DRIVER_LEGACY)) drm_lastclose(dev); @@ -959,6 +966,7 @@ static const struct file_operations drm_stub_fops = { static void drm_core_exit(void) { + drm_client_exit(); unregister_chrdev(DRM_MAJOR, "drm"); debugfs_remove(drm_debugfs_root); drm_sysfs_destroy(); diff --git a/drivers/gpu/drm/drm_fb_cma_helper.c b/drivers/gpu/drm/drm_fb_cma_helper.c index 76e2f7977779..aff3c215d9e5 100644 --- a/drivers/gpu/drm/drm_fb_cma_helper.c +++ b/drivers/gpu/drm/drm_fb_cma_helper.c @@ -180,7 +180,7 @@ struct drm_fbdev_cma *drm_fbdev_cma_init(struct drm_device *dev, if (!fbdev_cma) return ERR_PTR(-ENOMEM); - client = drm_client_new(dev); + client = drm_client_new(dev, NULL); if (IS_ERR(client)) { ret = PTR_ERR(client); goto err_free; 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 <linux/slab.h> #include <linux/module.h> +#include <drm/drm_client.h> #include <drm/drm_file.h> #include <drm/drmP.h> @@ -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 527743394150..26be57e28a9d 100644 --- a/drivers/gpu/drm/drm_probe_helper.c +++ b/drivers/gpu/drm/drm_probe_helper.c @@ -33,6 +33,7 @@ #include <linux/moduleparam.h> #include <drm/drmP.h> +#include <drm/drm_client.h> #include <drm/drm_crtc.h> #include <drm/drm_fourcc.h> #include <drm/drm_crtc_helper.h> @@ -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 index 11379eaf3118..011a87ad3406 100644 --- a/include/drm/drm_client.h +++ b/include/drm/drm_client.h @@ -5,11 +5,56 @@ #include <linux/types.h> +struct drm_client_dev; struct drm_device; struct drm_framebuffer; struct drm_gem_object; struct drm_minor; +/** + * struct drm_client_funcs - DRM client callbacks + */ +struct drm_client_funcs { + /** + * @name: + * + * Name of the client. Mandatory. + */ + const char *name; + + /** + * @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); +}; + /** * struct drm_client_dev - DRM client instance */ @@ -27,6 +72,11 @@ struct drm_client_dev { */ struct drm_device *dev; + /** + * @funcs: DRM client functions + */ + const struct drm_client_funcs *funcs; + /** * @file: DRM file */ @@ -39,7 +89,20 @@ struct drm_client_dev { }; void drm_client_free(struct drm_client_dev *client); -struct drm_client_dev *drm_client_new(struct drm_device *dev); +struct drm_client_dev * +drm_client_new(struct drm_device *dev, const struct drm_client_funcs *funcs); +struct drm_client_dev * +drm_client_new_from_id(unsigned int minor_id, const struct drm_client_funcs *funcs); +void drm_client_remove(struct drm_client_dev *client); +int drm_client_remove_defer(struct drm_client_dev *client); + +void drm_client_exit(void); + +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_debugfs_init(struct drm_minor *minor); /** * struct drm_client_buffer - DRM client buffer diff --git a/include/drm/drm_device.h b/include/drm/drm_device.h index 9e29976d4e98..f9c6e0e3aec7 100644 --- a/include/drm/drm_device.h +++ b/include/drm/drm_device.h @@ -81,6 +81,20 @@ struct drm_device { */ struct list_head filelist_internal; + /** + * @clientlist_mutex: + * + * Protects @clientlist access. + */ + struct mutex clientlist_mutex; + + /** + * @clientlist: + * + * List of in-kernel clients. Protected by @clientlist_mutex. + */ + struct list_head clientlist; + /** \name Memory management */ /*@{ */ struct list_head maplist; /**< Linked list of regions */ -- 2.15.1 _______________________________________________ Intel-gfx mailing list Intel-gfx@xxxxxxxxxxxxxxxxxxxxx https://lists.freedesktop.org/mailman/listinfo/intel-gfx