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 Videomode mode;
34 bool user_set_crtc;
35 vector<DumbFramebuffer*> fbs;
37 vector<PlaneInfo> planes;
38 };
40 static bool s_use_dmt;
41 static bool s_use_cea;
42 static unsigned s_num_buffers = 1;
44 static set<Crtc*> s_used_crtcs;
45 static set<Plane*> s_used_planes;
47 __attribute__ ((unused))
48 static void print_regex_match(smatch sm)
49 {
50 for (unsigned i = 0; i < sm.size(); ++i) {
51 string str = sm[i].str();
52 printf("%u: %s\n", i, str.c_str());
53 }
54 }
56 static void get_default_connector(Card& card, OutputInfo& output)
57 {
58 output.connector = card.get_first_connected_connector();
59 output.mode = output.connector->get_default_mode();
60 }
62 static void parse_connector(Card& card, const string& str, OutputInfo& output)
63 {
64 Connector* conn = nullptr;
66 auto connectors = card.get_connectors();
68 if (str[0] == '@') {
69 char* endptr;
70 unsigned idx = strtoul(str.c_str() + 1, &endptr, 10);
71 if (*endptr == 0) {
72 if (idx >= connectors.size())
73 EXIT("Bad connector number '%u'", idx);
75 conn = connectors[idx];
76 }
77 } else {
78 char* endptr;
79 unsigned id = strtoul(str.c_str(), &endptr, 10);
80 if (*endptr == 0) {
81 Connector* c = card.get_connector(id);
82 if (!c)
83 EXIT("Bad connector id '%u'", id);
85 conn = c;
86 }
87 }
89 if (!conn) {
90 auto iter = find_if(connectors.begin(), connectors.end(), [&str](Connector *c) { return c->fullname() == str; });
91 if (iter != connectors.end())
92 conn = *iter;
93 }
95 if (!conn)
96 EXIT("No connector '%s'", str.c_str());
98 if (!conn->connected())
99 EXIT("Connector '%s' not connected", conn->fullname().c_str());
101 output.connector = conn;
102 output.mode = output.connector->get_default_mode();
103 }
105 static void get_default_crtc(Card& card, OutputInfo& output)
106 {
107 Crtc* crtc = output.connector->get_current_crtc();
109 if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
110 s_used_crtcs.insert(crtc);
111 output.crtc = crtc;
112 return;
113 }
115 for (const auto& possible : output.connector->get_possible_crtcs()) {
116 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
117 s_used_crtcs.insert(possible);
118 output.crtc = possible;
119 return;
120 }
121 }
123 EXIT("Could not find available crtc");
124 }
126 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
127 {
128 // @12:1920x1200@60
129 const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+)(i)?)(?:@(\\d+))?");
131 smatch sm;
132 if (!regex_match(crtc_str, sm, mode_re))
133 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
135 if (sm[2].matched) {
136 bool use_idx = sm[1].length() == 1;
137 unsigned num = stoul(sm[2].str());
139 if (use_idx) {
140 auto crtcs = card.get_crtcs();
142 if (num >= crtcs.size())
143 EXIT("Bad crtc number '%u'", num);
145 output.crtc = crtcs[num];
146 } else {
147 Crtc* c = card.get_crtc(num);
148 if (!c)
149 EXIT("Bad crtc id '%u'", num);
151 output.crtc = c;
152 }
153 } else {
154 output.crtc = output.connector->get_current_crtc();
155 }
157 unsigned w = stoul(sm[3]);
158 unsigned h = stoul(sm[4]);
159 bool ilace = sm[5].matched ? true : false;
160 unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
162 bool found_mode = false;
164 try {
165 output.mode = output.connector->get_mode(w, h, refresh, ilace);
166 found_mode = true;
167 } catch (exception& e) { }
169 if (!found_mode && s_use_dmt) {
170 try {
171 output.mode = find_dmt(w, h, refresh, ilace);
172 found_mode = true;
173 printf("Found mode from DMT\n");
174 } catch (exception& e) { }
175 }
177 if (!found_mode && s_use_cea) {
178 try {
179 output.mode = find_cea(w, h, refresh, ilace);
180 found_mode = true;
181 printf("Found mode from CEA\n");
182 } catch (exception& e) { }
183 }
185 if (!found_mode)
186 throw invalid_argument("Mode not found");
187 }
189 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
190 {
191 // 3:400,400-400x400
192 const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
194 smatch sm;
195 if (!regex_match(plane_str, sm, plane_re))
196 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
198 if (sm[2].matched) {
199 bool use_idx = sm[1].length() == 1;
200 unsigned num = stoul(sm[2].str());
202 if (use_idx) {
203 auto planes = card.get_planes();
205 if (num >= planes.size())
206 EXIT("Bad plane number '%u'", num);
208 pinfo.plane = planes[num];
209 } else {
210 Plane* p = card.get_plane(num);
211 if (!p)
212 EXIT("Bad plane id '%u'", num);
214 pinfo.plane = p;
215 }
216 } else {
217 for (Plane* p : output.crtc->get_possible_planes()) {
218 if (s_used_planes.find(p) != s_used_planes.end())
219 continue;
221 if (p->plane_type() != PlaneType::Overlay)
222 continue;
224 pinfo.plane = p;
225 }
227 if (!pinfo.plane)
228 EXIT("Failed to find available plane");
229 }
231 s_used_planes.insert(pinfo.plane);
233 pinfo.w = stoul(sm[5]);
234 pinfo.h = stoul(sm[6]);
236 if (sm[3].matched)
237 pinfo.x = stoul(sm[3]);
238 else
239 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
241 if (sm[4].matched)
242 pinfo.y = stoul(sm[4]);
243 else
244 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
245 }
247 static vector<DumbFramebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
248 {
249 vector<DumbFramebuffer*> v;
251 for (unsigned i = 0; i < s_num_buffers; ++i)
252 v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
254 return v;
255 }
257 static vector<DumbFramebuffer*> parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
258 {
259 unsigned w = def_w;
260 unsigned h = def_h;
261 PixelFormat format = PixelFormat::XRGB8888;
263 if (!fb_str.empty()) {
264 // XXX the regexp is not quite correct
265 // 400x400-NV12
266 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
268 smatch sm;
269 if (!regex_match(fb_str, sm, fb_re))
270 EXIT("Failed to parse fb option '%s'", fb_str.c_str());
272 if (sm[1].matched)
273 w = stoul(sm[1]);
274 if (sm[2].matched)
275 h = stoul(sm[2]);
276 if (sm[3].matched)
277 format = FourCCToPixelFormat(sm[3]);
278 }
280 vector<DumbFramebuffer*> v;
282 for (unsigned i = 0; i < s_num_buffers; ++i)
283 v.push_back(new DumbFramebuffer(card, w, h, format));
285 return v;
286 }
288 static const char* usage_str =
289 "Usage: testpat [OPTION]...\n\n"
290 "Show a test pattern on a display or plane\n\n"
291 "Options:\n"
292 " --device=DEVICE DEVICE is the path to DRM card to open\n"
293 " -c, --connector=CONN CONN is <connector>\n"
294 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
295 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
296 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
297 " --dmt Search for the given mode from DMT tables\n"
298 " --cea Search for the given mode from CEA tables\n"
299 "\n"
300 "<connector>, <crtc> and <plane> can be given by id (<id>) or index (@<idx>).\n"
301 "<connector> can also be given by name.\n"
302 "\n"
303 "Options can be given multiple times to set up multiple displays or planes.\n"
304 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
305 "an earlier option.\n"
306 "If you omit parameters, testpat tries to guess what you mean\n"
307 "\n"
308 "Examples:\n"
309 "\n"
310 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
311 " testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
312 "XR24 framebuffer on first connected connector in the default mode:\n"
313 " testpat -f XR24\n\n"
314 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
315 " testpat -p 400x400 -f XR24\n\n"
316 "Test pattern on the second connector with default mode:\n"
317 " testpat -c @1\n"
318 ;
320 static void usage()
321 {
322 puts(usage_str);
323 }
325 enum class ObjectType
326 {
327 Connector,
328 Crtc,
329 Plane,
330 Framebuffer,
331 };
333 struct Arg
334 {
335 ObjectType type;
336 string arg;
337 };
339 static string s_device_path = "/dev/dri/card0";
341 static vector<Arg> parse_cmdline(int argc, char **argv)
342 {
343 vector<Arg> args;
345 OptionSet optionset = {
346 Option("|device=",
347 [&](string s)
348 {
349 s_device_path = s;
350 }),
351 Option("c|connector=",
352 [&](string s)
353 {
354 args.push_back(Arg { ObjectType::Connector, s });
355 }),
356 Option("r|crtc=", [&](string s)
357 {
358 args.push_back(Arg { ObjectType::Crtc, s });
359 }),
360 Option("p|plane=", [&](string s)
361 {
362 args.push_back(Arg { ObjectType::Plane, s });
363 }),
364 Option("f|fb=", [&](string s)
365 {
366 args.push_back(Arg { ObjectType::Framebuffer, s });
367 }),
368 Option("|dmt", []()
369 {
370 s_use_dmt = true;
371 }),
372 Option("|cea", []()
373 {
374 s_use_cea = true;
375 }),
376 Option("h|help", [&]()
377 {
378 usage();
379 exit(-1);
380 }),
381 };
383 optionset.parse(argc, argv);
385 if (optionset.params().size() > 0) {
386 usage();
387 exit(-1);
388 }
390 return args;
391 }
393 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
394 {
395 vector<OutputInfo> outputs;
397 if (output_args.size() == 0) {
398 // no output args, show a pattern on all screens
399 for (auto& pipe : card.get_connected_pipelines()) {
400 OutputInfo output = { };
401 output.connector = pipe.connector;
402 output.crtc = pipe.crtc;
403 output.mode = output.connector->get_default_mode();
405 output.fbs = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
407 outputs.push_back(output);
408 }
410 return outputs;
411 }
413 OutputInfo* current_output = 0;
414 PlaneInfo* current_plane = 0;
416 for (auto& arg : output_args) {
417 switch (arg.type) {
418 case ObjectType::Connector:
419 {
420 outputs.push_back(OutputInfo { });
421 current_output = &outputs.back();
423 parse_connector(card, arg.arg, *current_output);
424 current_plane = 0;
426 break;
427 }
429 case ObjectType::Crtc:
430 {
431 if (!current_output) {
432 outputs.push_back(OutputInfo { });
433 current_output = &outputs.back();
434 }
436 if (!current_output->connector)
437 get_default_connector(card, *current_output);
439 parse_crtc(card, arg.arg, *current_output);
441 current_output->user_set_crtc = true;
443 current_plane = 0;
445 break;
446 }
448 case ObjectType::Plane:
449 {
450 if (!current_output) {
451 outputs.push_back(OutputInfo { });
452 current_output = &outputs.back();
453 }
455 if (!current_output->connector)
456 get_default_connector(card, *current_output);
458 if (!current_output->crtc)
459 get_default_crtc(card, *current_output);
461 current_output->planes.push_back(PlaneInfo { });
462 current_plane = ¤t_output->planes.back();
464 parse_plane(card, arg.arg, *current_output, *current_plane);
466 break;
467 }
469 case ObjectType::Framebuffer:
470 {
471 if (!current_output) {
472 outputs.push_back(OutputInfo { });
473 current_output = &outputs.back();
474 }
476 if (!current_output->connector)
477 get_default_connector(card, *current_output);
479 if (!current_output->crtc)
480 get_default_crtc(card, *current_output);
482 int def_w, def_h;
484 if (current_plane) {
485 def_w = current_plane->w;
486 def_h = current_plane->h;
487 } else {
488 def_w = current_output->mode.hdisplay;
489 def_h = current_output->mode.vdisplay;
490 }
492 auto fbs = parse_fb(card, arg.arg, def_w, def_h);
494 if (current_plane)
495 current_plane->fbs = fbs;
496 else
497 current_output->fbs = fbs;
499 break;
500 }
501 }
502 }
504 // create default framebuffers if needed
505 for (OutputInfo& o : outputs) {
506 if (!o.crtc) {
507 get_default_crtc(card, *current_output);
508 o.user_set_crtc = true;
509 }
511 if (o.fbs.empty() && o.user_set_crtc)
512 o.fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
514 for (PlaneInfo &p : o.planes) {
515 if (p.fbs.empty())
516 p.fbs = get_default_fb(card, p.w, p.h);
517 }
518 }
520 return outputs;
521 }
523 static std::string videomode_to_string(const Videomode& mode)
524 {
525 unsigned hfp = mode.hsync_start - mode.hdisplay;
526 unsigned hsw = mode.hsync_end - mode.hsync_start;
527 unsigned hbp = mode.htotal - mode.hsync_end;
529 unsigned vfp = mode.vsync_start - mode.vdisplay;
530 unsigned vsw = mode.vsync_end - mode.vsync_start;
531 unsigned vbp = mode.vtotal - mode.vsync_end;
533 float hz = (mode.clock * 1000.0) / (mode.htotal * mode.vtotal);
534 if (mode.flags & (1<<4)) // XXX interlace
535 hz *= 2;
537 char buf[256];
539 sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz (%.2fHz)",
540 mode.clock / 1000.0,
541 mode.hdisplay, hfp, hsw, hbp,
542 mode.vdisplay, vfp, vsw, vbp,
543 mode.vrefresh, hz);
545 return std::string(buf);
546 }
548 static void print_outputs(const vector<OutputInfo>& outputs)
549 {
550 for (unsigned i = 0; i < outputs.size(); ++i) {
551 const OutputInfo& o = outputs[i];
553 printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
554 o.connector->fullname().c_str());
555 printf(" Crtc %u/@%u: %ux%u-%u (%s)\n", o.crtc->id(), o.crtc->idx(),
556 o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
557 videomode_to_string(o.mode).c_str());
558 if (!o.fbs.empty()) {
559 auto fb = o.fbs[0];
560 printf(" Fb %ux%u-%s\n", fb->width(), fb->height(),
561 PixelFormatToFourCC(fb->format()).c_str());
562 }
564 for (unsigned j = 0; j < o.planes.size(); ++j) {
565 const PlaneInfo& p = o.planes[j];
566 auto fb = p.fbs[0];
567 printf(" Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
568 p.x, p.y, p.w, p.h);
569 printf(" Fb %ux%u-%s\n", fb->width(), fb->height(),
570 PixelFormatToFourCC(fb->format()).c_str());
571 }
572 }
573 }
575 static void draw_test_patterns(const vector<OutputInfo>& outputs)
576 {
577 for (const OutputInfo& o : outputs) {
578 for (auto fb : o.fbs)
579 draw_test_pattern(*fb);
581 for (const PlaneInfo& p : o.planes)
582 for (auto fb : p.fbs)
583 draw_test_pattern(*fb);
584 }
585 }
587 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
588 {
589 for (const OutputInfo& o : outputs) {
590 auto conn = o.connector;
591 auto crtc = o.crtc;
593 if (!o.fbs.empty()) {
594 auto fb = o.fbs[0];
595 int r = crtc->set_mode(conn, *fb, o.mode);
596 if (r)
597 printf("crtc->set_mode() failed for crtc %u: %s\n",
598 crtc->id(), strerror(-r));
599 }
601 for (const PlaneInfo& p : o.planes) {
602 auto fb = p.fbs[0];
603 int r = crtc->set_plane(p.plane, *fb,
604 p.x, p.y, p.w, p.h,
605 0, 0, fb->width(), fb->height());
606 if (r)
607 printf("crtc->set_plane() failed for plane %u: %s\n",
608 p.plane->id(), strerror(-r));
609 }
610 }
611 }
613 int main(int argc, char **argv)
614 {
615 vector<Arg> output_args = parse_cmdline(argc, argv);
617 Card card(s_device_path);
619 vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
621 draw_test_patterns(outputs);
623 print_outputs(outputs);
625 set_crtcs_n_planes(card, outputs);
627 printf("press enter to exit\n");
629 getchar();
630 }