// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
 *
 * (C) COPYRIGHT 2018-2024 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.
 *
 */

/*
 * Implementation of hardware counter context and accumulator APIs.
 */

#include "hwcnt/mali_kbase_hwcnt_context.h"
#include "hwcnt/mali_kbase_hwcnt_accumulator.h"
#include "hwcnt/backend/mali_kbase_hwcnt_backend.h"
#include "hwcnt/mali_kbase_hwcnt_types.h"

#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/slab.h>

/**
 * enum kbase_hwcnt_accum_state - Hardware counter accumulator states.
 * @ACCUM_STATE_ERROR:    Error state, where all accumulator operations fail.
 * @ACCUM_STATE_DISABLED: Disabled state, where dumping is always disabled.
 * @ACCUM_STATE_ENABLED:  Enabled state, where dumping is enabled if there are
 *                        any enabled counters.
 */
enum kbase_hwcnt_accum_state { ACCUM_STATE_ERROR, ACCUM_STATE_DISABLED, ACCUM_STATE_ENABLED };

/**
 * struct kbase_hwcnt_accumulator - Hardware counter accumulator structure.
 * @metadata:               Pointer to immutable hwcnt metadata.
 * @backend:                Pointer to created counter backend.
 * @state:                  The current state of the accumulator.
 *                           - State transition from disabled->enabled or
 *                             disabled->error requires state_lock.
 *                           - State transition from enabled->disabled or
 *                             enabled->error requires both accum_lock and
 *                             state_lock.
 *                           - Error state persists until next disable.
 * @enable_map:             The current set of enabled counters.
 *                           - Must only be modified while holding both
 *                             accum_lock and state_lock.
 *                           - Can be read while holding either lock.
 *                           - Must stay in sync with enable_map_any_enabled.
 * @enable_map_any_enabled: True if any counters in the map are enabled, else
 *                          false. If true, and state is ACCUM_STATE_ENABLED,
 *                          then the counter backend will be enabled.
 *                           - Must only be modified while holding both
 *                             accum_lock and state_lock.
 *                           - Can be read while holding either lock.
 *                           - Must stay in sync with enable_map.
 * @scratch_map:            Scratch enable map, used as temporary enable map
 *                          storage during dumps.
 *                           - Must only be read or modified while holding
 *                             accum_lock.
 * @accum_buf:              Accumulation buffer, where dumps will be accumulated
 *                          into on transition to a disable state.
 *                           - Must only be read or modified while holding
 *                             accum_lock.
 * @ts_last_dump_ns:        Timestamp (ns) of the end time of the most recent
 *                          dump that was requested by the user.
 *                           - Must only be read or modified while holding
 *                             accum_lock.
 */
struct kbase_hwcnt_accumulator {
	const struct kbase_hwcnt_metadata *metadata;
	struct kbase_hwcnt_backend *backend;
	enum kbase_hwcnt_accum_state state;
	struct kbase_hwcnt_enable_map enable_map;
	bool enable_map_any_enabled;
	struct kbase_hwcnt_enable_map scratch_map;
	struct kbase_hwcnt_dump_buffer accum_buf;
	u64 ts_last_dump_ns;
};

/**
 * struct kbase_hwcnt_context - Hardware counter context structure.
 * @iface:         Pointer to hardware counter backend interface.
 * @state_lock:    Spinlock protecting state.
 * @disable_count: Disable count of the context. Initialised to 1.
 *                 Decremented when the accumulator is acquired, and incremented
 *                 on release. Incremented on calls to
 *                 kbase_hwcnt_context_disable[_atomic], and decremented on
 *                 calls to kbase_hwcnt_context_enable.
 *                  - Must only be read or modified while holding state_lock.
 * @accum_lock:    Mutex protecting accumulator.
 * @accum_inited:  Flag to prevent concurrent accumulator initialisation and/or
 *                 termination. Set to true before accumulator initialisation,
 *                 and false after accumulator termination.
 *                  - Must only be modified while holding both accum_lock and
 *                    state_lock.
 *                  - Can be read while holding either lock.
 * @accum:         Hardware counter accumulator structure.
 * @wq:            Centralized workqueue for users of hardware counters to
 *                 submit async hardware counter related work. Never directly
 *                 called, but it's expected that a lot of the functions in this
 *                 API will end up called from the enqueued async work.
 */
