// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
 *
 * (C) COPYRIGHT 2021-2023 ARM Limited. All rights reserved.
 *
 * This program is free software and is provided to you under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation, and any use by you of this program is subject to the terms
 * of such GNU license.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, you can access it online at
 * http://www.gnu.org/licenses/gpl-2.0.html.
 *
 */

#include "mali_kbase.h"
#include "mali_kbase_kinstr_prfcnt.h"
#include "hwcnt/mali_kbase_hwcnt_virtualizer.h"
#include "hwcnt/mali_kbase_hwcnt_gpu.h"
#include <uapi/gpu/arm/midgard/mali_kbase_ioctl.h>
#include "mali_malisw.h"
#include "mali_kbase_debug.h"

#include <linux/anon_inodes.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/hrtimer.h>
#include <linux/log2.h>
#include <linux/mm.h>
#include <linux/mutex.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/version_compat_defs.h>
#include <linux/workqueue.h>

/* Explicitly include epoll header for old kernels. Not required from 4.16. */
#if KERNEL_VERSION(4, 16, 0) > LINUX_VERSION_CODE
#include <uapi/linux/eventpoll.h>
#endif

/* The minimum allowed interval between dumps, in nanoseconds
 * (equivalent to 10KHz)
 */
#define DUMP_INTERVAL_MIN_NS (100 * NSEC_PER_USEC)

/* The maximum allowed buffers per client */
#define MAX_BUFFER_COUNT 32

/**
 * struct kbase_kinstr_prfcnt_context - IOCTL interface for userspace hardware
 *                                      counters.
 * @hvirt:           Hardware counter virtualizer used by kinstr_prfcnt.
 * @info_item_count: Number of metadata elements.
 * @metadata:        Hardware counter metadata provided by virtualizer.
 * @lock:            Lock protecting kinstr_prfcnt state.
 * @suspend_count:   Suspend reference count. If non-zero, timer and worker
 *                   are prevented from being re-scheduled.
 * @client_count:    Number of kinstr_prfcnt clients.
 * @clients:         List of kinstr_prfcnt clients.
 * @dump_timer:      Timer that enqueues dump_work to a workqueue.
 * @dump_work:       Worker for performing periodic counter dumps.
 */
struct kbase_kinstr_prfcnt_context {
	struct kbase_hwcnt_virtualizer *hvirt;
	u32 info_item_count;
	const struct kbase_hwcnt_metadata *metadata;
	struct mutex lock;
	size_t suspend_count;
	size_t client_count;
	struct list_head clients;
	struct hrtimer dump_timer;
	struct work_struct dump_work;
};

/**
 * struct kbase_kinstr_prfcnt_sample - Buffer and descriptor for sample data.
 * @sample_meta: Pointer to sample metadata.
 * @dump_buf:    Dump buffer containing sample data.
 */
struct kbase_kinstr_prfcnt_sample {
	struct prfcnt_metadata *sample_meta;
	struct kbase_hwcnt_dump_buffer dump_buf;
};

/**
 * struct kbase_kinstr_prfcnt_sample_array - Array of sample data.
 * @user_buf:        Address of allocated userspace buffer. A single allocation is used
 *                   for all Dump Buffers in the array.
 * @sample_count:    Number of allocated samples.
 * @blk_stt_scratch: Scratch buffer to redirect the block states from the internal
 *                   dump buffer when setting up the samples array. We use this
 *                   to ensure that the block state information is not duplicated
 *                   when using kbase_hwcnt_dump_buffer methods to copy the client
 *                   dump buffer to @user_buf .
 * @samples:         Non-NULL pointer to the array of Dump Buffers.
 */
struct kbase_kinstr_prfcnt_sample_array {
	u8 *user_buf;
	size_t sample_count;
	blk_stt_t *blk_stt_scratch;
	struct kbase_kinstr_prfcnt_sample *samples;
};

/**
 * struct kbase_kinstr_prfcnt_client_config - Client session configuration.
 * @prfcnt_mode:  Sampling mode: either manual or periodic.
 * @counter_set:  Set of performance counter blocks.
 * @scope:        Scope of performance counters to capture.
 * @buffer_count: Number of buffers used to store samples.
 * @period_ns:    Sampling period, in nanoseconds, or 0 if manual mode.
 * @enable_cm:    Requested counter selection bitmasks.
 */
struct kbase_kinstr_prfcnt_client_config {
	u8 prfcnt_mode;
	u8 counter_set;
	u8 scope;
	u16 buffer_count;
	u64 period_ns;
	struct kbase_hwcnt_enable_cm enable_cm;
};

/**
 * enum kbase_kinstr_prfcnt_client_init_state - A list of
 *                                              initialisation states that the
 *                                              kinstr_prfcnt client can be at
 *                                              during initialisation. Useful
 *                                              for terminating a partially
 *                                              initialised client.
 *
 * @KINSTR_PRFCNT_UNINITIALISED: Client is uninitialised
 * @KINSTR_PRFCNT_PARSE_SETUP: Parse the setup session
 * @KINSTR_PRFCNT_ENABLE_MAP: Allocate memory for enable map
 * @KINSTR_PRFCNT_DUMP_BUFFER: Allocate memory for dump buffer
 * @KINSTR_PRFCNT_SAMPLE_ARRAY: Allocate memory for and initialise sample array
 * @KINSTR_PRFCNT_VIRTUALIZER_CLIENT: Create virtualizer client
 * @KINSTR_PRFCNT_WAITQ_MUTEX: Create and initialise mutex and waitqueue
 * @KINSTR_PRFCNT_INITIALISED: Client is fully initialised
 */
enum kbase_kinstr_prfcnt_client_init_state {
	KINSTR_PRFCNT_UNINITIALISED,
	KINSTR_PRFCNT_PARSE_SETUP = KINSTR_PRFCNT_UNINITIALISED,
	KINSTR_PRFCNT_ENABLE_MAP,
	KINSTR_PRFCNT_DUMP_BUFFER,
	KINSTR_PRFCNT_SAMPLE_ARRAY,
	KINSTR_PRFCNT_VIRTUALIZER_CLIENT,
	KINSTR_PRFCNT_WAITQ_MUTEX,
	KINSTR_PRFCNT_INITIALISED
};

/**
 * struct kbase_kinstr_prfcnt_client - A kinstr_prfcnt client attached
 *                                     to a kinstr_prfcnt context.
 * @kinstr_ctx:           kinstr_prfcnt context client is attached to.
 * @hvcli:                Hardware counter virtualizer client.
 * @node:                 Node used to attach this client to list in
 *                        kinstr_prfcnt context.
 * @cmd_sync_lock:        Lock coordinating the reader interface for commands.
 * @next_dump_time_ns:    Time in ns when this client's next periodic dump must
 *                        occur. If 0, not a periodic client.
 * @dump_interval_ns:     Interval between periodic dumps. If 0, not a periodic
 *                        client.
 * @sample_flags:         Flags for the current active dumping sample, marking
 *                        the conditions/events during the dump duration.
 * @active:               True if the client has been started.
 * @config:               Configuration of the client session.
 * @enable_map:           Counters enable map.
 * @tmp_buf:              Temporary buffer to use before handing over dump to
 *                        client.
 * @sample_arr:           Array of dump buffers allocated by this client.
 * @read_idx:             Index of buffer read by userspace.
 * @write_idx:            Index of buffer being written by dump worker.
 * @fetch_idx:            Index of buffer being fetched by userspace, but
 *                        pending a confirmation of being read (consumed) if it
 *                        differs from the read_idx.
 * @waitq:                Client's notification queue.
 * @sample_size:          Size of the data required for one sample, in bytes.
 * @sample_count:         Number of samples the client is able to capture.
 * @user_data:            User data associated with the session.
 *                        This is set when the session is started and stopped.
 *                        This value is ignored for control commands that
 *                        provide another value.
 */
struct kbase_kinstr_prfcnt_client {
	struct kbase_kinstr_prfcnt_context *kinstr_ctx;
	struct kbase_hwcnt_virtualizer_client *hvcli;
	struct list_head node;
	struct mutex cmd_sync_lock;
	u64 next_dump_time_ns;
	u32 dump_interval_ns;
	u32 sample_flags;
	bool active;
	struct kbase_kinstr_prfcnt_client_config config;
	struct kbase_hwcnt_enable_map enable_map;
	struct kbase_hwcnt_dump_buffer tmp_buf;
	struct kbase_kinstr_prfcnt_sample_array sample_arr;
	atomic_t read_idx;
	atomic_t write_idx;
	atomic_t fetch_idx;
	wait_queue_head_t waitq;
	size_t sample_size;
	size_t sample_count;
	u64 user_data;
};

static struct prfcnt_enum_item kinstr_prfcnt_supported_requests[] = {
	{
		/* Request description for MODE request */
		.hdr = {
				.item_type = PRFCNT_ENUM_TYPE_REQUEST,
				.item_version = PRFCNT_READER_API_VERSION,
		},
		.u.request = {
				.request_item_type = PRFCNT_REQUEST_MODE,
				.versions_mask = 0x1,
		},
	},
	{
		/* Request description for ENABLE request */
		.hdr = {
				.item_type = PRFCNT_ENUM_TYPE_REQUEST,
				.item_version = PRFCNT_READER_API_VERSION,
		},
		.u.request = {
				.request_item_type = PRFCNT_REQUEST_ENABLE,
				.versions_mask = 0x1,
		},
	},
};

/**
 * kbasep_kinstr_prfcnt_hwcnt_reader_poll() - hwcnt reader's poll.
 * @filp: Non-NULL pointer to file structure.
 * @wait: Non-NULL pointer to poll table.
 *
 * Return: EPOLLIN | EPOLLRDNORM if data can be read without blocking, 0 if
 *         data can not be read without blocking, else EPOLLHUP | EPOLLERR.
 */
static __poll_t kbasep_kinstr_prfcnt_hwcnt_reader_poll(struct file *filp,
						       struct poll_table_struct *wait)
{
	struct kbase_kinstr_prfcnt_client *cli;

	if (!filp || !wait)
		return EPOLLHUP | EPOLLERR;

	cli = filp->private_data;

	if (!cli)
		return EPOLLHUP | EPOLLERR;

	poll_wait(filp, &cli->waitq, wait);

