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