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

#include <linux/fdtable.h>
#include <linux/module.h>

#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/ktime.h>
#include <linux/version.h>
#if (KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE)
#include <linux/sched/task.h>
#else
#include <linux/sched.h>
#endif
#include "mali_kbase.h"
#include "backend/gpu/mali_kbase_irq_internal.h"
#include "backend/gpu/mali_kbase_pm_internal.h"
#include "backend/gpu/mali_kbase_clk_rate_trace_mgr.h"

#include <kutf/kutf_suite.h>
#include <kutf/kutf_utils.h>
#include <kutf/kutf_helpers.h>
#include <kutf/kutf_helpers_user.h>

#include "../mali_kutf_clk_rate_trace_test.h"

#define MINOR_FOR_FIRST_KBASE_DEV (-1)

/* KUTF test application pointer for this test */
static struct kutf_application *kutf_app;

enum portal_server_state {
	PORTAL_STATE_NO_CLK,
	PORTAL_STATE_LIVE,
	PORTAL_STATE_CLOSING,
};

/**
 * struct clk_trace_snapshot - Trace info data on a clock.
 * @previous_rate:   Snapshot start point clock rate.
 * @current_rate:    End point clock rate. It becomes the start rate of the
 *                   next trace snapshot.
 * @rate_up_cnt:     Count in the snapshot duration when the clock trace
 *                   write is a rate of higher value than the last.
 * @rate_down_cnt:   Count in the snapshot duration when the clock trace write
 *                   is a rate of lower value than the last.
 */
struct clk_trace_snapshot {
	unsigned long previous_rate;
	unsigned long current_rate;
	u32 rate_up_cnt;
	u32 rate_down_cnt;
};

/**
 * struct kutf_clk_rate_trace_fixture_data - Fixture data for the test.
 * @kbdev:            kbase device for the GPU.
 * @listener:         Clock rate change listener structure.
 * @invoke_notify:    When true, invoke notify command is being executed.
 * @snapshot:         Clock trace update snapshot data array. A snapshot
 *                    for each clock contains info accumulated beteen two
 *                    GET_TRACE_SNAPSHOT requests.
 * @nclks:            Number of clocks visible to the trace portal.
 * @pm_ctx_cnt:       Net count of PM (Power Management) context INC/DEC
 *                    PM_CTX_CNT requests made to the portal. On change from
 *                    0 to 1 (INC), or, 1 to 0 (DEC), a PM context action is
 *                    triggered.
 * @total_update_cnt: Total number of received trace write callbacks.
 * @server_state:     Portal server operational state.
 * @result_msg:       Message for the test result.
 * @test_status:      Portal test reslt status.
 */
struct kutf_clk_rate_trace_fixture_data {
	struct kbase_device *kbdev;
	struct kbase_clk_rate_listener listener;
	bool invoke_notify;
	struct clk_trace_snapshot snapshot[BASE_MAX_NR_CLOCKS_REGULATORS];
	unsigned int nclks;
	unsigned int pm_ctx_cnt;
	unsigned int total_update_cnt;
	enum portal_server_state server_state;
	char const *result_msg;
	enum kutf_result_status test_status;
};

struct clk_trace_portal_input {
	struct kutf_helper_named_val cmd_input;
	enum kbasep_clk_rate_trace_req portal_cmd;
	int named_val_err;
};

struct kbasep_cmd_name_pair {
	enum kbasep_clk_rate_trace_req cmd;
	const char *name;
};

static const struct kbasep_cmd_name_pair kbasep_portal_cmd_name_map[] = {
	{ PORTAL_CMD_GET_PLATFORM, GET_PLATFORM },
	{ PORTAL_CMD_GET_CLK_RATE_MGR, GET_CLK_RATE_MGR },
	{ PORTAL_CMD_GET_CLK_RATE_TRACE, GET_CLK_RATE_TRACE },
	{ PORTAL_CMD_GET_TRACE_SNAPSHOT, GET_TRACE_SNAPSHOT },
	{ PORTAL_CMD_INC_PM_CTX_CNT, INC_PM_CTX_CNT },
	{ PORTAL_CMD_DEC_PM_CTX_CNT, DEC_PM_CTX_CNT },
	{ PORTAL_CMD_CLOSE_PORTAL, CLOSE_PORTAL },
	{ PORTAL_CMD_INVOKE_NOTIFY_42KHZ, INVOKE_NOTIFY_42KHZ },
};

/* Global pointer for the kutf_portal_trace_write() to use. When
 * this pointer is engaged, new requests for create fixture will fail
 * hence limiting the use of the portal at any time to a singleton.
 */
