On Sun, May 17, 2026 at 3:54 AM Akhil P Oommen <[email protected]> wrote:
>
> On 5/14/2026 7:10 PM, Rob Clark wrote:
> > Add new UABI and implementation of PERFCNTR_CONFIG ioctl.
> >
> > A bit more work is required to configure the pwrup_reglist for the GMU
> > to restore SELect regs on exist of IFPC, before we can stop disabling
> > IFPC while global counter collection.  This will follow in a later
> > commit, but will be transparent to userspace.
> >
> > Signed-off-by: Rob Clark <[email protected]>
> > Reviewed-by: Anna Maniscalco <[email protected]>
> > ---
> >  drivers/gpu/drm/msm/msm_drv.c      |   1 +
> >  drivers/gpu/drm/msm/msm_drv.h      |   2 +
> >  drivers/gpu/drm/msm/msm_gpu.h      |   3 +
> >  drivers/gpu/drm/msm/msm_perfcntr.c | 516 +++++++++++++++++++++++++++++
> >  drivers/gpu/drm/msm/msm_perfcntr.h |  51 +++
> >  include/uapi/drm/msm_drm.h         |  48 +++
> >  6 files changed, 621 insertions(+)
> >

[snip]

> > diff --git a/drivers/gpu/drm/msm/msm_perfcntr.c 
> > b/drivers/gpu/drm/msm/msm_perfcntr.c
> > index 04407260a4e1..d6716ce0657a 100644
> > --- a/drivers/gpu/drm/msm/msm_perfcntr.c
> > +++ b/drivers/gpu/drm/msm/msm_perfcntr.c

[snip]

> > +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];
> > +
> > +     /*
> > +      * Note that smp_load_acquire() is not strictly required
> > +      * as CIRC_CNT_TO_END() does not access the head more than
> > +      * once.
> > +      */
> > +     count = min_t(size_t, count, fifo_count_to_end(stream));
>
> We need smp_load_acquire() to ensure all prior writes from the producer
> side are visible before the 'head' is read.

circular-buffer.rst documents the case that smp_load_acquired() is not
needed.  Maybe that is assuming TSO?  But I think even without TSO
address and data dependencies should be maintained, so this should
still be true on arm64?

We've been using the same in a couple other places in msm, fwiw.

> > +     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;
> > +}

[snip]

> > +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->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");
> > +     } 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");
> > +     }
> > +
> > +     if (args->nr_groups && !args->groups)
> > +             return UERR(EINVAL, dev, "no groups");
> > +
> > +     /*
> > +      * 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(sizeof(*stream), GFP_KERNEL);
>
> How about using GFP_KERNEL_ACCOUNT everywhere in the ioctl path to
> account the allocated memory to the process?

Maybe the circular buffer itself should be?  But the size is bounded,
and I don't see much other use in drm..

> > +     if (!stream)
> > +             return -ENOMEM;
> > +
> > +     uint32_t *group_idx __free(kfree) =
> > +             kcalloc(args->nr_groups, sizeof(uint32_t), GFP_KERNEL);
> > +     if (!group_idx)
> > +             return -ENOMEM;
> > +
> > +     stream->gpu = gpu;
> > +     stream->sample_period_ns = args->period;
> > +     stream->nr_groups = args->nr_groups;
> > +     stream->fifo_size = 1 << 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};
> > +             void __user *userptr =
> > +                     u64_to_user_ptr(args->groups + (i * 
> > args->group_stride));
> > +
> > +             if (copy_from_user(&g, userptr, args->group_stride))
>
> use copy_struct_from_user?

I think this would cause a new userspace on old kernel to fail,
instead of simply ignoring new fields added at the end of the struct.
Which for better or worse is different from how drm_ioctl() works.

>
> > +                     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 (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, 
> > args->group_stride))
> > +                                     return -EFAULT;
> > +                     }
> > +             }
> > +
> > +             group_idx[i] = idx;
> > +             perfctx->reserved_counters[idx] = g.nr_countables;
> > +
> > +             if (args->flags & MSM_PERFCNTR_STREAM) {
> > +                     perfcntrs->groups[idx]->allocated_counters = 
> > g.nr_countables;
> > +
> > +                     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);
> > +
> > +             if (stream->fifo_size > SZ_128M)
> > +                     return UERR(EINVAL, dev, "buffer size too big 
> > (>128M): %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(1 << args->bufsz_shift, GFP_KERNEL);
> > +             if (!buf)
> > +                     return -ENOMEM;
> > +
> > +             stream_fd = anon_inode_getfd("[msm_perfcntrs]", &stream_fops, 
> > stream, 0);
>
> This call makes the fd visible to the userspace immediately. So better
> to move it after all initializations. Ie, after
> msm_perfcntr_resume_locked().

Hmm, but this doesn't play too nicely with cleanup.h stuff, because
the no_free_ptr() stuff must come after the last point of failure.

But I think instead we can use get_unused_fd_flags() + fd_install() to
defer exposing the fd.. I'll try this.

BR,
-R

>
> > +             if (stream_fd < 0)
> > +                     return stream_fd;
> > +
> > +             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);
> > +             perfcntrs->stream = no_free_ptr(stream);
> > +
> > +             msm_perfcntr_resume_locked(perfcntrs->stream);
> > +     } 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

Reply via email to