| // 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"); |