static struct kutf_clk_rate_trace_fixture_data *g_ptr_portal_data;

#define PORTAL_MSG_LEN (KUTF_MAX_LINE_LENGTH - MAX_REPLY_NAME_LEN)
static char portal_msg_buf[PORTAL_MSG_LEN];

static void kutf_portal_trace_write(struct kbase_clk_rate_listener *listener, u32 index,
				    u32 new_rate)
{
	struct clk_trace_snapshot *snapshot;
	struct kutf_clk_rate_trace_fixture_data *data;

	if (listener == NULL) {
		pr_err("%s - index: %u, new_rate: %u, listener is NULL\n", __func__, index,
		       new_rate);
		return;
	}

	data = container_of(listener, struct kutf_clk_rate_trace_fixture_data, listener);

	lockdep_assert_held(&data->kbdev->pm.clk_rtm.lock);

	if (WARN_ON(g_ptr_portal_data == NULL))
		return;
	if (WARN_ON(index >= g_ptr_portal_data->nclks))
		return;

	/* This callback is triggered by invoke notify command, skipping */
	if (data->invoke_notify)
		return;

	snapshot = &g_ptr_portal_data->snapshot[index];
	if (new_rate > snapshot->current_rate)
		snapshot->rate_up_cnt++;
	else
		snapshot->rate_down_cnt++;
	snapshot->current_rate = new_rate;
	g_ptr_portal_data->total_update_cnt++;
}

static void kutf_set_pm_ctx_active(struct kutf_context *context)
{
	struct kutf_clk_rate_trace_fixture_data *data = context->fixture;

	if (WARN_ON(data->pm_ctx_cnt != 1))
		return;

	kbase_pm_context_active(data->kbdev);
	kbase_pm_wait_for_desired_state(data->kbdev);
#if !MALI_USE_CSF
	kbase_pm_request_gpu_cycle_counter(data->kbdev);
#endif
}

static void kutf_set_pm_ctx_idle(struct kutf_context *context)
{
	struct kutf_clk_rate_trace_fixture_data *data = context->fixture;

	if (WARN_ON(data->pm_ctx_cnt > 0))
		return;
#if !MALI_USE_CSF
	kbase_pm_release_gpu_cycle_counter(data->kbdev);
#endif
	kbase_pm_context_idle(data->kbdev);
}

static const char *kutf_clk_trace_do_change_pm_ctx(struct kutf_context *context,
						   struct clk_trace_portal_input *cmd)
{
	struct kutf_clk_rate_trace_fixture_data *data = context->fixture;
	int seq = cmd->cmd_input.u.val_u64 & 0xFF;
	const unsigned int cnt = data->pm_ctx_cnt;
	const enum kbasep_clk_rate_trace_req req = cmd->portal_cmd;
	char const *errmsg = NULL;

	WARN_ON(req != PORTAL_CMD_INC_PM_CTX_CNT && req != PORTAL_CMD_DEC_PM_CTX_CNT);

	if (req == PORTAL_CMD_INC_PM_CTX_CNT && cnt < UINT_MAX) {
		data->pm_ctx_cnt++;
		if (data->pm_ctx_cnt == 1)
			kutf_set_pm_ctx_active(context);
	}

	if (req == PORTAL_CMD_DEC_PM_CTX_CNT && cnt > 0) {
		data->pm_ctx_cnt--;
		if (data->pm_ctx_cnt == 0)
			kutf_set_pm_ctx_idle(context);
	}

	/* Skip the length check, no chance of overflow for two ints */
	snprintf(portal_msg_buf, PORTAL_MSG_LEN, "{SEQ:%d, PM_CTX_CNT:%u}", seq, data->pm_ctx_cnt);

	if (kutf_helper_send_named_str(context, "ACK", portal_msg_buf)) {
		pr_warn("Error in sending ack for adjusting pm_ctx_cnt\n");
		errmsg = kutf_dsprintf(&context->fixture_pool,
				       "Error in sending ack for adjusting pm_ctx_cnt");
	}

	return errmsg;
}

static const char *kutf_clk_trace_do_get_rate(struct kutf_context *context,
					      struct clk_trace_portal_input *cmd)
{
	struct kutf_clk_rate_trace_fixture_data *data = context->fixture;
	struct kbase_device *kbdev = data->kbdev;
	int seq = cmd->cmd_input.u.val_u64 & 0xFF;
	unsigned long rate;
	bool idle;
	int ret;
	int i;
	char const *errmsg = NULL;

	WARN_ON((cmd->portal_cmd != PORTAL_CMD_GET_CLK_RATE_MGR) &&
		(cmd->portal_cmd != PORTAL_CMD_GET_CLK_RATE_TRACE));