struct kbase_hwcnt_context {
	const struct kbase_hwcnt_backend_interface *iface;
	spinlock_t state_lock;
	size_t disable_count;
	struct mutex accum_lock;
	bool accum_inited;
	struct kbase_hwcnt_accumulator accum;
	struct workqueue_struct *wq;
};

int kbase_hwcnt_context_init(const struct kbase_hwcnt_backend_interface *iface,
			     struct kbase_hwcnt_context **out_hctx)
{
	struct kbase_hwcnt_context *hctx = NULL;

	if (!iface || !out_hctx)
		return -EINVAL;

	hctx = kzalloc(sizeof(*hctx), GFP_KERNEL);
	if (!hctx)
		goto err_alloc_hctx;

	hctx->iface = iface;
	spin_lock_init(&hctx->state_lock);
	hctx->disable_count = 1;
	mutex_init(&hctx->accum_lock);
	hctx->accum_inited = false;

	hctx->wq = alloc_workqueue("mali_kbase_hwcnt", WQ_HIGHPRI | WQ_UNBOUND, 0);
	if (!hctx->wq)
		goto err_alloc_workqueue;

	*out_hctx = hctx;

	return 0;

err_alloc_workqueue:
	kfree(hctx);
err_alloc_hctx:
	return -ENOMEM;
}

void kbase_hwcnt_context_term(struct kbase_hwcnt_context *hctx)
{
	if (!hctx)
		return;

	/* Make sure we didn't leak the accumulator */
	WARN_ON(hctx->accum_inited);

	/* We don't expect any work to be pending on this workqueue.
	 * Regardless, this will safely drain and complete the work.
	 */
	destroy_workqueue(hctx->wq);
	kfree(hctx);
}

/**
 * kbasep_hwcnt_accumulator_term() - Terminate the accumulator for the context.
 * @hctx: Non-NULL pointer to hardware counter context.
 */
static void kbasep_hwcnt_accumulator_term(struct kbase_hwcnt_context *hctx)
{
	WARN_ON(!hctx);
	WARN_ON(!hctx->accum_inited);

	kbase_hwcnt_enable_map_free(&hctx->accum.scratch_map);
	kbase_hwcnt_dump_buffer_free(&hctx->accum.accum_buf);
	kbase_hwcnt_enable_map_free(&hctx->accum.enable_map);
	hctx->iface->term(hctx->accum.backend);
	memset(&hctx->accum, 0, sizeof(hctx->accum));
}

/**
 * kbasep_hwcnt_accumulator_init() - Initialise the accumulator for the context.
 * @hctx: Non-NULL pointer to hardware counter context.
 *
 * Return: 0 on success, else error code.
 */
static int kbasep_hwcnt_accumulator_init(struct kbase_hwcnt_context *hctx)
{
	int errcode;

	WARN_ON(!hctx);
	WARN_ON(!hctx->accum_inited);

	errcode = hctx->iface->init(hctx->iface->info, &hctx->accum.backend);
	if (errcode)
		goto error;

	hctx->accum.metadata = hctx->iface->metadata(hctx->iface->info);
	hctx->accum.state = ACCUM_STATE_ERROR;

	errcode = kbase_hwcnt_enable_map_alloc(hctx->accum.metadata, &hctx->accum.enable_map);
	if (errcode)
		goto error;

	hctx->accum.enable_map_any_enabled = false;

	errcode = kbase_hwcnt_dump_buffer_alloc(hctx->accum.metadata, &hctx->accum.accum_buf);
	if (errcode)
		goto error;

	errcode = kbase_hwcnt_enable_map_alloc(hctx->accum.metadata, &hctx->accum.scratch_map);
	if (errcode)
		goto error;

	hctx->accum.ts_last_dump_ns = hctx->iface->timestamp_ns(hctx->accum.backend);

	return 0;

error:
	kbasep_hwcnt_accumulator_term(hctx);
	return errcode;
}

