xref: /aosp_15_r20/external/igt-gpu-tools/benchmarks/kms_throughput.c (revision d83cc019efdc2edc6c4b16e9034a3ceb8d35d77c)
1 /*
2  * Copyright © 2019 Google LLC
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  * IN THE SOFTWARE.
22  */
23 
24 /** @file kms_throughput.c
25  */
26 
27 #include <drm.h>
28 #include <sys/time.h>
29 #include <xf86drm.h>
30 #include "drmtest.h"
31 #include "igt.h"
32 #include "ion.h"
33 
make_display(igt_display_t * display)34 static void make_display(igt_display_t *display)
35 {
36 	display->drm_fd = drm_open_driver_master(DRIVER_ANY);
37 	igt_display_require(display, display->drm_fd);
38 	igt_require(display->is_atomic);
39 	igt_display_require_output(display);
40 }
41 
get_output(igt_display_t * display,enum pipe * pipe,igt_output_t ** output)42 static bool get_output(igt_display_t *display,
43 		       enum pipe *pipe, igt_output_t **output)
44 {
45 	igt_info("Display %p has %d pipes\n", display, display->n_pipes);
46 	for (int i = 0; i < display->n_pipes; ++i)
47 	{
48 		igt_info("Pipe %d (crtc %u) has %d planes\n",
49 			 i, display->pipes[i].crtc_id,
50 			 display->pipes[i].n_planes);
51 	}
52 
53 	for_each_pipe_with_valid_output(display, *pipe, *output)
54 	{
55 		/* we are happy with the first one */
56 		return true;
57 	}
58 
59 	return false;
60 }
61 
get_pipe(igt_display_t * display,igt_pipe_t ** p,igt_output_t ** output)62 static void get_pipe(igt_display_t *display,
63 		     igt_pipe_t **p, igt_output_t **output)
64 {
65 	enum pipe pipe_idx = PIPE_NONE;
66 	*output = NULL;
67 	igt_require(get_output(display, &pipe_idx, output));
68 	igt_info("Using output id %u, name %s\n",
69 		 (*output)->id, (*output)->name);
70 
71 	/* I'd love to call this 'pipe' but pipe(2) is in the way */
72 	*p = &display->pipes[pipe_idx];
73 	igt_require(*p);
74 
75 	igt_info("Chosen pipe (crtc %u) has %d planes\n",
76 		 (*p)->crtc_id, (*p)->n_planes);
77 }
78 
prepare(igt_display_t * display,igt_pipe_t * p,igt_output_t * output)79 static void prepare(igt_display_t *display, igt_pipe_t *p, igt_output_t *output)
80 {
81 	igt_display_reset(display);
82 	igt_output_set_pipe(output, p->pipe);
83 }
84 
plane_for_index(igt_pipe_t * p,size_t index)85 static igt_plane_t *plane_for_index(igt_pipe_t *p, size_t index)
86 {
87 	return &p->planes[index];
88 }
89 
90 struct histogram
91 {
92 	struct timeval last_commit;
93 	size_t num_buckets;
94 	size_t *buckets;
95 };
96 
histogram_init(struct histogram * h)97 static void histogram_init(struct histogram *h)
98 {
99 	gettimeofday(&h->last_commit, NULL);
100 	h->num_buckets = 100;
101 	h->buckets = calloc(h->num_buckets, sizeof(*h->buckets));
102 }
103 
histogram_update(struct histogram * h)104 static void histogram_update(struct histogram *h)
105 {
106 	struct timeval this_commit;
107 	gettimeofday(&this_commit, NULL);
108 
109 	struct timeval diff;
110 	timersub(&this_commit, &h->last_commit, &diff);
111 	const size_t ms = (diff.tv_sec * 1000) + (diff.tv_usec / 1000);
112 	size_t bucket = ms;
113 	if (bucket >= h->num_buckets)
114 	{
115 		// the last bucket is a catch-all
116 		bucket = h->num_buckets - 1;
117 	}
118 	h->buckets[bucket]++;
119 
120 	memcpy(&h->last_commit, &this_commit, sizeof(this_commit));
121 }
122 
histogram_print(struct histogram * h)123 static void histogram_print(struct histogram *h)
124 {
125 	igt_info("Histogram buckets with 1 or more entries:\n");
126 
127 	for (size_t i = 0; i < h->num_buckets; ++i)
128 	{
129 		size_t value = h->buckets[i];
130 
131 		if (value)
132 		{
133 			if (i == h->num_buckets - 1)
134 			{
135 				igt_info("%zu+ ms: %zu\n", i, value);
136 			}
137 			else
138 			{
139 				igt_info("%zu ms: %zu\n", i, value);
140 			}
141 		}
142 	}
143 }
144 
histogram_cleanup(struct histogram * h)145 static void histogram_cleanup(struct histogram *h)
146 {
147 	free(h->buckets);
148 }
149 
150 static const size_t max_num_fbs = 32;
151 
152 struct tuning
153 {
154 	drmModeModeInfoPtr mode;
155 	size_t num_iterations;
156 	size_t num_fb_sets;
157 	size_t num_fbs;
158 	struct fbgeom {
159 		size_t width;
160 		size_t height;
161 	} fb_geom[max_num_fbs];
162 };
163 
info_timestamp(const char * text)164 static void info_timestamp(const char *text)
165 {
166 	struct timeval ts;
167 	gettimeofday(&ts, NULL);
168 	igt_debug("%ld: %s\n", ts.tv_usec, text);
169 }
170 
flip_overlays(igt_pipe_t * p,struct igt_fb ** fb_sets,const struct tuning * tuning,size_t iter)171 static void flip_overlays(igt_pipe_t *p, struct igt_fb **fb_sets,
172 			  const struct tuning *tuning,
173 			  size_t iter)
174 {
175 	size_t fb_set = iter % tuning->num_fb_sets;
176 	struct igt_fb *fbs = fb_sets[fb_set];
177 
178 	for (size_t i = 0; i < tuning->num_fbs; ++i)
179 	{
180 		igt_plane_t *plane = plane_for_index(p, i);
181 		igt_plane_set_prop_value(plane, IGT_PLANE_ZPOS, i);
182 		igt_plane_set_fb(plane, &fbs[i]);
183 	}
184 
185 	igt_pipe_obj_set_prop_value(p, IGT_CRTC_ACTIVE, 1);
186 
187 	info_timestamp("start commit");
188 	igt_display_commit2(p->display, COMMIT_ATOMIC);
189 	info_timestamp("end commit");
190 }
191 
repeat_flip(igt_pipe_t * p,struct igt_fb ** fb_sets,const struct tuning * tuning)192 static void repeat_flip(igt_pipe_t *p, struct igt_fb **fb_sets,
193 			const struct tuning *tuning)
194 {
195 	struct histogram h;
196 	histogram_init(&h);
197 
198 	for (size_t iter = 0; iter < tuning->num_iterations; ++iter)
199 	{
200 		igt_debug("Iteration %zu\n", iter);
201 		flip_overlays(p, fb_sets, tuning, iter);
202 		histogram_update(&h);
203 	}
204 
205 	igt_debug("About to clear fbs\n");
206 
207 	for (size_t i = 0; i < tuning->num_fbs; ++i)
208 	{
209 		igt_plane_t *plane = plane_for_index(p, i);
210 		igt_plane_set_fb(plane, NULL);
211 	}
212 
213 	igt_debug("About to flip with no fbs\n");
214 
215 	igt_display_commit2(p->display, COMMIT_ATOMIC);
216 	igt_wait_for_vblank(p->display->drm_fd, p->pipe);
217 
218 	igt_debug("About to deactivate the crtc\n");
219 
220 	igt_pipe_obj_set_prop_value(p, IGT_CRTC_ACTIVE, 0);
221 	igt_display_commit2(p->display, COMMIT_ATOMIC);
222 
223 	histogram_print(&h);
224 	histogram_cleanup(&h);
225 }
226 
create_dumb_fb(igt_display_t * display,size_t width,size_t height,struct igt_fb * fb)227 static void create_dumb_fb(igt_display_t *display,
228 			   size_t width, size_t height,
229 			   struct igt_fb *fb)
230 {
231 	igt_create_fb(display->drm_fd,
232 		      width, height,
233 		      DRM_FORMAT_ARGB8888, LOCAL_DRM_FORMAT_MOD_NONE, fb);
234 }
235 
236 
get_num_planes(igt_display_t * display)237 static int get_num_planes(igt_display_t *display)
238 {
239 	const int drm_fd = display->drm_fd;
240 	int ret;
241 
242 	drmModePlaneRes *const plane_resources =
243 		drmModeGetPlaneResources(drm_fd);
244 
245 	ret = plane_resources->count_planes;
246 
247 	drmModeFreePlaneResources(plane_resources);
248 
249 	return ret;
250 }
251 
get_max_zpos(igt_display_t * display,igt_pipe_t * p)252 static int get_max_zpos(igt_display_t *display, igt_pipe_t *p)
253 {
254 	igt_plane_t *primary = plane_for_index(p, 0);
255 
256 	drmModePropertyPtr zpos_prop = NULL;
257 
258 	if (kmstest_get_property(display->drm_fd,
259 				 primary->drm_plane->plane_id,
260 				 DRM_MODE_OBJECT_PLANE,
261 				 "zpos", NULL, NULL,
262 				 &zpos_prop) &&
263 	    zpos_prop &&
264 	    zpos_prop->flags & DRM_MODE_PROP_RANGE)
265 	{
266 		return zpos_prop->values[1];
267 	}
268 	else
269 	{
270 		return -1;
271 	}
272 }
273 
get_num_fbs(igt_display_t * display,igt_pipe_t * p)274 size_t get_num_fbs(igt_display_t *display, igt_pipe_t *p)
275 {
276 	const char *NUM_FBS = getenv("NUM_FBS");
277 
278 	if (NUM_FBS)
279 	{
280 		return (size_t)atoi(NUM_FBS);
281 	}
282 	else
283 	{
284 		const int num_planes = get_num_planes(display);
285 		const int max_zpos = get_max_zpos(display, p);
286 
287 		if (max_zpos >= 0 && max_zpos + 1 < num_planes)
288 		{
289 			return (size_t)max_zpos + 1;
290 		}
291 		else
292 		{
293 			return (size_t)num_planes;
294 		}
295 	}
296 }
297 
calculate_complexity(const drmModeModeInfoPtr mode)298 size_t calculate_complexity(const drmModeModeInfoPtr mode)
299 {
300 	return (size_t)mode->hdisplay * (size_t)mode->vdisplay * (size_t)mode->vrefresh;
301 }
302 
get_peak_mode(igt_output_t * output)303 drmModeModeInfoPtr get_peak_mode(igt_output_t *output)
304 {
305 	drmModeConnector *const connector = output->config.connector;
306 	if (!connector || connector->count_modes == 0)
307 	{
308 		return NULL;
309 	}
310 
311 	drmModeModeInfoPtr peak_mode = &connector->modes[0];
312 	size_t peak_complexity = calculate_complexity(peak_mode);
313 
314 	for (drmModeModeInfoPtr mode = &connector->modes[0];
315 	     mode < &connector->modes[connector->count_modes]; ++mode)
316 	{
317 		const size_t complexity = calculate_complexity(mode);
318 		igt_debug("Mode %zu is %hux%hu@%u\n",
319 			  (size_t)(mode - connector->modes),
320 			  mode->hdisplay, mode->vdisplay, mode->vrefresh);
321 		if (complexity > peak_complexity)
322 		{
323 			peak_mode = mode;
324 			peak_complexity = complexity;
325 		}
326 	}
327 
328 	return peak_mode;
329 }
330 
get_tuning(struct tuning * tuning,igt_display_t * display,igt_pipe_t * p,igt_output_t * output)331 void get_tuning(struct tuning *tuning,
332 		igt_display_t *display, igt_pipe_t *p,
333 		igt_output_t *output)
334 {
335 	tuning->mode = get_peak_mode(output);
336 	igt_require(tuning->mode);
337 
338 	if (igt_output_get_mode(output) != tuning->mode)
339 	{
340 		igt_output_override_mode(output, tuning->mode);
341 		igt_display_commit2(p->display, COMMIT_ATOMIC);
342 	}
343 
344 	igt_info("Chosen mode:\n");
345 	kmstest_dump_mode(tuning->mode);
346 
347 	tuning->num_iterations = 1000;
348 	tuning->num_fb_sets = 2;
349 
350 	tuning->num_fbs = get_num_fbs(display, p);
351 	igt_require(tuning->num_fbs <= max_num_fbs);
352 
353 	drmModeModeInfo *mode = igt_output_get_mode(output);
354 	const char *FB_WIDTH = getenv("FB_WIDTH");
355 	const char *FB_HEIGHT = getenv("FB_HEIGHT");
356 
357 	const size_t requested_fb_width = FB_WIDTH ?
358 		(size_t)atoi(FB_WIDTH) :
359 		mode->hdisplay;
360 
361 	const size_t requested_fb_height = FB_HEIGHT ?
362 		(size_t)atoi(FB_HEIGHT) :
363 		mode->vdisplay;
364 
365 	igt_display_commit2(p->display, COMMIT_ATOMIC);
366 
367 	struct igt_fb fb;
368 	create_dumb_fb(p->display, requested_fb_width, requested_fb_height, &fb);
369 
370 	for (size_t i = 0; i < tuning->num_fbs; ++i)
371 	{
372 		igt_plane_t *const plane = plane_for_index(p, i);
373 		igt_plane_set_prop_value(plane, IGT_PLANE_ZPOS, i);
374 		igt_plane_set_fb(plane, &fb);
375 
376 		int ret = igt_display_try_commit_atomic(p->display,
377 							DRM_MODE_ATOMIC_TEST_ONLY,
378 							NULL);
379 
380 		if (ret)
381 		{
382 			tuning->fb_geom[i].width = mode->hdisplay;
383 			tuning->fb_geom[i].height = mode->vdisplay;
384 		}
385 		else
386 		{
387 			tuning->fb_geom[i].width = requested_fb_width;
388 			tuning->fb_geom[i].height = requested_fb_height;
389 		}
390 
391 		igt_info("Plane %zu is %zux%zu\n", i,
392 			 tuning->fb_geom[i].width,
393 			 tuning->fb_geom[i].height);
394 
395 		igt_plane_set_fb(plane, NULL);
396 	}
397 
398 	igt_remove_fb(p->display->drm_fd, &fb);
399 }
400 
401 igt_main
402 {
403 	igt_display_t display = {};
404 	make_display(&display);
405 
406 	igt_pipe_t *p = NULL;
407 	igt_output_t *output = NULL;
408 	get_pipe(&display, &p, &output);
409 
410 	do_or_die(drmSetClientCap(
411 		display.drm_fd,
412 		DRM_CLIENT_CAP_ATOMIC,
413 		1));
414 
415 	do_or_die(drmSetClientCap(
416 		display.drm_fd,
417 		DRM_CLIENT_CAP_UNIVERSAL_PLANES,
418 		1));
419 
420 	igt_pipe_refresh(&display, p->pipe, true);
421 
422 	prepare(&display, p, output);
423 
424 	drmModeModeInfoPtr orig_mode = igt_output_get_mode(output);
425 
426 	struct tuning tuning;
427 	get_tuning(&tuning, &display, p, output);
428 
429 	{
430 		struct igt_fb **fb_sets =
431 				malloc(sizeof(struct igt_fb*[tuning.num_fb_sets]));
432 
433 		for (size_t i = 0; i < tuning.num_fb_sets; ++i)
434 		{
435 			fb_sets[i] = malloc(sizeof(struct igt_fb[tuning.num_fbs]));
436 			struct igt_fb *fbs = fb_sets[i];
437 			for (size_t j = 0; j < tuning.num_fbs; ++j)
438 			{
439 				create_dumb_fb(&display,
440 					       tuning.fb_geom[j].width,
441 					       tuning.fb_geom[j].height,
442 					       &fbs[j]);
443 			};
444 		}
445 
446 
447 		repeat_flip(p, fb_sets, &tuning);
448 
449 		for (size_t i = 0; i < tuning.num_fb_sets; ++i)
450 		{
451 			struct igt_fb *fbs = fb_sets[i];
452 			for (size_t j = 0; j < tuning.num_fbs; ++j)
453 			{
454 				igt_remove_fb(display.drm_fd, &fbs[j]);
455 			};
456 			free(fbs);
457 		}
458 		free(fb_sets);
459 	}
460 
461 	if (orig_mode != tuning.mode)
462 	{
463 		igt_output_override_mode(output, orig_mode);
464 		igt_display_commit2(&display, COMMIT_ATOMIC);
465 	}
466 
467 	igt_info("Success\n");
468 }
469