1 #include <cstdio>
2 #include <cstring>
3 #include <algorithm>
4 #include <regex>
5 #include <set>
7 #include <kms++.h>
8 #include <modedb.h>
10 #include <kms++util.h>
11 #include <opts.h>
13 using namespace std;
14 using namespace kms;
16 struct PlaneInfo
17 {
18 Plane* plane;
20 unsigned x;
21 unsigned y;
22 unsigned w;
23 unsigned h;
25 vector<DumbFramebuffer*> fbs;
26 };
28 struct OutputInfo
29 {
30 Connector* connector;
32 Crtc* crtc;
33 Plane* primary_plane;
34 Videomode mode;
35 bool user_set_crtc;
36 vector<DumbFramebuffer*> fbs;
38 vector<PlaneInfo> planes;
39 };
41 static bool s_use_dmt;
42 static bool s_use_cea;
43 static unsigned s_num_buffers = 1;
45 static set<Crtc*> s_used_crtcs;
46 static set<Plane*> s_used_planes;
48 __attribute__ ((unused))
49 static void print_regex_match(smatch sm)
50 {
51 for (unsigned i = 0; i < sm.size(); ++i) {
52 string str = sm[i].str();
53 printf("%u: %s\n", i, str.c_str());
54 }
55 }
57 static void get_default_connector(Card& card, OutputInfo& output)
58 {
59 output.connector = card.get_first_connected_connector();
60 output.mode = output.connector->get_default_mode();
61 }
63 static void parse_connector(Card& card, const string& str, OutputInfo& output)
64 {
65 Connector* conn = nullptr;
67 auto connectors = card.get_connectors();
69 if (str[0] == '@') {
70 char* endptr;
71 unsigned idx = strtoul(str.c_str() + 1, &endptr, 10);
72 if (*endptr == 0) {
73 if (idx >= connectors.size())
74 EXIT("Bad connector number '%u'", idx);
76 conn = connectors[idx];
77 }
78 } else {
79 char* endptr;
80 unsigned id = strtoul(str.c_str(), &endptr, 10);
81 if (*endptr == 0) {
82 Connector* c = card.get_connector(id);
83 if (!c)
84 EXIT("Bad connector id '%u'", id);
86 conn = c;
87 }
88 }
90 if (!conn) {
91 auto iter = find_if(connectors.begin(), connectors.end(), [&str](Connector *c) { return c->fullname() == str; });
92 if (iter != connectors.end())
93 conn = *iter;
94 }
96 if (!conn)
97 EXIT("No connector '%s'", str.c_str());
99 if (!conn->connected())
100 EXIT("Connector '%s' not connected", conn->fullname().c_str());
102 output.connector = conn;
103 output.mode = output.connector->get_default_mode();
104 }
106 static void get_default_crtc(Card& card, OutputInfo& output)
107 {
108 Crtc* crtc = output.connector->get_current_crtc();
110 if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
111 s_used_crtcs.insert(crtc);
112 output.crtc = crtc;
113 return;
114 }
116 for (const auto& possible : output.connector->get_possible_crtcs()) {
117 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
118 s_used_crtcs.insert(possible);
119 output.crtc = possible;
120 return;
121 }
122 }
124 EXIT("Could not find available crtc");
125 }
127 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
128 {
129 // @12:1920x1200@60
130 const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+)(i)?)(?:@(\\d+))?");
132 smatch sm;
133 if (!regex_match(crtc_str, sm, mode_re))
134 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
136 if (sm[2].matched) {
137 bool use_idx = sm[1].length() == 1;
138 unsigned num = stoul(sm[2].str());
140 if (use_idx) {
141 auto crtcs = card.get_crtcs();
143 if (num >= crtcs.size())
144 EXIT("Bad crtc number '%u'", num);
146 output.crtc = crtcs[num];
147 } else {
148 Crtc* c = card.get_crtc(num);
149 if (!c)
150 EXIT("Bad crtc id '%u'", num);
152 output.crtc = c;
153 }
154 } else {
155 output.crtc = output.connector->get_current_crtc();
156 }
158 unsigned w = stoul(sm[3]);
159 unsigned h = stoul(sm[4]);
160 bool ilace = sm[5].matched ? true : false;
161 unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
163 bool found_mode = false;
165 try {
166 output.mode = output.connector->get_mode(w, h, refresh, ilace);
167 found_mode = true;
168 } catch (exception& e) { }
170 if (!found_mode && s_use_dmt) {
171 try {
172 output.mode = find_dmt(w, h, refresh, ilace);
173 found_mode = true;
174 printf("Found mode from DMT\n");
175 } catch (exception& e) { }
176 }
178 if (!found_mode && s_use_cea) {
179 try {
180 output.mode = find_cea(w, h, refresh, ilace);
181 found_mode = true;
182 printf("Found mode from CEA\n");
183 } catch (exception& e) { }
184 }
186 if (!found_mode)
187 throw invalid_argument("Mode not found");
188 }
190 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
191 {
192 // 3:400,400-400x400
193 const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
195 smatch sm;
196 if (!regex_match(plane_str, sm, plane_re))
197 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
199 if (sm[2].matched) {
200 bool use_idx = sm[1].length() == 1;
201 unsigned num = stoul(sm[2].str());
203 if (use_idx) {
204 auto planes = card.get_planes();
206 if (num >= planes.size())
207 EXIT("Bad plane number '%u'", num);
209 pinfo.plane = planes[num];
210 } else {
211 Plane* p = card.get_plane(num);
212 if (!p)
213 EXIT("Bad plane id '%u'", num);
215 pinfo.plane = p;
216 }
217 } else {
218 for (Plane* p : output.crtc->get_possible_planes()) {
219 if (s_used_planes.find(p) != s_used_planes.end())
220 continue;
222 if (p->plane_type() != PlaneType::Overlay)
223 continue;
225 pinfo.plane = p;
226 }
228 if (!pinfo.plane)
229 EXIT("Failed to find available plane");
230 }
232 s_used_planes.insert(pinfo.plane);
234 pinfo.w = stoul(sm[5]);
235 pinfo.h = stoul(sm[6]);
237 if (sm[3].matched)
238 pinfo.x = stoul(sm[3]);
239 else
240 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
242 if (sm[4].matched)
243 pinfo.y = stoul(sm[4]);
244 else
245 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
246 }
248 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
249 {
250 vector<DumbFramebuffer*> v;
252 for (unsigned i = 0; i < s_num_buffers; ++i)
253 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
255 return v;
256 }
258 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
259 {
260 unsigned w = def_w;
261 unsigned h = def_h;
262 PixelFormat format = PixelFormat::XRGB8888;
264 if (!fb_str.empty()) {
265 // XXX the regexp is not quite correct
266 // 400x400-NV12
267 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
269 smatch sm;
270 if (!regex_match(fb_str, sm, fb_re))
271 EXIT("Failed to parse fb option '%s'", fb_str.c_str());
273 if (sm[1].matched)
274 w = stoul(sm[1]);
275 if (sm[2].matched)
276 h = stoul(sm[2]);
277 if (sm[3].matched)
278 format = FourCCToPixelFormat(sm[3]);
279 }
281 vector<DumbFramebuffer*> v;
283 for (unsigned i = 0; i < s_num_buffers; ++i)
284 v.push_back(new DumbFramebuffer(card, w, h, format));
286 return v;
287 }
289 static const char* usage_str =
290 "Usage: testpat [OPTION]...\n\n"
291 "Show a test pattern on a display or plane\n\n"
292 "Options:\n"
293 " --device=DEVICE DEVICE is the path to DRM card to open\n"
294 " -c, --connector=CONN CONN is <connector>\n"
295 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
296 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
297 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
298 " --dmt Search for the given mode from DMT tables\n"
299 " --cea Search for the given mode from CEA tables\n"
300 "\n"
301 "<connector>, <crtc> and <plane> can be given by id (<id>) or index (@<idx>).\n"
302 "<connector> can also be given by name.\n"
303 "\n"
304 "Options can be given multiple times to set up multiple displays or planes.\n"
305 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
306 "an earlier option.\n"
307 "If you omit parameters, testpat tries to guess what you mean\n"
308 "\n"
309 "Examples:\n"
310 "\n"
311 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
312 " testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
313 "XR24 framebuffer on first connected connector in the default mode:\n"
314 " testpat -f XR24\n\n"
315 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
316 " testpat -p 400x400 -f XR24\n\n"
317 "Test pattern on the second connector with default mode:\n"
318 " testpat -c @1\n"
319 ;
321 static void usage()
322 {
323 puts(usage_str);
324 }
326 enum class ObjectType
327 {
328 Connector,
329 Crtc,
330 Plane,
331 Framebuffer,
332 };
334 struct Arg
335 {
336 ObjectType type;
337 string arg;
338 };
340 static string s_device_path = "/dev/dri/card0";
342 static vector<Arg> parse_cmdline(int argc, char **argv)
343 {
344 vector<Arg> args;
346 OptionSet optionset = {
347 Option("|device=",
348 [&](string s)
349 {
350 s_device_path = s;
351 }),
352 Option("c|connector=",
353 [&](string s)
354 {
355 args.push_back(Arg { ObjectType::Connector, s });
356 }),
357 Option("r|crtc=", [&](string s)
358 {
359 args.push_back(Arg { ObjectType::Crtc, s });
360 }),
361 Option("p|plane=", [&](string s)
362 {
363 args.push_back(Arg { ObjectType::Plane, s });
364 }),
365 Option("f|fb=", [&](string s)
366 {
367 args.push_back(Arg { ObjectType::Framebuffer, s });
368 }),
369 Option("|dmt", []()
370 {
371 s_use_dmt = true;
372 }),
373 Option("|cea", []()
374 {
375 s_use_cea = true;
376 }),
377 Option("h|help", [&]()
378 {
379 usage();
380 exit(-1);
381 }),
382 };
384 optionset.parse(argc, argv);
386 if (optionset.params().size() > 0) {
387 usage();
388 exit(-1);
389 }
391 return args;
392 }
394 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
395 {
396 vector<OutputInfo> outputs;
398 if (output_args.size() == 0) {
399 // no output args, show a pattern on all screens
400 for (auto& pipe : card.get_connected_pipelines()) {
401 OutputInfo output = { };
402 output.connector = pipe.connector;
403 output.crtc = pipe.crtc;
404 output.mode = output.connector->get_default_mode();
406 output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
408 outputs.push_back(output);
409 }
411 return outputs;
412 }
414 OutputInfo* current_output = 0;
415 PlaneInfo* current_plane = 0;
417 for (auto& arg : output_args) {
418 switch (arg.type) {
419 case ObjectType::Connector:
420 {
421 outputs.push_back(OutputInfo { });
422 current_output = &outputs.back();
424 parse_connector(card, arg.arg, *current_output);
425 current_plane = 0;
427 break;
428 }
430 case ObjectType::Crtc:
431 {
432 if (!current_output) {
433 outputs.push_back(OutputInfo { });
434 current_output = &outputs.back();
435 }
437 if (!current_output->connector)
438 get_default_connector(card, *current_output);
440 parse_crtc(card, arg.arg, *current_output);
442 current_output->user_set_crtc = true;
444 current_plane = 0;
446 break;
447 }
449 case ObjectType::Plane:
450 {
451 if (!current_output) {
452 outputs.push_back(OutputInfo { });
453 current_output = &outputs.back();
454 }
456 if (!current_output->connector)
457 get_default_connector(card, *current_output);
459 if (!current_output->crtc)
460 get_default_crtc(card, *current_output);
462 current_output->planes.push_back(PlaneInfo { });
463 current_plane = ¤t_output->planes.back();
465 parse_plane(card, arg.arg, *current_output, *current_plane);
467 break;
468 }
470 case ObjectType::Framebuffer:
471 {
472 if (!current_output) {
473 outputs.push_back(OutputInfo { });
474 current_output = &outputs.back();
475 }
477 if (!current_output->connector)
478 get_default_connector(card, *current_output);
480 if (!current_output->crtc)
481 get_default_crtc(card, *current_output);
483 int def_w, def_h;
485 if (current_plane) {
486 def_w = current_plane->w;
487 def_h = current_plane->h;
488 } else {
489 def_w = current_output->mode.hdisplay;
490 def_h = current_output->mode.vdisplay;
491 }
493 auto fbs = parse_fb(card, arg.arg, def_w, def_h);
495 if (current_plane)
496 current_plane->fbs = fbs;
497 else
498 current_output->fbs = fbs;
500 break;
501 }
502 }
503 }
505 // create default framebuffers if needed
506 for (OutputInfo& o : outputs) {
507 if (!o.crtc) {
508 get_default_crtc(card, *current_output);
509 o.user_set_crtc = true;
510 }
512 if (o.fbs.empty() && o.user_set_crtc)
513 o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
515 for (PlaneInfo &p : o.planes) {
516 if (p.fbs.empty())
517 p.fbs = get_default_fb(card, p.w, p.h);
518 }
519 }
521 return outputs;
522 }
524 static std::string videomode_to_string(const Videomode& mode)
525 {
526 unsigned hfp = mode.hsync_start - mode.hdisplay;
527 unsigned hsw = mode.hsync_end - mode.hsync_start;
528 unsigned hbp = mode.htotal - mode.hsync_end;
530 unsigned vfp = mode.vsync_start - mode.vdisplay;
531 unsigned vsw = mode.vsync_end - mode.vsync_start;
532 unsigned vbp = mode.vtotal - mode.vsync_end;
534 float hz = (mode.clock * 1000.0) / (mode.htotal * mode.vtotal);
535 if (mode.flags & (1<<4)) // XXX interlace
536 hz *= 2;
538 char buf[256];
540 sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz (%.2fHz)",
541 mode.clock / 1000.0,
542 mode.hdisplay, hfp, hsw, hbp,
543 mode.vdisplay, vfp, vsw, vbp,
544 mode.vrefresh, hz);
546 return std::string(buf);
547 }
549 static void print_outputs(const vector<OutputInfo>& outputs)
550 {
551 for (unsigned i = 0; i < outputs.size(); ++i) {
552 const OutputInfo& o = outputs[i];
554 printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
555 o.connector->fullname().c_str());
556 printf(" Crtc %u/@%u", o.crtc->id(), o.crtc->idx());
557 if (o.primary_plane)
558 printf(" (plane %u/@%u)", o.primary_plane->id(), o.primary_plane->idx());
559 printf(": %ux%u-%u (%s)\n",
560 o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
561 videomode_to_string(o.mode).c_str());
562 if (!o.fbs.empty()) {
563 auto fb = o.fbs[0];
564 printf(" Fb %ux%u-%s\n", fb->width(), fb->height(),
565 PixelFormatToFourCC(fb->format()).c_str());
566 }
568 for (unsigned j = 0; j < o.planes.size(); ++j) {
569 const PlaneInfo& p = o.planes[j];
570 auto fb = p.fbs[0];
571 printf(" Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
572 p.x, p.y, p.w, p.h);
573 printf(" Fb %ux%u-%s\n", fb->width(), fb->height(),
574 PixelFormatToFourCC(fb->format()).c_str());
575 }
576 }
577 }
579 static void draw_test_patterns(const vector<OutputInfo>& outputs)
580 {
581 for (const OutputInfo& o : outputs) {
582 for (auto fb : o.fbs)
583 draw_test_pattern(*fb);
585 for (const PlaneInfo& p : o.planes)
586 for (auto fb : p.fbs)
587 draw_test_pattern(*fb);
588 }
589 }
591 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
592 {
593 for (const OutputInfo& o : outputs) {
594 auto conn = o.connector;
595 auto crtc = o.crtc;
597 if (!o.fbs.empty()) {
598 auto fb = o.fbs[0];
599 int r = crtc->set_mode(conn, *fb, o.mode);
600 if (r)
601 printf("crtc->set_mode() failed for crtc %u: %s\n",
602 crtc->id(), strerror(-r));
603 }
605 for (const PlaneInfo& p : o.planes) {
606 auto fb = p.fbs[0];
607 int r = crtc->set_plane(p.plane, *fb,
608 p.x, p.y, p.w, p.h,
609 0, 0, fb->width(), fb->height());
610 if (r)
611 printf("crtc->set_plane() failed for plane %u: %s\n",
612 p.plane->id(), strerror(-r));
613 }
614 }
615 }
617 int main(int argc, char **argv)
618 {
619 vector<Arg> output_args = parse_cmdline(argc, argv);
621 Card card(s_device_path);
623 vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
625 if (card.has_atomic()) {
626 for (OutputInfo& o : outputs) {
627 o.primary_plane = o.crtc->get_primary_plane();
629 if (!o.fbs.empty() && !o.primary_plane)
630 EXIT("Could not get primary plane for crtc '%u'", o.crtc->id());
631 }
632 }
634 draw_test_patterns(outputs);
636 print_outputs(outputs);
638 set_crtcs_n_planes(card, outputs);
640 printf("press enter to exit\n");
642 getchar();
643 }