	ret = snprintf(portal_msg_buf, PORTAL_MSG_LEN, "{SEQ:%d, RATE:[", seq);

	for (i = 0; i < data->nclks; i++) {
		spin_lock(&kbdev->pm.clk_rtm.lock);
		if (cmd->portal_cmd == PORTAL_CMD_GET_CLK_RATE_MGR)
			rate = kbdev->pm.clk_rtm.clks[i]->clock_val;
		else
			rate = data->snapshot[i].current_rate;
		idle = kbdev->pm.clk_rtm.gpu_idle;
		spin_unlock(&kbdev->pm.clk_rtm.lock);

		if ((i + 1) == data->nclks)
			ret += snprintf(portal_msg_buf + ret, PORTAL_MSG_LEN - (size_t)ret,
					"0x%lx], GPU_IDLE:%d}", rate, idle);
		else
			ret += snprintf(portal_msg_buf + ret, PORTAL_MSG_LEN - (size_t)ret,
					"0x%lx, ", rate);

		if (ret >= PORTAL_MSG_LEN) {
			pr_warn("Message buf overflow with rate array data\n");
			return kutf_dsprintf(&context->fixture_pool,
					     "Message buf overflow with rate array data");
		}
	}

	if (kutf_helper_send_named_str(context, "ACK", portal_msg_buf)) {
		pr_warn("Error in sending back rate array\n");
		errmsg = kutf_dsprintf(&context->fixture_pool, "Error in sending rate array");
	}

	return errmsg;
}

/**
 * kutf_clk_trace_do_get_snapshot() - Send back the current snapshot
 * @context:  KUTF context
 * @cmd:      The decoded portal input request
 *
 * The accumulated clock rate trace information is kept inside as an snapshot
 * record. A user request of getting the snapshot marks the closure of the
 * current snapshot record, and the start of the next one. The response
 * message contains the current snapshot record, with each clock's
 * data sequentially placed inside (array marker) [ ].
 *
 * Return: generated string
 */
static const char *kutf_clk_trace_do_get_snapshot(struct kutf_context *context,
						  struct clk_trace_portal_input *cmd)
{
	struct kutf_clk_rate_trace_fixture_data *data = context->fixture;
	struct clk_trace_snapshot snapshot;
	int seq = cmd->cmd_input.u.val_u64 & 0xFF;
	int ret;
	int i;
	char const *fmt;
	char const *errmsg = NULL;

	WARN_ON(cmd->portal_cmd != PORTAL_CMD_GET_TRACE_SNAPSHOT);

	ret = snprintf(portal_msg_buf, PORTAL_MSG_LEN, "{SEQ:%d, SNAPSHOT_ARRAY:[", seq);

	for (i = 0; i < data->nclks; i++) {
		spin_lock(&data->kbdev->pm.clk_rtm.lock);
		/* copy out the snapshot of the clock */
		snapshot = data->snapshot[i];
		/* Set the next snapshot start condition */
		data->snapshot[i].previous_rate = snapshot.current_rate;
		data->snapshot[i].rate_up_cnt = 0;
		data->snapshot[i].rate_down_cnt = 0;
		spin_unlock(&data->kbdev->pm.clk_rtm.lock);

		/* Check i corresponding to the last clock */
		if ((i + 1) == data->nclks)
			fmt = "(0x%lx, 0x%lx, %u, %u)]}";
		else
			fmt = "(0x%lx, 0x%lx, %u, %u), ";
		ret += snprintf(portal_msg_buf + ret, PORTAL_MSG_LEN - (size_t)ret, fmt,
				snapshot.previous_rate, snapshot.current_rate, snapshot.rate_up_cnt,
				snapshot.rate_down_cnt);
		if (ret >= PORTAL_MSG_LEN) {
			pr_warn("Message buf overflow with snapshot data\n");
			return kutf_dsprintf(&context->fixture_pool,
					     "Message buf overflow with snapshot data");
		}
	}

	if (kutf_helper_send_named_str(context, "ACK", portal_msg_buf)) {
		pr_warn("Error in sending back snapshot array\n");
		errmsg = kutf_dsprintf(&context->fixture_pool, "Error in sending snapshot array");
	}

	return errmsg;
}

/**
 * kutf_clk_trace_do_invoke_notify_42k() - Invokes the stored notification callback
 * @context:  KUTF context
 * @cmd:      The decoded portal input request
 *
 * Invokes frequency change notification callbacks with a fake
 * GPU frequency 42 kHz for the top clock domain.
 *
 * Return: generated string
 */