/**
 * kbasep_hwcnt_accumulator_disable() - Transition the accumulator into the
 *                                      disabled state, from the enabled or
 *                                      error states.
 * @hctx:       Non-NULL pointer to hardware counter context.
 * @accumulate: True if we should accumulate before disabling, else false.
 *              This is an optimization to allow for skipping an accumulation
 *              when terminating the accumulator.
 */
static void kbasep_hwcnt_accumulator_disable(struct kbase_hwcnt_context *hctx, bool accumulate)
{
	int errcode = 0;
	bool backend_enabled = false;
	struct kbase_hwcnt_accumulator *accum;
	unsigned long flags;
	u64 dump_time_ns;

	WARN_ON(!hctx);
	lockdep_assert_held(&hctx->accum_lock);
	WARN_ON(!hctx->accum_inited);

	accum = &hctx->accum;

	spin_lock_irqsave(&hctx->state_lock, flags);

	WARN_ON(hctx->disable_count != 0);
	WARN_ON(hctx->accum.state == ACCUM_STATE_DISABLED);

	if ((hctx->accum.state == ACCUM_STATE_ENABLED) && (accum->enable_map_any_enabled))
		backend_enabled = true;

	if (!backend_enabled)
		hctx->accum.state = ACCUM_STATE_DISABLED;

	spin_unlock_irqrestore(&hctx->state_lock, flags);

	/* Early out if the backend is not already enabled */
	if (!backend_enabled)
		return;

	if (!accumulate)
		goto disable;

	/* Try and accumulate before disabling */
	errcode = hctx->iface->dump_request(accum->backend);
	if (errcode)
		goto disable;

	errcode = hctx->iface->dump_wait(accum->backend);
	if (errcode)
		goto disable;

	errcode = hctx->iface->dump_get(accum->backend, &accum->accum_buf, &accum->enable_map,
					accumulate, &dump_time_ns);
	if (errcode)
		goto disable;

disable:
	hctx->iface->dump_disable(accum->backend, &accum->accum_buf, &accum->enable_map);

	/* Regardless of any errors during the accumulate, put the accumulator
	 * in the disabled state.
	 */
	spin_lock_irqsave(&hctx->state_lock, flags);

	hctx->accum.state = ACCUM_STATE_DISABLED;

	spin_unlock_irqrestore(&hctx->state_lock, flags);
}

/**
 * kbasep_hwcnt_accumulator_enable() - Transition the accumulator into the
 *                                     enabled state, from the disabled state.
 * @hctx: Non-NULL pointer to hardware counter context.
 */
static void kbasep_hwcnt_accumulator_enable(struct kbase_hwcnt_context *hctx)
{
	int errcode = 0;
	struct kbase_hwcnt_accumulator *accum;

	WARN_ON(!hctx);
	lockdep_assert_held(&hctx->state_lock);
	WARN_ON(!hctx->accum_inited);
	WARN_ON(hctx->accum.state != ACCUM_STATE_DISABLED);

	accum = &hctx->accum;

	/* The backend only needs enabling if any counters are enabled */
	if (accum->enable_map_any_enabled)
		errcode = hctx->iface->dump_enable_nolock(accum->backend, &accum->enable_map);

	if (!errcode)
		accum->state = ACCUM_STATE_ENABLED;
	else
		accum->state = ACCUM_STATE_ERROR;
}

/**
 * kbasep_hwcnt_accumulator_dump() - Perform a dump with the most up-to-date
 *                                   values of enabled counters possible, and
 *                                   optionally update the set of enabled
 *                                   counters.
 * @hctx:        Non-NULL pointer to the hardware counter context
 * @ts_start_ns: Non-NULL pointer where the start timestamp of the dump will
 *               be written out to on success
 * @ts_end_ns:   Non-NULL pointer where the end timestamp of the dump will
 *               be written out to on success
 * @dump_buf:    Pointer to the buffer where the dump will be written out to on
 *               success. If non-NULL, must have the same metadata as the
 *               accumulator. If NULL, the dump will be discarded.
 * @new_map:     Pointer to the new counter enable map. If non-NULL, must have
 *               the same metadata as the accumulator. If NULL, the set of
 *               enabled counters will be unchanged.
 *
 * Return:       0 on success, else error code.
 */