	if (atomic_read(&cli->write_idx) != atomic_read(&cli->fetch_idx))
		return EPOLLIN | EPOLLRDNORM;

	return (__poll_t)0;
}

/**
 * kbasep_kinstr_prfcnt_next_dump_time_ns() - Calculate the next periodic
 *                                            dump time.
 * @cur_ts_ns: Current time in nanoseconds.
 * @interval:  Interval between dumps in nanoseconds.
 *
 * Return: 0 if interval is 0 (i.e. a non-periodic client), or the next dump
 *         time that occurs after cur_ts_ns.
 */
static u64 kbasep_kinstr_prfcnt_next_dump_time_ns(u64 cur_ts_ns, u32 interval)
{
	/* Non-periodic client */
	if (interval == 0)
		return 0;

	/*
	 * Return the next interval after the current time relative to t=0.
	 * This means multiple clients with the same period will synchronize,
	 * regardless of when they were started, allowing the worker to be
	 * scheduled less frequently.
	 */
	do_div(cur_ts_ns, interval);

	return (cur_ts_ns + 1) * interval;
}

/**
 * kbasep_kinstr_prfcnt_timestamp_ns() - Get the current time in nanoseconds.
 *
 * Return: Current time in nanoseconds.
 */
static u64 kbasep_kinstr_prfcnt_timestamp_ns(void)
{
	return ktime_get_raw_ns();
}

/**
 * kbasep_kinstr_prfcnt_reschedule_worker() - Update next dump times for all
 *                                            periodic kinstr_prfcnt clients,
 *                                            then reschedule the dump worker
 *                                            appropriately.
 * @kinstr_ctx: Non-NULL pointer to the kinstr_prfcnt context.
 *
 * If there are no periodic clients, then the dump worker will not be
 * rescheduled. Else, the dump worker will be rescheduled for the next
 * periodic client dump.
 */
static void kbasep_kinstr_prfcnt_reschedule_worker(struct kbase_kinstr_prfcnt_context *kinstr_ctx)
{
	u64 cur_ts_ns;
	u64 shortest_period_ns = U64_MAX;
	struct kbase_kinstr_prfcnt_client *pos;

	WARN_ON(!kinstr_ctx);
	lockdep_assert_held(&kinstr_ctx->lock);
	cur_ts_ns = kbasep_kinstr_prfcnt_timestamp_ns();

	/*
	 * This loop fulfills 2 separate tasks that don't affect each other:
	 *
	 * 1) Determine the shortest period.
	 * 2) Update the next dump time of clients that have already been
	 *    dumped. It's important not to alter the next dump time of clients
	 *    that haven't been dumped yet.
	 *
	 * For the sake of efficiency, the rescheduling decision ignores the time
	 * of the next dump and just uses the shortest period among all periodic
	 * clients. It is more efficient to serve multiple dump requests at once,
	 * rather than trying to reschedule the worker to serve each request
	 * individually.
	 */
	list_for_each_entry(pos, &kinstr_ctx->clients, node) {
		/* Ignore clients that are not periodic or not active. */
		if (pos->active && pos->dump_interval_ns > 0) {
			shortest_period_ns = MIN(shortest_period_ns, pos->dump_interval_ns);

			/* Next dump should happen exactly one period after the last dump.
			 * If last dump was overdue and scheduled to happen more than one
			 * period ago, compensate for that by scheduling next dump in the
			 * immediate future.
			 */
			if (pos->next_dump_time_ns < cur_ts_ns)
				pos->next_dump_time_ns =
					MAX(cur_ts_ns + 1,
					    pos->next_dump_time_ns + pos->dump_interval_ns);
		}
	}

	/* Cancel the timer if it is already pending */
	hrtimer_cancel(&kinstr_ctx->dump_timer);

	/* Start the timer if there are periodic clients and kinstr_prfcnt is not
	 * suspended.
	 */
	if ((shortest_period_ns != U64_MAX) && (kinstr_ctx->suspend_count == 0)) {
		u64 next_schedule_time_ns =
			kbasep_kinstr_prfcnt_next_dump_time_ns(cur_ts_ns, shortest_period_ns);
		hrtimer_start(&kinstr_ctx->dump_timer,
			      ns_to_ktime(next_schedule_time_ns - cur_ts_ns), HRTIMER_MODE_REL);
	}
}

/**
 * kbase_hwcnt_block_state_to_prfcnt_block_state - convert internal HW
 *                                                 block state enum to
 *                                                 the UAPI bitmask.
 *
 * @hw_blk_stt: internal representation of the hardware counter block
 *              state
 * Return: UAPI-compliant bitmap of the states.
 */
static u32 kbase_hwcnt_block_state_to_prfcnt_block_state(blk_stt_t hw_blk_stt)
{
	u32 prfcnt_block_state = 0;
	unsigned long block_state_mask = hw_blk_stt;
	unsigned long bit_shift;

	for_each_set_bit(bit_shift, &block_state_mask, BITS_PER_TYPE(blk_stt_t)) {
		blk_stt_t set_bit = 1u << bit_shift;

		switch (set_bit) {
		case KBASE_HWCNT_STATE_UNKNOWN:
			prfcnt_block_state |= BLOCK_STATE_UNKNOWN;
			break;
		case KBASE_HWCNT_STATE_ON:
			prfcnt_block_state |= BLOCK_STATE_ON;
			break;
		case KBASE_HWCNT_STATE_OFF:
			prfcnt_block_state |= BLOCK_STATE_OFF;
			break;
		case KBASE_HWCNT_STATE_AVAILABLE:
			prfcnt_block_state |= BLOCK_STATE_AVAILABLE;
			break;
		case KBASE_HWCNT_STATE_UNAVAILABLE:
			prfcnt_block_state |= BLOCK_STATE_UNAVAILABLE;
			break;
		case KBASE_HWCNT_STATE_NORMAL:
			prfcnt_block_state |= BLOCK_STATE_NORMAL;
			break;
		case KBASE_HWCNT_STATE_PROTECTED:
			prfcnt_block_state |= BLOCK_STATE_PROTECTED;
			break;
		default:
			WARN(true, "Unknown block state bit set: 0x%x", set_bit);
			break;
		}
	}

	return prfcnt_block_state;
}

static enum prfcnt_block_type kbase_hwcnt_metadata_block_type_to_prfcnt_block_type(u64 type)
{
	enum prfcnt_block_type block_type;

	switch (type) {
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_FE:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_FE2:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_FE3:
		block_type = PRFCNT_BLOCK_TYPE_FE;
		break;

	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_TILER:
		block_type = PRFCNT_BLOCK_TYPE_TILER;
		break;

	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_SC:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_SC2:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_SC3:
		block_type = PRFCNT_BLOCK_TYPE_SHADER_CORE;
		break;

	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_MEMSYS:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_MEMSYS2:
		block_type = PRFCNT_BLOCK_TYPE_MEMORY;
		break;

	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_FW:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_FW2:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_FW3:
		block_type = PRFCNT_BLOCK_TYPE_FW;
		break;

	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_CSG:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_CSG2:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_CSG3:
		block_type = PRFCNT_BLOCK_TYPE_CSG;
		break;


	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_FE_UNDEFINED:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_SC_UNDEFINED:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_FW_UNDEFINED:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_CSG_UNDEFINED:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_TILER_UNDEFINED:
	case KBASE_HWCNT_GPU_V5_BLOCK_TYPE_PERF_MEMSYS_UNDEFINED:
	default:
		block_type = PRFCNT_BLOCK_TYPE_RESERVED;
		break;
	}

	return block_type;
}

static bool kbase_kinstr_is_block_type_reserved(const struct kbase_hwcnt_metadata *metadata,
						size_t blk)
{
	enum prfcnt_block_type block_type = kbase_hwcnt_metadata_block_type_to_prfcnt_block_type(
		kbase_hwcnt_metadata_block_type(metadata, blk));

	return block_type == PRFCNT_BLOCK_TYPE_RESERVED;
}

/**
 * kbasep_kinstr_prfcnt_set_block_meta_items() - Populate a sample's block meta
 *                                               item array.
 * @enable_map:      Non-NULL pointer to the map of enabled counters.
 * @dst:             Non-NULL pointer to the sample's dump buffer object.
 * @block_meta_base: Non-NULL double pointer to the start of the block meta
 *                   data items.
 * @base_addr:       Address of allocated pages for array of samples. Used
 *                   to calculate offset of block values.
 * @counter_set:     The SET which blocks represent.
 *
 * Return: 0 on success, else error code.
 */
int kbasep_kinstr_prfcnt_set_block_meta_items(struct kbase_hwcnt_enable_map *enable_map,
					      struct kbase_hwcnt_dump_buffer *dst,
					      struct prfcnt_metadata **block_meta_base,
					      u8 *base_addr, u8 counter_set)
{
	size_t blk, blk_inst;
	struct prfcnt_metadata **ptr_md = block_meta_base;
	const struct kbase_hwcnt_metadata *metadata;
	uint8_t block_idx = 0;

	if (!dst || !*block_meta_base)
		return -EINVAL;

	metadata = dst->metadata;
	kbase_hwcnt_metadata_for_each_block(metadata, blk, blk_inst) {
		u8 *dst_blk;
		blk_stt_t hw_blk_stt;

		/* Block indices must be reported with no gaps. */
		if (blk_inst == 0)
			block_idx = 0;

		/* Skip unavailable or non-enabled blocks */
		if (kbase_kinstr_is_block_type_reserved(metadata, blk) ||
		    !kbase_hwcnt_metadata_block_instance_avail(metadata, blk, blk_inst) ||
		    !kbase_hwcnt_enable_map_block_enabled(enable_map, blk, blk_inst))
			continue;

		dst_blk = (u8 *)kbase_hwcnt_dump_buffer_block_instance(dst, blk, blk_inst);
		hw_blk_stt = *kbase_hwcnt_dump_buffer_block_state_instance(dst, blk, blk_inst);
		(*ptr_md)->hdr.item_type = PRFCNT_SAMPLE_META_TYPE_BLOCK;
		(*ptr_md)->hdr.item_version = PRFCNT_READER_API_VERSION;
		(*ptr_md)->u.block_md.block_type =
			kbase_hwcnt_metadata_block_type_to_prfcnt_block_type(
				kbase_hwcnt_metadata_block_type(metadata, blk));
		(*ptr_md)->u.block_md.block_idx = block_idx;
		(*ptr_md)->u.block_md.set = counter_set;
		(*ptr_md)->u.block_md.block_state =
			kbase_hwcnt_block_state_to_prfcnt_block_state(hw_blk_stt);
		(*ptr_md)->u.block_md.values_offset = (u32)(dst_blk - base_addr);

		/* update the buf meta data block pointer to next item */
		(*ptr_md)++;
		block_idx++;
	}

	return 0;
}

