1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 #include <regex>
5 #include <set>
6 #include <chrono>
7 #include <cstdint>
8 #include <cinttypes>
9
10 #include <sys/select.h>
11
12 #include <fmt/format.h>
13
14 #include <kms++/kms++.h>
15 #include <kms++/modedb.h>
16 #include <kms++/mode_cvt.h>
17
18 #include <kms++util/kms++util.h>
19
20 using namespace std;
21 using namespace kms;
22
23 struct PropInfo {
PropInfoPropInfo24 PropInfo(string n, uint64_t v)
25 : prop(NULL), name(n), val(v) {}
26
27 Property* prop;
28 string name;
29 uint64_t val;
30 };
31
32 struct PlaneInfo {
33 Plane* plane;
34
35 signed x;
36 signed y;
37 unsigned w;
38 unsigned h;
39
40 unsigned view_x;
41 unsigned view_y;
42 unsigned view_w;
43 unsigned view_h;
44
45 vector<Framebuffer*> fbs;
46
47 vector<PropInfo> props;
48 };
49
50 struct OutputInfo {
51 Connector* connector;
52
53 Crtc* crtc;
54 Videomode mode;
55 vector<Framebuffer*> legacy_fbs;
56
57 vector<PlaneInfo> planes;
58
59 vector<PropInfo> conn_props;
60 vector<PropInfo> crtc_props;
61 };
62
63 static bool s_use_dmt;
64 static bool s_use_cea;
65 static unsigned s_num_buffers = 1;
66 static bool s_flip_mode;
67 static bool s_flip_sync;
68 static bool s_cvt;
69 static bool s_cvt_v2;
70 static bool s_cvt_vid_opt;
71 static unsigned s_max_flips;
72 static bool s_print_crc;
73
print_regex_match(smatch sm)74 __attribute__((unused)) static void print_regex_match(smatch sm)
75 {
76 for (unsigned i = 0; i < sm.size(); ++i) {
77 string str = sm[i].str();
78 fmt::print("{}: {}\n", i, str);
79 }
80 }
81
get_connector(ResourceManager & resman,OutputInfo & output,const string & str="")82 static void get_connector(ResourceManager& resman, OutputInfo& output, const string& str = "")
83 {
84 Connector* conn = resman.reserve_connector(str);
85
86 if (!conn)
87 EXIT("No connector '%s'", str.c_str());
88
89 output.connector = conn;
90 output.mode = output.connector->get_default_mode();
91 }
92
get_default_crtc(ResourceManager & resman,OutputInfo & output)93 static void get_default_crtc(ResourceManager& resman, OutputInfo& output)
94 {
95 output.crtc = resman.reserve_crtc(output.connector);
96
97 if (!output.crtc)
98 EXIT("Could not find available crtc");
99 }
100
add_default_planeinfo(OutputInfo * output)101 static PlaneInfo* add_default_planeinfo(OutputInfo* output)
102 {
103 output->planes.push_back(PlaneInfo{});
104 PlaneInfo* ret = &output->planes.back();
105 ret->w = output->mode.hdisplay;
106 ret->h = output->mode.vdisplay;
107 return ret;
108 }
109
parse_crtc(ResourceManager & resman,Card & card,const string & crtc_str,OutputInfo & output)110 static void parse_crtc(ResourceManager& resman, Card& card, const string& crtc_str, OutputInfo& output)
111 {
112 // @12:1920x1200i@60
113 // @12:33000000,800/210/30/16/-,480/22/13/10/-,i
114
115 const regex modename_re("(?:(@?)(\\d+):)?" // @12:
116 "(?:(\\d+)x(\\d+)(i)?)" // 1920x1200i
117 "(?:@([\\d\\.]+))?"); // @60
118
119 const regex modeline_re("(?:(@?)(\\d+):)?" // @12:
120 "(\\d+)," // 33000000,
121 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-\\?])," // 800/210/30/16/-,
122 "(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-\\?])" // 480/22/13/10/-
123 "(?:,([i]+))?" // ,i
124 );
125
126 smatch sm;
127 if (regex_match(crtc_str, sm, modename_re)) {
128 if (sm[2].matched) {
129 bool use_id = sm[1].length() == 1;
130 unsigned num = stoul(sm[2].str());
131
132 if (use_id) {
133 Crtc* c = card.get_crtc(num);
134 if (!c)
135 EXIT("Bad crtc id '%u'", num);
136
137 output.crtc = c;
138 } else {
139 auto crtcs = card.get_crtcs();
140
141 if (num >= crtcs.size())
142 EXIT("Bad crtc number '%u'", num);
143
144 output.crtc = crtcs[num];
145 }
146 } else {
147 output.crtc = output.connector->get_current_crtc();
148 }
149
150 unsigned w = stoul(sm[3]);
151 unsigned h = stoul(sm[4]);
152 bool ilace = sm[5].matched ? true : false;
153 float refresh = sm[6].matched ? stof(sm[6]) : 0;
154
155 if (s_cvt) {
156 output.mode = videomode_from_cvt(w, h, refresh, ilace, s_cvt_v2, s_cvt_vid_opt);
157 } else if (s_use_dmt) {
158 try {
159 output.mode = find_dmt(w, h, refresh, ilace);
160 } catch (exception& e) {
161 EXIT("Mode not found from DMT tables\n");
162 }
163 } else if (s_use_cea) {
164 try {
165 output.mode = find_cea(w, h, refresh, ilace);
166 } catch (exception& e) {
167 EXIT("Mode not found from CEA tables\n");
168 }
169 } else {
170 try {
171 output.mode = output.connector->get_mode(w, h, refresh, ilace);
172 } catch (exception& e) {
173 EXIT("Mode not found from the connector\n");
174 }
175 }
176 } else if (regex_match(crtc_str, sm, modeline_re)) {
177 if (sm[2].matched) {
178 bool use_id = sm[1].length() == 1;
179 unsigned num = stoul(sm[2].str());
180
181 if (use_id) {
182 Crtc* c = card.get_crtc(num);
183 if (!c)
184 EXIT("Bad crtc id '%u'", num);
185
186 output.crtc = c;
187 } else {
188 auto crtcs = card.get_crtcs();
189
190 if (num >= crtcs.size())
191 EXIT("Bad crtc number '%u'", num);
192
193 output.crtc = crtcs[num];
194 }
195 } else {
196 output.crtc = output.connector->get_current_crtc();
197 }
198
199 unsigned clock = stoul(sm[3]);
200
201 unsigned hact = stoul(sm[4]);
202 unsigned hfp = stoul(sm[5]);
203 unsigned hsw = stoul(sm[6]);
204 unsigned hbp = stoul(sm[7]);
205
206 SyncPolarity h_sync;
207 switch (sm[8].str()[0]) {
208 case '+': h_sync = SyncPolarity::Positive; break;
209 case '-': h_sync = SyncPolarity::Negative; break;
210 default: h_sync = SyncPolarity::Undefined; break;
211 }
212
213 unsigned vact = stoul(sm[9]);
214 unsigned vfp = stoul(sm[10]);
215 unsigned vsw = stoul(sm[11]);
216 unsigned vbp = stoul(sm[12]);
217
218 SyncPolarity v_sync;
219 switch (sm[13].str()[0]) {
220 case '+': v_sync = SyncPolarity::Positive; break;
221 case '-': v_sync = SyncPolarity::Negative; break;
222 default: v_sync = SyncPolarity::Undefined; break;
223 }
224
225 output.mode = videomode_from_timings(clock / 1000, hact, hfp, hsw, hbp, vact, vfp, vsw, vbp);
226 output.mode.set_hsync(h_sync);
227 output.mode.set_vsync(v_sync);
228
229 if (sm[14].matched) {
230 for (int i = 0; i < sm[14].length(); ++i) {
231 char f = string(sm[14])[i];
232
233 switch (f) {
234 case 'i':
235 output.mode.set_interlace(true);
236 break;
237 default:
238 EXIT("Bad mode flag %c", f);
239 }
240 }
241 }
242 } else {
243 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
244 }
245
246 if (output.crtc)
247 output.crtc = resman.reserve_crtc(output.crtc);
248 else
249 output.crtc = resman.reserve_crtc(output.connector);
250
251 if (!output.crtc)
252 EXIT("Could not find available crtc");
253 }
254
parse_plane(ResourceManager & resman,Card & card,const string & plane_str,const OutputInfo & output,PlaneInfo & pinfo)255 static void parse_plane(ResourceManager& resman, Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
256 {
257 // 3:400,400-400x400
258 const regex plane_re("(?:(@?)(\\d+):)?" // 3:
259 "(?:(-?\\d+),(-?\\d+)-)?" // 400,400-
260 "(\\d+)x(\\d+)"); // 400x400
261
262 smatch sm;
263 if (!regex_match(plane_str, sm, plane_re))
264 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
265
266 if (sm[2].matched) {
267 bool use_id = sm[1].length() == 1;
268 unsigned num = stoul(sm[2].str());
269
270 if (use_id) {
271 Plane* p = card.get_plane(num);
272 if (!p)
273 EXIT("Bad plane id '%u'", num);
274
275 pinfo.plane = p;
276 } else {
277 auto planes = card.get_planes();
278
279 if (num >= planes.size())
280 EXIT("Bad plane number '%u'", num);
281
282 pinfo.plane = planes[num];
283 }
284
285 auto plane = resman.reserve_plane(pinfo.plane);
286 if (!plane)
287 EXIT("Plane id %u is not available", pinfo.plane->id());
288 }
289
290 pinfo.w = stoul(sm[5]);
291 pinfo.h = stoul(sm[6]);
292
293 if (sm[3].matched)
294 pinfo.x = stol(sm[3]);
295 else
296 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
297
298 if (sm[4].matched)
299 pinfo.y = stol(sm[4]);
300 else
301 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
302 }
303
parse_prop(const string & prop_str,vector<PropInfo> & props)304 static void parse_prop(const string& prop_str, vector<PropInfo>& props)
305 {
306 string name, val;
307
308 size_t split = prop_str.find("=");
309
310 if (split == string::npos)
311 EXIT("Equal sign ('=') not found in %s", prop_str.c_str());
312
313 name = prop_str.substr(0, split);
314 val = prop_str.substr(split + 1);
315
316 props.push_back(PropInfo(name, stoull(val, 0, 0)));
317 }
318
get_props(Card & card,vector<PropInfo> & props,const DrmPropObject * propobj)319 static void get_props(Card& card, vector<PropInfo>& props, const DrmPropObject* propobj)
320 {
321 for (auto& pi : props)
322 pi.prop = propobj->get_prop(pi.name);
323 }
324
get_default_fb(Card & card,unsigned width,unsigned height)325 static vector<Framebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
326 {
327 vector<Framebuffer*> v;
328
329 for (unsigned i = 0; i < s_num_buffers; ++i)
330 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
331
332 return v;
333 }
334
parse_fb(Card & card,const string & fb_str,OutputInfo * output,PlaneInfo * pinfo)335 static void parse_fb(Card& card, const string& fb_str, OutputInfo* output, PlaneInfo* pinfo)
336 {
337 unsigned w, h;
338 PixelFormat format = PixelFormat::XRGB8888;
339
340 if (pinfo) {
341 w = pinfo->w;
342 h = pinfo->h;
343 } else {
344 w = output->mode.hdisplay;
345 h = output->mode.vdisplay;
346 }
347
348 if (!fb_str.empty()) {
349 // XXX the regexp is not quite correct
350 // 400x400-NV12
351 const regex fb_re("(?:(\\d+)x(\\d+))?" // 400x400
352 "(?:-)?" // -
353 "(\\w\\w\\w\\w)?"); // NV12
354
355 smatch sm;
356 if (!regex_match(fb_str, sm, fb_re))
357 EXIT("Failed to parse fb option '%s'", fb_str.c_str());
358
359 if (sm[1].matched)
360 w = stoul(sm[1]);
361 if (sm[2].matched)
362 h = stoul(sm[2]);
363 if (sm[3].matched)
364 format = FourCCToPixelFormat(sm[3]);
365 }
366
367 vector<Framebuffer*> v;
368
369 for (unsigned i = 0; i < s_num_buffers; ++i)
370 v.push_back(new DumbFramebuffer(card, w, h, format));
371
372 if (pinfo)
373 pinfo->fbs = v;
374 else
375 output->legacy_fbs = v;
376 }
377
parse_view(const string & view_str,PlaneInfo & pinfo)378 static void parse_view(const string& view_str, PlaneInfo& pinfo)
379 {
380 const regex view_re("(\\d+),(\\d+)-(\\d+)x(\\d+)"); // 400,400-400x400
381
382 smatch sm;
383 if (!regex_match(view_str, sm, view_re))
384 EXIT("Failed to parse view option '%s'", view_str.c_str());
385
386 pinfo.view_x = stoul(sm[1]);
387 pinfo.view_y = stoul(sm[2]);
388 pinfo.view_w = stoul(sm[3]);
389 pinfo.view_h = stoul(sm[4]);
390 }
391
392 static const char* usage_str =
393 "Usage: kmstest [OPTION]...\n\n"
394 "Show a test pattern on a display or plane\n\n"
395 "Options:\n"
396 " --device=DEVICE DEVICE is the path to DRM card to open\n"
397 " -c, --connector=CONN CONN is <connector>\n"
398 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
399 " or\n"
400 " [<crtc>:]<pclk>,<hact>/<hfp>/<hsw>/<hbp>/<hsp>,<vact>/<vfp>/<vsw>/<vbp>/<vsp>[,i]\n"
401 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
402 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
403 " -v, --view=VIEW VIEW is <x>,<y>-<w>x<h>\n"
404 " -P, --property=PROP=VAL Set PROP to VAL in the previous DRM object\n"
405 " --dmt Search for the given mode from DMT tables\n"
406 " --cea Search for the given mode from CEA tables\n"
407 " --cvt=CVT Create videomode with CVT. CVT is 'v1', 'v2' or 'v2o'\n"
408 " --flip[=max] Do page flipping for each output with an optional maximum flips count\n"
409 " --sync Synchronize page flipping\n"
410 " --crc Print CRC16 for framebuffer contents\n"
411 "\n"
412 "<connector>, <crtc> and <plane> can be given by index (<idx>) or id (@<id>).\n"
413 "<connector> can also be given by name.\n"
414 "\n"
415 "Options can be given multiple times to set up multiple displays or planes.\n"
416 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
417 "an earlier option.\n"
418 "If you omit parameters, kmstest tries to guess what you mean\n"
419 "\n"
420 "Examples:\n"
421 "\n"
422 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
423 " kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
424 "XR24 framebuffer on first connected connector in the default mode:\n"
425 " kmstest -f XR24\n\n"
426 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
427 " kmstest -p 400x400 -f XR24\n\n"
428 "Test pattern on the second connector with default mode:\n"
429 " kmstest -c 1\n"
430 "\n"
431 "Environmental variables:\n"
432 " KMSXX_DISABLE_UNIVERSAL_PLANES Don't enable universal planes even if available\n"
433 " KMSXX_DISABLE_ATOMIC Don't enable atomic modesetting even if available\n";
434
usage()435 static void usage()
436 {
437 puts(usage_str);
438 }
439
440 enum class ArgType {
441 Connector,
442 Crtc,
443 Plane,
444 Framebuffer,
445 View,
446 Property,
447 };
448
449 struct Arg {
450 ArgType type;
451 string arg;
452 };
453
454 static string s_device_path;
455
parse_cmdline(int argc,char ** argv)456 static vector<Arg> parse_cmdline(int argc, char** argv)
457 {
458 vector<Arg> args;
459
460 OptionSet optionset = {
461 Option("|device=",
462 [&](string s) {
463 s_device_path = s;
464 }),
465 Option("c|connector=",
466 [&](string s) {
467 args.push_back(Arg{ ArgType::Connector, s });
468 }),
469 Option("r|crtc=", [&](string s) {
470 args.push_back(Arg{ ArgType::Crtc, s });
471 }),
472 Option("p|plane=", [&](string s) {
473 args.push_back(Arg{ ArgType::Plane, s });
474 }),
475 Option("f|fb=", [&](string s) {
476 args.push_back(Arg{ ArgType::Framebuffer, s });
477 }),
478 Option("v|view=", [&](string s) {
479 args.push_back(Arg{ ArgType::View, s });
480 }),
481 Option("P|property=", [&](string s) {
482 args.push_back(Arg{ ArgType::Property, s });
483 }),
484 Option("|dmt", []() {
485 s_use_dmt = true;
486 }),
487 Option("|cea", []() {
488 s_use_cea = true;
489 }),
490 Option("|flip?", [&](string s) {
491 s_flip_mode = true;
492 s_num_buffers = 2;
493 if (!s.empty())
494 s_max_flips = stoi(s);
495 }),
496 Option("|sync", []() {
497 s_flip_sync = true;
498 }),
499 Option("|cvt=", [&](string s) {
500 if (s == "v1")
501 s_cvt = true;
502 else if (s == "v2")
503 s_cvt = s_cvt_v2 = true;
504 else if (s == "v2o")
505 s_cvt = s_cvt_v2 = s_cvt_vid_opt = true;
506 else {
507 usage();
508 exit(-1);
509 }
510 }),
511 Option("|crc", []() {
512 s_print_crc = true;
513 }),
514 Option("h|help", [&]() {
515 usage();
516 exit(-1);
517 }),
518 };
519
520 optionset.parse(argc, argv);
521
522 if (optionset.params().size() > 0) {
523 usage();
524 exit(-1);
525 }
526
527 return args;
528 }
529
setups_to_outputs(Card & card,ResourceManager & resman,const vector<Arg> & output_args)530 static vector<OutputInfo> setups_to_outputs(Card& card, ResourceManager& resman, const vector<Arg>& output_args)
531 {
532 vector<OutputInfo> outputs;
533
534 OutputInfo* current_output = 0;
535 PlaneInfo* current_plane = 0;
536
537 for (auto& arg : output_args) {
538 switch (arg.type) {
539 case ArgType::Connector: {
540 outputs.push_back(OutputInfo{});
541 current_output = &outputs.back();
542
543 get_connector(resman, *current_output, arg.arg);
544 current_plane = 0;
545
546 break;
547 }
548
549 case ArgType::Crtc: {
550 if (!current_output) {
551 outputs.push_back(OutputInfo{});
552 current_output = &outputs.back();
553 }
554
555 if (!current_output->connector)
556 get_connector(resman, *current_output);
557
558 parse_crtc(resman, card, arg.arg, *current_output);
559
560 current_plane = 0;
561
562 break;
563 }
564
565 case ArgType::Plane: {
566 if (!current_output) {
567 outputs.push_back(OutputInfo{});
568 current_output = &outputs.back();
569 }
570
571 if (!current_output->connector)
572 get_connector(resman, *current_output);
573
574 if (!current_output->crtc)
575 get_default_crtc(resman, *current_output);
576
577 current_plane = add_default_planeinfo(current_output);
578
579 parse_plane(resman, card, arg.arg, *current_output, *current_plane);
580
581 break;
582 }
583
584 case ArgType::Framebuffer: {
585 if (!current_output) {
586 outputs.push_back(OutputInfo{});
587 current_output = &outputs.back();
588 }
589
590 if (!current_output->connector)
591 get_connector(resman, *current_output);
592
593 if (!current_output->crtc)
594 get_default_crtc(resman, *current_output);
595
596 if (!current_plane && card.has_atomic())
597 current_plane = add_default_planeinfo(current_output);
598
599 parse_fb(card, arg.arg, current_output, current_plane);
600
601 break;
602 }
603
604 case ArgType::View: {
605 if (!current_plane || current_plane->fbs.empty())
606 EXIT("'view' parameter requires a plane and a fb");
607
608 parse_view(arg.arg, *current_plane);
609 break;
610 }
611
612 case ArgType::Property: {
613 if (!current_output)
614 EXIT("No object to which set the property");
615
616 if (current_plane)
617 parse_prop(arg.arg, current_plane->props);
618 else if (current_output->crtc)
619 parse_prop(arg.arg, current_output->crtc_props);
620 else if (current_output->connector)
621 parse_prop(arg.arg, current_output->conn_props);
622 else
623 EXIT("no object");
624
625 break;
626 }
627 }
628 }
629
630 if (outputs.empty()) {
631 // no outputs defined, show a pattern on all connected screens
632 for (Connector* conn : card.get_connectors()) {
633 if (!conn->connected())
634 continue;
635
636 OutputInfo output = {};
637 output.connector = resman.reserve_connector(conn);
638 EXIT_IF(!output.connector, "Failed to reserve connector %s", conn->fullname().c_str());
639 output.crtc = resman.reserve_crtc(conn);
640 EXIT_IF(!output.crtc, "Failed to reserve crtc for %s", conn->fullname().c_str());
641 output.mode = output.connector->get_default_mode();
642
643 outputs.push_back(output);
644 }
645 }
646
647 for (OutputInfo& o : outputs) {
648 get_props(card, o.conn_props, o.connector);
649
650 if (!o.crtc)
651 get_default_crtc(resman, o);
652
653 get_props(card, o.crtc_props, o.crtc);
654
655 if (!o.mode.valid())
656 EXIT("Mode not valid for %s", o.connector->fullname().c_str());
657
658 if (card.has_atomic()) {
659 if (o.planes.empty())
660 add_default_planeinfo(&o);
661 } else {
662 if (o.legacy_fbs.empty())
663 o.legacy_fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
664 }
665
666 for (PlaneInfo& p : o.planes) {
667 if (p.fbs.empty())
668 p.fbs = get_default_fb(card, p.w, p.h);
669 }
670
671 for (PlaneInfo& p : o.planes) {
672 if (!p.plane) {
673 if (card.has_atomic())
674 p.plane = resman.reserve_generic_plane(o.crtc, p.fbs[0]->format());
675 else
676 p.plane = resman.reserve_overlay_plane(o.crtc, p.fbs[0]->format());
677
678 if (!p.plane)
679 EXIT("Failed to find available plane");
680 }
681 get_props(card, p.props, p.plane);
682 }
683 }
684
685 return outputs;
686 }
687
crc16(uint16_t crc,uint8_t data)688 static uint16_t crc16(uint16_t crc, uint8_t data)
689 {
690 const uint16_t CRC16_IBM = 0x8005;
691
692 for (uint8_t i = 0; i < 8; i++) {
693 if (((crc & 0x8000) >> 8) ^ (data & 0x80))
694 crc = (crc << 1) ^ CRC16_IBM;
695 else
696 crc = (crc << 1);
697
698 data <<= 1;
699 }
700
701 return crc;
702 }
703
fb_crc(IFramebuffer * fb)704 static string fb_crc(IFramebuffer* fb)
705 {
706 uint8_t* p = fb->map(0);
707 uint16_t r, g, b;
708
709 r = g = b = 0;
710
711 for (unsigned y = 0; y < fb->height(); ++y) {
712 for (unsigned x = 0; x < fb->width(); ++x) {
713 uint32_t* p32 = (uint32_t*)(p + fb->stride(0) * y + x * 4);
714 RGB rgb(*p32);
715
716 r = crc16(r, rgb.r);
717 r = crc16(r, 0);
718
719 g = crc16(g, rgb.g);
720 g = crc16(g, 0);
721
722 b = crc16(b, rgb.b);
723 b = crc16(b, 0);
724 }
725 }
726
727 return fmt::format("{:#06x} {:#06x} {:#06x}", r, g, b);
728 }
729
print_outputs(const vector<OutputInfo> & outputs)730 static void print_outputs(const vector<OutputInfo>& outputs)
731 {
732 for (unsigned i = 0; i < outputs.size(); ++i) {
733 const OutputInfo& o = outputs[i];
734
735 fmt::print("Connector {}/@{}: {}", o.connector->idx(), o.connector->id(),
736 o.connector->fullname());
737
738 for (const PropInfo& prop : o.conn_props)
739 fmt::print(" {}={}", prop.prop->name(), prop.val);
740
741 fmt::print("\n Crtc {}/@{}", o.crtc->idx(), o.crtc->id());
742
743 for (const PropInfo& prop : o.crtc_props)
744 fmt::print(" {}={}", prop.prop->name(), prop.val);
745
746 fmt::print(": {}\n", o.mode.to_string_long());
747
748 if (!o.legacy_fbs.empty()) {
749 auto fb = o.legacy_fbs[0];
750 fmt::print(" Fb {} {}x{}-{}\n", fb->id(), fb->width(), fb->height(), PixelFormatToFourCC(fb->format()));
751 }
752
753 for (unsigned j = 0; j < o.planes.size(); ++j) {
754 const PlaneInfo& p = o.planes[j];
755 auto fb = p.fbs[0];
756 fmt::print(" Plane {}/@{}: {},{}-{}x{}", p.plane->idx(), p.plane->id(),
757 p.x, p.y, p.w, p.h);
758 for (const PropInfo& prop : p.props)
759 fmt::print(" {}={}", prop.prop->name(), prop.val);
760 fmt::print("\n");
761
762 fmt::print(" Fb {} {}x{}-{}\n", fb->id(), fb->width(), fb->height(),
763 PixelFormatToFourCC(fb->format()));
764 if (s_print_crc)
765 fmt::print(" CRC16 {}\n", fb_crc(fb).c_str());
766 }
767 }
768 }
769
draw_test_patterns(const vector<OutputInfo> & outputs)770 static void draw_test_patterns(const vector<OutputInfo>& outputs)
771 {
772 for (const OutputInfo& o : outputs) {
773 for (auto fb : o.legacy_fbs)
774 draw_test_pattern(*fb);
775
776 for (const PlaneInfo& p : o.planes)
777 for (auto fb : p.fbs)
778 draw_test_pattern(*fb);
779 }
780 }
781
set_crtcs_n_planes_legacy(Card & card,const vector<OutputInfo> & outputs)782 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
783 {
784 // Disable unused crtcs
785 for (Crtc* crtc : card.get_crtcs()) {
786 if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
787 continue;
788
789 crtc->disable_mode();
790 }
791
792 for (const OutputInfo& o : outputs) {
793 int r;
794 auto conn = o.connector;
795 auto crtc = o.crtc;
796
797 for (const PropInfo& prop : o.conn_props) {
798 r = conn->set_prop_value(prop.prop, prop.val);
799 EXIT_IF(r, "failed to set connector property %s\n", prop.name.c_str());
800 }
801
802 for (const PropInfo& prop : o.crtc_props) {
803 r = crtc->set_prop_value(prop.prop, prop.val);
804 EXIT_IF(r, "failed to set crtc property %s\n", prop.name.c_str());
805 }
806
807 if (!o.legacy_fbs.empty()) {
808 auto fb = o.legacy_fbs[0];
809 r = crtc->set_mode(conn, *fb, o.mode);
810 if (r)
811 fmt::print(stderr, "crtc->set_mode() failed for crtc {}: {}\n",
812 crtc->id(), strerror(-r));
813 }
814
815 for (const PlaneInfo& p : o.planes) {
816 for (const PropInfo& prop : p.props) {
817 r = p.plane->set_prop_value(prop.prop, prop.val);
818 EXIT_IF(r, "failed to set plane property %s\n", prop.name.c_str());
819 }
820
821 auto fb = p.fbs[0];
822 r = crtc->set_plane(p.plane, *fb,
823 p.x, p.y, p.w, p.h,
824 0, 0, fb->width(), fb->height());
825 if (r)
826 fmt::print(stderr, "crtc->set_plane() failed for plane {}: {}\n",
827 p.plane->id(), strerror(-r));
828 }
829 }
830 }
831
set_crtcs_n_planes_atomic(Card & card,const vector<OutputInfo> & outputs)832 static void set_crtcs_n_planes_atomic(Card& card, const vector<OutputInfo>& outputs)
833 {
834 int r;
835
836 // XXX DRM framework doesn't allow moving an active plane from one crtc to another.
837 // See drm_atomic.c::plane_switching_crtc().
838 // For the time being, try and disable all crtcs and planes here.
839 // Do not check the return value as some simple displays don't support the crtc being
840 // enabled but the primary plane being disabled.
841
842 AtomicReq disable_req(card);
843
844 // Disable unused crtcs
845 for (Crtc* crtc : card.get_crtcs()) {
846 //if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
847 // continue;
848
849 disable_req.add(crtc, {
850 { "ACTIVE", 0 },
851 });
852 }
853
854 // Disable unused planes
855 for (Plane* plane : card.get_planes())
856 disable_req.add(plane, {
857 { "FB_ID", 0 },
858 { "CRTC_ID", 0 },
859 });
860
861 disable_req.commit_sync(true);
862
863 // Keep blobs here so that we keep ref to them until we have committed the req
864 vector<unique_ptr<Blob>> blobs;
865
866 AtomicReq req(card);
867
868 for (const OutputInfo& o : outputs) {
869 auto conn = o.connector;
870 auto crtc = o.crtc;
871
872 blobs.emplace_back(o.mode.to_blob(card));
873 Blob* mode_blob = blobs.back().get();
874
875 req.add(conn, {
876 { "CRTC_ID", crtc->id() },
877 });
878
879 for (const PropInfo& prop : o.conn_props)
880 req.add(conn, prop.prop, prop.val);
881
882 req.add(crtc, {
883 { "ACTIVE", 1 },
884 { "MODE_ID", mode_blob->id() },
885 });
886
887 for (const PropInfo& prop : o.crtc_props)
888 req.add(crtc, prop.prop, prop.val);
889
890 for (const PlaneInfo& p : o.planes) {
891 auto fb = p.fbs[0];
892
893 req.add(p.plane, {
894 { "FB_ID", fb->id() },
895 { "CRTC_ID", crtc->id() },
896 { "SRC_X", (p.view_x ?: 0) << 16 },
897 { "SRC_Y", (p.view_y ?: 0) << 16 },
898 { "SRC_W", (p.view_w ?: fb->width()) << 16 },
899 { "SRC_H", (p.view_h ?: fb->height()) << 16 },
900 { "CRTC_X", p.x },
901 { "CRTC_Y", p.y },
902 { "CRTC_W", p.w },
903 { "CRTC_H", p.h },
904 });
905
906 for (const PropInfo& prop : p.props)
907 req.add(p.plane, prop.prop, prop.val);
908 }
909 }
910
911 r = req.test(true);
912 if (r)
913 EXIT("Atomic test failed: %d\n", r);
914
915 r = req.commit_sync(true);
916 if (r)
917 EXIT("Atomic commit failed: %d\n", r);
918 }
919
set_crtcs_n_planes(Card & card,const vector<OutputInfo> & outputs)920 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
921 {
922 if (card.has_atomic())
923 set_crtcs_n_planes_atomic(card, outputs);
924 else
925 set_crtcs_n_planes_legacy(card, outputs);
926 }
927
928 static bool max_flips_reached;
929
930 class FlipState : private PageFlipHandlerBase
931 {
932 public:
FlipState(Card & card,const string & name,vector<const OutputInfo * > outputs)933 FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
934 : m_card(card), m_name(name), m_outputs(outputs)
935 {
936 }
937
start_flipping()938 void start_flipping()
939 {
940 m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
941 m_slowest_frame = std::chrono::duration<float>::min();
942 m_frame_num = 0;
943 queue_next();
944 }
945
946 private:
handle_page_flip(uint32_t frame,double time)947 void handle_page_flip(uint32_t frame, double time)
948 {
949 /*
950 * We get flip event for each crtc in this flipstate. We can commit the next frames
951 * only after we've gotten the flip event for all crtcs
952 */
953 if (++m_flip_count < m_outputs.size())
954 return;
955
956 m_frame_num++;
957 if (s_max_flips && m_frame_num >= s_max_flips)
958 max_flips_reached = true;
959
960 auto now = std::chrono::steady_clock::now();
961
962 std::chrono::duration<float> diff = now - m_prev_frame;
963 if (diff > m_slowest_frame)
964 m_slowest_frame = diff;
965
966 if (m_frame_num % 100 == 0) {
967 std::chrono::duration<float> fsec = now - m_prev_print;
968 fmt::print("Connector {}: fps {:.2f}, slowest {:.2f} ms\n",
969 m_name.c_str(),
970 100.0 / fsec.count(),
971 m_slowest_frame.count() * 1000);
972 m_prev_print = now;
973 m_slowest_frame = std::chrono::duration<float>::min();
974 }
975
976 m_prev_frame = now;
977
978 queue_next();
979 }
980
get_bar_pos(Framebuffer * fb,unsigned frame_num)981 static unsigned get_bar_pos(Framebuffer* fb, unsigned frame_num)
982 {
983 return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
984 }
985
draw_bar(Framebuffer * fb,unsigned frame_num)986 static void draw_bar(Framebuffer* fb, unsigned frame_num)
987 {
988 int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
989 int new_xpos = get_bar_pos(fb, frame_num);
990
991 draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
992 draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
993 }
994
do_flip_output(AtomicReq & req,unsigned frame_num,const OutputInfo & o)995 static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
996 {
997 unsigned cur = frame_num % s_num_buffers;
998
999 for (const PlaneInfo& p : o.planes) {
1000 auto fb = p.fbs[cur];
1001
1002 draw_bar(fb, frame_num);
1003
1004 req.add(p.plane, {
1005 { "FB_ID", fb->id() },
1006 });
1007 }
1008 }
1009
do_flip_output_legacy(unsigned frame_num,const OutputInfo & o)1010 void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
1011 {
1012 unsigned cur = frame_num % s_num_buffers;
1013
1014 if (!o.legacy_fbs.empty()) {
1015 auto fb = o.legacy_fbs[cur];
1016
1017 draw_bar(fb, frame_num);
1018
1019 int r = o.crtc->page_flip(*fb, this);
1020 ASSERT(r == 0);
1021 }
1022
1023 for (const PlaneInfo& p : o.planes) {
1024 auto fb = p.fbs[cur];
1025
1026 draw_bar(fb, frame_num);
1027
1028 int r = o.crtc->set_plane(p.plane, *fb,
1029 p.x, p.y, p.w, p.h,
1030 0, 0, fb->width(), fb->height());
1031 ASSERT(r == 0);
1032 }
1033 }
1034
queue_next()1035 void queue_next()
1036 {
1037 m_flip_count = 0;
1038
1039 if (m_card.has_atomic()) {
1040 AtomicReq req(m_card);
1041
1042 for (auto o : m_outputs)
1043 do_flip_output(req, m_frame_num, *o);
1044
1045 int r = req.commit(this);
1046 if (r)
1047 EXIT("Flip commit failed: %d\n", r);
1048 } else {
1049 ASSERT(m_outputs.size() == 1);
1050 do_flip_output_legacy(m_frame_num, *m_outputs[0]);
1051 }
1052 }
1053
1054 Card& m_card;
1055 string m_name;
1056 vector<const OutputInfo*> m_outputs;
1057 unsigned m_frame_num;
1058 unsigned m_flip_count;
1059
1060 chrono::steady_clock::time_point m_prev_print;
1061 chrono::steady_clock::time_point m_prev_frame;
1062 chrono::duration<float> m_slowest_frame;
1063
1064 static const unsigned bar_width = 20;
1065 static const unsigned bar_speed = 8;
1066 };
1067
main_flip(Card & card,const vector<OutputInfo> & outputs)1068 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
1069 {
1070 // clang-tidy does not seem to handle FD_xxx macros
1071 #ifndef __clang_analyzer__
1072 fd_set fds;
1073
1074 FD_ZERO(&fds);
1075
1076 int fd = card.fd();
1077
1078 vector<unique_ptr<FlipState>> flipstates;
1079
1080 if (!s_flip_sync) {
1081 for (const OutputInfo& o : outputs) {
1082 auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
1083 flipstates.push_back(move(fs));
1084 }
1085 } else {
1086 vector<const OutputInfo*> ois;
1087
1088 string name;
1089 for (const OutputInfo& o : outputs) {
1090 name += to_string(o.connector->idx()) + ",";
1091 ois.push_back(&o);
1092 }
1093
1094 auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
1095 flipstates.push_back(move(fs));
1096 }
1097
1098 for (unique_ptr<FlipState>& fs : flipstates)
1099 fs->start_flipping();
1100
1101 while (!max_flips_reached) {
1102 int r;
1103
1104 FD_SET(0, &fds);
1105 FD_SET(fd, &fds);
1106
1107 r = select(fd + 1, &fds, NULL, NULL, NULL);
1108 if (r < 0) {
1109 fmt::print(stderr, "select() failed with {}: {}\n", errno, strerror(errno));
1110 break;
1111 } else if (FD_ISSET(0, &fds)) {
1112 fmt::print(stderr, "Exit due to user-input\n");
1113 break;
1114 } else if (FD_ISSET(fd, &fds)) {
1115 card.call_page_flip_handlers();
1116 }
1117 }
1118 #endif
1119 }
1120
main(int argc,char ** argv)1121 int main(int argc, char** argv)
1122 {
1123 vector<Arg> output_args = parse_cmdline(argc, argv);
1124
1125 Card card(s_device_path);
1126
1127 if (!card.is_master())
1128 EXIT("Could not get DRM master permission. Card already in use?");
1129
1130 if (!card.has_atomic() && s_flip_sync)
1131 EXIT("Synchronized flipping requires atomic modesetting");
1132
1133 ResourceManager resman(card);
1134
1135 vector<OutputInfo> outputs = setups_to_outputs(card, resman, output_args);
1136
1137 if (!s_flip_mode)
1138 draw_test_patterns(outputs);
1139
1140 print_outputs(outputs);
1141
1142 set_crtcs_n_planes(card, outputs);
1143
1144 fmt::print("press enter to exit\n");
1145
1146 if (s_flip_mode)
1147 main_flip(card, outputs);
1148 else
1149 getchar();
1150 }
1151