static const char *kutf_clk_trace_do_invoke_notify_42k(struct kutf_context *context,
						       struct clk_trace_portal_input *cmd)
{
	struct kutf_clk_rate_trace_fixture_data *data = context->fixture;
	int seq = cmd->cmd_input.u.val_u64 & 0xFF;
	const unsigned long new_rate_hz = 42000;
	int ret;
	char const *errmsg = NULL;
	struct kbase_clk_rate_trace_manager *clk_rtm = &data->kbdev->pm.clk_rtm;

	WARN_ON(cmd->portal_cmd != PORTAL_CMD_INVOKE_NOTIFY_42KHZ);

	spin_lock(&clk_rtm->lock);

	data->invoke_notify = true;
	kbase_clk_rate_trace_manager_notify_all(clk_rtm, 0, new_rate_hz);
	data->invoke_notify = false;

	spin_unlock(&clk_rtm->lock);

	ret = snprintf(portal_msg_buf, PORTAL_MSG_LEN, "{SEQ:%d, HZ:%lu}", seq, new_rate_hz);

	if (ret >= PORTAL_MSG_LEN) {
		pr_warn("Message buf overflow with invoked data\n");
		return kutf_dsprintf(&context->fixture_pool,
				     "Message buf overflow with invoked data");
	}

	if (kutf_helper_send_named_str(context, "ACK", portal_msg_buf)) {
		pr_warn("Error in sending ack for " INVOKE_NOTIFY_42KHZ "request\n");
		errmsg = kutf_dsprintf(&context->fixture_pool,
				       "Error in sending ack for " INVOKE_NOTIFY_42KHZ "request");
	}

	return errmsg;
}

static const char *kutf_clk_trace_do_close_portal(struct kutf_context *context,
						  struct clk_trace_portal_input *cmd)
{
	struct kutf_clk_rate_trace_fixture_data *data = context->fixture;
	int seq = cmd->cmd_input.u.val_u64 & 0xFF;
	char const *errmsg = NULL;

	WARN_ON(cmd->portal_cmd != PORTAL_CMD_CLOSE_PORTAL);

	data->server_state = PORTAL_STATE_CLOSING;

	/* Skip the length check, no chance of overflow for two ints */
	snprintf(portal_msg_buf, PORTAL_MSG_LEN, "{SEQ:%d, PM_CTX_CNT:%u}", seq, data->pm_ctx_cnt);

	if (kutf_helper_send_named_str(context, "ACK", portal_msg_buf)) {
		pr_warn("Error in sending ack for " CLOSE_PORTAL "reuquest\n");
		errmsg = kutf_dsprintf(&context->fixture_pool,
				       "Error in sending ack for " CLOSE_PORTAL "reuquest");
	}

	return errmsg;
}

/**
 * kutf_clk_trace_do_get_platform() - Gets platform information
 * @context:  KUTF context
 * @cmd:      The decoded portal input request
 *
 * Checks the gpu node in the device tree to see if arbitration is enabled
 * If so determines device tree whether platform is PV or PTM
 *
 * Return: A string to indicate the platform (PV/PTM/GPU/UNKNOWN)
 */
static const char *kutf_clk_trace_do_get_platform(struct kutf_context *context,
						  struct clk_trace_portal_input *cmd)
{
	int seq = cmd->cmd_input.u.val_u64 & 0xFF;
	char const *errmsg = NULL;
	const void *arbiter_if_node = NULL;
	const void *power_node = NULL;
	const char *platform = "GPU";
#if defined(CONFIG_OF)
	struct kutf_clk_rate_trace_fixture_data *data = context->fixture;

	arbiter_if_node = of_get_property(data->kbdev->dev->of_node, "arbiter-if", NULL);
	if (!arbiter_if_node)
		arbiter_if_node = of_get_property(data->kbdev->dev->of_node, "arbiter_if", NULL);
#endif
	if (arbiter_if_node) {
		power_node = of_find_compatible_node(NULL, NULL, "arm,mali-gpu-power");
		if (power_node) {
			platform = "PV";
		} else {
			power_node = of_find_compatible_node(NULL, NULL, "arm,mali-ptm");
			if (power_node)
				platform = "PTM";
			else
				platform = "UNKNOWN";
		}
	} else {
		platform = "GPU";
	}

	pr_debug("%s - platform is %s\n", __func__, platform);
	snprintf(portal_msg_buf, PORTAL_MSG_LEN, "{SEQ:%d, PLATFORM:%s}", seq, platform);

	WARN_ON(cmd->portal_cmd != PORTAL_CMD_GET_PLATFORM);

