linux-yocto/drivers/gpu/drm/v3d/v3d_perfmon.c
Christian Gmeiner 95036d4c01 drm/v3d: Stop active perfmon if it is being destroyed
commit 21f1435b1e6b012a07c42f36b206d2b66fc8f13b upstream.

If the active performance monitor (`v3d->active_perfmon`) is being
destroyed, stop it first. Currently, the active perfmon is not
stopped during destruction, leaving the `v3d->active_perfmon` pointer
stale. This can lead to undefined behavior and instability.

This patch ensures that the active perfmon is stopped before being
destroyed, aligning with the behavior introduced in commit
7d1fd3638e ("drm/v3d: Stop the active perfmon before being destroyed").

Cc: stable@vger.kernel.org # v5.15+
Fixes: 26a4dc29b7 ("drm/v3d: Expose performance counters to userspace")
Signed-off-by: Christian Gmeiner <cgmeiner@igalia.com>
Signed-off-by: Maíra Canal <mcanal@igalia.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20241118221948.1758130-1-christian.gmeiner@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2025-02-21 13:50:09 +01:00

227 lines
5.2 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2021 Raspberry Pi
*/
#include "v3d_drv.h"
#include "v3d_regs.h"
#define V3D_PERFMONID_MIN 1
#define V3D_PERFMONID_MAX U32_MAX
void v3d_perfmon_get(struct v3d_perfmon *perfmon)
{
if (perfmon)
refcount_inc(&perfmon->refcnt);
}
void v3d_perfmon_put(struct v3d_perfmon *perfmon)
{
if (perfmon && refcount_dec_and_test(&perfmon->refcnt))
kfree(perfmon);
}
void v3d_perfmon_start(struct v3d_dev *v3d, struct v3d_perfmon *perfmon)
{
unsigned int i;
u32 mask;
u8 ncounters;
if (WARN_ON_ONCE(!perfmon || v3d->active_perfmon))
return;
ncounters = perfmon->ncounters;
mask = GENMASK(ncounters - 1, 0);
for (i = 0; i < ncounters; i++) {
u32 source = i / 4;
u32 channel = V3D_SET_FIELD(perfmon->counters[i], V3D_PCTR_S0);
i++;
channel |= V3D_SET_FIELD(i < ncounters ? perfmon->counters[i] : 0,
V3D_PCTR_S1);
i++;
channel |= V3D_SET_FIELD(i < ncounters ? perfmon->counters[i] : 0,
V3D_PCTR_S2);
i++;
channel |= V3D_SET_FIELD(i < ncounters ? perfmon->counters[i] : 0,
V3D_PCTR_S3);
V3D_CORE_WRITE(0, V3D_V4_PCTR_0_SRC_X(source), channel);
}
V3D_CORE_WRITE(0, V3D_V4_PCTR_0_EN, mask);
V3D_CORE_WRITE(0, V3D_V4_PCTR_0_CLR, mask);
V3D_CORE_WRITE(0, V3D_PCTR_0_OVERFLOW, mask);
v3d->active_perfmon = perfmon;
}
void v3d_perfmon_stop(struct v3d_dev *v3d, struct v3d_perfmon *perfmon,
bool capture)
{
unsigned int i;
if (!perfmon || !v3d->active_perfmon)
return;
mutex_lock(&perfmon->lock);
if (perfmon != v3d->active_perfmon) {
mutex_unlock(&perfmon->lock);
return;
}
if (capture)
for (i = 0; i < perfmon->ncounters; i++)
perfmon->values[i] += V3D_CORE_READ(0, V3D_PCTR_0_PCTRX(i));
V3D_CORE_WRITE(0, V3D_V4_PCTR_0_EN, 0);
v3d->active_perfmon = NULL;
mutex_unlock(&perfmon->lock);
}
struct v3d_perfmon *v3d_perfmon_find(struct v3d_file_priv *v3d_priv, int id)
{
struct v3d_perfmon *perfmon;
mutex_lock(&v3d_priv->perfmon.lock);
perfmon = idr_find(&v3d_priv->perfmon.idr, id);
v3d_perfmon_get(perfmon);
mutex_unlock(&v3d_priv->perfmon.lock);
return perfmon;
}
void v3d_perfmon_open_file(struct v3d_file_priv *v3d_priv)
{
mutex_init(&v3d_priv->perfmon.lock);
idr_init_base(&v3d_priv->perfmon.idr, 1);
}
static int v3d_perfmon_idr_del(int id, void *elem, void *data)
{
struct v3d_perfmon *perfmon = elem;
struct v3d_dev *v3d = (struct v3d_dev *)data;
/* If the active perfmon is being destroyed, stop it first */
if (perfmon == v3d->active_perfmon)
v3d_perfmon_stop(v3d, perfmon, false);
v3d_perfmon_put(perfmon);
return 0;
}
void v3d_perfmon_close_file(struct v3d_file_priv *v3d_priv)
{
struct v3d_dev *v3d = v3d_priv->v3d;
mutex_lock(&v3d_priv->perfmon.lock);
idr_for_each(&v3d_priv->perfmon.idr, v3d_perfmon_idr_del, v3d);
idr_destroy(&v3d_priv->perfmon.idr);
mutex_unlock(&v3d_priv->perfmon.lock);
}
int v3d_perfmon_create_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct v3d_file_priv *v3d_priv = file_priv->driver_priv;
struct drm_v3d_perfmon_create *req = data;
struct v3d_perfmon *perfmon;
unsigned int i;
int ret;
/* Number of monitored counters cannot exceed HW limits. */
if (req->ncounters > DRM_V3D_MAX_PERF_COUNTERS ||
!req->ncounters)
return -EINVAL;
/* Make sure all counters are valid. */
for (i = 0; i < req->ncounters; i++) {
if (req->counters[i] >= V3D_PERFCNT_NUM)
return -EINVAL;
}
perfmon = kzalloc(struct_size(perfmon, values, req->ncounters),
GFP_KERNEL);
if (!perfmon)
return -ENOMEM;
for (i = 0; i < req->ncounters; i++)
perfmon->counters[i] = req->counters[i];
perfmon->ncounters = req->ncounters;
refcount_set(&perfmon->refcnt, 1);
mutex_init(&perfmon->lock);
mutex_lock(&v3d_priv->perfmon.lock);
ret = idr_alloc(&v3d_priv->perfmon.idr, perfmon, V3D_PERFMONID_MIN,
V3D_PERFMONID_MAX, GFP_KERNEL);
mutex_unlock(&v3d_priv->perfmon.lock);
if (ret < 0) {
kfree(perfmon);
return ret;
}
req->id = ret;
return 0;
}
int v3d_perfmon_destroy_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct v3d_file_priv *v3d_priv = file_priv->driver_priv;
struct drm_v3d_perfmon_destroy *req = data;
struct v3d_dev *v3d = v3d_priv->v3d;
struct v3d_perfmon *perfmon;
mutex_lock(&v3d_priv->perfmon.lock);
perfmon = idr_remove(&v3d_priv->perfmon.idr, req->id);
mutex_unlock(&v3d_priv->perfmon.lock);
if (!perfmon)
return -EINVAL;
/* If the active perfmon is being destroyed, stop it first */
if (perfmon == v3d->active_perfmon)
v3d_perfmon_stop(v3d, perfmon, false);
v3d_perfmon_put(perfmon);
return 0;
}
int v3d_perfmon_get_values_ioctl(struct drm_device *dev, void *data,
struct drm_file *file_priv)
{
struct v3d_dev *v3d = to_v3d_dev(dev);
struct v3d_file_priv *v3d_priv = file_priv->driver_priv;
struct drm_v3d_perfmon_get_values *req = data;
struct v3d_perfmon *perfmon;
int ret = 0;
if (req->pad != 0)
return -EINVAL;
mutex_lock(&v3d_priv->perfmon.lock);
perfmon = idr_find(&v3d_priv->perfmon.idr, req->id);
v3d_perfmon_get(perfmon);
mutex_unlock(&v3d_priv->perfmon.lock);
if (!perfmon)
return -EINVAL;
v3d_perfmon_stop(v3d, perfmon, true);
if (copy_to_user(u64_to_user_ptr(req->values_ptr), perfmon->values,
perfmon->ncounters * sizeof(u64)))
ret = -EFAULT;
v3d_perfmon_put(perfmon);
return ret;
}