static int kbasep_hwcnt_accumulator_dump(struct kbase_hwcnt_context *hctx, u64 *ts_start_ns,
					 u64 *ts_end_ns, struct kbase_hwcnt_dump_buffer *dump_buf,
					 const struct kbase_hwcnt_enable_map *new_map)
{
	int errcode = 0;
	unsigned long flags;
	enum kbase_hwcnt_accum_state state;
	bool cur_map_any_enabled;
	struct kbase_hwcnt_enable_map *cur_map;
	bool new_map_any_enabled = false;
	u64 dump_time_ns = 0;
	struct kbase_hwcnt_accumulator *accum;

	WARN_ON(!hctx);
	WARN_ON(!ts_start_ns);
	WARN_ON(!ts_end_ns);
	WARN_ON(dump_buf && (dump_buf->metadata != hctx->accum.metadata));
	WARN_ON(new_map && (new_map->metadata != hctx->accum.metadata));
	WARN_ON(!hctx->accum_inited);
	lockdep_assert_held(&hctx->accum_lock);

	accum = &hctx->accum;
	cur_map = &accum->scratch_map;

	/* Save out info about the current enable map */
	cur_map_any_enabled = accum->enable_map_any_enabled;
	kbase_hwcnt_enable_map_copy(cur_map, &accum->enable_map);

	if (new_map)
		new_map_any_enabled = kbase_hwcnt_enable_map_any_enabled(new_map);

	/*
	 * We're holding accum_lock, so the accumulator state might transition
	 * from disabled to enabled during this function (as enabling is lock
	 * free), but it will never disable (as disabling needs to hold the
	 * accum_lock), nor will it ever transition from enabled to error (as
	 * an enable while we're already enabled is impossible).
	 *
	 * If we're already disabled, we'll only look at the accumulation buffer
	 * rather than do a real dump, so a concurrent enable does not affect
	 * us.
	 *
	 * If a concurrent enable fails, we might transition to the error
	 * state, but again, as we're only looking at the accumulation buffer,
	 * it's not an issue.
	 */
	spin_lock_irqsave(&hctx->state_lock, flags);

	state = accum->state;

	/*
	 * Update the new map now, such that if an enable occurs during this
	 * dump then that enable will set the new map. If we're already enabled,
	 * then we'll do it ourselves after the dump.
	 */
	if (new_map) {
		kbase_hwcnt_enable_map_copy(&accum->enable_map, new_map);
		accum->enable_map_any_enabled = new_map_any_enabled;
	}

	spin_unlock_irqrestore(&hctx->state_lock, flags);

	/* Error state, so early out. No need to roll back any map updates */
	if (state == ACCUM_STATE_ERROR)
		return -EIO;

	/* Initiate the dump if the backend is enabled. */
	if ((state == ACCUM_STATE_ENABLED) && cur_map_any_enabled) {
		if (dump_buf) {
			errcode = hctx->iface->dump_request(accum->backend);
		} else {
			dump_time_ns = hctx->iface->timestamp_ns(accum->backend);
			errcode = hctx->iface->dump_clear(accum->backend);
		}

		if (errcode)
			goto error;
	} else {
		dump_time_ns = hctx->iface->timestamp_ns(accum->backend);
	}

	/* Wait for any requested dumps to complete */
	if ((state == ACCUM_STATE_ENABLED) && cur_map_any_enabled && dump_buf) {
		errcode = hctx->iface->dump_wait(accum->backend);
		if (errcode)
			goto error;
	}

	/* If we're enabled and there's a new enable map, change the enabled set
	 * as soon after the dump has completed as possible.
	 */
	if ((state == ACCUM_STATE_ENABLED) && new_map) {
		/* Backend is only enabled if there were any enabled counters */
		if (cur_map_any_enabled) {
			/* In this case we do *not* want to have the buffer updated with extra
			 * block state, it should instead remain in the backend until the next dump
			 * happens, hence supplying NULL as the dump_buffer parameter here.
			 *
			 * Attempting to take ownership of backend-accumulated block state at this
			 * point will instead give inaccurate information. For example the dump
			 * buffer for 'set_counters' operation might be dumping a period that
			 * should've been entirely in the 'ON' state, but would report it as
			 * partially in the 'OFF' state. Instead, that 'OFF' state should be
			 * reported in the _next_ dump.
			 */
			hctx->iface->dump_disable(accum->backend, NULL, NULL);
		}

		/* (Re-)enable the backend if the new map has enabled counters.
		 * No need to acquire the spinlock, as concurrent enable while
		 * we're already enabled and holding accum_lock is impossible.
		 */
		if (new_map_any_enabled) {
			errcode = hctx->iface->dump_enable(accum->backend, new_map);
			if (errcode)
				goto error;
		}
	}

	/* Copy, accumulate, or zero into the dest buffer to finish */
	if ((state == ACCUM_STATE_ENABLED) && cur_map_any_enabled) {
		/* If we dumped, copy or accumulate it into the destination */
		errcode = hctx->iface->dump_get(accum->backend, &accum->accum_buf, cur_map, true,
						&dump_time_ns);
		if (errcode)
			goto error;
	}

	/* Write out timestamps */
	*ts_start_ns = accum->ts_last_dump_ns;
	*ts_end_ns = dump_time_ns;

	accum->ts_last_dump_ns = dump_time_ns;

	/* Copy any accumulation into the dest buffer */
	/* If the caller of the interface provided a dump buffer, then the
	 * accumulation buffer will have been copied over and we need to prepare
	 * it for the next invocation of the accumulator dump.
	 */
	if (dump_buf) {
		kbase_hwcnt_dump_buffer_copy(dump_buf, &accum->accum_buf, cur_map);
		kbase_hwcnt_dump_buffer_zero(&accum->accum_buf, &accum->enable_map);
	}

	/* If a new enable map is programmed onto the hardware, we need to ensure
	 * that no counters remain in the accumulation buffer that could erroneously
	 * be copied over to the caller of the interface during the next invocation,
	 * using the new enable map as the current enable map.
	 */
	if (new_map && !dump_buf)
		kbase_hwcnt_dump_buffer_zero_strict(&accum->accum_buf);

	/* If the accumulator was not enabled when a dump was requested, then
	 * we never queried the backend for the state of the blocks during this
	 * period. This means that, if the accumulator was enabled and disabled,
	 * the next sample may not have an OFF block state unless we append it
	 * at the level of the accumulator.
	 */
	if (state != ACCUM_STATE_ENABLED)
		kbase_hwcnt_dump_buffer_block_state_update(&accum->accum_buf, &accum->enable_map,
							   KBASE_HWCNT_STATE_OFF);

	return 0;
error:
	/* An error was only physically possible if the backend was enabled */
	WARN_ON(state != ACCUM_STATE_ENABLED);

	/* Disable the backend, and transition to the error state. In this case, we can try to save
	 * the block state into the accumulated buffer, but there's no guarantee we'll have one, so
	 * this is more of a 'best effort' for error cases. There would be an suitable block
	 * state recorded on the next dump_enable() anyway.
	 */
	hctx->iface->dump_disable(accum->backend, &accum->accum_buf, cur_map);
	spin_lock_irqsave(&hctx->state_lock, flags);

	accum->state = ACCUM_STATE_ERROR;

	spin_unlock_irqrestore(&hctx->state_lock, flags);

	return errcode;
}