	if (kutf_helper_send_named_str(context, "ACK", portal_msg_buf)) {
		pr_warn("Error in sending ack for " CLOSE_PORTAL "reuquest\n");
		errmsg = kutf_dsprintf(&context->fixture_pool,
				       "Error in sending ack for " GET_PLATFORM "request");
	}

	return errmsg;
}

static bool kutf_clk_trace_dequeue_portal_cmd(struct kutf_context *context,
					      struct clk_trace_portal_input *cmd)
{
	int i;
	int err = kutf_helper_receive_named_val(context, &cmd->cmd_input);

	cmd->named_val_err = err;
	if (err == KUTF_HELPER_ERR_NONE && cmd->cmd_input.type == KUTF_HELPER_VALTYPE_U64) {
		/* All portal request commands are of format (named u64):
		 *   CMD_NAME=1234
		 * where, 1234 is a (variable) sequence number tag.
		 */
		for (i = 0; i < PORTAL_TOTAL_CMDS; i++) {
			if (strcmp(cmd->cmd_input.val_name, kbasep_portal_cmd_name_map[i].name))
				continue;

			cmd->portal_cmd = kbasep_portal_cmd_name_map[i].cmd;
			return true;
		}
	}

	cmd->portal_cmd = PORTAL_CMD_INVALID;
	return false;
}

static void kutf_clk_trace_flag_result(struct kutf_context *context, enum kutf_result_status result,
				       char const *msg)
{
	struct kutf_clk_rate_trace_fixture_data *data = context->fixture;

	if (result > data->test_status) {
		data->test_status = result;
		if (msg)
			data->result_msg = msg;
		if (data->server_state == PORTAL_STATE_LIVE && result > KUTF_RESULT_WARN) {
			data->server_state = PORTAL_STATE_CLOSING;
		}
	}
}

static bool kutf_clk_trace_process_portal_cmd(struct kutf_context *context,
					      struct clk_trace_portal_input *cmd)
{
	char const *errmsg = NULL;

	BUILD_BUG_ON(ARRAY_SIZE(kbasep_portal_cmd_name_map) != PORTAL_TOTAL_CMDS);
	WARN_ON(cmd->portal_cmd == PORTAL_CMD_INVALID);

	switch (cmd->portal_cmd) {
	case PORTAL_CMD_GET_PLATFORM:
		errmsg = kutf_clk_trace_do_get_platform(context, cmd);
		break;
	case PORTAL_CMD_GET_CLK_RATE_MGR:
		fallthrough;
	case PORTAL_CMD_GET_CLK_RATE_TRACE:
		errmsg = kutf_clk_trace_do_get_rate(context, cmd);
		break;
	case PORTAL_CMD_GET_TRACE_SNAPSHOT:
		errmsg = kutf_clk_trace_do_get_snapshot(context, cmd);
		break;
	case PORTAL_CMD_INC_PM_CTX_CNT:
		fallthrough;
	case PORTAL_CMD_DEC_PM_CTX_CNT:
		errmsg = kutf_clk_trace_do_change_pm_ctx(context, cmd);
		break;
	case PORTAL_CMD_CLOSE_PORTAL:
		errmsg = kutf_clk_trace_do_close_portal(context, cmd);
		break;
	case PORTAL_CMD_INVOKE_NOTIFY_42KHZ:
		errmsg = kutf_clk_trace_do_invoke_notify_42k(context, cmd);
		break;
	default:
		pr_warn("Don't know how to handle portal_cmd: %d, abort session.\n",
			cmd->portal_cmd);
		errmsg = kutf_dsprintf(&context->fixture_pool,
				       "Don't know how to handle portal_cmd: %d", cmd->portal_cmd);
		break;
	}

	if (errmsg)
		kutf_clk_trace_flag_result(context, KUTF_RESULT_FAIL, errmsg);

	return (errmsg == NULL);
}

/**
 * kutf_clk_trace_do_nack_response() - respond a NACK to erroneous input
 * @context:  KUTF context
 * @cmd:      The erroneous input request
 *
 * This function deal with an erroneous input request, and respond with
 * a proper 'NACK' message.
 *
 * Return: 0 on success, non-zero on failure
 */
static int kutf_clk_trace_do_nack_response(struct kutf_context *context,
					   struct clk_trace_portal_input *cmd)
{
	int seq;
	int err;
	char const *errmsg = NULL;

	WARN_ON(cmd->portal_cmd != PORTAL_CMD_INVALID);

