drm/panel: Add refcount support

Allocate panel via reference counting. Add _get() and _put() helper
functions to ensure panel allocations are refcounted. Avoid use after
free by ensuring panel pointer is valid and can be usable till the last
reference is put.

Reviewed-by: Luca Ceresoli <luca.ceresoli@bootlin.com>
Reviewed-by: Maxime Ripard <mripard@kernel.org>
Signed-off-by: Anusha Srivatsa <asrivats@redhat.com>
Link: https://lore.kernel.org/r/20250331-b4-panel-refcounting-v4-2-dad50c60c6c9@redhat.com
Signed-off-by: Maxime Ripard <mripard@kernel.org>
This commit is contained in:
Anusha Srivatsa 2025-03-31 11:15:26 -04:00 committed by Maxime Ripard
parent ed9c594d49
commit dcba396f69
No known key found for this signature in database
GPG Key ID: E3EF0D6F671851C5
2 changed files with 82 additions and 1 deletions

View File

@ -355,24 +355,86 @@ struct drm_panel *of_drm_find_panel(const struct device_node *np)
}
EXPORT_SYMBOL(of_drm_find_panel);
static void __drm_panel_free(struct kref *kref)
{
struct drm_panel *panel = container_of(kref, struct drm_panel, refcount);
kfree(panel->container);
}
/**
* drm_panel_get - Acquire a panel reference
* @panel: DRM panel
*
* This function increments the panel's refcount.
* Returns:
* Pointer to @panel
*/
struct drm_panel *drm_panel_get(struct drm_panel *panel)
{
if (!panel)
return panel;
kref_get(&panel->refcount);
return panel;
}
EXPORT_SYMBOL(drm_panel_get);
/**
* drm_panel_put - Release a panel reference
* @panel: DRM panel
*
* This function decrements the panel's reference count and frees the
* object if the reference count drops to zero.
*/
void drm_panel_put(struct drm_panel *panel)
{
if (panel)
kref_put(&panel->refcount, __drm_panel_free);
}
EXPORT_SYMBOL(drm_panel_put);
/**
* drm_panel_put_void - wrapper to drm_panel_put() taking a void pointer
*
* @data: pointer to @struct drm_panel, cast to a void pointer
*
* Wrapper of drm_panel_put() to be used when a function taking a void
* pointer is needed, for example as a devm action.
*/
static void drm_panel_put_void(void *data)
{
struct drm_panel *panel = (struct drm_panel *)data;
drm_panel_put(panel);
}
void *__devm_drm_panel_alloc(struct device *dev, size_t size, size_t offset,
const struct drm_panel_funcs *funcs,
int connector_type)
{
void *container;
struct drm_panel *panel;
int err;
if (!funcs) {
dev_warn(dev, "Missing funcs pointer\n");
return ERR_PTR(-EINVAL);
}
container = devm_kzalloc(dev, size, GFP_KERNEL);
container = kzalloc(size, GFP_KERNEL);
if (!container)
return ERR_PTR(-ENOMEM);
panel = container + offset;
panel->container = container;
panel->funcs = funcs;
kref_init(&panel->refcount);
err = devm_add_action_or_reset(dev, drm_panel_put_void, panel);
if (err)
return ERR_PTR(err);
drm_panel_init(panel, dev, funcs, connector_type);

View File

@ -28,6 +28,7 @@
#include <linux/errno.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/kref.h>
struct backlight_device;
struct dentry;
@ -266,6 +267,17 @@ struct drm_panel {
* If true then the panel has been enabled.
*/
bool enabled;
/**
* @container: Pointer to the private driver struct embedding this
* @struct drm_panel.
*/
void *container;
/**
* @refcount: reference count of users referencing this panel.
*/
struct kref refcount;
};
void *__devm_drm_panel_alloc(struct device *dev, size_t size, size_t offset,
@ -282,6 +294,10 @@ void *__devm_drm_panel_alloc(struct device *dev, size_t size, size_t offset,
* @connector_type: the connector type (DRM_MODE_CONNECTOR_*) corresponding to
* the panel interface
*
* The reference count of the returned panel is initialized to 1. This
* reference will be automatically dropped via devm (by calling
* drm_panel_put()) when @dev is removed.
*
* Returns:
* Pointer to container structure embedding the panel, ERR_PTR on failure.
*/
@ -294,6 +310,9 @@ void drm_panel_init(struct drm_panel *panel, struct device *dev,
const struct drm_panel_funcs *funcs,
int connector_type);
struct drm_panel *drm_panel_get(struct drm_panel *panel);
void drm_panel_put(struct drm_panel *panel);
void drm_panel_add(struct drm_panel *panel);
void drm_panel_remove(struct drm_panel *panel);