/**
 * kbasep_hwcnt_context_disable() - Increment the disable count of the context.
 * @hctx:       Non-NULL pointer to hardware counter context.
 * @accumulate: True if we should accumulate before disabling, else false.
 */
static void kbasep_hwcnt_context_disable(struct kbase_hwcnt_context *hctx, bool accumulate)
{
	unsigned long flags;

	WARN_ON(!hctx);
	lockdep_assert_held(&hctx->accum_lock);

	if (!kbase_hwcnt_context_disable_atomic(hctx)) {
		kbasep_hwcnt_accumulator_disable(hctx, accumulate);

		spin_lock_irqsave(&hctx->state_lock, flags);

		/* Atomic disable failed and we're holding the mutex, so current
		 * disable count must be 0.
		 */
		WARN_ON(hctx->disable_count != 0);
		hctx->disable_count++;

		spin_unlock_irqrestore(&hctx->state_lock, flags);
	}
}

int kbase_hwcnt_accumulator_acquire(struct kbase_hwcnt_context *hctx,
				    struct kbase_hwcnt_accumulator **accum)
{
	int errcode = 0;
	unsigned long flags;

	if (!hctx || !accum)
		return -EINVAL;

	mutex_lock(&hctx->accum_lock);
	spin_lock_irqsave(&hctx->state_lock, flags);