	if (cmd->named_val_err == KUTF_HELPER_ERR_NONE &&
	    cmd->cmd_input.type == KUTF_HELPER_VALTYPE_U64) {
		/* Keep seq number as % 256 */
		seq = cmd->cmd_input.u.val_u64 & 255;
		snprintf(portal_msg_buf, PORTAL_MSG_LEN, "{SEQ:%d, MSG: Unknown command '%s'.}",
			 seq, cmd->cmd_input.val_name);
		err = kutf_helper_send_named_str(context, "NACK", portal_msg_buf);
	} else
		err = kutf_helper_send_named_str(
			context, "NACK", "Wrong portal cmd format (Ref example: CMD_NAME=0X16)");

	if (err) {
		errmsg = kutf_dsprintf(&context->fixture_pool,
				       "Failed to send portal NACK response");
		kutf_clk_trace_flag_result(context, KUTF_RESULT_FAIL, errmsg);
	}

	return err;
}

/**
 * kutf_clk_trace_barebone_check() - Sanity test on the clock tracing
 * @context:	KUTF context
 *
 * This function carries out some basic test on the tracing operation:
 *     1). GPU idle on test start, trace rate should be 0 (low power state)
 *     2). Make sure GPU is powered up, the trace rate should match
 *         that from the clcok manager's internal recorded rate
 *     3). If the GPU active transition occurs following 2), there
 *         must be rate change event from tracing.
 */
static void kutf_clk_trace_barebone_check(struct kutf_context *context)
{
	struct kutf_clk_rate_trace_fixture_data *data = context->fixture;
	struct kbase_device *kbdev = data->kbdev;
	bool fail = false;
	bool idle[2] = { false };
	char const *msg = NULL;
	int i;

	/* Check consistency if gpu happens to be idle */
	spin_lock(&kbdev->pm.clk_rtm.lock);
	idle[0] = kbdev->pm.clk_rtm.gpu_idle;
	if (kbdev->pm.clk_rtm.gpu_idle) {
		for (i = 0; i < data->nclks; i++) {
			if (data->snapshot[i].current_rate) {
				/* Idle should have a rate 0 */
				fail = true;
				break;
			}
		}
	}
	spin_unlock(&kbdev->pm.clk_rtm.lock);
	if (fail) {
		msg = kutf_dsprintf(&context->fixture_pool, "GPU Idle not yielding 0-rate");
		pr_err("Trace did not see idle rate\n");
	} else {
		/* Make local PM active if not done so yet */
		if (data->pm_ctx_cnt == 0) {
			/* Ensure the GPU is powered */
			data->pm_ctx_cnt++;
			kutf_set_pm_ctx_active(context);
		}
		/* Checking the rate is consistent */
		spin_lock(&kbdev->pm.clk_rtm.lock);
		idle[1] = kbdev->pm.clk_rtm.gpu_idle;
		for (i = 0; i < data->nclks; i++) {
			/* Rate match between the manager and the trace */
			if (kbdev->pm.clk_rtm.clks[i]->clock_val !=
			    data->snapshot[i].current_rate) {
				fail = true;
				break;
			}
		}
		spin_unlock(&kbdev->pm.clk_rtm.lock);

		if (idle[1]) {
			msg = kutf_dsprintf(&context->fixture_pool,
					    "GPU still idle after set_pm_ctx_active");
			pr_err("GPU still idle after set_pm_ctx_active\n");
		}

		if (!msg && fail) {
			msg = kutf_dsprintf(&context->fixture_pool,
					    "Trace rate not matching Clk manager's read");
			pr_err("Trace rate not matching Clk manager's read\n");
		}
	}

	if (!msg && idle[0] && !idle[1] && !data->total_update_cnt) {
		msg = kutf_dsprintf(&context->fixture_pool, "Trace update did not occur");
		pr_err("Trace update did not occur\n");
	}
	if (msg)
		kutf_clk_trace_flag_result(context, KUTF_RESULT_FAIL, msg);
	else if (!data->total_update_cnt) {
		msg = kutf_dsprintf(&context->fixture_pool,
				    "No trace update seen during the test!");
		kutf_clk_trace_flag_result(context, KUTF_RESULT_WARN, msg);
	}
}

static bool kutf_clk_trace_end_of_stream(struct clk_trace_portal_input *cmd)
{
	return (cmd->named_val_err == -EBUSY);
}