/**
 * kbasep_kinstr_prfcnt_set_sample_metadata() - Set sample metadata for sample
 *                                              output.
 * @cli:       Non-NULL pointer to a kinstr_prfcnt client.
 * @dump_buf:  Non-NULL pointer to dump buffer where sample is stored.
 * @ptr_md:    Non-NULL pointer to sample metadata.
 */
static void kbasep_kinstr_prfcnt_set_sample_metadata(struct kbase_kinstr_prfcnt_client *cli,
						     struct kbase_hwcnt_dump_buffer *dump_buf,
						     struct prfcnt_metadata *ptr_md)
{
	u8 clk_cnt, i;

	clk_cnt = cli->kinstr_ctx->metadata->clk_cnt;

	/* PRFCNT_SAMPLE_META_TYPE_SAMPLE must be the first item */
	ptr_md->hdr.item_type = PRFCNT_SAMPLE_META_TYPE_SAMPLE;
	ptr_md->hdr.item_version = PRFCNT_READER_API_VERSION;
	ptr_md->u.sample_md.seq = (u64)atomic_read(&cli->write_idx);
	ptr_md->u.sample_md.flags = cli->sample_flags;

	/* Place the PRFCNT_SAMPLE_META_TYPE_CLOCK optionally as the 2nd */
	ptr_md++;
	if (clk_cnt > MAX_REPORTED_DOMAINS)
		clk_cnt = MAX_REPORTED_DOMAINS;

	/* Handle the prfcnt_clock_metadata meta item */
	ptr_md->hdr.item_type = PRFCNT_SAMPLE_META_TYPE_CLOCK;
	ptr_md->hdr.item_version = PRFCNT_READER_API_VERSION;
	ptr_md->u.clock_md.num_domains = clk_cnt;
	for (i = 0; i < clk_cnt; i++)
		ptr_md->u.clock_md.cycles[i] = dump_buf->clk_cnt_buf[i];

	/* Dealing with counter blocks */
	ptr_md++;
	if (WARN_ON(kbasep_kinstr_prfcnt_set_block_meta_items(&cli->enable_map, dump_buf, &ptr_md,
							      cli->sample_arr.user_buf,
							      cli->config.counter_set)))
		return;

	/* Handle the last sentinel item */
	ptr_md->hdr.item_type = FLEX_LIST_TYPE_NONE;
	ptr_md->hdr.item_version = 0;
}

/**
 * kbasep_kinstr_prfcnt_client_output_sample() - Assemble a sample for output.
 * @cli:          Non-NULL pointer to a kinstr_prfcnt client.
 * @buf_idx:      The index to the sample array for saving the sample.
 * @user_data:    User data to return to the user.
 * @ts_start_ns:  Time stamp for the start point of the sample dump.
 * @ts_end_ns:    Time stamp for the end point of the sample dump.
 */
static void kbasep_kinstr_prfcnt_client_output_sample(struct kbase_kinstr_prfcnt_client *cli,
						      int buf_idx, u64 user_data, u64 ts_start_ns,
						      u64 ts_end_ns)
{
	struct kbase_hwcnt_dump_buffer *dump_buf;
	struct kbase_hwcnt_dump_buffer *tmp_buf = &cli->tmp_buf;
	struct prfcnt_metadata *ptr_md;

	if (WARN_ON(buf_idx >= cli->sample_arr.sample_count))
		return;

	dump_buf = &cli->sample_arr.samples[buf_idx].dump_buf;
	ptr_md = cli->sample_arr.samples[buf_idx].sample_meta;

	/* Patch the dump buf headers, to hide the counters that other hwcnt
	 * clients are using.
	 */
	kbase_hwcnt_gpu_patch_dump_headers(tmp_buf, &cli->enable_map);

	/* Copy the temp buffer to the userspace visible buffer. The strict
	 * variant will explicitly zero any non-enabled counters to ensure
	 * nothing except exactly what the user asked for is made visible.
	 */
	kbase_hwcnt_dump_buffer_copy_strict(dump_buf, tmp_buf, &cli->enable_map);

	/* PRFCNT_SAMPLE_META_TYPE_SAMPLE must be the first item.
	 * Set timestamp and user data for real dump.
	 */
	ptr_md->u.sample_md.timestamp_start = ts_start_ns;
	ptr_md->u.sample_md.timestamp_end = ts_end_ns;
	ptr_md->u.sample_md.user_data = user_data;

	kbasep_kinstr_prfcnt_set_sample_metadata(cli, dump_buf, ptr_md);
}

/**
 * kbasep_kinstr_prfcnt_client_dump() - Perform a dump for a client.
 * @cli:          Non-NULL pointer to a kinstr_prfcnt client.
 * @event_id:     Event type that triggered the dump.
 * @user_data:    User data to return to the user.
 *
 * Return: 0 on success, else error code.
 */
static int kbasep_kinstr_prfcnt_client_dump(struct kbase_kinstr_prfcnt_client *cli,
					    enum base_hwcnt_reader_event event_id, u64 user_data)
{
	int ret;
	u64 ts_start_ns = 0;
	u64 ts_end_ns = 0;
	int write_idx;
	int read_idx;
	size_t available_samples_count;

	WARN_ON(!cli);
	lockdep_assert_held(&cli->kinstr_ctx->lock);

	write_idx = atomic_read(&cli->write_idx);
	read_idx = atomic_read(&cli->read_idx);

	/* Check if there is a place to copy HWC block into. Calculate the
	 * number of available samples count, by taking into account the type
	 * of dump.
	 */
	available_samples_count = cli->sample_arr.sample_count;
	WARN_ON(available_samples_count < 1);
	/* Reserve one slot to store the implicit sample taken on CMD_STOP */
	available_samples_count -= 1;
	if ((size_t)(write_idx - read_idx) == available_samples_count) {
		/* For periodic sampling, the current active dump
		 * will be accumulated in the next sample, when
		 * a buffer becomes available.
		 */
		if (event_id == BASE_HWCNT_READER_EVENT_PERIODIC)
			cli->sample_flags |= SAMPLE_FLAG_OVERFLOW;
		return -EBUSY;
	}

	/* For the rest of the function, use the actual sample_count
	 * that represents the real size of the array.
	 */
	write_idx %= cli->sample_arr.sample_count;

	ret = kbase_hwcnt_virtualizer_client_dump(cli->hvcli, &ts_start_ns, &ts_end_ns,
						  &cli->tmp_buf);
	/* HWC dump error, set the sample with error flag */
	if (ret)
		cli->sample_flags |= SAMPLE_FLAG_ERROR;

	/* Make the sample ready and copy it to the userspace mapped buffer */
	kbasep_kinstr_prfcnt_client_output_sample(cli, write_idx, user_data, ts_start_ns,
						  ts_end_ns);

	/* Notify client. Make sure all changes to memory are visible. */
	wmb();
	atomic_inc(&cli->write_idx);
	wake_up_interruptible(&cli->waitq);
	/* Reset the flags for the next sample dump */
	cli->sample_flags = 0;

	return 0;
}

static int kbasep_kinstr_prfcnt_client_start(struct kbase_kinstr_prfcnt_client *cli, u64 user_data)
{
	int ret;
	u64 tm_start, tm_end;
	int write_idx;
	int read_idx;
	size_t available_samples_count;

	WARN_ON(!cli);
	lockdep_assert_held(&cli->cmd_sync_lock);

	/* If the client is already started, the command is a no-op */
	if (cli->active)
		return 0;

	write_idx = atomic_read(&cli->write_idx);
	read_idx = atomic_read(&cli->read_idx);

	/* Check whether there is space to store atleast an implicit sample
	 * corresponding to CMD_STOP.
	 */
	available_samples_count = cli->sample_count - (size_t)(write_idx - read_idx);
	if (!available_samples_count)
		return -EBUSY;

	kbase_hwcnt_gpu_enable_map_from_cm(&cli->enable_map, &cli->config.enable_cm);

	/* Enable all the available clk_enable_map. */
	cli->enable_map.clk_enable_map = (1ull << cli->kinstr_ctx->metadata->clk_cnt) - 1;

	mutex_lock(&cli->kinstr_ctx->lock);
	/* Enable HWC from the configuration of the client creation */
	ret = kbase_hwcnt_virtualizer_client_set_counters(cli->hvcli, &cli->enable_map, &tm_start,
							  &tm_end, NULL);

	if (!ret) {
		cli->active = true;
		cli->user_data = user_data;
		cli->sample_flags = 0;

		if (cli->dump_interval_ns)
			kbasep_kinstr_prfcnt_reschedule_worker(cli->kinstr_ctx);
	}

	mutex_unlock(&cli->kinstr_ctx->lock);

	return ret;
}

