mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 09:36:22 +02:00
Now that the dynamic pwrup reglist has SEL reg values to restore appended, so that SEL regs are restored on IFPC exit, we can stop completely disabling IFPC while global counter sampling is active. To accomplish this, we re-use sysprof_setup() with a force_on param to inhibit IFPC specifically while the counter regs are being read, while leaving IFPC enabled the rest of the time. Signed-off-by: Rob Clark <robin.clark@oss.qualcomm.com> Reviewed-by: Anna Maniscalco <anna.maniscalco2000@gmail.com> Reviewed-by: Akhil P Oommen <akhilpo@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/728219/ Message-ID: <20260526145137.160554-17-robin.clark@oss.qualcomm.com>
671 lines
18 KiB
C
671 lines
18 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
|
|
*/
|
|
|
|
#include "drm/drm_file.h"
|
|
#include "drm/msm_drm.h"
|
|
|
|
#include "linux/anon_inodes.h"
|
|
#include "linux/gfp_types.h"
|
|
#include "linux/poll.h"
|
|
#include "linux/slab.h"
|
|
|
|
#include "msm_drv.h"
|
|
#include "msm_gpu.h"
|
|
#include "msm_perfcntr.h"
|
|
|
|
#include "adreno/adreno_gpu.h"
|
|
|
|
/* space used: */
|
|
#define fifo_count(stream) \
|
|
(CIRC_CNT((stream)->fifo.head, (stream)->fifo.tail, (stream)->fifo_size))
|
|
#define fifo_count_to_end(stream) \
|
|
(CIRC_CNT_TO_END(smp_load_acquire(&(stream)->fifo.head), (stream)->fifo.tail, (stream)->fifo_size))
|
|
/* space available: */
|
|
#define fifo_space(stream) \
|
|
(CIRC_SPACE((stream)->fifo.head, (stream)->fifo.tail, (stream)->fifo_size))
|
|
|
|
static int
|
|
msm_perfcntr_resume_locked(struct msm_perfcntr_stream *stream)
|
|
{
|
|
if (!stream)
|
|
return 0;
|
|
|
|
/* Reprogram SEL regs on highest priority rb: */
|
|
struct msm_ringbuffer *ring = stream->gpu->rb[0];
|
|
|
|
queue_work(ring->sched.submit_wq, &stream->sel_work);
|
|
|
|
hrtimer_start(&stream->sample_timer,
|
|
ns_to_ktime(stream->sample_period_ns),
|
|
HRTIMER_MODE_REL_PINNED);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
msm_perfcntr_resume(struct msm_gpu *gpu)
|
|
{
|
|
if (!gpu->perfcntrs)
|
|
return 0;
|
|
guard(mutex)(&gpu->perfcntr_lock);
|
|
return msm_perfcntr_resume_locked(gpu->perfcntrs->stream);
|
|
}
|
|
|
|
static void
|
|
msm_perfcntr_suspend_locked(struct msm_perfcntr_stream *stream)
|
|
{
|
|
if (!stream)
|
|
return;
|
|
|
|
hrtimer_cancel(&stream->sample_timer);
|
|
kthread_cancel_work_sync(&stream->sample_work);
|
|
|
|
/*
|
|
* We can't use cancel_work_sync() here, since sel_work acquires
|
|
* gpu->lock which (a) in suspend path can already be held, or
|
|
* (b) in release path would invert the order of gpu->lock and
|
|
* gpu->perfcntr_lock. Either would cause deadlock.
|
|
*/
|
|
cancel_work(&stream->sel_work);
|
|
|
|
stream->sel_fence = ++stream->gpu->perfcntrs->sel_seqno;
|
|
stream->seqno = 0;
|
|
}
|
|
|
|
void
|
|
msm_perfcntr_suspend(struct msm_gpu *gpu)
|
|
{
|
|
if (!gpu->perfcntrs)
|
|
return;
|
|
guard(mutex)(&gpu->perfcntr_lock);
|
|
msm_perfcntr_suspend_locked(gpu->perfcntrs->stream);
|
|
}
|
|
|
|
static int
|
|
msm_perfcntrs_stream_release(struct inode *inode, struct file *file)
|
|
{
|
|
struct msm_perfcntr_stream *stream = file->private_data;
|
|
struct msm_gpu *gpu = stream->gpu;
|
|
|
|
scoped_guard (mutex, &gpu->perfcntr_lock) {
|
|
struct msm_perfcntr_state *perfcntrs = gpu->perfcntrs;
|
|
|
|
msm_perfcntr_suspend_locked(stream);
|
|
perfcntrs->stream = NULL;
|
|
|
|
/* release previously allocated counters: */
|
|
for (unsigned i = 0; i < gpu->num_perfcntr_groups; i++)
|
|
perfcntrs->groups[i]->allocated_counters = 0;
|
|
}
|
|
|
|
/*
|
|
* In the suspend path we use async cancel_work(), to avoid blocking
|
|
* on sel_work, which acquires gpu->lock (which could deadlock since
|
|
* other paths acquire gpu->lock before perfcntr_lock) or already
|
|
* hold gpu->lock.
|
|
*
|
|
* But since we are freeing the stream, after dropping perfcntr_lock
|
|
* we need to block until sel_work is done:
|
|
*/
|
|
cancel_work_sync(&stream->sel_work);
|
|
|
|
kfree(stream->group_idx);
|
|
kfree(stream->fifo.buf);
|
|
kfree(stream);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static __poll_t
|
|
msm_perfcntrs_stream_poll(struct file *file, poll_table *wait)
|
|
{
|
|
struct msm_perfcntr_stream *stream = file->private_data;
|
|
__poll_t events = 0;
|
|
|
|
poll_wait(file, &stream->poll_wq, wait);
|
|
|
|
/* Are there samples to read? */
|
|
if (fifo_count(stream) > 0)
|
|
events |= EPOLLIN;
|
|
|
|
return events;
|
|
}
|
|
|
|
static ssize_t
|
|
msm_perfcntrs_stream_read(struct file *file, char __user *buf,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
struct msm_perfcntr_stream *stream = file->private_data;
|
|
int ret;
|
|
|
|
if (!(file->f_flags & O_NONBLOCK)) {
|
|
ret = wait_event_interruptible(stream->poll_wq,
|
|
fifo_count(stream) > 0);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
guard(mutex)(&stream->read_lock);
|
|
|
|
struct circ_buf *fifo = &stream->fifo;
|
|
const char *fptr = &fifo->buf[fifo->tail];
|
|
|
|
count = min_t(size_t, count, fifo_count_to_end(stream));
|
|
if (!count)
|
|
return -EAGAIN;
|
|
if (copy_to_user(buf, fptr, count))
|
|
return -EFAULT;
|
|
|
|
smp_store_release(&fifo->tail, (fifo->tail + count) & (stream->fifo_size - 1));
|
|
*ppos += count;
|
|
|
|
return count;
|
|
}
|
|
|
|
static const struct file_operations stream_fops = {
|
|
.owner = THIS_MODULE,
|
|
.release = msm_perfcntrs_stream_release,
|
|
.poll = msm_perfcntrs_stream_poll,
|
|
.read = msm_perfcntrs_stream_read,
|
|
};
|
|
|
|
static void
|
|
sel_worker(struct work_struct *w)
|
|
{
|
|
struct msm_perfcntr_stream *stream =
|
|
container_of(w, typeof(*stream), sel_work);
|
|
struct msm_gpu *gpu = stream->gpu;
|
|
/* Reprogram SEL regs on highest priority rb: */
|
|
struct msm_ringbuffer *ring = stream->gpu->rb[0];
|
|
|
|
/*
|
|
* If in the process of resuming, wait for that. Otherwise sel_worker
|
|
* which is enqueued in the resume path can be scheduled before the
|
|
* resume completes.
|
|
*/
|
|
pm_runtime_barrier(&gpu->pdev->dev);
|
|
|
|
/*
|
|
* sel_work could end up scheduled before suspend, but running
|
|
* after. See msm_perfcntr_suspend_locked()
|
|
*
|
|
* So if we end up running sel_work after the GPU is already
|
|
* suspended, just bail. It will be scheduled again after
|
|
* the GPU is resumed.
|
|
*/
|
|
if (!pm_runtime_get_if_active(&gpu->pdev->dev))
|
|
return;
|
|
|
|
scoped_guard (mutex, &gpu->lock) {
|
|
guard(mutex)(&gpu->perfcntr_lock);
|
|
|
|
if (stream == gpu->perfcntrs->stream) {
|
|
msm_gpu_hw_init(gpu);
|
|
gpu->funcs->perfcntr_configure(gpu, ring, stream);
|
|
}
|
|
}
|
|
|
|
pm_runtime_put_autosuspend(&gpu->pdev->dev);
|
|
}
|
|
|
|
static void
|
|
sample_write(struct msm_perfcntr_stream *stream, int *head, const void *buf, size_t sz)
|
|
{
|
|
/*
|
|
* FIFO size is power-of-two, and guaranteed to have enough space to
|
|
* fit what we are writing. So we should not hit the wrap-around
|
|
* point writing things that are power-of-two sized
|
|
*/
|
|
WARN_ON(CIRC_SPACE_TO_END(*head, stream->fifo.tail, stream->fifo_size) < sz);
|
|
|
|
memcpy(&stream->fifo.buf[*head], buf, sz);
|
|
|
|
/* Advance head, wrapping around if necessary: */
|
|
*head = (*head + sz) & (stream->fifo_size - 1);
|
|
}
|
|
|
|
static void
|
|
sample_write_u32(struct msm_perfcntr_stream *stream, int *head, uint32_t val)
|
|
{
|
|
sample_write(stream, head, &val, sizeof(val));
|
|
}
|
|
|
|
static void
|
|
sample_write_u64(struct msm_perfcntr_stream *stream, int *head, uint64_t val)
|
|
{
|
|
sample_write(stream, head, &val, sizeof(val));
|
|
}
|
|
|
|
static void
|
|
sample_worker(struct kthread_work *work)
|
|
{
|
|
struct msm_perfcntr_stream *stream =
|
|
container_of(work, typeof(*stream), sample_work);
|
|
struct msm_gpu *gpu = stream->gpu;
|
|
struct msm_rbmemptrs *memptrs = gpu->rb[0]->memptrs;
|
|
|
|
if (memptrs->perfcntr_fence != stream->sel_fence)
|
|
return;
|
|
|
|
/*
|
|
* Ensure we have enough space to capture a sample period's
|
|
* worth of data:
|
|
*/
|
|
if (stream->period_size > fifo_space(stream)) {
|
|
stream->seqno = 0;
|
|
return;
|
|
}
|
|
|
|
/* Inhibit IFPC while accessing registers: */
|
|
if (gpu->funcs->sysprof_setup)
|
|
gpu->funcs->sysprof_setup(gpu, true);
|
|
|
|
if (gpu->funcs->perfcntr_flush)
|
|
gpu->funcs->perfcntr_flush(gpu);
|
|
|
|
/* Keep local copy of head to avoid updating fifo until the end: */
|
|
int head = stream->fifo.head;
|
|
|
|
/*
|
|
* We expect the GPU to be powered at this point, as the timer
|
|
* and kthread work are canceled/flushed in the suspend path:
|
|
*/
|
|
sample_write_u64(stream, &head,
|
|
to_adreno_gpu(gpu)->funcs->get_timestamp(gpu));
|
|
sample_write_u32(stream, &head, stream->seqno++);
|
|
sample_write_u32(stream, &head, 0);
|
|
|
|
for (unsigned i = 0; i < stream->nr_groups; i++) {
|
|
unsigned group_idx = msm_perfcntr_group_idx(stream, i);
|
|
unsigned base = msm_perfcntr_counter_base(stream, group_idx);
|
|
|
|
const struct msm_perfcntr_group *group =
|
|
&gpu->perfcntr_groups[group_idx];
|
|
|
|
struct msm_perfcntr_group_state *group_state =
|
|
gpu->perfcntrs->groups[group_idx];
|
|
|
|
unsigned nr = group_state->allocated_counters;
|
|
for (unsigned j = 0; j < nr; j++) {
|
|
const struct msm_perfcntr_counter *counter =
|
|
&group->counters[j + base];
|
|
uint64_t val = gpu_read64(gpu, counter->counter_reg_lo);
|
|
sample_write_u64(stream, &head, val);
|
|
}
|
|
}
|
|
|
|
/* Re-enable IFPC: */
|
|
if (gpu->funcs->sysprof_setup)
|
|
gpu->funcs->sysprof_setup(gpu, false);
|
|
|
|
smp_store_release(&stream->fifo.head, head);
|
|
wake_up_all(&stream->poll_wq);
|
|
}
|
|
|
|
static enum hrtimer_restart
|
|
sample_timer(struct hrtimer *hrtimer)
|
|
{
|
|
struct msm_perfcntr_stream *stream =
|
|
container_of(hrtimer, typeof(*stream), sample_timer);
|
|
|
|
kthread_queue_work(stream->gpu->worker, &stream->sample_work);
|
|
|
|
hrtimer_forward_now(hrtimer, ns_to_ktime(stream->sample_period_ns));
|
|
|
|
return HRTIMER_RESTART;
|
|
}
|
|
|
|
static int
|
|
get_group_idx(struct msm_gpu *gpu, const char *name, size_t len)
|
|
{
|
|
for (unsigned i = 0; i < gpu->num_perfcntr_groups; i++) {
|
|
const struct msm_perfcntr_group *group =
|
|
&gpu->perfcntr_groups[i];
|
|
if (!strncmp(group->name, name, len))
|
|
return i;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
get_available_counters(struct msm_gpu *gpu, int group_idx, uint32_t flags)
|
|
{
|
|
struct msm_perfcntr_state *perfcntrs = gpu->perfcntrs;
|
|
|
|
/*
|
|
* For local counter reservation, anything that is not used by
|
|
* global perfcntr stream is available:
|
|
*/
|
|
if (!(flags & MSM_PERFCNTR_STREAM)) {
|
|
return gpu->perfcntr_groups[group_idx].num_counters -
|
|
perfcntrs->groups[group_idx]->allocated_counters;
|
|
}
|
|
|
|
/*
|
|
* For global counter collection, anything that is not reserved by
|
|
* one or more contexts is available:
|
|
*/
|
|
guard(mutex)(&gpu->dev->filelist_mutex);
|
|
|
|
unsigned reserved_counters = 0;
|
|
struct drm_file *file;
|
|
|
|
list_for_each_entry (file, &gpu->dev->filelist, lhead) {
|
|
struct msm_context *ctx = file->driver_priv;
|
|
|
|
if (!ctx || !ctx->perfctx)
|
|
continue;
|
|
|
|
unsigned n = ctx->perfctx->reserved_counters[group_idx];
|
|
reserved_counters = max(reserved_counters, n);
|
|
}
|
|
|
|
return gpu->perfcntr_groups[group_idx].num_counters - reserved_counters;
|
|
}
|
|
|
|
int
|
|
msm_ioctl_perfcntr_config(struct drm_device *dev, void *data, struct drm_file *file)
|
|
{
|
|
struct msm_drm_private *priv = dev->dev_private;
|
|
const struct drm_msm_perfcntr_config *args = data;
|
|
struct msm_context *ctx = file->driver_priv;
|
|
struct msm_gpu *gpu = priv->gpu;
|
|
int stream_fd = 0;
|
|
|
|
if (!gpu || !gpu->num_perfcntr_groups)
|
|
return -ENXIO;
|
|
|
|
struct msm_perfcntr_state *perfcntrs = gpu->perfcntrs;
|
|
|
|
/*
|
|
* Validate args that don't require locks/power first:
|
|
*/
|
|
|
|
if (args->flags & ~MSM_PERFCNTR_FLAGS)
|
|
return UERR(EINVAL, dev, "invalid flags");
|
|
|
|
if (args->nr_groups && !args->group_stride)
|
|
return UERR(EINVAL, dev, "invalid group_stride");
|
|
|
|
if (args->nr_groups > gpu->num_perfcntr_groups)
|
|
return UERR(EINVAL, dev, "too many groups");
|
|
|
|
if (args->nr_groups && !args->groups)
|
|
return UERR(EINVAL, dev, "no groups");
|
|
|
|
if (args->flags & MSM_PERFCNTR_STREAM) {
|
|
if (!perfmon_capable())
|
|
return UERR(EPERM, dev, "invalid permissions");
|
|
if (!args->nr_groups)
|
|
return UERR(EINVAL, dev, "invalid nr_groups");
|
|
if (!args->period)
|
|
return UERR(EINVAL, dev, "invalid sampling period");
|
|
if (args->bufsz_shift > const_ilog2(SZ_128M))
|
|
return UERR(EINVAL, dev, "buffer size too big (>128M)");
|
|
} else {
|
|
if (args->period)
|
|
return UERR(EINVAL, dev, "sampling period not allowed");
|
|
if (args->bufsz_shift)
|
|
return UERR(EINVAL, dev, "sample buf size not allowed");
|
|
}
|
|
|
|
/*
|
|
* To avoid iterating over the groups multiple times, allocate and setup
|
|
* both a ctx and global stream object. Only one of the two will be
|
|
* kept in the end.
|
|
*/
|
|
|
|
struct msm_perfcntr_context_state *perfctx __free(kfree) = kzalloc(
|
|
struct_size(perfctx, reserved_counters, gpu->num_perfcntr_groups),
|
|
GFP_KERNEL);
|
|
if (!perfctx)
|
|
return -ENOMEM;
|
|
|
|
struct msm_perfcntr_stream *stream __free(kfree) = kzalloc_obj(*stream);
|
|
if (!stream)
|
|
return -ENOMEM;
|
|
|
|
uint8_t *nr_counters __free(kfree) = kzalloc_objs(uint8_t, gpu->num_perfcntr_groups);
|
|
if (!nr_counters)
|
|
return -ENOMEM;
|
|
|
|
uint32_t *group_idx __free(kfree) = kzalloc_objs(uint32_t, args->nr_groups);
|
|
if (!group_idx)
|
|
return -ENOMEM;
|
|
|
|
stream->gpu = gpu;
|
|
stream->sample_period_ns = args->period;
|
|
stream->nr_groups = args->nr_groups;
|
|
stream->fifo_size = 1ull << args->bufsz_shift;
|
|
|
|
mutex_init(&stream->read_lock);
|
|
|
|
guard(mutex)(&gpu->perfcntr_lock);
|
|
|
|
if (args->flags & MSM_PERFCNTR_STREAM) {
|
|
if (perfcntrs->stream)
|
|
return UERR(EBUSY, dev, "perfcntr stream already open");
|
|
}
|
|
|
|
size_t bufsz = 16; /* header size includes seqno and 64b timestamp: */
|
|
int ret = 0;
|
|
|
|
for (unsigned i = 0; i < args->nr_groups; i++) {
|
|
struct drm_msm_perfcntr_group g = {0};
|
|
size_t sz = min_t(size_t, args->group_stride, sizeof(g));
|
|
void __user *userptr =
|
|
u64_to_user_ptr(args->groups + (i * args->group_stride));
|
|
|
|
if (copy_from_user(&g, userptr, sz))
|
|
return -EFAULT;
|
|
|
|
if (g.pad)
|
|
return UERR(EINVAL, dev, "groups[%d]: invalid pad", i);
|
|
|
|
int idx = get_group_idx(gpu, g.group_name, sizeof(g.group_name));
|
|
|
|
if (idx < 0)
|
|
return UERR(EINVAL, dev, "groups[%d]: unknown group", i);
|
|
|
|
if (nr_counters[idx])
|
|
return UERR(EINVAL, dev, "groups[%d]: duplicate group", i);
|
|
|
|
if (g.nr_countables > gpu->perfcntr_groups[idx].num_counters)
|
|
return UERR(EINVAL, dev, "groups[%d]: too many counters", i);
|
|
|
|
if (args->flags & MSM_PERFCNTR_STREAM) {
|
|
if (g.nr_countables && !g.countables)
|
|
return UERR(EINVAL, dev, "groups[%d]: no countables", i);
|
|
} else {
|
|
if (g.countables)
|
|
return UERR(EINVAL, dev, "groups[%d]: countables should be NULL", i);
|
|
}
|
|
|
|
int avail_counters = get_available_counters(gpu, idx, args->flags);
|
|
if (g.nr_countables > avail_counters) {
|
|
/*
|
|
* Defer error return until we process all groups, in
|
|
* case there are other E2BIG groups:
|
|
*/
|
|
ret = UERR(E2BIG, dev, "groups[%d]: too few counters available", i);
|
|
|
|
if (args->flags & MSM_PERFCNTR_UPDATE) {
|
|
/* Let userspace know how many counters are actually avail: */
|
|
g.nr_countables = avail_counters;
|
|
if (copy_to_user(userptr, &g, sz))
|
|
return -EFAULT;
|
|
}
|
|
}
|
|
|
|
group_idx[i] = idx;
|
|
perfctx->reserved_counters[idx] = g.nr_countables;
|
|
|
|
/* +1 to catch duplicate zero sized groups: */
|
|
nr_counters[idx] = g.nr_countables + 1;
|
|
|
|
if (args->flags & MSM_PERFCNTR_STREAM) {
|
|
size_t sz = sizeof(uint32_t) * g.nr_countables;
|
|
void __user *userptr = u64_to_user_ptr(g.countables);
|
|
|
|
if (copy_from_user(perfcntrs->groups[idx]->countables, userptr, sz))
|
|
return -EFAULT;
|
|
|
|
/* Samples are 64b per countable: */
|
|
bufsz += 2 * sz;
|
|
}
|
|
}
|
|
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (args->flags & MSM_PERFCNTR_STREAM) {
|
|
/*
|
|
* Validate requested buffer size is large enough for at least
|
|
* a single sample period.
|
|
*
|
|
* Note the circ_buf implementation needs to be 1 byte larger
|
|
* than max it can hold (see CIRC_SPACE()).
|
|
*/
|
|
if (stream->fifo_size <= bufsz)
|
|
return UERR(EINVAL, dev, "required buffer size: %zu", bufsz);
|
|
|
|
/* There aren't enough counters to hit this limit: */
|
|
WARN_ON(bufsz > SZ_128M);
|
|
|
|
stream->period_size = bufsz;
|
|
|
|
void *buf __free(kfree) = kmalloc(stream->fifo_size, GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
FD_PREPARE(fdf, O_CLOEXEC,
|
|
anon_inode_getfile("[msm_perfcntrs]", &stream_fops, stream, 0));
|
|
if (fdf.err)
|
|
return fdf.err;
|
|
|
|
INIT_WORK(&stream->sel_work, sel_worker);
|
|
kthread_init_work(&stream->sample_work, sample_worker);
|
|
init_waitqueue_head(&stream->poll_wq);
|
|
hrtimer_setup(&stream->sample_timer, sample_timer,
|
|
CLOCK_MONOTONIC, HRTIMER_MODE_REL);
|
|
|
|
stream->sel_fence = ++perfcntrs->sel_seqno;
|
|
stream->group_idx = no_free_ptr(group_idx);
|
|
stream->fifo.buf = no_free_ptr(buf);
|
|
|
|
/* commit the allocated counters, subtracting off original +1: */
|
|
for (unsigned i = 0; i < gpu->num_perfcntr_groups; i++)
|
|
perfcntrs->groups[i]->allocated_counters = nr_counters[i] - 1;
|
|
|
|
perfcntrs->stream = no_free_ptr(stream);
|
|
|
|
msm_perfcntr_resume_locked(perfcntrs->stream);
|
|
|
|
stream_fd = fd_publish(fdf);
|
|
} else {
|
|
kfree(ctx->perfctx);
|
|
ctx->perfctx = no_free_ptr(perfctx);
|
|
}
|
|
|
|
return stream_fd;
|
|
}
|
|
|
|
/**
|
|
* msm_perfcntr_group_idx - map idx of perfcntr group to group_idx
|
|
* @stream: The global perfcntr stream
|
|
* @n: The requested group_idx
|
|
*
|
|
* The PERFCNTR_CONFIG ioctl requested N counters/countables per perfcntr
|
|
* group, but the order of groups is not required to match the order they
|
|
* are defined in the perfcntr tables (which is not stable/UABI, only the
|
|
* group names are UABI).
|
|
*
|
|
* But the order samples are returned in the stream should match the
|
|
* order they are requested in the PERFCNTR_CONFIG ioctl. This helper
|
|
* handles the order remapping.
|
|
*
|
|
* Returns an index into gpu->perfcntr_groups[] and perfcntrs->groups[].
|
|
*/
|
|
uint32_t
|
|
msm_perfcntr_group_idx(const struct msm_perfcntr_stream *stream, uint32_t n)
|
|
{
|
|
WARN_ON_ONCE(n >= stream->nr_groups);
|
|
return stream->group_idx[n];
|
|
}
|
|
|
|
/**
|
|
* msm_perfcntr_counter_base - get idx of the first counter in group
|
|
* @stream: The global perfcntr stream
|
|
* @group_idx: the index of the counter group
|
|
*
|
|
* For global counter collection, counters are allocated from the end
|
|
* (last counter) while UMD allocates them from the start (0..N-1).
|
|
* Since UMD always allocated them from the start this also minimizes
|
|
* the chance of conflict when using old UMD which predates
|
|
* PERFCNTR_CONFIG ioctl.
|
|
*
|
|
* Returns the index of first counter to use. An index into
|
|
* msm_perfcntr_group::counters[].
|
|
*/
|
|
uint32_t
|
|
msm_perfcntr_counter_base(const struct msm_perfcntr_stream *stream, uint32_t group_idx)
|
|
{
|
|
struct msm_gpu *gpu = stream->gpu;
|
|
struct msm_perfcntr_state *perfcntrs = gpu->perfcntrs;
|
|
unsigned num_counters = gpu->perfcntr_groups[group_idx].num_counters;
|
|
unsigned allocated_counters = perfcntrs->groups[group_idx]->allocated_counters;
|
|
|
|
return num_counters - allocated_counters;
|
|
}
|
|
|
|
static void
|
|
__msm_perfcntr_cleanup(struct msm_gpu *gpu, struct msm_perfcntr_state *perfcntrs)
|
|
{
|
|
struct device *dev = &gpu->pdev->dev;
|
|
|
|
for (unsigned i = 0; i < gpu->num_perfcntr_groups; i++)
|
|
devm_kfree(dev, perfcntrs->groups[i]);
|
|
|
|
devm_kfree(dev, perfcntrs);
|
|
}
|
|
|
|
void
|
|
msm_perfcntr_cleanup(struct msm_gpu *gpu)
|
|
{
|
|
if (!gpu->perfcntrs)
|
|
return;
|
|
|
|
__msm_perfcntr_cleanup(gpu, gpu->perfcntrs);
|
|
gpu->perfcntrs = NULL;
|
|
}
|
|
|
|
struct msm_perfcntr_state *
|
|
msm_perfcntr_init(struct msm_gpu *gpu)
|
|
{
|
|
struct msm_perfcntr_state *perfcntrs;
|
|
struct device *dev = &gpu->pdev->dev;
|
|
size_t sz;
|
|
|
|
sz = struct_size(perfcntrs, groups, gpu->num_perfcntr_groups);
|
|
perfcntrs = devm_kzalloc(dev, sz, GFP_KERNEL);
|
|
if (!perfcntrs)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
for (unsigned i = 0; i < gpu->num_perfcntr_groups; i++) {
|
|
const struct msm_perfcntr_group *group =
|
|
&gpu->perfcntr_groups[i];
|
|
|
|
sz = struct_size(perfcntrs->groups[i], countables, group->num_counters);
|
|
perfcntrs->groups[i] = devm_kzalloc(dev, sz, GFP_KERNEL);
|
|
if (!perfcntrs->groups[i]) {
|
|
__msm_perfcntr_cleanup(gpu, perfcntrs);
|
|
return ERR_PTR(-ENOMEM);
|
|
}
|
|
}
|
|
|
|
return perfcntrs;
|
|
}
|