blob: f9410a5d342a6a2b3468ce0b958cc622a4fff2b5 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
* (C) COPYRIGHT 2020-2021 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 */
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;
};
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.
*/
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 char const *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 char const *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 - ret, "0x%lx], GPU_IDLE:%d}",
rate, idle);
else
ret += snprintf(portal_msg_buf + ret,
PORTAL_MSG_LEN - 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) [ ].
*/
static char const *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 - 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.
*/
static char const *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 char const *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 char const *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_MALI_ARBITER_SUPPORT) && 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);
#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:
/* Fall through */
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:
/* Fall through */
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.
*/
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.
*/
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);
}
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;
*data = (const struct kutf_clk_rate_trace_fixture_data) { 0 };
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");
kutf_test_fail(context, "Portal allows single session only");
spin_unlock(&kbdev->pm.clk_rtm.lock);
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;
}
/**
* 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.
*/
int mali_kutf_clk_rate_trace_test_module_init(void)
{
struct kutf_suite *suite;
unsigned int filters;
union kutf_callback_data suite_data = { 0 };
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.
*/
void 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");