static int kbasep_kinstr_prfcnt_client_stop(struct kbase_kinstr_prfcnt_client *cli, u64 user_data)
{
	int ret;
	u64 tm_start = 0;
	u64 tm_end = 0;
	size_t available_samples_count;
	int write_idx;
	int read_idx;

	WARN_ON(!cli);
	lockdep_assert_held(&cli->cmd_sync_lock);

	/* If the client is not started, the command is invalid */
	if (!cli->active)
		return -EINVAL;

	mutex_lock(&cli->kinstr_ctx->lock);

	/* Set enable map to 0 */
	kbase_hwcnt_gpu_enable_map_from_cm(&cli->enable_map, &(struct kbase_hwcnt_enable_cm){});

	/* Check whether one has the buffer to hold the last sample */
	write_idx = atomic_read(&cli->write_idx);
	read_idx = atomic_read(&cli->read_idx);

	available_samples_count = cli->sample_count - (size_t)(write_idx - read_idx);

	ret = kbase_hwcnt_virtualizer_client_set_counters(cli->hvcli, &cli->enable_map, &tm_start,
							  &tm_end, &cli->tmp_buf);
	/* If the last stop sample is in error, set the sample flag */
	if (ret)
		cli->sample_flags |= SAMPLE_FLAG_ERROR;

	/* There must be a place to save the last stop produced sample */
	if (!WARN_ON(!available_samples_count)) {
		write_idx %= cli->sample_arr.sample_count;
		/* Handle the last stop sample */
		kbase_hwcnt_gpu_enable_map_from_cm(&cli->enable_map, &cli->config.enable_cm);
		/* As this is a stop sample, mark it as MANUAL */
		kbasep_kinstr_prfcnt_client_output_sample(cli, write_idx, user_data, tm_start,
							  tm_end);
		/* Notify client. Make sure all changes to memory are visible. */
		wmb();
		atomic_inc(&cli->write_idx);
		wake_up_interruptible(&cli->waitq);
	}

	cli->active = false;
	cli->user_data = user_data;

	if (cli->dump_interval_ns)
		kbasep_kinstr_prfcnt_reschedule_worker(cli->kinstr_ctx);

	mutex_unlock(&cli->kinstr_ctx->lock);

	return 0;
}

static int kbasep_kinstr_prfcnt_client_sync_dump(struct kbase_kinstr_prfcnt_client *cli,
						 u64 user_data)
{
	int ret;

	lockdep_assert_held(&cli->cmd_sync_lock);

	/* If the client is not started, or not manual, the command invalid */
	if (!cli->active || cli->dump_interval_ns)
		return -EINVAL;

	mutex_lock(&cli->kinstr_ctx->lock);

	ret = kbasep_kinstr_prfcnt_client_dump(cli, BASE_HWCNT_READER_EVENT_MANUAL, user_data);

	mutex_unlock(&cli->kinstr_ctx->lock);

	return ret;
}

static int kbasep_kinstr_prfcnt_client_discard(struct kbase_kinstr_prfcnt_client *cli)
{
	int write_idx;

	WARN_ON(!cli);
	lockdep_assert_held(&cli->cmd_sync_lock);

	mutex_lock(&cli->kinstr_ctx->lock);

	write_idx = atomic_read(&cli->write_idx);

	/* Discard (clear) all internally buffered samples. Note, if there
	 * is a fetched sample in flight, one should not touch the read index,
	 * leaving it alone for the put-sample operation to update it. The
	 * consistency between the read_idx and the fetch_idx is coordinated by
	 * holding the cli->cmd_sync_lock.
	 */
	if (atomic_read(&cli->fetch_idx) != atomic_read(&cli->read_idx)) {
		atomic_set(&cli->fetch_idx, write_idx);
	} else {
		atomic_set(&cli->fetch_idx, write_idx);
		atomic_set(&cli->read_idx, write_idx);
	}

	mutex_unlock(&cli->kinstr_ctx->lock);

	return 0;
}

int kbasep_kinstr_prfcnt_cmd(struct kbase_kinstr_prfcnt_client *cli,
			     struct prfcnt_control_cmd *control_cmd)
{
	int ret = 0;

	mutex_lock(&cli->cmd_sync_lock);

	switch (control_cmd->cmd) {
	case PRFCNT_CONTROL_CMD_START:
		ret = kbasep_kinstr_prfcnt_client_start(cli, control_cmd->user_data);
		break;
	case PRFCNT_CONTROL_CMD_STOP:
		ret = kbasep_kinstr_prfcnt_client_stop(cli, control_cmd->user_data);
		break;
	case PRFCNT_CONTROL_CMD_SAMPLE_SYNC:
		ret = kbasep_kinstr_prfcnt_client_sync_dump(cli, control_cmd->user_data);
		break;
	case PRFCNT_CONTROL_CMD_DISCARD:
		ret = kbasep_kinstr_prfcnt_client_discard(cli);
		break;
	default:
		ret = -EINVAL;
		break;
	}

	mutex_unlock(&cli->cmd_sync_lock);

	return ret;
}

static int kbasep_kinstr_prfcnt_get_sample(struct kbase_kinstr_prfcnt_client *cli,
					   struct prfcnt_sample_access *sample_access)
{
	int write_idx;
	int read_idx;
	int fetch_idx;
	u64 sample_offset_bytes;
	struct prfcnt_metadata *sample_meta;
	int err = 0;

	mutex_lock(&cli->cmd_sync_lock);
	write_idx = atomic_read(&cli->write_idx);
	read_idx = atomic_read(&cli->read_idx);

	if (write_idx == read_idx) {
		err = -EINVAL;
		goto error_out;
	}

	/* If the client interface has already had a sample been fetched,
	 * reflected by the fetch index not equal to read_idx, i.e., typically
	 *   read_idx + 1 == fetch_idx,
	 * further fetch is not allowed until the previously fetched buffer
	 * is put back (which brings the read_idx == fetch_idx). As a design,
	 * the above add one equal condition (i.e. typical cases) may only be
	 * untrue if there had been an interface operation on sample discard,
	 * after the sample in question already been fetched, in which case,
	 * the fetch_idx could have a delta larger than 1 relative to the
	 * read_idx.
	 */
	fetch_idx = atomic_read(&cli->fetch_idx);
	if (read_idx != fetch_idx) {
		err = -EBUSY;
		goto error_out;
	}

	read_idx %= cli->sample_arr.sample_count;
	sample_meta = cli->sample_arr.samples[read_idx].sample_meta;
	sample_offset_bytes = (u64)((u8 *)sample_meta - cli->sample_arr.user_buf);

	sample_access->sequence = sample_meta->u.sample_md.seq;
	sample_access->sample_offset_bytes = sample_offset_bytes;

	/* Marking a sample has been fetched by advancing the fetch index */
	atomic_inc(&cli->fetch_idx);

error_out:
	mutex_unlock(&cli->cmd_sync_lock);
	return err;
}

static int kbasep_kinstr_prfcnt_put_sample(struct kbase_kinstr_prfcnt_client *cli,
					   struct prfcnt_sample_access *sample_access)
{
	int write_idx;
	int read_idx;
	int fetch_idx;
	u64 sample_offset_bytes;
	int err = 0;

	mutex_lock(&cli->cmd_sync_lock);
	write_idx = atomic_read(&cli->write_idx);
	read_idx = atomic_read(&cli->read_idx);

	if (write_idx == read_idx || sample_access->sequence != read_idx) {
		err = -EINVAL;
		goto error_out;
	}

	read_idx %= cli->sample_arr.sample_count;
	sample_offset_bytes = (u64)((u8 *)cli->sample_arr.samples[read_idx].sample_meta -
				    cli->sample_arr.user_buf);

	if (sample_access->sample_offset_bytes != sample_offset_bytes) {
		err = -EINVAL;
		goto error_out;
	}

	fetch_idx = atomic_read(&cli->fetch_idx);
	WARN_ON(read_idx == fetch_idx);
	/* Setting the read_idx matching the fetch_idx, signals no in-flight
	 * fetched sample.
	 */
	atomic_set(&cli->read_idx, fetch_idx);

error_out:
	mutex_unlock(&cli->cmd_sync_lock);
	return err;
}

/**
 * kbasep_kinstr_prfcnt_hwcnt_reader_ioctl() - hwcnt reader's ioctl.
 * @filp:   Non-NULL pointer to file structure.
 * @cmd:    User command.
 * @arg:    Command's argument.
 *
 * Return: 0 on success, else error code.
 */
static long kbasep_kinstr_prfcnt_hwcnt_reader_ioctl(struct file *filp, unsigned int cmd,
						    unsigned long arg)
{
	long rcode = 0;
	struct kbase_kinstr_prfcnt_client *cli;
	void __user *uarg = (void __user *)arg;

	if (!filp)
		return -EINVAL;

	cli = filp->private_data;

	if (!cli)
		return -EINVAL;

	switch (_IOC_NR(cmd)) {
	case _IOC_NR(KBASE_IOCTL_KINSTR_PRFCNT_CMD): {
		struct prfcnt_control_cmd control_cmd;
		int err;

		err = copy_from_user(&control_cmd, uarg, sizeof(control_cmd));
		if (err)
			return -EFAULT;
		rcode = kbasep_kinstr_prfcnt_cmd(cli, &control_cmd);
	} break;
	case _IOC_NR(KBASE_IOCTL_KINSTR_PRFCNT_GET_SAMPLE): {
		struct prfcnt_sample_access sample_access;
		int err;

		memset(&sample_access, 0, sizeof(sample_access));
		rcode = kbasep_kinstr_prfcnt_get_sample(cli, &sample_access);
		err = copy_to_user(uarg, &sample_access, sizeof(sample_access));
		if (err)
			return -EFAULT;
	} break;
	case _IOC_NR(KBASE_IOCTL_KINSTR_PRFCNT_PUT_SAMPLE): {
		struct prfcnt_sample_access sample_access;
		int err;

		err = copy_from_user(&sample_access, uarg, sizeof(sample_access));
		if (err)
			return -EFAULT;
		rcode = kbasep_kinstr_prfcnt_put_sample(cli, &sample_access);
	} break;
	default:
		rcode = -EINVAL;
		break;
	}

	return rcode;
}

/**
 * kbasep_kinstr_prfcnt_hwcnt_reader_mmap() - hwcnt reader's mmap.
 * @filp: Non-NULL pointer to file structure.
 * @vma:  Non-NULL pointer to vma structure.
 *
 * Return: 0 on success, else error code.
 */
static int kbasep_kinstr_prfcnt_hwcnt_reader_mmap(struct file *filp, struct vm_area_struct *vma)
{
	struct kbase_kinstr_prfcnt_client *cli;

	if (!filp || !vma)
		return -EINVAL;

	cli = filp->private_data;
	if (!cli)
		return -EINVAL;

	return remap_vmalloc_range(vma, cli->sample_arr.user_buf, 0);
}

static void
kbasep_kinstr_prfcnt_sample_array_free(struct kbase_kinstr_prfcnt_sample_array *sample_arr)
{
	if (!sample_arr)
		return;

	kfree(sample_arr->samples);
	kfree(sample_arr->blk_stt_scratch);
	vfree(sample_arr->user_buf);
	memset(sample_arr, 0, sizeof(*sample_arr));
}

