From: Liviu Dudau <liviu.dudau@arm.com>
To: Lukas Zapolskas <lukas.zapolskas@arm.com>
Cc: "Daniel Almeida" <daniel.almeida@collabora.com>,
"Alice Ryhl" <aliceryhl@google.com>,
"Boris Brezillon" <boris.brezillon@collabora.com>,
"Steven Price" <steven.price@arm.com>,
"David Airlie" <airlied@gmail.com>,
"Simona Vetter" <simona@ffwll.ch>,
"Maarten Lankhorst" <maarten.lankhorst@linux.intel.com>,
"Maxime Ripard" <mripard@kernel.org>,
"Thomas Zimmermann" <tzimmermann@suse.de>,
dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org,
nd@arm.com, "Adrián Larumbe" <adrian.larumbe@collabora.com>
Subject: Re: [PATCH v7 6/8] drm/panthor: Implement sample handling
Date: Fri, 23 Oct 2026 15:30:18 +0100 [thread overview]
Message-ID: <attveq1mEszeTSsH@e142607> (raw)
In-Reply-To: <20261011-panthor-performance-counters-v7-6-12c32495062f@arm.com>
On Sun, Oct 11, 2026 at 06:36:53PM +0100, Lukas Zapolskas wrote:
> From: Adrián Larumbe <adrian.larumbe@collabora.com>
>
> panthor_perf aggregates counter and set requests coming from userspace
> and mediates interactions with the FW interface, to ensure that user
> sessions cannot override the global configuration.
>
> From the top-level interface, the two types of samples are supported:
> clearing samples and regular samples. Clearing samples are
> a special sample type that allow for the creation of a sampling
> baseline, to ensure that a session does not obtain counter data from
> before its creation.
>
> Upon receipt of a relevant interrupt, corresponding to one of the
> three relevant bits of the GLB_ACK register, panthor_perf takes any
> samples that occurred, and, based on the insert and extract indices,
> accumulates them to an internal storage buffer after zero-extending
> the counters from the 32-bit counters emitted by the hardware to
> 64-bit counters for internal accumulation.
>
> When the performance counters are enabled, the FW ensures no counter
> data is lost when entering and leaving non-counting regions by
> producing automatic samples that do not correspond to a
> GLB_REQ.PRFCNT_SAMPLE request. Such regions may be per hardware unit,
> such as when a shader core powers down, or global. Most of these
> events do not directly correspond to session sample requests, so any
> intermediary counter data must be stored into a temporary
> accumulation buffer.
>
> If there are sessions waiting for a sample, this accumulated buffer
> will be taken, and emitted for each waiting client. During this phase,
> information like the timestamps of sample request and sample emission,
> type of the counter block and block index annotations are added to the
> sample header and block headers. If no sessions are waiting for
> a sample, this accumulation buffer is kept until the next time a
> sample is requested.
>
> Special handling is needed for the PRFCNT_OVERFLOW interrupt, which is
> an indication that the internal sample handling rate was insufficient.
>
> A semi-static table maintains the mappings between the offsets from
> the base of a FW sample to the corresponding block type. Since the
> counter blocks themselves do not contain this information, this needs to
> be done in the kernel.
>
> This table also handles the sparseness of the shader cores,
> wherein the physical core mask contains holes, but the memory allocated
> for it is done based on the position of the most significant bit. In
> cases with highly sparse core masks, this means that a lot of shader
> counter blocks are empty, and must be skipped.
>
> The number of ring buffer slots is configurable through module param
> to allow for a lower memory footprint on memory constrained systems.
>
> Signed-off-by: Adrián Larumbe <adrian.larumbe@collabora.com>
> Co-developed-by: Lukas Zapolskas <lukas.zapolskas@arm.com>
> Signed-off-by: Lukas Zapolskas <lukas.zapolskas@arm.com>
> ---
> drivers/gpu/drm/panthor/panthor_fw.c | 5 +
> drivers/gpu/drm/panthor/panthor_perf.c | 1075 +++++++++++++++++++++++++++++++-
> drivers/gpu/drm/panthor/panthor_perf.h | 2 +
> 3 files changed, 1069 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/gpu/drm/panthor/panthor_fw.c b/drivers/gpu/drm/panthor/panthor_fw.c
> index 4f1fab66a13b..7d4b253bfe0c 100644
> --- a/drivers/gpu/drm/panthor/panthor_fw.c
> +++ b/drivers/gpu/drm/panthor/panthor_fw.c
> @@ -25,6 +25,7 @@
> #include "panthor_gpu.h"
> #include "panthor_hw.h"
> #include "panthor_mmu.h"
> +#include "panthor_perf.h"
> #include "panthor_sched.h"
> #include "panthor_trace.h"
>
> @@ -1072,9 +1073,12 @@ static void panthor_fw_init_global_iface(struct panthor_device *ptdev)
>
> /* Enable interrupts we care about. */
> glb_iface->input->ack_irq_mask = GLB_CFG_ALLOC_EN |
> + GLB_PERFCNT_SAMPLE |
> GLB_PING |
> GLB_CFG_PROGRESS_TIMER |
> GLB_CFG_POWEROFF_TIMER |
> + GLB_PERFCNT_THRESHOLD |
> + GLB_PERFCNT_OVERFLOW |
> GLB_IDLE_EN |
> GLB_IDLE;
>
> @@ -1122,6 +1126,7 @@ static void panthor_job_irq_handler(struct panthor_irq *pirq, u32 status)
> duration = U32_MAX;
> trace_gpu_job_irq(ptdev->base.dev, status, duration);
> }
> + panthor_perf_report_irq(ptdev, status);
> }
>
> static irqreturn_t panthor_job_irq_threaded_handler(int irq, void *data)
> diff --git a/drivers/gpu/drm/panthor/panthor_perf.c b/drivers/gpu/drm/panthor/panthor_perf.c
> index 76c346981150..c66ad658a95d 100644
> --- a/drivers/gpu/drm/panthor/panthor_perf.c
> +++ b/drivers/gpu/drm/panthor/panthor_perf.c
> @@ -11,8 +11,11 @@
>
> #include "panthor_device.h"
> #include "panthor_fw.h"
> +#include "panthor_fw_regs.h"
> +#include "panthor_gem.h"
> #include "panthor_gpu.h"
> #include "panthor_gpu_regs.h"
> +#include "panthor_mmu.h"
> #include "panthor_perf.h"
>
> /**
> @@ -36,6 +39,76 @@
> */
> #define PANTHOR_PERF_EM_BITS (BITS_PER_TYPE(u64) * 2)
>
> +/**
> + * PANTHOR_CTR_TIMESTAMP_LO - The first architecturally mandated counter of every block type
> + * contains the low 32-bits of the TIMESTAMP value.
> + */
> +#define PANTHOR_CTR_TIMESTAMP_LO (0)
> +
> +/**
> + * PANTHOR_CTR_TIMESTAMP_HI - The register offset containinig the high 32-bits of the TIMESTAMP
> + * value.
> + */
> +#define PANTHOR_CTR_TIMESTAMP_HI (1)
> +
> +/**
> + * PANTHOR_CTR_PRFCNT_EN - The register offset containing the enable mask for the enabled counters
> + * that were written to memory.
> + */
> +#define PANTHOR_CTR_PRFCNT_EN (2)
> +
> +/**
> + * PANTHOR_HEADER_COUNTERS - The first four counters of every block type are architecturally
> + * defined to be equivalent. The fourth counter is always reserved,
> + * and should be zero and as such, does not have a separate define.
> + *
> + * These are the only four counters that are the same between different
> + * blocks and are consistent between different architectures.
> + */
> +#define PANTHOR_HEADER_COUNTERS (4)
> +
> +/**
> + * PANTHOR_CTR_SAMPLE_REASON - The metadata block has a single value in position three which
> + * indicates the reason a sample was taken.
> + */
> +#define PANTHOR_CTR_SAMPLE_REASON (3)
> +
> +/**
> + * PANTHOR_PERF_RINGBUF_SLOTS_MIN - The minimum permitted number of slots in the Panthor perf
> + * ring buffer.
> + */
> +#define PANTHOR_PERF_RINGBUF_SLOTS_MIN (16)
> +
> +/**
> + * PANTHOR_PERF_RINGBUF_SLOTS_MAX - The maximum permitted number of slots in the Panthor perf
> + * ring buffer.
> + */
> +#define PANTHOR_PERF_RINGBUF_SLOTS_MAX (256)
> +
> +static unsigned int perf_ringbuf_slots = 32;
> +
> +static int perf_ringbuf_slots_set(const char *val, const struct kernel_param *kp)
> +{
> + unsigned int slots;
> + int ret = kstrtouint(val, 0, &slots);
> +
> + if (ret)
> + return ret;
> +
> + if (!is_power_of_2(slots))
> + return -EINVAL;
> +
> + return param_set_uint_minmax(val, kp, 16, 128);
> +}
> +
> +static const struct kernel_param_ops perf_ringbuf_ops = {
> + .set = perf_ringbuf_slots_set,
> + .get = param_get_uint,
> +};
> +module_param_cb(perf_ringbuf_slots, &perf_ringbuf_ops, &perf_ringbuf_slots, 0400);
> +MODULE_PARM_DESC(perf_ringbuf_slots,
> + "Power of two slots allocated for the Panthor perf kernel-FW ringbuffer");
> +
> enum panthor_perf_session_state {
> /** @PANTHOR_PERF_SESSION_STOPPED: The session cannot sample. It starts out stopped. */
> PANTHOR_PERF_SESSION_STOPPED = 0,
> @@ -68,12 +141,33 @@ struct panthor_perf_counter_block {
> u64 counters[];
> };
>
> +struct panthor_perf_fw_block {
> + /** @block_count: Number of blocks of the given @type in the FW ringbuffer. */
> + size_t block_count;
> +
> + /** @phys_mask: Bitmask of the physically available blocks. */
> + u64 phys_mask;
> +};
> +
> struct panthor_perf_bo {
> struct drm_gem_object *obj;
> void *vaddr;
> };
>
> +struct panthor_perf_sample_block_iter {
> + const u8 *src;
> + u8 *dst;
> + size_t offset;
> + size_t data_size;
> + size_t block_size;
> + const struct panthor_perf_counter_block *src_blk;
> + struct panthor_perf_counter_block *dst_blk;
> +};
> +
> struct panthor_perf_session {
> + /** @state_wq: Wait queue for state transitions driven by the sampling path. */
> + wait_queue_head_t state_wq;
> +
> /** @state_lock: Used to protect session state information and overflow bit. */
> struct mutex state_lock;
>
> @@ -92,6 +186,12 @@ struct panthor_perf_session {
> */
> u64 accum_idx;
>
> + /** @pending_sample: Whether this session has a sample requested on it. */
> + bool pending_sample;
> +
> + /** @sample_flags: Accumulated @drm_panthor_perf_sample_flags for the pending sample. */
> + u32 sample_flags;
> +
> /** @sample_start_ns: Sample request time, obtained from a monotonic raw clock. */
> u64 sample_start_ns;
>
> @@ -148,11 +248,88 @@ struct panthor_perf {
> /** @info: Pointer to the previously initialized perf info. */
> struct drm_panthor_perf_info *info;
>
> + /** @rb: Kernel BO in the FW AS containing the sample ringbuffer. */
> + struct panthor_kernel_bo *rb;
> +
> + /** @sample_slots: Number of sample slots in the FW ringbuffer. */
> + size_t sample_slots;
> +
> + /** @em: Combined enable mask for all of the active sessions. */
> + struct panthor_perf_enable_masks *em;
> +
> /**
> - * @op_lock: Serializes session operations. Acquire before looking up session
> - * handles and before a session's state_lock.
> + * @fw_block_size: The size of a single block in the FW ring buffer, equal to
> + * sizeof(u32) * counters_per_block.
> + */
> + size_t fw_block_size;
> +
> + /**
> + * @fw_sample_size: The size of a single FW sample in the FW ringbuffer.
> + */
> + size_t fw_sample_size;
> +
> + /**
> + * @blocks: FW block layout for one sample. Metadata blocks are consumed
> + * internally and omitted from userspace samples.
> + */
> + struct panthor_perf_fw_block blocks[DRM_PANTHOR_PERF_BLOCK_MAX];
> +
> + /**
> + * @sample: Pointer to an upscaled and annotated sample that may be emitted to userspace.
> + * This is used both as an intermediate buffer to do the zero-extension of the
> + * 32-bit counters to 64-bits and as a storage buffer in case of automatic samples.
> + */
> + u8 *sample;
> +
> + /**
> + * @op_lock: Serializes session operations and global FW configuration. Acquire
> + * before looking up session handles and before @session_lock or a
> + * session's state_lock. The sample worker must not acquire this lock,
> + * so synchronous session operations can wait for sample delivery.
> + * Runtime-PM references must be acquired and released outside this
> + * lock, as the PM callbacks also acquire it.
> */
> struct mutex op_lock;
> +
> + /**
> + * @session_lock: Lock used to guard the @session_list.
> + */
> + struct mutex session_lock;
> +
> + /** @session_list: List of all sessions. */
> + struct list_head session_list;
> +
> + /** @pend_lock: Lock used to guard the requested sample field. */
> + spinlock_t pend_lock;
> +
> +
> + /** @fw_sample_requested: A sample has been requested. */
> + bool fw_sample_requested;
> +
> + /** @set_config: The set that will be configured onto the hardware. */
> + u8 set_config;
> +
> + /** @last_fw_sample_time: The CPU-side timestamp of the latest consumed FW sample. */
> + u64 last_fw_sample_time;
> +
> + /** @sample_workq: Workqueue to handle and accumulate samples. */
> + struct workqueue_struct *sample_workq;
> +
> + /** @sample_work: Worker to consume available FW samples and expose them to the user. */
> + struct work_struct sample_work;
> +
> + /**
> + * @ptdev: Backpointer to the Panthor device, needed to ring the global doorbell and
> + * interface with FW.
> + */
> + struct panthor_device *ptdev;
> +};
> +
> +struct panthor_perf_fw_block_iter {
> + const struct panthor_perf *perf_ctx;
> + enum drm_panthor_perf_block_type type;
> + u32 idx;
> + size_t offset;
> };
>
> struct panthor_perf_pool {
> @@ -171,6 +348,33 @@ static size_t get_annotated_block_size(size_t counters_per_block)
> return struct_size_t(struct panthor_perf_counter_block, counters, counters_per_block);
> }
>
> +static void
> +sample_block_iter_init(const struct drm_panthor_perf_info *info,
> + const u8 *src, u8 *dst,
> + struct panthor_perf_sample_block_iter *iter)
> +{
> + const size_t sample_header_size = info->sample_header_size;
> +
> + *iter = (struct panthor_perf_sample_block_iter) {
> + .src = src + sample_header_size,
> + .dst = dst + sample_header_size,
> + .data_size = info->sample_size - sample_header_size,
> + .block_size = get_annotated_block_size(info->counters_per_block),
> + };
> +}
> +
> +static bool sample_block_iter_next(struct panthor_perf_sample_block_iter *iter)
> +{
> + if (iter->offset >= iter->data_size)
> + return false;
> +
> + iter->src_blk = (const struct panthor_perf_counter_block *)(iter->src + iter->offset);
> + iter->dst_blk = (struct panthor_perf_counter_block *)(iter->dst + iter->offset);
> + iter->offset += iter->block_size;
> +
> + return true;
> +}
> +
> static size_t session_get_sample_size(const struct drm_panthor_perf_info *const info)
> {
> const size_t block_size = get_annotated_block_size(info->counters_per_block);
> @@ -239,6 +443,30 @@ session_get(struct panthor_perf_session *session)
> return session;
> }
>
> +static void panthor_perf_em_add(struct panthor_perf_enable_masks *const dst_em,
> + const struct panthor_perf_enable_masks *const src_em)
> +{
> + size_t i = 0;
> +
> + for (i = DRM_PANTHOR_PERF_BLOCK_FIRST; i <= DRM_PANTHOR_PERF_BLOCK_LAST; i++)
> + bitmap_or(dst_em->mask[i], dst_em->mask[i], src_em->mask[i], PANTHOR_PERF_EM_BITS);
> +}
> +
> +static u64 session_read_extract_idx(struct panthor_perf_session *session)
> +{
> + /* Userspace will update their own extract index to indicate that a sample is consumed
> + * from the ringbuffer, and we must ensure we read the latest value.
> + */
> + return smp_load_acquire(&session->control->extract_idx);
> +}
> +
> +static void session_write_insert_idx(struct panthor_perf_session *session, u64 idx)
> +{
> + /* Userspace needs the insert index to know where to look for the sample. */
> + smp_store_release(&session->control->insert_idx, idx);
> + session->insert_idx = idx;
> +}
> +
> static int
> panthor_perf_bo_get(struct drm_file *file, u32 handle, size_t expected_size,
> struct panthor_perf_bo *bo)
> @@ -338,6 +566,277 @@ session_from_handle(struct panthor_perf_pool *pool, unsigned long handle)
> return session;
> }
>
> +static u32 compress_enable_mask(u32 counters, const unsigned long *const src)
> +{
> + size_t i;
> + u32 result = 0;
> + const unsigned long ctr_per_bit = counters / BITS_PER_TYPE(u32);
> +
> + for_each_set_bit(i, src, counters) {
> + const unsigned long pos = div_u64(i, ctr_per_bit);
> +
> + result |= BIT(pos);
> + }
> +
> + return result;
> +}
> +
> +static void expand_enable_mask(u32 counters, u32 em, unsigned long *const dst)
> +{
> + size_t i;
> + const unsigned long ctrs_per_bit = counters / BITS_PER_TYPE(u32);
> + DECLARE_BITMAP(emb, BITS_PER_TYPE(u32));
> +
> + bitmap_from_arr32(emb, &em, BITS_PER_TYPE(u32));
> +
> + for_each_set_bit(i, emb, BITS_PER_TYPE(u32))
> + bitmap_set(dst, i * ctrs_per_bit, ctrs_per_bit);
> +}
> +
> +static inline bool
> +panthor_perf_fw_block_iter_valid(struct panthor_perf_fw_block_iter *iter)
> +{
> + const struct panthor_perf *perf = iter->perf_ctx;
> +
> + while (iter->type < DRM_PANTHOR_PERF_BLOCK_MAX) {
> + if (iter->idx < perf->blocks[iter->type].block_count)
> + return true;
> +
> + iter->type++;
> + iter->idx = 0;
> + }
> +
> + return false;
> +}
> +
> +static void panthor_perf_handle_fw_samples(struct panthor_device *ptdev,
> + u32 extract_idx, u32 insert_idx)
> +{
> + struct panthor_perf *perf = ptdev->perf;
> + const size_t ann_block_size =
> + get_annotated_block_size(ptdev->perf_info.counters_per_block);
> + u32 i;
> +
> + for (i = extract_idx; i != insert_idx; i++) {
> + u32 slot = i % perf->sample_slots;
> + u8 *fw_sample = (u8 *)perf->rb->kmap + slot * perf->fw_sample_size;
> + struct panthor_perf_fw_block_iter fw_iter = { .perf_ctx = perf };
> + size_t ann_off = ptdev->perf_info.sample_header_size;
> +
> + for (; panthor_perf_fw_block_iter_valid(&fw_iter);
> + fw_iter.offset += fw_iter.perf_ctx->fw_block_size, fw_iter.idx++) {
> + struct panthor_perf_counter_block *blk =
> + (typeof(blk))(perf->sample + ann_off);
> + u32 *const block = (u32 *)(fw_sample + fw_iter.offset);
> + const u32 prfcnt_en = block[PANTHOR_CTR_PRFCNT_EN];
> +
> + if (fw_iter.type == DRM_PANTHOR_PERF_BLOCK_METADATA) {
> + /*
> + * The host must clear the SAMPLE_REASON to acknowledge it has
> + * consumed the sample.
> + */
> + block[PANTHOR_CTR_SAMPLE_REASON] = 0;
> + continue;
> + }
> +
> + if (!(perf->blocks[fw_iter.type].phys_mask & BIT_ULL(fw_iter.idx)))
> + continue;
> +
> + blk->header = (struct drm_panthor_perf_block_header) {
> + .block_idx = fw_iter.idx,
> + .block_type = fw_iter.type,
> + };
> +
> + if (prfcnt_en == 0)
> + goto advance_block;
> +
> + /*
> + * The four header counters must be treated differently, because they are
> + * not additive. For the fourth, the assignment does not matter, as it
> + * is reserved and should be zero.
> + */
> + blk->counters[PANTHOR_CTR_TIMESTAMP_LO] = block[PANTHOR_CTR_TIMESTAMP_LO];
> + blk->counters[PANTHOR_CTR_TIMESTAMP_HI] = block[PANTHOR_CTR_TIMESTAMP_HI];
> + blk->counters[PANTHOR_CTR_PRFCNT_EN] = block[PANTHOR_CTR_PRFCNT_EN];
> +
> + /*
> + * The host must clear PRFCNT_EN to acknowledge it has consumed the sample.
> + */
> + block[PANTHOR_CTR_PRFCNT_EN] = 0;
> +
> + if (prfcnt_en == 1)
> + goto advance_block;
> +
> + for (size_t k = PANTHOR_HEADER_COUNTERS;
> + k < ptdev->perf_info.counters_per_block; k++)
> + blk->counters[k] += block[k];
> +
> +advance_block:
> + ann_off += ann_block_size;
> + }
> +
> + perf->last_fw_sample_time = ktime_get_raw_ns();
> + }
> +}
> +
> +static size_t panthor_perf_get_fw_reported_size(struct panthor_device *ptdev)
> +{
> + struct panthor_fw_global_iface *glb_iface = panthor_fw_get_glb_iface(ptdev);
> +
> + size_t fw_size = GLB_PERFCNT_FW_SIZE(glb_iface->control->perfcnt_size);
> + size_t hw_size = GLB_PERFCNT_HW_SIZE(glb_iface->control->perfcnt_size);
> + size_t md_size = PERFCNT_FEATURES_MD_SIZE(glb_iface->control->perfcnt_features);
> +
> + return md_size + fw_size + hw_size;
> +}
> +
> +static size_t get_reserved_sc_blocks(struct panthor_device *ptdev)
> +{
> + const u64 sc_mask = ptdev->gpu_info.shader_present;
> +
> + if (GPU_ARCH_MAJOR(ptdev->gpu_info.gpu_id) < 14)
> + return fls64(sc_mask);
> +
> + return ptdev->perf_info.shader_blocks;
> +}
> +
> +#define BLK_MASK(x) GENMASK_ULL((x) - 1, 0)
> +
> +static u64 get_sc_mask(struct panthor_device *ptdev)
> +{
> + const u64 sc_mask = ptdev->gpu_info.shader_present;
> +
> + if (GPU_ARCH_MAJOR(ptdev->gpu_info.gpu_id) < 14)
> + return sc_mask;
> +
> + return BLK_MASK(hweight64(sc_mask));
> +}
> +
> +static int panthor_perf_setup_fw_buffer(struct panthor_device *ptdev, struct panthor_perf *perf)
> +{
> + struct drm_panthor_perf_info *info = &ptdev->perf_info;
> + const size_t block_size = info->counters_per_block * PANTHOR_HW_COUNTER_SIZE;
> + struct panthor_fw_global_iface *glb_iface = panthor_fw_get_glb_iface(ptdev);
> + const size_t reported_sample_size = panthor_perf_get_fw_reported_size(ptdev);
> + const bool has_metadata = PERFCNT_FEATURES_MD_SIZE(glb_iface->control->perfcnt_features);
> + const struct panthor_perf_fw_block blocks[DRM_PANTHOR_PERF_BLOCK_MAX] = {
> + [DRM_PANTHOR_PERF_BLOCK_METADATA] = {
> + .block_count = has_metadata ? 1 : 0,
> + .phys_mask = has_metadata ? BIT_ULL(0) : 0,
> + },
> + [DRM_PANTHOR_PERF_BLOCK_FW] = {
> + .block_count = info->fw_blocks,
> + .phys_mask = info->fw_blocks ? BLK_MASK(info->fw_blocks) : 0,
> + },
> + [DRM_PANTHOR_PERF_BLOCK_CSHW] = {
> + .block_count = info->cshw_blocks,
> + .phys_mask = BLK_MASK(info->cshw_blocks),
> + },
> + [DRM_PANTHOR_PERF_BLOCK_TILER] = {
> + .block_count = info->tiler_blocks,
> + .phys_mask = BLK_MASK(info->tiler_blocks),
> + },
> + [DRM_PANTHOR_PERF_BLOCK_MEMSYS] = {
> + .block_count = info->memsys_blocks,
> + .phys_mask = BLK_MASK(info->memsys_blocks),
> + },
> + [DRM_PANTHOR_PERF_BLOCK_SHADER] = {
> + .block_count = get_reserved_sc_blocks(ptdev),
> + .phys_mask = get_sc_mask(ptdev),
> + },
> + };
> + size_t offset = 0;
> +
> + for (enum drm_panthor_perf_block_type type = 0; type < DRM_PANTHOR_PERF_BLOCK_MAX; type++)
> + offset += blocks[type].block_count * block_size;
> +
> + if (offset != reported_sample_size) {
> + drm_warn(&ptdev->base,
> + "Could not align fw and kernel perf buffers, feature disabled.");
> + return -EINVAL;
> + }
> +
> + memcpy(perf->blocks, blocks, sizeof(blocks));
> + perf->fw_block_size = block_size;
> + perf->fw_sample_size = offset;
> +
> + return 0;
> +}
> +
> +static int panthor_perf_fw_stop_sampling(struct panthor_device *ptdev)
> +{
> + struct panthor_fw_global_iface *glb_iface = panthor_fw_get_glb_iface(ptdev);
> + u32 acked;
> + int ret;
> +
> + if (~READ_ONCE(glb_iface->input->req) & GLB_PERFCNT_ENABLE)
> + return 0;
> +
> + panthor_fw_update_reqs(glb_iface, req, 0, GLB_PERFCNT_ENABLE);
> + panthor_fw_ring_doorbell(ptdev, CSF_GLB_DOORBELL_ID);
> + ret = panthor_fw_glb_wait_acks(ptdev, GLB_PERFCNT_ENABLE, &acked, 100);
> + if (ret)
> + drm_warn(&ptdev->base, "Could not disable performance counters");
> +
> + return ret;
> +}
> +
> +static int panthor_perf_fw_start_sampling(struct panthor_device *ptdev)
> +{
> + struct panthor_fw_global_iface *glb_iface = panthor_fw_get_glb_iface(ptdev);
> + u32 acked;
> + int ret;
> +
> + if (READ_ONCE(glb_iface->input->req) & GLB_PERFCNT_ENABLE)
> + return 0;
> +
> + /*
> + * The spec mandates that the host zero the PRFCNT_EXTRACT register before an enable
> + * operation, and each (re-)enable will require an enable-disable pair to program
> + * the new changes onto the FW interface.
> + */
> + WRITE_ONCE(glb_iface->input->perfcnt_extract, 0);
> +
> + panthor_fw_update_reqs(glb_iface, req, GLB_PERFCNT_ENABLE, GLB_PERFCNT_ENABLE);
> + panthor_fw_ring_doorbell(ptdev, CSF_GLB_DOORBELL_ID);
> + ret = panthor_fw_glb_wait_acks(ptdev, GLB_PERFCNT_ENABLE, &acked, 100);
> + if (ret)
> + drm_warn(&ptdev->base, "Could not enable performance counters");
> +
> + return ret;
> +}
> +
> +static void panthor_perf_fw_write_config(struct panthor_perf *perf)
> +{
> + struct panthor_fw_global_iface *glb_iface = panthor_fw_get_glb_iface(perf->ptdev);
> + const u32 counters = perf->info->counters_per_block;
> + const struct panthor_perf_enable_masks *const em = perf->em;
> + u32 perfcnt_config;
> +
> + glb_iface->input->perfcnt_csf_enable =
> + compress_enable_mask(counters, em->mask[DRM_PANTHOR_PERF_BLOCK_CSHW]);
> + glb_iface->input->perfcnt_shader_enable =
> + compress_enable_mask(counters, em->mask[DRM_PANTHOR_PERF_BLOCK_SHADER]);
> + glb_iface->input->perfcnt_mmu_l2_enable =
> + compress_enable_mask(counters, em->mask[DRM_PANTHOR_PERF_BLOCK_MEMSYS]);
> + glb_iface->input->perfcnt_tiler_enable =
> + compress_enable_mask(counters, em->mask[DRM_PANTHOR_PERF_BLOCK_TILER]);
> + glb_iface->input->perfcnt_fw_enable =
> + compress_enable_mask(counters, em->mask[DRM_PANTHOR_PERF_BLOCK_FW]);
> + glb_iface->input->perfcnt_csg_enable = 0;
> +
> + WRITE_ONCE(glb_iface->input->perfcnt_as, panthor_vm_as(panthor_fw_vm(perf->ptdev)));
> + WRITE_ONCE(glb_iface->input->perfcnt_base, panthor_kernel_bo_gpuva(perf->rb));
> +
> + perfcnt_config = GLB_PERFCNT_CONFIG_SIZE(perf_ringbuf_slots);
> + perfcnt_config |= GLB_PERFCNT_CONFIG_SET(perf->set_config);
> +
> + if (PERFCNT_FEATURES_MD_SIZE(glb_iface->control->perfcnt_features))
> + perfcnt_config |= GLB_PERFCNT_METADATA_ENABLE;
> +
> + WRITE_ONCE(glb_iface->input->perfcnt_config, perfcnt_config);
> +}
> +
> #define DEFINE_SESSION_STATE_CHECK(name, _state) \
> static bool session_is_## name(struct panthor_perf_session *session) \
> { \
> @@ -349,6 +848,7 @@ session_from_handle(struct panthor_perf_pool *pool, unsigned long handle)
> DEFINE_SESSION_STATE_CHECK(active, PANTHOR_PERF_SESSION_ACTIVE);
> DEFINE_SESSION_STATE_CHECK(starting, PANTHOR_PERF_SESSION_STARTING);
> DEFINE_SESSION_STATE_CHECK(stopping, PANTHOR_PERF_SESSION_STOPPING);
> +DEFINE_SESSION_STATE_CHECK(stopped, PANTHOR_PERF_SESSION_STOPPED);
>
> static bool session_has_overflow(struct panthor_perf_session *session)
> {
> @@ -365,6 +865,388 @@ static void session_set_state(struct panthor_perf_session *session,
> session->state = state;
> }
>
> +static bool session_has_space(struct panthor_perf_session *session, u32 needed)
> +{
> + const u64 insert = session->insert_idx;
> + const u64 extract = session_read_extract_idx(session);
> +
> + return insert - extract + needed <= session->ringbuf_slots;
> +}
> +
> +/**
> + * session_accumulate_sample - Accumulate requested counters into an existing sample.
> + * @ptdev: Panthor device.
> + * @session: Perf session.
> + * @session_sample: Start of the destination sample, including its header.
> + * @perf_sample: Start of the intermediate sample, including its header.
> + *
> + * Firmware enable masks select groups of counters. Copy timestamps and accumulate
> + * only the counters requested by this session.
> + */
> +static void session_accumulate_sample(struct panthor_device *ptdev,
> + struct panthor_perf_session *session,
> + u8 *session_sample, u8 *perf_sample)
> +{
> + const struct drm_panthor_perf_info *const perf_info = &ptdev->perf_info;
> + struct panthor_perf_enable_masks *em = session->enabled_counters;
> + struct panthor_perf_sample_block_iter iter;
> + const u32 counters = perf_info->counters_per_block;
> +
> + sample_block_iter_init(perf_info, perf_sample, session_sample, &iter);
> +
> + while (sample_block_iter_next(&iter)) {
> + DECLARE_BITMAP(enabled_ctrs, PANTHOR_PERF_EM_BITS);
> + const struct panthor_perf_counter_block *src_blk = iter.src_blk;
> + struct panthor_perf_counter_block *dst_blk = iter.dst_blk;
> +
> + bitmap_from_arr64(enabled_ctrs, em->mask[dst_blk->header.block_type],
> + PANTHOR_PERF_EM_BITS);
> + bitmap_clear(enabled_ctrs, 0, PANTHOR_HEADER_COUNTERS);
> +
> + dst_blk->counters[PANTHOR_CTR_TIMESTAMP_HI] =
> + src_blk->counters[PANTHOR_CTR_TIMESTAMP_HI];
> + dst_blk->counters[PANTHOR_CTR_TIMESTAMP_LO] =
> + src_blk->counters[PANTHOR_CTR_TIMESTAMP_LO];
> +
> + for (size_t ctr_idx = PANTHOR_HEADER_COUNTERS; ctr_idx < counters; ctr_idx++)
> + if (test_bit(ctr_idx, enabled_ctrs))
> + dst_blk->counters[ctr_idx] += src_blk->counters[ctr_idx];
> + }
> +}
> +
> +static void perf_fw_request_sample(struct panthor_perf *perf)
> +{
> + struct panthor_fw_global_iface *glb_iface = panthor_fw_get_glb_iface(perf->ptdev);
> + bool sample;
> +
> + scoped_guard(spinlock_irqsave, &perf->pend_lock) {
> + sample = !perf->fw_sample_requested;
> +
> + if (sample)
> + perf->fw_sample_requested = true;
> + }
> +
> + if (sample) {
> + panthor_fw_toggle_reqs(glb_iface, req, ack, GLB_PERFCNT_SAMPLE);
> + panthor_fw_ring_doorbell(perf->ptdev, CSF_GLB_DOORBELL_ID);
> + }
> +}
> +
> +/**
> + * session_populate_sample - Populate a new userspace sample slot.
> + * @ptdev: Panthor device.
> + * @session: Perf session.
> + * @session_sample: Start of the destination sample, including its header.
> + * @perf_sample: Start of the intermediate sample, including its header.
> + *
> + * Copy the counter blocks and clear counters not requested by this session.
> + * Update each block's PRFCNT_EN value to reflect this session's enable mask.
> + * The sample header is filled when the sample is published.
> + */
> +static void session_populate_sample(struct panthor_device *ptdev,
> + struct panthor_perf_session *session, u8 *session_sample,
> + u8 *perf_sample)
> +{
> + const struct drm_panthor_perf_info *const perf_info = &ptdev->perf_info;
> + struct panthor_perf_sample_block_iter iter;
> + const size_t sample_header_size = perf_info->sample_header_size;
> + const size_t data_size = perf_info->sample_size - sample_header_size;
> + const u32 counters = perf_info->counters_per_block;
> +
> + memcpy(session_sample + sample_header_size, perf_sample + sample_header_size, data_size);
> +
> + sample_block_iter_init(perf_info, perf_sample, session_sample, &iter);
> +
> + while (sample_block_iter_next(&iter)) {
> + DECLARE_BITMAP(em_diff, PANTHOR_PERF_EM_BITS);
> + DECLARE_BITMAP(enabled_ctrs, PANTHOR_PERF_EM_BITS);
> + struct panthor_perf_counter_block *blk = iter.dst_blk;
> + enum drm_panthor_perf_block_type type = blk->header.block_type;
> + unsigned long *blk_em = session->enabled_counters->mask[type];
> + const u32 blk_prfcnt_en = blk->counters[PANTHOR_CTR_PRFCNT_EN];
> +
> + bitmap_zero(enabled_ctrs, PANTHOR_PERF_EM_BITS);
> + expand_enable_mask(counters, blk_prfcnt_en, enabled_ctrs);
> + bitmap_andnot(em_diff, enabled_ctrs, blk_em, PANTHOR_PERF_EM_BITS);
> + bitmap_clear(em_diff, 0, PANTHOR_HEADER_COUNTERS);
> +
> + blk->counters[PANTHOR_CTR_PRFCNT_EN] = compress_enable_mask(counters, blk_em);
> +
> + for (size_t ctr_idx = PANTHOR_HEADER_COUNTERS; ctr_idx < counters; ctr_idx++) {
> + if (test_bit(ctr_idx, em_diff))
> + blk->counters[ctr_idx] = 0;
> + }
> + }
> +}
> +
> +static void session_copy_sample(struct panthor_device *ptdev,
> + struct panthor_perf_session *session, u32 flags)
> +{
> + struct panthor_perf *perf = ptdev->perf;
> + const size_t sample_size = ptdev->perf_info.sample_size;
> + const u64 insert_idx = session->insert_idx;
> + u8 *new_sample;
> +
> + scoped_guard(mutex, &session->state_lock) {
> + if (flags & DRM_PANTHOR_PERF_SAMPLE_OVERFLOW)
> + session->overflow = true;
> +
> + if (session_is_starting(session) ||
> + (!session_is_active(session) && !session_is_stopping(session)))
> + return;
> + }
> +
> + new_sample = session->samples + (insert_idx % session->ringbuf_slots) * sample_size;
> +
> + if (session->accum_idx != insert_idx) {
> + session->sample_flags = flags;
> + session_populate_sample(ptdev, session, new_sample, perf->sample);
> + session->accum_idx = insert_idx;
> + } else {
> + session->sample_flags |= flags;
> + session_accumulate_sample(ptdev, session, new_sample, perf->sample);
> + }
> +}
> +
> +static void session_publish_sample(struct panthor_perf *perf,
> + struct panthor_perf_session *session)
> +{
> + const struct drm_panthor_perf_info *const info = &perf->ptdev->perf_info;
> + u8 *sample = session->samples +
> + (session->insert_idx % session->ringbuf_slots) * info->sample_size;
> + struct drm_panthor_perf_sample_header *hdr = (typeof(hdr))sample;
> +
> + *hdr = (struct drm_panthor_perf_sample_header) {
> + .user_data = session->user_data,
> + .timestamp_start_ns = session->sample_start_ns,
> + .timestamp_end_ns = perf->last_fw_sample_time,
> + .flags = session->sample_flags,
> + };
> +
> + session_write_insert_idx(session, session->insert_idx + 1);
> +
> + /* Order sample and insert-index writes before the eventfd notification. */
> + wmb();
> +
> + eventfd_signal(session->eventfd);
> +}
> +
> +static void session_emit_sample(struct panthor_perf *perf, struct panthor_perf_session *session)
> +{
> + guard(mutex)(&session->state_lock);
> +
> + if (session_is_starting(session)) {
> + session_set_state(session, PANTHOR_PERF_SESSION_ACTIVE);
> + goto update_session;
> + }
> +
> + if (session_is_stopping(session)) {
> + session_publish_sample(perf, session);
> + goto handle_stopping;
> + }
> +
> + if (!session_is_active(session))
> + return;
> +
> + if (session->pending_sample) {
> + session->pending_sample = false;
> + session_publish_sample(perf, session);
> + goto update_session;
> + }
> +
> + return;
> +
> +handle_stopping:
> + if (session_is_stopping(session))
> + session_set_state(session, PANTHOR_PERF_SESSION_STOPPED);
> +
> +update_session:
> + session->sample_flags = 0;
> + session->sample_start_ns = perf->last_fw_sample_time;
> + wake_up_all(&session->state_wq);
> +}
> +
> +static void panthor_perf_sample_worker(struct work_struct *work)
> +{
> + struct panthor_perf *perf = container_of(work, struct panthor_perf, sample_work);
> + struct panthor_device *const ptdev = perf->ptdev;
> + struct panthor_fw_global_iface *glb_iface = panthor_fw_get_glb_iface(ptdev);
> + const u32 extract_idx = READ_ONCE(glb_iface->input->perfcnt_extract);
> + const u32 insert_idx = READ_ONCE(glb_iface->output->perfcnt_insert);
> + const u32 ack = READ_ONCE(glb_iface->output->ack);
> + const u32 req = READ_ONCE(glb_iface->input->req);
> + const u32 events = (req ^ ack) & (GLB_PERFCNT_THRESHOLD | GLB_PERFCNT_OVERFLOW);
> + const u32 flags = events & GLB_PERFCNT_OVERFLOW ? DRM_PANTHOR_PERF_SAMPLE_OVERFLOW : 0;
> +
> + /* Defer sample processing until the pending reset completes. */
> + if (panthor_device_reset_is_pending(ptdev)) {
> + queue_work(perf->sample_workq, &perf->sample_work);
> + return;
> + }
> +
> + /* A stale notification must not complete a userspace sample request. */
> + if (extract_idx == insert_idx)
> + goto ack_events;
> +
> + panthor_perf_handle_fw_samples(ptdev, extract_idx, insert_idx);
> + WRITE_ONCE(glb_iface->input->perfcnt_extract, insert_idx);
> +
> + scoped_guard(mutex, &perf->session_lock) {
> + struct panthor_perf_session *session;
> +
> + list_for_each_entry(session, &perf->session_list, sessions)
> + session_copy_sample(ptdev, session, flags);
> +
> + list_for_each_entry(session, &perf->session_list, sessions)
> + session_emit_sample(perf, session);
> + }
> +
> + memset(perf->sample, 0, ptdev->perf_info.sample_size);
> +
> +ack_events:
> + if (events)
> + panthor_fw_update_reqs(glb_iface, req, ack, events);
> +}
> +
> +void panthor_perf_report_irq(struct panthor_device *ptdev, u32 status)
> +{
> + struct panthor_fw_global_iface *glb_iface = panthor_fw_get_glb_iface(ptdev);
> + struct panthor_perf *const perf = ptdev->perf;
> +
> + if (!(status & JOB_INT_GLOBAL_IF))
> + return;
> +
> + if (!perf)
> + return;
> +
> + const u32 ack = READ_ONCE(glb_iface->output->ack);
> + const u32 req = READ_ONCE(glb_iface->input->req);
> + const u32 events = (req ^ ack) & (GLB_PERFCNT_THRESHOLD | GLB_PERFCNT_OVERFLOW);
> + const bool sample_acked = ((req & GLB_PERFCNT_SAMPLE) == (ack & GLB_PERFCNT_SAMPLE));
> + const u32 extract_idx = READ_ONCE(glb_iface->input->perfcnt_extract);
> + const u32 insert_idx = READ_ONCE(glb_iface->output->perfcnt_insert);
> + const bool has_sample = sample_acked && insert_idx != extract_idx;
> +
> + if (sample_acked) {
> + guard(spinlock_irqsave)(&perf->pend_lock);
> + perf->fw_sample_requested = false;
> + }
> +
> + if (events || has_sample)
> + queue_work(perf->sample_workq, &perf->sample_work);
> +}
> +
> +static int perf_enable(struct panthor_perf *perf)
> +{
> + lockdep_assert_held(&perf->op_lock);
> +
> + guard(mutex)(&perf->session_lock);
> +
> + if (list_empty(&perf->session_list))
> + return 0;
> +
> + panthor_perf_fw_write_config(perf);
> + return panthor_perf_fw_start_sampling(perf->ptdev);
> +}
> +
> +static int perf_disable(struct panthor_perf *perf)
> +{
> + lockdep_assert_held(&perf->op_lock);
> +
> + guard(mutex)(&perf->session_lock);
> +
> + if (list_empty(&perf->session_list))
> + return 0;
> +
> + return panthor_perf_fw_stop_sampling(perf->ptdev);
> +}
> +
> +static void compute_enable_mask(struct panthor_perf *perf)
> +{
> + lockdep_assert_held(&perf->session_lock);
> +
> + struct list_head *snode;
> + struct panthor_perf_enable_masks em = {};
> +
> + list_for_each(snode, &perf->session_list)
> + {
> + struct panthor_perf_session *session =
> + container_of(snode, typeof(*session), sessions);
> +
> + panthor_perf_em_add(&em, session->enabled_counters);
> + }
> +
> + *perf->em = em;
> +}
> +
> +static void perf_attach_session(struct panthor_perf *perf, struct panthor_perf_session *session)
> +{
> + lockdep_assert_held(&perf->session_lock);
> +
> + session_get(session);
> + list_add_tail(&session->sessions, &perf->session_list);
> + compute_enable_mask(perf);
> +}
> +
> +static void perf_detach_session(struct panthor_perf *perf, struct panthor_perf_session *session)
> +{
> + lockdep_assert_held(&perf->session_lock);
> +
> + list_del_init(&session->sessions);
> + compute_enable_mask(perf);
> + session_put(session);
> +}
> +
> +static int panthor_perf_session_add(struct panthor_perf *perf,
> + struct panthor_perf_session *session, u8 set)
> +{
> + int ret = 0;
> +
> + guard(mutex)(&perf->op_lock);
> +
> + scoped_guard(mutex, &perf->session_lock) {
> + if (!list_empty(&perf->session_list))
> + return -EBUSY;
> +
> + perf->set_config = set;
> + }
> +
> + ret = perf_disable(perf);
> + if (ret)
> + return ret;
> +
> + scoped_guard(mutex, &perf->session_lock)
> + perf_attach_session(perf, session);
> +
> + ret = perf_enable(perf);
> + if (ret)
> + goto drop_session_ref;
> +
> + return ret;
> +drop_session_ref:
> + scoped_guard(mutex, &perf->session_lock)
> + perf_detach_session(perf, session);
> +
> + return ret;
> +}
> +
> +static int panthor_perf_session_remove(struct panthor_perf *perf,
> + struct panthor_perf_session *session)
> +{
> + guard(mutex)(&perf->op_lock);
> +
> + /*
> + * Even if the disable fails, the session must be removed, so the error
> + * is ignored.
> + */
> + perf_disable(perf);
> +
> + scoped_guard(mutex, &perf->session_lock)
> + perf_detach_session(perf, session);
> +
> + return perf_enable(perf);
> +}
> +
> /**
> * panthor_perf_init - Initialize the performance counter subsystem.
> * @ptdev: Panthor device
> @@ -377,7 +1259,10 @@ static void session_set_state(struct panthor_perf_session *session,
> int panthor_perf_init(struct panthor_device *ptdev)
> {
> struct panthor_perf *perf;
> - int ret = 0;
> + struct panthor_kernel_bo *bo;
> + struct panthor_perf_enable_masks *em;
> + u8 *sample;
> + int ret;
>
> if (!ptdev)
> return -EINVAL;
> @@ -392,12 +1277,107 @@ int panthor_perf_init(struct panthor_device *ptdev)
> if (ret)
> return ret;
>
> + ret = drmm_mutex_init(&ptdev->base, &perf->session_lock);
> + if (ret)
> + return ret;
> +
> + spin_lock_init(&perf->pend_lock);
> +
> + ret = panthor_perf_setup_fw_buffer(ptdev, perf);
> + if (ret) {
> + drm_err(&ptdev->base,
> + "Failed to setup FW ring buffer, disabling perf.");
> + return -EOPNOTSUPP;
> + }
> +
> + sample = drmm_kzalloc(&ptdev->base, ptdev->perf_info.sample_size, GFP_KERNEL);
> + if (!sample)
> + return -ENOMEM;
> +
> + em = drmm_kzalloc(&ptdev->base, sizeof(*em), GFP_KERNEL);
> + if (!em)
> + return -ENOMEM;
> +
> + bo = panthor_kernel_bo_create(ptdev, panthor_fw_vm(ptdev),
> + perf->fw_sample_size * perf_ringbuf_slots,
> + DRM_PANTHOR_BO_NO_MMAP,
> + DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC |
> + DRM_PANTHOR_VM_BIND_OP_MAP_UNCACHED,
> + PANTHOR_VM_KERNEL_AUTO_VA,
> + "perf_ringbuf");
> +
> + if (IS_ERR_OR_NULL(bo))
> + return IS_ERR(bo) ? PTR_ERR(bo) : -ENOMEM;
> +
> + ret = panthor_kernel_bo_vmap(bo);
> + if (ret)
> + goto cleanup_bo;
> +
> + perf->sample_workq = alloc_workqueue("panthor_perf_wq", WQ_HIGHPRI | WQ_UNBOUND, 1);
> + if (!perf->sample_workq) {
> + ret = -ENOMEM;
> + goto cleanup_vmap;
> + }
> +
> + ret = drmm_add_action_or_reset(&ptdev->base, __drmm_workqueue_release,
> + perf->sample_workq);
> + if (ret)
> + goto cleanup_vmap;
> +
> + INIT_WORK(&perf->sample_work, panthor_perf_sample_worker);
> + perf->rb = bo;
> + perf->sample = sample;
> + perf->sample_slots = perf_ringbuf_slots;
> + perf->em = em;
> + perf->last_fw_sample_time = ktime_get_raw_ns();
> +
> + INIT_LIST_HEAD(&perf->session_list);
> +
> drm_info(&ptdev->base, "Performance counter subsystem initialized");
>
> perf->info = &ptdev->perf_info;
> + perf->ptdev = ptdev;
> ptdev->perf = perf;
>
> return ret;
> +
> +cleanup_vmap:
> + panthor_kernel_bo_vunmap(bo);
> +
> +cleanup_bo:
> + panthor_kernel_bo_destroy(bo);
> +
> + return ret;
> +}
> +
> +typedef bool (*session_state_cb)(struct panthor_perf_session *session);
> +
> +/**
> + * panthor_perf_request_sync - Request synchronous sample causing
> + * @perf: Perf struct
> + * @session: Target session
> + * @cb: Callback for the expected state of the session at the end of a sample.
> + *
> + * Perform a synchronous sample for scenarios where the sample must complete for the user to
> + * unambiguously know the state of the session. The two cases where this may be used are:
> + * - starting a new session (initial samples): any data from before this sample must not show up
> + * in a new sampling session.
> + * - stopping an existing session: the eventfd may already have been signalled, meaning the
> + * user may not find out when the session is stopped and continue waiting for more samples.
> + *
> + * Return: 0 on success, -ETIMEDOUT on wait failure
> + */
> +static int panthor_perf_request_sync(struct panthor_perf *perf,
> + struct panthor_perf_session *session, session_state_cb cb)
> +{
> + int ret;
> +
> + perf_fw_request_sample(perf);
> +
> + ret = wait_event_timeout(session->state_wq, cb(session),
> + msecs_to_jiffies(1000));
> +
> + return ret ? 0 : -ETIMEDOUT;
> }
>
> /**
> @@ -473,6 +1453,7 @@ int panthor_perf_session_create(struct drm_file *file,
>
> kref_init(&session->ref);
> INIT_LIST_HEAD(&session->sessions);
> + init_waitqueue_head(&session->state_wq);
> mutex_init(&session->state_lock);
> session->control = control.vaddr;
> *session->control = (struct drm_panthor_perf_ringbuf_control) { 0 };
> @@ -483,18 +1464,32 @@ int panthor_perf_session_create(struct drm_file *file,
> session->control_buf = control;
> session->accum_idx = U32_MAX;
>
> + ret = pm_runtime_resume_and_get(ptdev->base.dev);
> + if (ret)
> + goto cleanup_obj;
> +
> + ret = panthor_perf_session_add(ptdev->perf, session, create_args->block_set);
> + if (ret)
> + goto cleanup_pm;
> +
> ret = xa_alloc_cyclic(&pfile->sessions->xa, &session_id, session,
> - pfile->sessions->session_range,
> - &pfile->sessions->next, GFP_KERNEL);
> + pfile->sessions->session_range,
> + &pfile->sessions->next, GFP_KERNEL);
Minor nit: these changes should not exist, it looks like your editor could not decide on the indent.
> if (ret < 0) {
> drm_err(&ptdev->base, "File session limit exceeded.\n");
> ret = -EBUSY;
> - goto cleanup_obj;
> + goto cleanup_tracked;
> }
>
> *sid = session_id;
> return 0;
>
> +cleanup_tracked:
> + panthor_perf_session_remove(pfile->ptdev->perf, session);
> +
> +cleanup_pm:
> + pm_runtime_put(ptdev->base.dev);
> +
> cleanup_obj:
> session_put(session);
> return ret;
> @@ -524,26 +1519,38 @@ static int session_try_stop(struct panthor_perf_session *session, u64 user_data)
> if (session_is_starting(session))
> return -EBUSY;
>
> + if (session->pending_sample)
> + return -EBUSY;
> +
> + if (!session_has_space(session, 1))
> + return -EBUSY;
> +
> if (session_is_stopping(session))
> return 1;
>
> session_set_state(session, PANTHOR_PERF_SESSION_STOPPING);
> + session->sample_start_ns = ktime_get_raw_ns();
> session->user_data = user_data;
>
> return 1;
> }
>
> +static bool session_is_stopped_locked(struct panthor_perf_session *session)
> +{
> + guard(mutex)(&session->state_lock);
> +
> + return session_is_stopped(session);
> +}
> +
> static int session_stop(struct panthor_perf *perf, struct panthor_perf_session *session,
> u64 user_data)
> {
> int ret;
> -
> ret = session_try_stop(session, user_data);
> if (ret <= 0)
> return ret;
>
> - /* TODO Calls to the FW interface will go here in later patches. */
> - return 0;
> + return panthor_perf_request_sync(perf, session, session_is_stopped_locked);
> }
>
> static int session_try_start(struct panthor_perf_session *session, u64 user_data)
> @@ -560,11 +1567,20 @@ static int session_try_start(struct panthor_perf_session *session, u64 user_data
> return 0;
>
> session_set_state(session, PANTHOR_PERF_SESSION_STARTING);
> + session->overflow = false;
> + session->sample_start_ns = ktime_get_raw_ns();
> session->user_data = user_data;
>
> return 1;
> }
>
> +static bool session_is_active_locked(struct panthor_perf_session *session)
> +{
> + guard(mutex)(&session->state_lock);
> +
> + return session_is_active(session);
> +}
> +
> static int session_start(struct panthor_perf *perf, struct panthor_perf_session *session,
> u64 user_data)
> {
> @@ -574,8 +1590,7 @@ static int session_start(struct panthor_perf *perf, struct panthor_perf_session
> if (ret <= 0)
> return ret;
>
> - /* TODO Calls to the FW interface will go here in later patches. */
> - return 0;
> + return panthor_perf_request_sync(perf, session, session_is_active_locked);
> }
>
> static int session_try_sample(struct panthor_perf_session *session, u64 user_data)
> @@ -591,7 +1606,20 @@ static int session_try_sample(struct panthor_perf_session *session, u64 user_dat
> if (session_has_overflow(session))
> return -EACCES;
>
> + if (session->pending_sample)
> + return -EBUSY;
> +
> + /*
> + * Must have at least two slots remaining in the ringbuffer to sample: one for
> + * the current sample, and one for a stop sample, since a stop command should
> + * always be acknowledged by taking a final sample and stopping the session.
> + */
> + if (!session_has_space(session, 2))
> + return -ENOSPC;
> +
> session->user_data = user_data;
> + session->sample_start_ns = ktime_get_raw_ns();
> + session->pending_sample = true;
>
> return 1;
> }
> @@ -599,14 +1627,14 @@ static int session_try_sample(struct panthor_perf_session *session, u64 user_dat
> static int session_sample(struct panthor_perf *perf, struct panthor_perf_session *session,
> u64 user_data)
> {
> -
> int ret;
>
> ret = session_try_sample(session, user_data);
> if (ret <= 0)
> return ret;
>
> - /* TODO Calls to the FW interface will go here in later patches. */
> + perf_fw_request_sample(perf);
> +
> return 0;
> }
>
> @@ -651,9 +1679,14 @@ int panthor_perf_session_destroy(struct panthor_file *pfile, u32 handle)
> return ret;
>
> xa_erase(&pfile->sessions->xa, handle);
> + ret = perf_disable(perf);
> +
> + scoped_guard(mutex, &perf->session_lock)
> + perf_detach_session(perf, session);
> }
>
> session_put(session);
> + pm_runtime_put_sync(perf->ptdev->base.dev);
>
> return ret;
> }
> @@ -774,11 +1807,22 @@ int panthor_perf_session_sample(struct panthor_file *pfile, u32 handle, u64 user
> */
> void panthor_perf_unplug(struct panthor_device *ptdev)
> {
> + int ret;
> struct panthor_perf *perf = ptdev->perf;
>
> if (!perf)
> return;
>
> + scoped_guard(mutex, &perf->op_lock)
> + ret = perf_disable(perf);
> + if (ret)
> + drm_err(&perf->ptdev->base, "Failed to disable performance counter sampling!");
> +
> + disable_work_sync(&perf->sample_work);
> +
> + panthor_kernel_bo_vunmap(perf->rb);
> + panthor_kernel_bo_destroy(perf->rb);
> +
> ptdev->perf = NULL;
> }
>
> @@ -809,9 +1853,14 @@ void panthor_perf_pool_destroy(struct panthor_file *pfile)
> if (IS_ERR_OR_NULL(pool))
> return;
>
> + if (!pfile->ptdev->perf)
> + return;
> +
> xa_for_each(&pool->xa, handle, session) {
> xa_erase(&pool->xa, handle);
> + panthor_perf_session_remove(pfile->ptdev->perf, session);
> session_put(session);
> + pm_runtime_put_sync(pfile->ptdev->base.dev);
> }
>
> xa_destroy(&pool->xa);
> diff --git a/drivers/gpu/drm/panthor/panthor_perf.h b/drivers/gpu/drm/panthor/panthor_perf.h
> index 641a88edd75a..0e1172dbae39 100644
> --- a/drivers/gpu/drm/panthor/panthor_perf.h
> +++ b/drivers/gpu/drm/panthor/panthor_perf.h
> @@ -28,5 +28,7 @@ int panthor_perf_session_start(struct panthor_file *pfile, u32 handle, u64 user_
> int panthor_perf_session_stop(struct panthor_file *pfile, unsigned int sid, u64 user_data);
> int panthor_perf_session_sample(struct panthor_file *pfile, unsigned int handle, u64 user_data);
>
> +void panthor_perf_report_irq(struct panthor_device *ptdev, u32 status);
> +
> #endif /* __PANTHOR_PERF_H__ */
>
>
> --
> 2.43.0
>
Reviewed-by: Liviu Dudau <liviu.dudau@arm.com>
Best regards,
Liviu
--
====================
| I would like to |
| fix the world, |
| but they're not |
| giving me the |
\ source code! /
---------------
¯\_(ツ)_/¯
next prev parent reply other threads:[~2026-10-23 14:30 UTC|newest]
Thread overview: 18+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-11 17:36 [PATCH v7 0/8] drm/panthor: Performance counter implementation with single manual client support Lukas Zapolskas
2026-10-11 17:36 ` [PATCH v7 1/8] drm/panthor: Add performance counter uAPI Lukas Zapolskas
2026-10-22 12:15 ` Liviu Dudau
2026-10-11 17:36 ` [PATCH v7 2/8] drm/panthor: Add register definitions for perf Lukas Zapolskas
2026-10-22 12:16 ` Liviu Dudau
2026-10-11 17:36 ` [PATCH v7 3/8] drm/panthor: Add DEV_QUERY.PERF_INFO handling Lukas Zapolskas
2026-10-23 13:54 ` Liviu Dudau
2026-10-11 17:36 ` [PATCH v7 4/8] drm/panthor: Add panthor perf initialization and termination Lukas Zapolskas
2026-10-23 13:58 ` Liviu Dudau
2026-10-11 17:36 ` [PATCH v7 5/8] drm/panthor: Introduce sampling sessions to handle userspace clients Lukas Zapolskas
2026-10-23 14:24 ` Liviu Dudau
2026-10-11 17:36 ` [PATCH v7 6/8] drm/panthor: Implement sample handling Lukas Zapolskas
2026-10-13 4:52 ` kernel test robot
2026-10-23 14:30 ` Liviu Dudau [this message]
2026-10-11 17:36 ` [PATCH v7 7/8] drm/panthor: Add suspend, resume and reset handling Lukas Zapolskas
2026-10-23 14:32 ` Liviu Dudau
2026-10-11 17:36 ` [PATCH v7 8/8] drm/panthor: Expose the panthor perf ioctls Lukas Zapolskas
2026-10-23 14:33 ` Liviu Dudau
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=attveq1mEszeTSsH@e142607 \
--to=liviu.dudau@arm.com \
--cc=adrian.larumbe@collabora.com \
--cc=airlied@gmail.com \
--cc=aliceryhl@google.com \
--cc=boris.brezillon@collabora.com \
--cc=daniel.almeida@collabora.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lukas.zapolskas@arm.com \
--cc=maarten.lankhorst@linux.intel.com \
--cc=mripard@kernel.org \
--cc=nd@arm.com \
--cc=simona@ffwll.ch \
--cc=steven.price@arm.com \
--cc=tzimmermann@suse.de \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®