diff options
Diffstat (limited to 'mali_kbase/mali_kbase_gpu_metrics.c')
-rw-r--r-- | mali_kbase/mali_kbase_gpu_metrics.c | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/mali_kbase/mali_kbase_gpu_metrics.c b/mali_kbase/mali_kbase_gpu_metrics.c new file mode 100644 index 0000000..af3a08d --- /dev/null +++ b/mali_kbase/mali_kbase_gpu_metrics.c @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note +/* + * + * (C) COPYRIGHT 2023 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. + * + */ + +#if IS_ENABLED(CONFIG_MALI_TRACE_POWER_GPU_WORK_PERIOD) +#include "mali_power_gpu_work_period_trace.h" +#include <mali_kbase_gpu_metrics.h> + +/** + * enum gpu_metrics_ctx_flags - Flags for the GPU metrics context + * + * @ACTIVE_INTERVAL_IN_WP: Flag set when the application first becomes active in + * the current work period. + * + * @INSIDE_ACTIVE_LIST: Flag to track if object is in kbase_device::gpu_metrics::active_list + * + * All members need to be separate bits. This enum is intended for use in a + * bitmask where multiple values get OR-ed together. + */ +enum gpu_metrics_ctx_flags { + ACTIVE_INTERVAL_IN_WP = 1 << 0, + INSIDE_ACTIVE_LIST = 1 << 1, +}; + +static inline bool gpu_metrics_ctx_flag(struct kbase_gpu_metrics_ctx *gpu_metrics_ctx, + enum gpu_metrics_ctx_flags flag) +{ + return (gpu_metrics_ctx->flags & flag); +} + +static inline void gpu_metrics_ctx_flag_set(struct kbase_gpu_metrics_ctx *gpu_metrics_ctx, + enum gpu_metrics_ctx_flags flag) +{ + gpu_metrics_ctx->flags |= flag; +} + +static inline void gpu_metrics_ctx_flag_clear(struct kbase_gpu_metrics_ctx *gpu_metrics_ctx, + enum gpu_metrics_ctx_flags flag) +{ + gpu_metrics_ctx->flags &= ~flag; +} + +static inline void validate_tracepoint_data(struct kbase_gpu_metrics_ctx *gpu_metrics_ctx, + u64 start_time, u64 end_time, u64 total_active) +{ +#ifdef CONFIG_MALI_DEBUG + WARN(total_active > NSEC_PER_SEC, + "total_active %llu > 1 second for aid %u active_cnt %u", + total_active, gpu_metrics_ctx->aid, gpu_metrics_ctx->active_cnt); + + WARN(start_time >= end_time, + "start_time %llu >= end_time %llu for aid %u active_cnt %u", + start_time, end_time, gpu_metrics_ctx->aid, gpu_metrics_ctx->active_cnt); + + WARN(total_active > (end_time - start_time), + "total_active %llu > end_time %llu - start_time %llu for aid %u active_cnt %u", + total_active, end_time, start_time, + gpu_metrics_ctx->aid, gpu_metrics_ctx->active_cnt); + + WARN(gpu_metrics_ctx->prev_wp_active_end_time > start_time, + "prev_wp_active_end_time %llu > start_time %llu for aid %u active_cnt %u", + gpu_metrics_ctx->prev_wp_active_end_time, start_time, + gpu_metrics_ctx->aid, gpu_metrics_ctx->active_cnt); +#endif +} + +static void emit_tracepoint_for_active_gpu_metrics_ctx(struct kbase_device *kbdev, + struct kbase_gpu_metrics_ctx *gpu_metrics_ctx, u64 current_time) +{ + const u64 start_time = gpu_metrics_ctx->first_active_start_time; + u64 total_active = gpu_metrics_ctx->total_active; + u64 end_time; + + /* Check if the GPU activity is currently ongoing */ + if (gpu_metrics_ctx->active_cnt) { + end_time = current_time; + total_active += + end_time - gpu_metrics_ctx->last_active_start_time; + + gpu_metrics_ctx->first_active_start_time = current_time; + gpu_metrics_ctx->last_active_start_time = current_time; + } else { + end_time = gpu_metrics_ctx->last_active_end_time; + gpu_metrics_ctx_flag_clear(gpu_metrics_ctx, ACTIVE_INTERVAL_IN_WP); + } + + trace_gpu_work_period(kbdev->id, gpu_metrics_ctx->aid, + start_time, end_time, total_active); + + validate_tracepoint_data(gpu_metrics_ctx, start_time, end_time, total_active); + gpu_metrics_ctx->prev_wp_active_end_time = end_time; + gpu_metrics_ctx->total_active = 0; +} + +void kbase_gpu_metrics_ctx_put(struct kbase_device *kbdev, + struct kbase_gpu_metrics_ctx *gpu_metrics_ctx) +{ + WARN_ON(list_empty(&gpu_metrics_ctx->link)); + WARN_ON(!gpu_metrics_ctx->kctx_count); + + gpu_metrics_ctx->kctx_count--; + if (gpu_metrics_ctx->kctx_count) + return; + + if (gpu_metrics_ctx_flag(gpu_metrics_ctx, ACTIVE_INTERVAL_IN_WP)) + emit_tracepoint_for_active_gpu_metrics_ctx(kbdev, + gpu_metrics_ctx, ktime_get_raw_ns()); + + list_del_init(&gpu_metrics_ctx->link); + kfree(gpu_metrics_ctx); +} + +struct kbase_gpu_metrics_ctx *kbase_gpu_metrics_ctx_get(struct kbase_device *kbdev, u32 aid) +{ + struct kbase_gpu_metrics *gpu_metrics = &kbdev->gpu_metrics; + struct kbase_gpu_metrics_ctx *gpu_metrics_ctx; + + list_for_each_entry(gpu_metrics_ctx, &gpu_metrics->active_list, link) { + if (gpu_metrics_ctx->aid == aid) { + WARN_ON(!gpu_metrics_ctx->kctx_count); + gpu_metrics_ctx->kctx_count++; + return gpu_metrics_ctx; + } + } + + list_for_each_entry(gpu_metrics_ctx, &gpu_metrics->inactive_list, link) { + if (gpu_metrics_ctx->aid == aid) { + WARN_ON(!gpu_metrics_ctx->kctx_count); + gpu_metrics_ctx->kctx_count++; + return gpu_metrics_ctx; + } + } + + return NULL; +} + +void kbase_gpu_metrics_ctx_init(struct kbase_device *kbdev, + struct kbase_gpu_metrics_ctx *gpu_metrics_ctx, unsigned int aid) +{ + gpu_metrics_ctx->aid = aid; + gpu_metrics_ctx->total_active = 0; + gpu_metrics_ctx->kctx_count = 1; + gpu_metrics_ctx->active_cnt = 0; + gpu_metrics_ctx->prev_wp_active_end_time = 0; + gpu_metrics_ctx->flags = 0; + list_add_tail(&gpu_metrics_ctx->link, &kbdev->gpu_metrics.inactive_list); +} + +void kbase_gpu_metrics_ctx_start_activity(struct kbase_context *kctx, u64 timestamp_ns) +{ + struct kbase_gpu_metrics_ctx *gpu_metrics_ctx = kctx->gpu_metrics_ctx; + + gpu_metrics_ctx->active_cnt++; + if (gpu_metrics_ctx->active_cnt == 1) + gpu_metrics_ctx->last_active_start_time = timestamp_ns; + + if (!gpu_metrics_ctx_flag(gpu_metrics_ctx, ACTIVE_INTERVAL_IN_WP)) { + gpu_metrics_ctx->first_active_start_time = timestamp_ns; + gpu_metrics_ctx_flag_set(gpu_metrics_ctx, ACTIVE_INTERVAL_IN_WP); + } + + if (!gpu_metrics_ctx_flag(gpu_metrics_ctx, INSIDE_ACTIVE_LIST)) { + list_move_tail(&gpu_metrics_ctx->link, &kctx->kbdev->gpu_metrics.active_list); + gpu_metrics_ctx_flag_set(gpu_metrics_ctx, INSIDE_ACTIVE_LIST); + } +} + +void kbase_gpu_metrics_ctx_end_activity(struct kbase_context *kctx, u64 timestamp_ns) +{ + struct kbase_gpu_metrics_ctx *gpu_metrics_ctx = kctx->gpu_metrics_ctx; + + if (WARN_ON_ONCE(!gpu_metrics_ctx->active_cnt)) + return; + + if (--gpu_metrics_ctx->active_cnt) + return; + + if (likely(timestamp_ns > gpu_metrics_ctx->last_active_start_time)) { + gpu_metrics_ctx->last_active_end_time = timestamp_ns; + gpu_metrics_ctx->total_active += + timestamp_ns - gpu_metrics_ctx->last_active_start_time; + return; + } + + /* Due to conversion from system timestamp to CPU timestamp (which involves rounding) + * the value for start and end timestamp could come as same. + */ + if (timestamp_ns == gpu_metrics_ctx->last_active_start_time) { + gpu_metrics_ctx->last_active_end_time = timestamp_ns + 1; + gpu_metrics_ctx->total_active += 1; + return; + } + + /* The following check is to detect the situation where 'ACT=0' event was not visible to + * the Kbase even though the system timestamp value sampled by FW was less than the system + * timestamp value sampled by Kbase just before the draining of trace buffer. + */ + if (gpu_metrics_ctx->last_active_start_time == gpu_metrics_ctx->first_active_start_time && + gpu_metrics_ctx->prev_wp_active_end_time == gpu_metrics_ctx->first_active_start_time) { + WARN_ON_ONCE(gpu_metrics_ctx->total_active); + gpu_metrics_ctx->last_active_end_time = + gpu_metrics_ctx->prev_wp_active_end_time + 1; + gpu_metrics_ctx->total_active = 1; + return; + } + + WARN_ON_ONCE(1); +} + +void kbase_gpu_metrics_emit_tracepoint(struct kbase_device *kbdev, u64 ts) +{ + struct kbase_gpu_metrics *gpu_metrics = &kbdev->gpu_metrics; + struct kbase_gpu_metrics_ctx *gpu_metrics_ctx, *tmp; + + list_for_each_entry_safe(gpu_metrics_ctx, tmp, &gpu_metrics->active_list, link) { + if (!gpu_metrics_ctx_flag(gpu_metrics_ctx, ACTIVE_INTERVAL_IN_WP)) { + WARN_ON(!gpu_metrics_ctx_flag(gpu_metrics_ctx, INSIDE_ACTIVE_LIST)); + WARN_ON(gpu_metrics_ctx->active_cnt); + list_move_tail(&gpu_metrics_ctx->link, &gpu_metrics->inactive_list); + gpu_metrics_ctx_flag_clear(gpu_metrics_ctx, INSIDE_ACTIVE_LIST); + continue; + } + + emit_tracepoint_for_active_gpu_metrics_ctx(kbdev, gpu_metrics_ctx, ts); + } +} + +int kbase_gpu_metrics_init(struct kbase_device *kbdev) +{ + INIT_LIST_HEAD(&kbdev->gpu_metrics.active_list); + INIT_LIST_HEAD(&kbdev->gpu_metrics.inactive_list); + + dev_info(kbdev->dev, "GPU metrics tracepoint support enabled"); + return 0; +} + +void kbase_gpu_metrics_term(struct kbase_device *kbdev) +{ + WARN_ON_ONCE(!list_empty(&kbdev->gpu_metrics.active_list)); + WARN_ON_ONCE(!list_empty(&kbdev->gpu_metrics.inactive_list)); +} + +#endif |