static void
kbasep_kinstr_prfcnt_client_destroy_partial(struct kbase_kinstr_prfcnt_client *cli,
					    enum kbase_kinstr_prfcnt_client_init_state init_state)
{
	if (!cli)
		return;

	while (init_state-- > KINSTR_PRFCNT_UNINITIALISED) {
		switch (init_state) {
		case KINSTR_PRFCNT_INITIALISED:
			/* This shouldn't be reached */
			break;
		case KINSTR_PRFCNT_WAITQ_MUTEX:
			mutex_destroy(&cli->cmd_sync_lock);
			break;
		case KINSTR_PRFCNT_VIRTUALIZER_CLIENT:
			kbase_hwcnt_virtualizer_client_destroy(cli->hvcli);
			break;
		case KINSTR_PRFCNT_SAMPLE_ARRAY:
			kbasep_kinstr_prfcnt_sample_array_free(&cli->sample_arr);
			break;
		case KINSTR_PRFCNT_DUMP_BUFFER:
			kbase_hwcnt_dump_buffer_free(&cli->tmp_buf);
			break;
		case KINSTR_PRFCNT_ENABLE_MAP:
			kbase_hwcnt_enable_map_free(&cli->enable_map);
			break;
		case KINSTR_PRFCNT_PARSE_SETUP:
			/* Nothing to do here */
			break;
		}
	}
	kfree(cli);
}

void kbasep_kinstr_prfcnt_client_destroy(struct kbase_kinstr_prfcnt_client *cli)
{
	kbasep_kinstr_prfcnt_client_destroy_partial(cli, KINSTR_PRFCNT_INITIALISED);
}

/**
 * kbasep_kinstr_prfcnt_hwcnt_reader_release() - hwcnt reader's release.
 * @inode: Non-NULL pointer to inode structure.
 * @filp:  Non-NULL pointer to file structure.
 *
 * Return: 0 always.
 */
static int kbasep_kinstr_prfcnt_hwcnt_reader_release(struct inode *inode, struct file *filp)
{
	struct kbase_kinstr_prfcnt_client *cli = filp->private_data;

	CSTD_UNUSED(inode);

	mutex_lock(&cli->kinstr_ctx->lock);

	WARN_ON(cli->kinstr_ctx->client_count == 0);
	if (cli->kinstr_ctx->client_count > 0)
		cli->kinstr_ctx->client_count--;
	list_del(&cli->node);

	mutex_unlock(&cli->kinstr_ctx->lock);

	kbasep_kinstr_prfcnt_client_destroy(cli);

	return 0;
}

/* kinstr_prfcnt client file operations */
static const struct file_operations kinstr_prfcnt_client_fops = {
	.owner = THIS_MODULE,
	.poll = kbasep_kinstr_prfcnt_hwcnt_reader_poll,
	.unlocked_ioctl = kbasep_kinstr_prfcnt_hwcnt_reader_ioctl,
	.compat_ioctl = kbasep_kinstr_prfcnt_hwcnt_reader_ioctl,
	.mmap = kbasep_kinstr_prfcnt_hwcnt_reader_mmap,
	.release = kbasep_kinstr_prfcnt_hwcnt_reader_release,
};

size_t kbasep_kinstr_prfcnt_get_sample_md_count(const struct kbase_hwcnt_metadata *metadata,
						struct kbase_hwcnt_enable_map *enable_map)
{
	size_t blk, blk_inst;
	size_t md_count = 0;

	if (!metadata)
		return 0;

	kbase_hwcnt_metadata_for_each_block(metadata, blk, blk_inst) {
		/* Skip unavailable, non-enabled or reserved blocks */
		if (kbase_kinstr_is_block_type_reserved(metadata, blk) ||
		    !kbase_hwcnt_metadata_block_instance_avail(metadata, blk, blk_inst) ||
		    !kbase_hwcnt_enable_map_block_enabled(enable_map, blk, blk_inst))
			continue;

		md_count++;
	}

	/* add counts for clock_meta and sample meta, respectively */
	md_count += 2;

	/* Reserve one for last sentinel item. */
	md_count++;

	return md_count;
}

static size_t kbasep_kinstr_prfcnt_get_sample_size(struct kbase_kinstr_prfcnt_client *cli,
						   const struct kbase_hwcnt_metadata *metadata)
{
	size_t dump_buf_bytes;
	size_t clk_cnt_buf_bytes;
	size_t sample_meta_bytes;
	struct kbase_hwcnt_dump_buffer *dump_buf = &cli->tmp_buf;
	size_t md_count = kbasep_kinstr_prfcnt_get_sample_md_count(metadata, &cli->enable_map);

	if (!metadata)
		return 0;

	/* Note that while we have the block state bytes that form part of the dump
	 * buffer, we do not want to output them as part of the sample as they are already
	 * part of the in-band metadata.
	 */
	sample_meta_bytes = sizeof(struct prfcnt_metadata) * md_count;
	dump_buf_bytes = metadata->dump_buf_bytes;
	clk_cnt_buf_bytes = sizeof(*dump_buf->clk_cnt_buf) * metadata->clk_cnt;

	return (sample_meta_bytes + dump_buf_bytes + clk_cnt_buf_bytes);
}

/**
 * kbasep_kinstr_prfcnt_dump_worker()- Dump worker, that dumps all periodic
 *                                     clients that need to be dumped, then
 *                                     reschedules itself.
 * @work: Work structure.
 */
static void kbasep_kinstr_prfcnt_dump_worker(struct work_struct *work)
{
	struct kbase_kinstr_prfcnt_context *kinstr_ctx =
		container_of(work, struct kbase_kinstr_prfcnt_context, dump_work);
	struct kbase_kinstr_prfcnt_client *pos;
	u64 cur_time_ns;

	mutex_lock(&kinstr_ctx->lock);

	cur_time_ns = kbasep_kinstr_prfcnt_timestamp_ns();

	list_for_each_entry(pos, &kinstr_ctx->clients, node) {
		if (pos->active && (pos->next_dump_time_ns != 0) &&
		    (pos->next_dump_time_ns < cur_time_ns))
			kbasep_kinstr_prfcnt_client_dump(pos, BASE_HWCNT_READER_EVENT_PERIODIC,
							 pos->user_data);
	}

	kbasep_kinstr_prfcnt_reschedule_worker(kinstr_ctx);

	mutex_unlock(&kinstr_ctx->lock);
}

/**
 * kbasep_kinstr_prfcnt_dump_timer() - Dump timer that schedules the dump worker for
 *                              execution as soon as possible.
 * @timer: Timer structure.
 *
 * Return: HRTIMER_NORESTART always.
 */
static enum hrtimer_restart kbasep_kinstr_prfcnt_dump_timer(struct hrtimer *timer)
{
	struct kbase_kinstr_prfcnt_context *kinstr_ctx =
		container_of(timer, struct kbase_kinstr_prfcnt_context, dump_timer);

	/* We don't need to check kinstr_ctx->suspend_count here.
	 * Suspend and resume functions already ensure that the worker
	 * is cancelled when the driver is suspended, and resumed when
	 * the suspend_count reaches 0.
	 */
	kbase_hwcnt_virtualizer_queue_work(kinstr_ctx->hvirt, &kinstr_ctx->dump_work);

	return HRTIMER_NORESTART;
}

int kbase_kinstr_prfcnt_init(struct kbase_hwcnt_virtualizer *hvirt,
			     struct kbase_kinstr_prfcnt_context **out_kinstr_ctx)
{
	struct kbase_kinstr_prfcnt_context *kinstr_ctx;
	const struct kbase_hwcnt_metadata *metadata;

	if (!hvirt || !out_kinstr_ctx)
		return -EINVAL;

	metadata = kbase_hwcnt_virtualizer_metadata(hvirt);

	if (!metadata)
		return -EINVAL;

	kinstr_ctx = kzalloc(sizeof(*kinstr_ctx), GFP_KERNEL);

	if (!kinstr_ctx)
		return -ENOMEM;

	kinstr_ctx->hvirt = hvirt;
	kinstr_ctx->metadata = metadata;