static void kutf_clk_trace_no_clks_dummy(struct kutf_context *context)
{
	struct clk_trace_portal_input cmd;
	unsigned long timeout = jiffies + HZ * 2;
	bool has_cmd;

	while (time_before(jiffies, timeout)) {
		if (kutf_helper_pending_input(context)) {
			has_cmd = kutf_clk_trace_dequeue_portal_cmd(context, &cmd);
			if (!has_cmd && kutf_clk_trace_end_of_stream(&cmd))
				break;

			kutf_helper_send_named_str(context, "NACK",
						   "Fatal! No clocks visible, aborting");
		}
		msleep(20);
	}

	kutf_clk_trace_flag_result(context, KUTF_RESULT_FATAL, "No clocks visble to the portal");
}

/**
 * mali_kutf_clk_rate_trace_test_portal() - Service portal input
 * @context:	KUTF context
 *
 * The test portal operates on input requests. If the input request is one
 * of the recognized portal commands, it handles it accordingly. Otherwise
 * a negative response 'NACK' is returned. The portal service terminates
 * when a 'CLOSE_PORTAL' request is received, or due to an internal error.
 * Both case would result in the server_state transitioned to CLOSING.
 *
 * If the portal is closed on request, a sanity test on the clock rate
 * trace operation is undertaken via function:
 *    kutf_clk_trace_barebone_check();
 */
static void mali_kutf_clk_rate_trace_test_portal(struct kutf_context *context)
{
	struct kutf_clk_rate_trace_fixture_data *data = context->fixture;
	struct clk_trace_portal_input new_cmd;

	pr_debug("Test portal service start\n");

	while (data->server_state == PORTAL_STATE_LIVE) {
		if (kutf_clk_trace_dequeue_portal_cmd(context, &new_cmd))
			kutf_clk_trace_process_portal_cmd(context, &new_cmd);
		else if (kutf_clk_trace_end_of_stream(&new_cmd))
			/* Dequeue on portal input, end of stream */
			data->server_state = PORTAL_STATE_CLOSING;
		else
			kutf_clk_trace_do_nack_response(context, &new_cmd);
	}

	/* Closing, exhausting all the pending inputs with NACKs. */
	if (data->server_state == PORTAL_STATE_CLOSING) {
		while (kutf_helper_pending_input(context) &&
		       (kutf_clk_trace_dequeue_portal_cmd(context, &new_cmd) ||
			!kutf_clk_trace_end_of_stream(&new_cmd))) {
			kutf_helper_send_named_str(context, "NACK", "Portal closing down");
		}
	}

	/* If no portal error, do a barebone test here irrespective
	 * whatever the portal live session has been testing, which
	 * is entirely driven by the user-side via portal requests.
	 */
	if (data->test_status <= KUTF_RESULT_WARN) {
		if (data->server_state != PORTAL_STATE_NO_CLK)
			kutf_clk_trace_barebone_check(context);
		else {
			/* No clocks case, NACK 2-sec for the fatal situation */
			kutf_clk_trace_no_clks_dummy(context);
		}
	}

	/* If we have changed pm_ctx count, drop it back */
	if (data->pm_ctx_cnt) {
		/* Although we count on portal requests, it only has material
		 * impact when from 0 -> 1. So the reverse is a simple one off.
		 */
		data->pm_ctx_cnt = 0;
		kutf_set_pm_ctx_idle(context);
	}

	/* Finally log the test result line */
	if (data->test_status < KUTF_RESULT_WARN)
		kutf_test_pass(context, data->result_msg);
	else if (data->test_status == KUTF_RESULT_WARN)
		kutf_test_warn(context, data->result_msg);
	else if (data->test_status == KUTF_RESULT_FATAL)
		kutf_test_fatal(context, data->result_msg);
	else
		kutf_test_fail(context, data->result_msg);

	pr_debug("Test end\n");
}

/**
 * mali_kutf_clk_rate_trace_create_fixture() - Creates the fixture data
 *                           required for mali_kutf_clk_rate_trace_test_portal.
 * @context:	KUTF context.
 *
 * Return: Fixture data created on success or NULL on failure
 */
