summaryrefslogtreecommitdiff
path: root/mali_kbase/mali_kbase_gpu_metrics.c
blob: af3a08d691b20dc6d75dddf4e3ff25f772f504fa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
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