	mutex_init(&kinstr_ctx->lock);
	INIT_LIST_HEAD(&kinstr_ctx->clients);
	hrtimer_init(&kinstr_ctx->dump_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	kinstr_ctx->dump_timer.function = kbasep_kinstr_prfcnt_dump_timer;
	INIT_WORK(&kinstr_ctx->dump_work, kbasep_kinstr_prfcnt_dump_worker);

	*out_kinstr_ctx = kinstr_ctx;
	return 0;
}

void kbase_kinstr_prfcnt_term(struct kbase_kinstr_prfcnt_context *kinstr_ctx)
{
	if (!kinstr_ctx)
		return;

	/* Non-zero client count implies client leak */
	if (WARN_ON(kinstr_ctx->client_count > 0)) {
		struct kbase_kinstr_prfcnt_client *pos, *n;

		list_for_each_entry_safe(pos, n, &kinstr_ctx->clients, node) {
			list_del(&pos->node);
			kinstr_ctx->client_count--;
			kbasep_kinstr_prfcnt_client_destroy(pos);
		}
	}

	cancel_work_sync(&kinstr_ctx->dump_work);

	WARN_ON(kinstr_ctx->client_count > 0);
	kfree(kinstr_ctx);
}

void kbase_kinstr_prfcnt_suspend(struct kbase_kinstr_prfcnt_context *kinstr_ctx)
{
	if (!kinstr_ctx) {
		pr_warn("%s: kinstr_ctx is NULL\n", __func__);
		return;
	}

	mutex_lock(&kinstr_ctx->lock);

	if (!WARN_ON(kinstr_ctx->suspend_count == SIZE_MAX))
		kinstr_ctx->suspend_count++;

	mutex_unlock(&kinstr_ctx->lock);

	/* Always sync cancel the timer and then the worker, regardless of the
	 * new suspend count.
	 *
	 * This ensures concurrent calls to kbase_kinstr_prfcnt_suspend() always block
	 * until kinstr_prfcnt is fully suspended.
	 *
	 * The timer is canceled before the worker, as the timer
	 * unconditionally re-enqueues the worker, but the worker checks the
	 * suspend_count that we just incremented before rescheduling the timer.
	 *
	 * Therefore if we cancel the worker first, the timer might re-enqueue
	 * the worker before we cancel the timer, but the opposite is not
	 * possible.
	 */
	hrtimer_cancel(&kinstr_ctx->dump_timer);
	cancel_work_sync(&kinstr_ctx->dump_work);
}

void kbase_kinstr_prfcnt_resume(struct kbase_kinstr_prfcnt_context *kinstr_ctx)
{
	if (!kinstr_ctx) {
		pr_warn("%s: kinstr_ctx is NULL\n", __func__);
		return;
	}

	mutex_lock(&kinstr_ctx->lock);

	if (!WARN_ON(kinstr_ctx->suspend_count == 0)) {
		kinstr_ctx->suspend_count--;

		/* Last resume, so re-enqueue the worker if we have any periodic
		 * clients.
		 */
		if (kinstr_ctx->suspend_count == 0) {
			struct kbase_kinstr_prfcnt_client *pos;
			bool has_periodic_clients = false;

			list_for_each_entry(pos, &kinstr_ctx->clients, node) {
				if (pos->dump_interval_ns != 0) {
					has_periodic_clients = true;
					break;
				}
			}

			if (has_periodic_clients)
				kbase_hwcnt_virtualizer_queue_work(kinstr_ctx->hvirt,
								   &kinstr_ctx->dump_work);
		}
	}

	mutex_unlock(&kinstr_ctx->lock);
}

static int kbasep_kinstr_prfcnt_sample_array_alloc(struct kbase_kinstr_prfcnt_client *cli,
						   const struct kbase_hwcnt_metadata *metadata)
{
	struct kbase_kinstr_prfcnt_sample_array *sample_arr = &cli->sample_arr;
	struct kbase_kinstr_prfcnt_sample *samples;
	blk_stt_t *scratch_buffer;
	size_t sample_idx;
	size_t dump_buf_bytes;
	size_t clk_cnt_buf_bytes;
	size_t sample_meta_bytes;
	size_t scratch_buffer_bytes;
	size_t md_count;
	size_t sample_size;
	size_t buffer_count = cli->config.buffer_count;

	if (!metadata || !sample_arr)
		return -EINVAL;

	md_count = kbasep_kinstr_prfcnt_get_sample_md_count(metadata, &cli->enable_map);
	sample_meta_bytes = sizeof(struct prfcnt_metadata) * md_count;
	dump_buf_bytes = metadata->dump_buf_bytes;
	clk_cnt_buf_bytes = sizeof(*samples->dump_buf.clk_cnt_buf) * metadata->clk_cnt;
	sample_size = sample_meta_bytes + dump_buf_bytes + clk_cnt_buf_bytes;

	/* In order to not run amok of the structured dump into the user_buf, we
	 * will be redirecting the block state copy to a scratch buffer instead.
	 * Note that this will not be mapped to userspace as part of the user_buf.
	 */
	scratch_buffer_bytes = metadata->blk_stt_bytes;

	samples = kmalloc_array(buffer_count, sizeof(*samples), GFP_KERNEL);

	if (!samples)
		return -ENOMEM;

	sample_arr->user_buf = vmalloc_user(sample_size * buffer_count);

	if (!sample_arr->user_buf) {
		kfree(samples);
		return -ENOMEM;
	}

	scratch_buffer = kmalloc(scratch_buffer_bytes * sizeof(*scratch_buffer), GFP_KERNEL);
	if (!scratch_buffer) {
		vfree(sample_arr->user_buf);
		kfree(samples);
		return -ENOMEM;
	}

	sample_arr->blk_stt_scratch = scratch_buffer;
	sample_arr->sample_count = buffer_count;
	sample_arr->samples = samples;

	for (sample_idx = 0; sample_idx < buffer_count; sample_idx++) {
		const size_t sample_meta_offset = sample_size * sample_idx;
		const size_t dump_buf_offset = sample_meta_offset + sample_meta_bytes;
		const size_t clk_cnt_buf_offset = dump_buf_offset + dump_buf_bytes;

		/* Internal layout in a sample buffer:
		 *   [sample metadata, dump_buf, clk_cnt_buf].
		 * The block state buffer is passed as part of the sample
		 * metadata and not as part of the sample buffer, so we redirect it
		 * to the scratch buffer allocated above.
		 */
		samples[sample_idx].dump_buf.metadata = metadata;
		samples[sample_idx].sample_meta =
			(struct prfcnt_metadata *)(sample_arr->user_buf + sample_meta_offset);
		samples[sample_idx].dump_buf.dump_buf =
			(u64 *)(sample_arr->user_buf + dump_buf_offset);
		samples[sample_idx].dump_buf.clk_cnt_buf =
			(u64 *)(sample_arr->user_buf + clk_cnt_buf_offset);
		samples[sample_idx].dump_buf.blk_stt_buf = scratch_buffer;
	}

	return 0;
}

static bool prfcnt_mode_supported(u8 mode)
{
	return (mode == PRFCNT_MODE_MANUAL) || (mode == PRFCNT_MODE_PERIODIC);
}

/**
 * prfcnt_block_supported - Check if the current GPU supports a given block
 *                          type.
 * @metadata:   GPU-specific metadata
 * @req_enable: Requested block type coming from userspace.
 *
 * Return: true if the block is supported by the underlying GPU, false
 *         otherwise.
 */
static bool prfcnt_block_supported(const struct kbase_hwcnt_metadata *metadata,
				   const struct prfcnt_request_enable *req_enable)
{
	size_t blk, blk_inst;

	kbase_hwcnt_metadata_for_each_block(metadata, blk, blk_inst) {
		const enum kbase_hwcnt_gpu_v5_block_type blk_type =
			kbase_hwcnt_metadata_block_type(metadata, blk);
		const enum prfcnt_block_type prfcnt_block_type =
			kbase_hwcnt_metadata_block_type_to_prfcnt_block_type(blk_type);

		if (prfcnt_block_type == req_enable->block_type)
			return true;
	}
	return false;
}

static void kbasep_kinstr_prfcnt_block_enable_req_to_cfg(uint64_t *cfg_cm,
							 const uint64_t *enable_mask)
{
	/* Adding a baseline value '0xF' to cfg_cm on any type that has been requested. This
	 * ensures the block states will always be reflected in the client's
	 * sample outputs, even when the client provided an all zero value mask.
	 */
	cfg_cm[0] |= (0xF | enable_mask[0]);
	cfg_cm[1] |= enable_mask[1];
}

/**
 * kbasep_kinstr_prfcnt_parse_request_enable - Parse an enable request
 * @req_enable: Performance counters enable request to parse.
 * @config:     Client object the session configuration should be written to.
 *
 * This function parses a performance counters enable request.
 * This type of request specifies a bitmask of HW counters to enable
 * for one performance counters block type. In addition to that,
 * a performance counters enable request may also set "global"
 * configuration properties that affect the whole session, like the
 * performance counters set, which shall be compatible with the same value
 * set by other performance request items.
 *
 * Return: 0 on success, else error code.
 */
static int
kbasep_kinstr_prfcnt_parse_request_enable(const struct prfcnt_request_enable *req_enable,
					  struct kbase_kinstr_prfcnt_client_config *config)
{
	int err = 0;
	u8 req_set = KBASE_HWCNT_SET_UNDEFINED, default_set;

	switch (req_enable->set) {
	case PRFCNT_SET_PRIMARY:
		req_set = KBASE_HWCNT_SET_PRIMARY;
		break;
	case PRFCNT_SET_SECONDARY:
		req_set = KBASE_HWCNT_SET_SECONDARY;
		break;
	case PRFCNT_SET_TERTIARY:
		req_set = KBASE_HWCNT_SET_TERTIARY;
		break;
	default:
		err = -EINVAL;
		break;
	}

	/* The performance counter set is a "global" property that affects
	 * the whole session. Either this is the first request that sets
	 * the value, or it shall be identical to all previous requests.
	 */
	if (!err) {
		if (config->counter_set == KBASE_HWCNT_SET_UNDEFINED)
			config->counter_set = req_set;
		else if (config->counter_set != req_set)
			err = -EINVAL;
	}

	/* Temporarily, the requested set cannot be different from the default
	 * set because it's the only one to be supported. This will change in
	 * the future.
	 */
#if defined(CONFIG_MALI_PRFCNT_SET_SECONDARY)
	default_set = KBASE_HWCNT_SET_SECONDARY;
#elif defined(CONFIG_MALI_PRFCNT_SET_TERTIARY)
	default_set = KBASE_HWCNT_SET_TERTIARY;
#else
	/* Default to primary */
	default_set = KBASE_HWCNT_SET_PRIMARY;
#endif

	if (req_set != default_set)
		err = -EINVAL;

	if (err < 0)
		return err;

	/* Enable the performance counters based on the bitmask provided
	 * by the user space client.
	 * It is possible to receive multiple requests for the same counter
	 * block, in which case the bitmask will be a logical OR of all the
	 * bitmasks given by the client.
	 */
	switch (req_enable->block_type) {
	case PRFCNT_BLOCK_TYPE_FE:
		kbasep_kinstr_prfcnt_block_enable_req_to_cfg(config->enable_cm.fe_bm,
							     req_enable->enable_mask);
		break;
	case PRFCNT_BLOCK_TYPE_TILER:
		kbasep_kinstr_prfcnt_block_enable_req_to_cfg(config->enable_cm.tiler_bm,
							     req_enable->enable_mask);
		break;
	case PRFCNT_BLOCK_TYPE_MEMORY:
		kbasep_kinstr_prfcnt_block_enable_req_to_cfg(config->enable_cm.mmu_l2_bm,
							     req_enable->enable_mask);
		break;
	case PRFCNT_BLOCK_TYPE_SHADER_CORE:
		kbasep_kinstr_prfcnt_block_enable_req_to_cfg(config->enable_cm.shader_bm,
							     req_enable->enable_mask);
		break;
	case PRFCNT_BLOCK_TYPE_FW:
		kbasep_kinstr_prfcnt_block_enable_req_to_cfg(config->enable_cm.fw_bm,
							     req_enable->enable_mask);
		break;
	case PRFCNT_BLOCK_TYPE_CSG:
		kbasep_kinstr_prfcnt_block_enable_req_to_cfg(config->enable_cm.csg_bm,
							     req_enable->enable_mask);
		break;
	default:
		err = -EINVAL;
		break;
	}