	if (!hctx->accum_inited)
		/* Set accum initing now to prevent concurrent init */
		hctx->accum_inited = true;
	else
		/* Already have an accum, or already being inited */
		errcode = -EBUSY;

	spin_unlock_irqrestore(&hctx->state_lock, flags);
	mutex_unlock(&hctx->accum_lock);

	if (errcode)
		return errcode;

	errcode = kbasep_hwcnt_accumulator_init(hctx);

	if (errcode) {
		mutex_lock(&hctx->accum_lock);
		spin_lock_irqsave(&hctx->state_lock, flags);

		hctx->accum_inited = false;

		spin_unlock_irqrestore(&hctx->state_lock, flags);
		mutex_unlock(&hctx->accum_lock);

		return errcode;
	}

	/* Inform the backend that counter collection has been enabled. */
	hctx->iface->acquire(hctx->accum.backend);

	spin_lock_irqsave(&hctx->state_lock, flags);

	WARN_ON(hctx->disable_count == 0);
	WARN_ON(hctx->accum.enable_map_any_enabled);

	/* Decrement the disable count to allow the accumulator to be accessible
	 * now that it's fully constructed.
	 */
	hctx->disable_count--;

	/*
	 * Make sure the accumulator is initialised to the correct state.
	 * Regardless of initial state, counters don't need to be enabled via
	 * the backend, as the initial enable map has no enabled counters.
	 */
	hctx->accum.state = (hctx->disable_count == 0) ? ACCUM_STATE_ENABLED : ACCUM_STATE_DISABLED;

	spin_unlock_irqrestore(&hctx->state_lock, flags);

	*accum = &hctx->accum;

	return 0;
}

void kbase_hwcnt_accumulator_release(struct kbase_hwcnt_accumulator *accum)
{
	unsigned long flags;
	struct kbase_hwcnt_context *hctx;

	if (!accum)
		return;

	hctx = container_of(accum, struct kbase_hwcnt_context, accum);

	mutex_lock(&hctx->accum_lock);

	/* Double release is a programming error */
	WARN_ON(!hctx->accum_inited);

	/* Disable the context to ensure the accumulator is inaccesible while
	 * we're destroying it. This performs the corresponding disable count
	 * increment to the decrement done during acquisition.
	 */
	kbasep_hwcnt_context_disable(hctx, false);

	mutex_unlock(&hctx->accum_lock);

	/* Inform the backend that counter collection has been disabled. */
	hctx->iface->release(hctx->accum.backend);

	kbasep_hwcnt_accumulator_term(hctx);

	mutex_lock(&hctx->accum_lock);
	spin_lock_irqsave(&hctx->state_lock, flags);

	hctx->accum_inited = false;

	spin_unlock_irqrestore(&hctx->state_lock, flags);
	mutex_unlock(&hctx->accum_lock);
}

void kbase_hwcnt_context_disable(struct kbase_hwcnt_context *hctx)
{
	if (WARN_ON(!hctx))
		return;

	/* Try and atomically disable first, so we can avoid locking the mutex
	 * if we don't need to.
	 */
	if (kbase_hwcnt_context_disable_atomic(hctx))
		return;

	mutex_lock(&hctx->accum_lock);

	kbasep_hwcnt_context_disable(hctx, true);

	mutex_unlock(&hctx->accum_lock);
}

