diff options
Diffstat (limited to 'dvalin/kernel/drivers/gpu/arm/midgard/tests/mali_kutf_clk_rate_trace/kernel/mali_kutf_clk_rate_trace_test.c')
-rw-r--r-- | dvalin/kernel/drivers/gpu/arm/midgard/tests/mali_kutf_clk_rate_trace/kernel/mali_kutf_clk_rate_trace_test.c | 957 |
1 files changed, 957 insertions, 0 deletions
diff --git a/dvalin/kernel/drivers/gpu/arm/midgard/tests/mali_kutf_clk_rate_trace/kernel/mali_kutf_clk_rate_trace_test.c b/dvalin/kernel/drivers/gpu/arm/midgard/tests/mali_kutf_clk_rate_trace/kernel/mali_kutf_clk_rate_trace_test.c new file mode 100644 index 0000000..f9410a5 --- /dev/null +++ b/dvalin/kernel/drivers/gpu/arm/midgard/tests/mali_kutf_clk_rate_trace/kernel/mali_kutf_clk_rate_trace_test.c @@ -0,0 +1,957 @@ +// 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"); |