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