bool kbase_hwcnt_context_disable_atomic(struct kbase_hwcnt_context *hctx)
{
	unsigned long flags;
	bool atomic_disabled = false;

	if (WARN_ON(!hctx))
		return false;

	spin_lock_irqsave(&hctx->state_lock, flags);

	if (!WARN_ON(hctx->disable_count == SIZE_MAX)) {
		/*
		 * If disable count is non-zero, we can just bump the disable
		 * count.
		 *
		 * Otherwise, we can't disable in an atomic context.
		 */
		if (hctx->disable_count != 0) {
			hctx->disable_count++;
			atomic_disabled = true;
		}
	}

	spin_unlock_irqrestore(&hctx->state_lock, flags);

	return atomic_disabled;
}

void kbase_hwcnt_context_enable(struct kbase_hwcnt_context *hctx)
{
	unsigned long flags;

	if (WARN_ON(!hctx))
		return;

	spin_lock_irqsave(&hctx->state_lock, flags);

	if (!WARN_ON(hctx->disable_count == 0)) {
		if (hctx->disable_count == 1)
			kbasep_hwcnt_accumulator_enable(hctx);

		hctx->disable_count--;
	}

	spin_unlock_irqrestore(&hctx->state_lock, flags);
}

const struct kbase_hwcnt_metadata *kbase_hwcnt_context_metadata(struct kbase_hwcnt_context *hctx)
{
	if (!hctx)
		return NULL;

	return hctx->iface->metadata(hctx->iface->info);
}

bool kbase_hwcnt_context_queue_work(struct kbase_hwcnt_context *hctx, struct work_struct *work)
{
	if (WARN_ON(!hctx) || WARN_ON(!work))
		return false;

	return queue_work(hctx->wq, work);
}

int kbase_hwcnt_accumulator_set_counters(struct kbase_hwcnt_accumulator *accum,
					 const struct kbase_hwcnt_enable_map *new_map,
					 u64 *ts_start_ns, u64 *ts_end_ns,
					 struct kbase_hwcnt_dump_buffer *dump_buf)
{
	int errcode;
	struct kbase_hwcnt_context *hctx;

	if (!accum || !new_map || !ts_start_ns || !ts_end_ns)
		return -EINVAL;

	hctx = container_of(accum, struct kbase_hwcnt_context, accum);

	if ((new_map->metadata != hctx->accum.metadata) ||
	    (dump_buf && (dump_buf->metadata != hctx->accum.metadata)))
		return -EINVAL;

	mutex_lock(&hctx->accum_lock);

	errcode = kbasep_hwcnt_accumulator_dump(hctx, ts_start_ns, ts_end_ns, dump_buf, new_map);

	mutex_unlock(&hctx->accum_lock);

	return errcode;
}

int kbase_hwcnt_accumulator_dump(struct kbase_hwcnt_accumulator *accum, u64 *ts_start_ns,
				 u64 *ts_end_ns, struct kbase_hwcnt_dump_buffer *dump_buf)
{
	int errcode;
	struct kbase_hwcnt_context *hctx;

	if (!accum || !ts_start_ns || !ts_end_ns)
		return -EINVAL;

	hctx = container_of(accum, struct kbase_hwcnt_context, accum);

	if (dump_buf && (dump_buf->metadata != hctx->accum.metadata))
		return -EINVAL;

	mutex_lock(&hctx->accum_lock);

	errcode = kbasep_hwcnt_accumulator_dump(hctx, ts_start_ns, ts_end_ns, dump_buf, NULL);

	mutex_unlock(&hctx->accum_lock);

	return errcode;
}

u64 kbase_hwcnt_accumulator_timestamp_ns(struct kbase_hwcnt_accumulator *accum)
{
	struct kbase_hwcnt_context *hctx;

	if (WARN_ON(!accum))
		return 0;

	hctx = container_of(accum, struct kbase_hwcnt_context, accum);
	return hctx->iface->timestamp_ns(accum->backend);
}