	return err;
}

/**
 * kbasep_kinstr_prfcnt_parse_request_scope - Parse a scope request
 * @req_scope: Performance counters scope request to parse.
 * @config:    Client object the session configuration should be written to.
 *
 * This function parses a performance counters scope request.
 * There are only 2 acceptable outcomes: either the client leaves the scope
 * as undefined, or all the scope requests are set to the same value.
 *
 * Return: 0 on success, else error code.
 */
static int
kbasep_kinstr_prfcnt_parse_request_scope(const struct prfcnt_request_scope *req_scope,
					 struct kbase_kinstr_prfcnt_client_config *config)
{
	int err = 0;

	if (config->scope == PRFCNT_SCOPE_RESERVED)
		config->scope = req_scope->scope;
	else if (config->scope != req_scope->scope)
		err = -EINVAL;

	return err;
}

/**
 * kbasep_kinstr_prfcnt_parse_setup - Parse session setup
 * @kinstr_ctx: Pointer to the kinstr_prfcnt context.
 * @setup:      Session setup information to parse.
 * @config:     Client object the session configuration should be written to.
 * @req_arr:    Pointer to array of request items for client session.
 *
 * This function parses the list of "request" items sent by the user space
 * client, and writes the configuration for the new client to be created
 * for the session.
 *
 * Return: 0 on success, else error code.
 */
static int kbasep_kinstr_prfcnt_parse_setup(struct kbase_kinstr_prfcnt_context *kinstr_ctx,
					    union kbase_ioctl_kinstr_prfcnt_setup *setup,
					    struct kbase_kinstr_prfcnt_client_config *config,
					    struct prfcnt_request_item *req_arr)
{
	uint32_t i;
	unsigned int item_count = setup->in.request_item_count;
	int err = 0;

	CSTD_UNUSED(kinstr_ctx);

	if (req_arr[item_count - 1].hdr.item_type != FLEX_LIST_TYPE_NONE ||
	    req_arr[item_count - 1].hdr.item_version != 0) {
		return -EINVAL;
	}

	/* The session configuration can only feature one value for some
	 * properties (like capture mode, block counter set and scope), but the
	 * client may potential issue multiple requests and try to set more than
	 * one value for those properties. While issuing multiple requests for the
	 * same property is allowed by the protocol, asking for different values
	 * is illegal. Leaving these properties as undefined is illegal, too.
	 */
	config->prfcnt_mode = PRFCNT_MODE_RESERVED;
	config->counter_set = KBASE_HWCNT_SET_UNDEFINED;
	config->scope = PRFCNT_SCOPE_RESERVED;

	for (i = 0; i < item_count - 1; i++) {
		if (req_arr[i].hdr.item_version > PRFCNT_READER_API_VERSION) {
			err = -EINVAL;
			break;
		}

		switch (req_arr[i].hdr.item_type) {
		/* Capture mode is initialized as undefined.
		 * The first request of this type sets the capture mode.
		 * The protocol allows the client to send redundant requests,
		 * but only if they replicate the same value that has already
		 * been set by the first request.
		 */
		case PRFCNT_REQUEST_TYPE_MODE:
			if (!prfcnt_mode_supported(req_arr[i].u.req_mode.mode))
				err = -EINVAL;
			else if (config->prfcnt_mode == PRFCNT_MODE_RESERVED)
				config->prfcnt_mode = req_arr[i].u.req_mode.mode;
			else if (req_arr[i].u.req_mode.mode != config->prfcnt_mode)
				err = -EINVAL;

			if (err < 0)
				break;

			if (config->prfcnt_mode == PRFCNT_MODE_PERIODIC) {
				config->period_ns =
					req_arr[i].u.req_mode.mode_config.periodic.period_ns;

				if ((config->period_ns != 0) &&
				    (config->period_ns < DUMP_INTERVAL_MIN_NS)) {
					config->period_ns = DUMP_INTERVAL_MIN_NS;
				}

				if (config->period_ns == 0)
					err = -EINVAL;
			}
			break;

		case PRFCNT_REQUEST_TYPE_ENABLE:
			if (!prfcnt_block_supported(kinstr_ctx->metadata,
						    &req_arr[i].u.req_enable)) {
				err = -EINVAL;
				break;
			}

			err = kbasep_kinstr_prfcnt_parse_request_enable(&req_arr[i].u.req_enable,
									config);
			break;

		case PRFCNT_REQUEST_TYPE_SCOPE:
			err = kbasep_kinstr_prfcnt_parse_request_scope(&req_arr[i].u.req_scope,
								       config);
			break;

		default:
			err = -EINVAL;
			break;
		}

		if (err < 0)
			break;
	}

	if (!err) {
		/* Verify that properties (like capture mode and block counter
		 * set) have been defined by the user space client.
		 */
		if (config->prfcnt_mode == PRFCNT_MODE_RESERVED)
			err = -EINVAL;

		if (config->counter_set == KBASE_HWCNT_SET_UNDEFINED)
			err = -EINVAL;
	}

	return err;
}

int kbasep_kinstr_prfcnt_client_create(struct kbase_kinstr_prfcnt_context *kinstr_ctx,
				       union kbase_ioctl_kinstr_prfcnt_setup *setup,
				       struct kbase_kinstr_prfcnt_client **out_vcli,
				       struct prfcnt_request_item *req_arr)
{
	int err;
	struct kbase_kinstr_prfcnt_client *cli;
	enum kbase_kinstr_prfcnt_client_init_state init_state;

	if (WARN_ON(!kinstr_ctx))
		return -EINVAL;

	if (WARN_ON(!setup))
		return -EINVAL;

	if (WARN_ON(!req_arr))
		return -EINVAL;

	cli = kzalloc(sizeof(*cli), GFP_KERNEL);

	if (!cli)
		return -ENOMEM;

	for (init_state = KINSTR_PRFCNT_UNINITIALISED; init_state < KINSTR_PRFCNT_INITIALISED;
	     init_state++) {
		err = 0;
		switch (init_state) {
		case KINSTR_PRFCNT_PARSE_SETUP:
			cli->kinstr_ctx = kinstr_ctx;
			err = kbasep_kinstr_prfcnt_parse_setup(kinstr_ctx, setup, &cli->config,
							       req_arr);

			break;

		case KINSTR_PRFCNT_ENABLE_MAP:
			cli->config.buffer_count = MAX_BUFFER_COUNT;
			cli->dump_interval_ns = cli->config.period_ns;
			cli->next_dump_time_ns = 0;
			cli->active = false;
			atomic_set(&cli->write_idx, 0);
			atomic_set(&cli->read_idx, 0);
			atomic_set(&cli->fetch_idx, 0);

			err = kbase_hwcnt_enable_map_alloc(kinstr_ctx->metadata, &cli->enable_map);
			break;

		case KINSTR_PRFCNT_DUMP_BUFFER:
			kbase_hwcnt_gpu_enable_map_from_cm(&cli->enable_map,
							   &cli->config.enable_cm);

			cli->sample_count = cli->config.buffer_count;
			cli->sample_size =
				kbasep_kinstr_prfcnt_get_sample_size(cli, kinstr_ctx->metadata);

			/* Use virtualizer's metadata to alloc tmp buffer which interacts with
			 * the HWC virtualizer.
			 */
			err = kbase_hwcnt_dump_buffer_alloc(kinstr_ctx->metadata, &cli->tmp_buf);
			break;

		case KINSTR_PRFCNT_SAMPLE_ARRAY:
			/* Disable clock map in setup, and enable clock map when start */
			cli->enable_map.clk_enable_map = 0;

			/* Use metadata from virtualizer to allocate dump buffers  if
			 * kinstr_prfcnt doesn't have the truncated metadata.
			 */
			err = kbasep_kinstr_prfcnt_sample_array_alloc(cli, kinstr_ctx->metadata);

			break;

		case KINSTR_PRFCNT_VIRTUALIZER_CLIENT:
			/* Set enable map to be 0 to prevent virtualizer to init and kick the
			 * backend to count.
			 */
			kbase_hwcnt_gpu_enable_map_from_cm(&cli->enable_map,
							   &(struct kbase_hwcnt_enable_cm){});

			err = kbase_hwcnt_virtualizer_client_create(kinstr_ctx->hvirt,
								    &cli->enable_map, &cli->hvcli);
			break;

		case KINSTR_PRFCNT_WAITQ_MUTEX:
			init_waitqueue_head(&cli->waitq);
			mutex_init(&cli->cmd_sync_lock);
			break;

		case KINSTR_PRFCNT_INITIALISED:
			/* This shouldn't be reached */
			break;
		}

		if (err < 0) {
			kbasep_kinstr_prfcnt_client_destroy_partial(cli, init_state);
			return err;
		}
	}
	*out_vcli = cli;