static void *mali_kutf_clk_rate_trace_create_fixture(struct kutf_context *context)
{
	struct kutf_clk_rate_trace_fixture_data *data;
	struct kbase_device *kbdev;
	unsigned long rate;
	int i;

	/* Acquire the kbase device */
	pr_debug("Finding device\n");
	kbdev = kbase_find_device(MINOR_FOR_FIRST_KBASE_DEV);
	if (kbdev == NULL) {
		kutf_test_fail(context, "Failed to find kbase device");
		return NULL;
	}

	pr_debug("Creating fixture\n");
	data = kutf_mempool_alloc(&context->fixture_pool,
				  sizeof(struct kutf_clk_rate_trace_fixture_data));
	if (!data)
		return NULL;

	memset(data, 0, sizeof(*data));
	pr_debug("Hooking up the test portal to kbdev clk rate trace\n");
	spin_lock(&kbdev->pm.clk_rtm.lock);

	if (g_ptr_portal_data != NULL) {
		pr_warn("Test portal is already in use, run aborted\n");
		spin_unlock(&kbdev->pm.clk_rtm.lock);
		kutf_test_fail(context, "Portal allows single session only");
		return NULL;
	}

	for (i = 0; i < BASE_MAX_NR_CLOCKS_REGULATORS; i++) {
		if (kbdev->pm.clk_rtm.clks[i]) {
			data->nclks++;
			if (kbdev->pm.clk_rtm.gpu_idle)
				rate = 0;
			else
				rate = kbdev->pm.clk_rtm.clks[i]->clock_val;
			data->snapshot[i].previous_rate = rate;
			data->snapshot[i].current_rate = rate;
		}
	}

	spin_unlock(&kbdev->pm.clk_rtm.lock);

	if (data->nclks) {
		/* Subscribe this test server portal */
		data->listener.notify = kutf_portal_trace_write;
		data->invoke_notify = false;

		kbase_clk_rate_trace_manager_subscribe(&kbdev->pm.clk_rtm, &data->listener);
		/* Update the kutf_server_portal fixture_data pointer */
		g_ptr_portal_data = data;
	}

	data->kbdev = kbdev;
	data->result_msg = NULL;
	data->test_status = KUTF_RESULT_PASS;

	if (data->nclks == 0) {
		data->server_state = PORTAL_STATE_NO_CLK;
		pr_debug("Kbdev has no clocks for rate trace");
	} else
		data->server_state = PORTAL_STATE_LIVE;

	pr_debug("Created fixture\n");

	return data;
}

/**
 * mali_kutf_clk_rate_trace_remove_fixture - Destroy fixture data previously created by
 *                                           mali_kutf_clk_rate_trace_create_fixture.
 *
 * @context:             KUTF context.
 */
static void mali_kutf_clk_rate_trace_remove_fixture(struct kutf_context *context)
{
	struct kutf_clk_rate_trace_fixture_data *data = context->fixture;
	struct kbase_device *kbdev = data->kbdev;

	if (data->nclks) {
		/* Clean up the portal trace write arrangement */
		g_ptr_portal_data = NULL;

		kbase_clk_rate_trace_manager_unsubscribe(&kbdev->pm.clk_rtm, &data->listener);
	}
	pr_debug("Destroying fixture\n");
	kbase_release_device(kbdev);
	pr_debug("Destroyed fixture\n");
}

/**
 * mali_kutf_clk_rate_trace_test_module_init() - Entry point for test mdoule.
 *
 * Return: 0 on success, error code otherwise
 */
static int __init mali_kutf_clk_rate_trace_test_module_init(void)
{
	struct kutf_suite *suite;
	unsigned int filters;
	union kutf_callback_data suite_data = { NULL };

	pr_debug("Creating app\n");

	g_ptr_portal_data = NULL;
	kutf_app = kutf_create_application(CLK_RATE_TRACE_APP_NAME);

	if (!kutf_app) {
		pr_warn("Creation of app " CLK_RATE_TRACE_APP_NAME " failed!\n");
		return -ENOMEM;
	}

	pr_debug("Create suite %s\n", CLK_RATE_TRACE_SUITE_NAME);
	suite = kutf_create_suite_with_filters_and_data(kutf_app, CLK_RATE_TRACE_SUITE_NAME, 1,
							mali_kutf_clk_rate_trace_create_fixture,
							mali_kutf_clk_rate_trace_remove_fixture,
							KUTF_F_TEST_GENERIC, suite_data);

	if (!suite) {
		pr_warn("Creation of suite %s failed!\n", CLK_RATE_TRACE_SUITE_NAME);
		kutf_destroy_application(kutf_app);
		return -ENOMEM;
	}

	filters = suite->suite_default_flags;
	kutf_add_test_with_filters(suite, 0x0, CLK_RATE_TRACE_PORTAL,
				   mali_kutf_clk_rate_trace_test_portal, filters);

	pr_debug("Init complete\n");
	return 0;
}

/**
 * mali_kutf_clk_rate_trace_test_module_exit() - Module exit point for this
 *                                               test.
 */
static void __exit mali_kutf_clk_rate_trace_test_module_exit(void)
{
	pr_debug("Exit start\n");
	kutf_destroy_application(kutf_app);
	pr_debug("Exit complete\n");
}

module_init(mali_kutf_clk_rate_trace_test_module_init);
module_exit(mali_kutf_clk_rate_trace_test_module_exit);

MODULE_LICENSE("GPL");