	return 0;
}

static size_t kbasep_kinstr_prfcnt_get_block_info_count(const struct kbase_hwcnt_metadata *metadata)
{
	size_t blk;
	size_t block_info_count = 0;

	if (!metadata)
		return 0;

	for (blk = 0; blk < kbase_hwcnt_metadata_block_count(metadata); blk++) {
		if (!kbase_kinstr_is_block_type_reserved(metadata, blk))
			block_info_count++;
	}

	return block_info_count;
}

static size_t kbasep_kinstr_prfcnt_get_enum_info_count(const struct kbase_hwcnt_metadata *metadata)
{
	/* First we indicate the number of non-block enable request types that
	 * are available, defined by a static array in this file.
	 */
	uint32_t count = ARRAY_SIZE(kinstr_prfcnt_supported_requests);

	/* Then we count the number of block types that should be indicated to
	 * userspace by checking the metadata.
	 */
	count += kbasep_kinstr_prfcnt_get_block_info_count(metadata);

	/* We finally include one item for sample_info (clock domain) description
	 * and one item for a sentinel to indicate that the list is done.
	 */
	return count + 2;
}

static void kbasep_kinstr_prfcnt_get_request_info_list(struct prfcnt_enum_item *item_arr,
						       size_t *arr_idx)
{
	memcpy(&item_arr[*arr_idx], kinstr_prfcnt_supported_requests,
	       sizeof(kinstr_prfcnt_supported_requests));
	*arr_idx += ARRAY_SIZE(kinstr_prfcnt_supported_requests);
}

static void kbasep_kinstr_prfcnt_get_sample_info_item(const struct kbase_hwcnt_metadata *metadata,
						      struct prfcnt_enum_item *item_arr,
						      size_t *arr_idx)
{
	struct prfcnt_enum_item sample_info = {
		.hdr = {
				.item_type = PRFCNT_ENUM_TYPE_SAMPLE_INFO,
				.item_version = PRFCNT_READER_API_VERSION,
			},
		.u.sample_info = {
				.num_clock_domains = metadata->clk_cnt,
			},
	};

	item_arr[*arr_idx] = sample_info;
	*arr_idx += 1;
}

int kbasep_kinstr_prfcnt_get_block_info_list(const struct kbase_hwcnt_metadata *metadata,
					     size_t block_set, struct prfcnt_enum_item *item_arr,
					     size_t *arr_idx)
{
	size_t blk;

	if (!metadata || !item_arr || !arr_idx)
		return -EINVAL;

	for (blk = 0; blk < kbase_hwcnt_metadata_block_count(metadata); blk++) {
		size_t blk_inst;
		size_t unused_blk_inst_count = 0;
		size_t blk_inst_count = kbase_hwcnt_metadata_block_instance_count(metadata, blk);
		enum prfcnt_block_type block_type =
			kbase_hwcnt_metadata_block_type_to_prfcnt_block_type(
				kbase_hwcnt_metadata_block_type(metadata, blk));

		if (block_type == PRFCNT_BLOCK_TYPE_RESERVED)
			continue;

		/* Count number of unused blocks to updated number of instances */
		for (blk_inst = 0; blk_inst < blk_inst_count; blk_inst++) {
			if (!kbase_hwcnt_metadata_block_instance_avail(metadata, blk, blk_inst))
				unused_blk_inst_count++;
		}

		item_arr[(*arr_idx)++] = (struct prfcnt_enum_item){
			.hdr = {
				.item_type = PRFCNT_ENUM_TYPE_BLOCK,
				.item_version = PRFCNT_READER_API_VERSION,
			},
				.u.block_counter = {
					.set = block_set,
					.block_type = block_type,
					.num_instances = blk_inst_count - unused_blk_inst_count,
					.num_values = kbase_hwcnt_metadata_block_values_count(
							metadata, blk),
					/* The bitmask of available counters should be dynamic.
					 * Temporarily, it is set to U64_MAX, waiting for the
					 * required functionality to be available in the future.
					 */
					.counter_mask = {U64_MAX, U64_MAX},
				},
		};
	}

	return 0;
}

static int
kbasep_kinstr_prfcnt_enum_info_count(struct kbase_kinstr_prfcnt_context *kinstr_ctx,
				     struct kbase_ioctl_kinstr_prfcnt_enum_info *enum_info)
{
	const struct kbase_hwcnt_metadata *metadata;

	metadata = kbase_hwcnt_virtualizer_metadata(kinstr_ctx->hvirt);

	enum_info->info_item_count = kbasep_kinstr_prfcnt_get_enum_info_count(metadata);
	enum_info->info_item_size = sizeof(struct prfcnt_enum_item);

	return 0;
}

int kbasep_kinstr_populate_prfcnt_enum_list(const struct kbase_hwcnt_metadata *metadata,
					    struct prfcnt_enum_item *prfcnt_item_arr,
					    size_t prfcnt_item_array_size)
{
	size_t arr_idx = 0;
	int err = 0;
	size_t block_info_count;

	if (!metadata)
		return -EINVAL;

	block_info_count = kbasep_kinstr_prfcnt_get_block_info_count(metadata);
	/* Some basic sanity check on the provided prfcnt_item_array_size */
	if (block_info_count >= prfcnt_item_array_size)
		return -EINVAL;

	kbasep_kinstr_prfcnt_get_request_info_list(prfcnt_item_arr, &arr_idx);

	/* Place the sample_info item */
	kbasep_kinstr_prfcnt_get_sample_info_item(metadata, prfcnt_item_arr, &arr_idx);

	if (arr_idx + block_info_count >= prfcnt_item_array_size)
		err = -EINVAL;

	if (!err) {
		size_t counter_set;

#if defined(CONFIG_MALI_PRFCNT_SET_SECONDARY)
		counter_set = KBASE_HWCNT_SET_SECONDARY;
#elif defined(CONFIG_MALI_PRFCNT_SET_TERTIARY)
		counter_set = KBASE_HWCNT_SET_TERTIARY;
#else
		/* Default to primary */
		counter_set = KBASE_HWCNT_SET_PRIMARY;
#endif
		kbasep_kinstr_prfcnt_get_block_info_list(metadata, counter_set, prfcnt_item_arr,
							 &arr_idx);
		if (arr_idx != prfcnt_item_array_size - 1)
			err = -EINVAL;
	}

	/* The last sentinel item. */
	prfcnt_item_arr[prfcnt_item_array_size - 1].hdr.item_type = FLEX_LIST_TYPE_NONE;
	prfcnt_item_arr[prfcnt_item_array_size - 1].hdr.item_version = 0;

	return err;
}

static int
kbasep_kinstr_prfcnt_enum_info_list(struct kbase_kinstr_prfcnt_context *kinstr_ctx,
				    struct kbase_ioctl_kinstr_prfcnt_enum_info *enum_info)
{
	struct prfcnt_enum_item *prfcnt_item_arr;
	int err = 0;
	const struct kbase_hwcnt_metadata *metadata;

	if ((enum_info->info_item_size == 0) || (enum_info->info_item_count == 0) ||
	    !enum_info->info_list_ptr)
		return -EINVAL;

	metadata = kbase_hwcnt_virtualizer_metadata(kinstr_ctx->hvirt);

	if (enum_info->info_item_count != kbasep_kinstr_prfcnt_get_enum_info_count(metadata))
		return -EINVAL;

	prfcnt_item_arr = kcalloc(enum_info->info_item_count, sizeof(*prfcnt_item_arr), GFP_KERNEL);
	if (!prfcnt_item_arr)
		return -ENOMEM;

	err = kbasep_kinstr_populate_prfcnt_enum_list(metadata, prfcnt_item_arr,
						      enum_info->info_item_count);

	if (!err) {
		unsigned long bytes = enum_info->info_item_count * sizeof(*prfcnt_item_arr);

		if (copy_to_user(u64_to_user_ptr(enum_info->info_list_ptr), prfcnt_item_arr, bytes))
			err = -EFAULT;
	}

	kfree(prfcnt_item_arr);
	return err;
}

int kbase_kinstr_prfcnt_enum_info(struct kbase_kinstr_prfcnt_context *kinstr_ctx,
				  struct kbase_ioctl_kinstr_prfcnt_enum_info *enum_info)
{
	int err;

	if (!kinstr_ctx || !enum_info)
		return -EINVAL;

	if (!enum_info->info_list_ptr)
		err = kbasep_kinstr_prfcnt_enum_info_count(kinstr_ctx, enum_info);
	else
		err = kbasep_kinstr_prfcnt_enum_info_list(kinstr_ctx, enum_info);

	return err;
}

int kbase_kinstr_prfcnt_setup(struct kbase_kinstr_prfcnt_context *kinstr_ctx,
			      union kbase_ioctl_kinstr_prfcnt_setup *setup)
{
	int err;
	size_t item_count, max_item_count;
	size_t bytes;
	struct prfcnt_request_item *req_arr = NULL;
	struct kbase_kinstr_prfcnt_client *cli = NULL;
	const size_t max_bytes = 32 * sizeof(*req_arr);
	const struct kbase_hwcnt_metadata *metadata;

	if (!kinstr_ctx || !setup)
		return -EINVAL;

	item_count = setup->in.request_item_count;

	if (!setup->in.requests_ptr || (item_count < 2) || (setup->in.request_item_size == 0))
		return -EINVAL;

	metadata = kbase_hwcnt_virtualizer_metadata(kinstr_ctx->hvirt);

	max_item_count = kbasep_kinstr_prfcnt_get_enum_info_count(metadata);

	/* Limiting the request items to 2x of the expected: accommodating
	 * moderate duplications but rejecting excessive abuses.
	 */
	if (item_count > 2 * max_item_count)
		return -EINVAL;

	/* Only after the initial validation do we want to add more information to the
	 * prfcnt context, in order to avoid the dependency on the enum_info IOCTL
	 * before setting up the context.
	 */
	kinstr_ctx->info_item_count = max_item_count;

	if (check_mul_overflow(item_count, sizeof(*req_arr), &bytes))
		return -EINVAL;

	/* Further limiting the max bytes to copy from userspace by setting it in the following
	 * fashion: a maximum of 1 mode item, 4 types of 3 sets for a total of 12 enable items,
	 * each currently at the size of prfcnt_request_item.
	 *
	 * Note: if more request types get added, this max limit needs to be updated.
	 */
	if (bytes > max_bytes)
		return -EINVAL;

	req_arr = memdup_user(u64_to_user_ptr(setup->in.requests_ptr), bytes);

	if (IS_ERR(req_arr))
		return PTR_ERR(req_arr);

	err = kbasep_kinstr_prfcnt_client_create(kinstr_ctx, setup, &cli, req_arr);

	if (err < 0)
		goto error;

	mutex_lock(&kinstr_ctx->lock);
	kinstr_ctx->client_count++;
	list_add(&cli->node, &kinstr_ctx->clients);
	mutex_unlock(&kinstr_ctx->lock);

	setup->out.prfcnt_metadata_item_size = sizeof(struct prfcnt_metadata);
	setup->out.prfcnt_mmap_size_bytes = cli->sample_size * cli->sample_count;

	/* Expose to user-space only once the client is fully initialized */
	err = anon_inode_getfd("[mali_kinstr_prfcnt_desc]", &kinstr_prfcnt_client_fops, cli,
			       O_RDONLY | O_CLOEXEC);

	if (err < 0)
		goto client_installed_error;

	goto free_buf;

client_installed_error:
	mutex_lock(&kinstr_ctx->lock);
	kinstr_ctx->client_count--;
	list_del(&cli->node);
	mutex_unlock(&kinstr_ctx->lock);
error:
	kbasep_kinstr_prfcnt_client_destroy(cli);
free_buf:
	kfree(req_arr);
	return err;
}
