0dc7c1735a7de2ba93e4e36fe8ceb8f8984ebe28
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 DumbFramebuffer* fb;
26 };
28 struct OutputInfo
29 {
30 Connector* connector;
32 Crtc* crtc;
33 Videomode mode;
34 bool user_set_crtc;
35 DumbFramebuffer* fb;
37 vector<PlaneInfo> planes;
38 };
40 static bool s_use_dmt;
41 static bool s_use_cea;
43 static set<Crtc*> s_used_crtcs;
44 static set<Plane*> s_used_planes;
46 __attribute__ ((unused))
47 static void print_regex_match(smatch sm)
48 {
49 for (unsigned i = 0; i < sm.size(); ++i) {
50 string str = sm[i].str();
51 printf("%u: %s\n", i, str.c_str());
52 }
53 }
55 static void get_default_connector(Card& card, OutputInfo& output)
56 {
57 output.connector = card.get_first_connected_connector();
58 output.mode = output.connector->get_default_mode();
59 }
61 static void parse_connector(Card& card, const string& str, OutputInfo& output)
62 {
63 Connector* conn = nullptr;
65 auto connectors = card.get_connectors();
67 if (str[0] == '@') {
68 char* endptr;
69 unsigned idx = strtoul(str.c_str() + 1, &endptr, 10);
70 if (*endptr == 0) {
71 if (idx >= connectors.size())
72 EXIT("Bad connector number '%u'", idx);
74 conn = connectors[idx];
75 }
76 } else {
77 char* endptr;
78 unsigned id = strtoul(str.c_str(), &endptr, 10);
79 if (*endptr == 0) {
80 Connector* c = card.get_connector(id);
81 if (!c)
82 EXIT("Bad connector id '%u'", id);
84 conn = c;
85 }
86 }
88 if (!conn) {
89 auto iter = find_if(connectors.begin(), connectors.end(), [&str](Connector *c) { return c->fullname() == str; });
90 if (iter != connectors.end())
91 conn = *iter;
92 }
94 if (!conn)
95 EXIT("No connector '%s'", str.c_str());
97 if (!conn->connected())
98 EXIT("Connector '%s' not connected", conn->fullname().c_str());
100 output.connector = conn;
101 output.mode = output.connector->get_default_mode();
102 }
104 static void get_default_crtc(Card& card, OutputInfo& output)
105 {
106 Crtc* crtc = output.connector->get_current_crtc();
108 if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
109 s_used_crtcs.insert(crtc);
110 output.crtc = crtc;
111 return;
112 }
114 for (const auto& possible : output.connector->get_possible_crtcs()) {
115 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
116 s_used_crtcs.insert(possible);
117 output.crtc = possible;
118 return;
119 }
120 }
122 EXIT("Could not find available crtc");
123 }
125 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
126 {
127 // @12:1920x1200@60
128 const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+)(i)?)(?:@(\\d+))?");
130 smatch sm;
131 if (!regex_match(crtc_str, sm, mode_re))
132 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
134 if (sm[2].matched) {
135 bool use_idx = sm[1].length() == 1;
136 unsigned num = stoul(sm[2].str());
138 if (use_idx) {
139 auto crtcs = card.get_crtcs();
141 if (num >= crtcs.size())
142 EXIT("Bad crtc number '%u'", num);
144 output.crtc = crtcs[num];
145 } else {
146 Crtc* c = card.get_crtc(num);
147 if (!c)
148 EXIT("Bad crtc id '%u'", num);
150 output.crtc = c;
151 }
152 } else {
153 output.crtc = output.connector->get_current_crtc();
154 }
156 unsigned w = stoul(sm[3]);
157 unsigned h = stoul(sm[4]);
158 bool ilace = sm[5].matched ? true : false;
159 unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
161 bool found_mode = false;
163 try {
164 output.mode = output.connector->get_mode(w, h, refresh, ilace);
165 found_mode = true;
166 } catch (exception& e) { }
168 if (!found_mode && s_use_dmt) {
169 try {
170 output.mode = find_dmt(w, h, refresh, ilace);
171 found_mode = true;
172 printf("Found mode from DMT\n");
173 } catch (exception& e) { }
174 }
176 if (!found_mode && s_use_cea) {
177 try {
178 output.mode = find_cea(w, h, refresh, ilace);
179 found_mode = true;
180 printf("Found mode from CEA\n");
181 } catch (exception& e) { }
182 }
184 if (!found_mode)
185 throw invalid_argument("Mode not found");
186 }
188 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
189 {
190 // 3:400,400-400x400
191 const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
193 smatch sm;
194 if (!regex_match(plane_str, sm, plane_re))
195 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
197 if (sm[2].matched) {
198 bool use_idx = sm[1].length() == 1;
199 unsigned num = stoul(sm[2].str());
201 if (use_idx) {
202 auto planes = card.get_planes();
204 if (num >= planes.size())
205 EXIT("Bad plane number '%u'", num);
207 pinfo.plane = planes[num];
208 } else {
209 Plane* p = card.get_plane(num);
210 if (!p)
211 EXIT("Bad plane id '%u'", num);
213 pinfo.plane = p;
214 }
215 } else {
216 for (Plane* p : output.crtc->get_possible_planes()) {
217 if (s_used_planes.find(p) != s_used_planes.end())
218 continue;
220 if (p->plane_type() != PlaneType::Overlay)
221 continue;
223 pinfo.plane = p;
224 }
226 if (!pinfo.plane)
227 EXIT("Failed to find available plane");
228 }
230 s_used_planes.insert(pinfo.plane);
232 pinfo.w = stoul(sm[5]);
233 pinfo.h = stoul(sm[6]);
235 if (sm[3].matched)
236 pinfo.x = stoul(sm[3]);
237 else
238 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
240 if (sm[4].matched)
241 pinfo.y = stoul(sm[4]);
242 else
243 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
244 }
246 static DumbFramebuffer* get_default_fb(Card& card, unsigned width, unsigned height)
247 {
248 return new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888);
249 }
251 static DumbFramebuffer* parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
252 {
253 unsigned w = def_w;
254 unsigned h = def_h;
255 PixelFormat format = PixelFormat::XRGB8888;
257 if (!fb_str.empty()) {
258 // XXX the regexp is not quite correct
259 // 400x400-NV12
260 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
262 smatch sm;
263 if (!regex_match(fb_str, sm, fb_re))
264 EXIT("Failed to parse fb option '%s'", fb_str.c_str());
266 if (sm[1].matched)
267 w = stoul(sm[1]);
268 if (sm[2].matched)
269 h = stoul(sm[2]);
270 if (sm[3].matched)
271 format = FourCCToPixelFormat(sm[3]);
272 }
274 return new DumbFramebuffer(card, w, h, format);
275 }
277 static const char* usage_str =
278 "Usage: testpat [OPTION]...\n\n"
279 "Show a test pattern on a display or plane\n\n"
280 "Options:\n"
281 " --device=DEVICE DEVICE is the path to DRM card to open\n"
282 " -c, --connector=CONN CONN is <connector>\n"
283 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
284 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
285 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
286 " --dmt Search for the given mode from DMT tables\n"
287 " --cea Search for the given mode from CEA tables\n"
288 "\n"
289 "<connector>, <crtc> and <plane> can be given by id (<id>) or index (@<idx>).\n"
290 "<connector> can also be given by name.\n"
291 "\n"
292 "Options can be given multiple times to set up multiple displays or planes.\n"
293 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
294 "an earlier option.\n"
295 "If you omit parameters, testpat tries to guess what you mean\n"
296 "\n"
297 "Examples:\n"
298 "\n"
299 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
300 " testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
301 "XR24 framebuffer on first connected connector in the default mode:\n"
302 " testpat -f XR24\n\n"
303 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
304 " testpat -p 400x400 -f XR24\n\n"
305 "Test pattern on the second connector with default mode:\n"
306 " testpat -c @1\n"
307 ;
309 static void usage()
310 {
311 puts(usage_str);
312 }
314 enum class ObjectType
315 {
316 Connector,
317 Crtc,
318 Plane,
319 Framebuffer,
320 };
322 struct Arg
323 {
324 ObjectType type;
325 string arg;
326 };
328 static string s_device_path = "/dev/dri/card0";
330 static vector<Arg> parse_cmdline(int argc, char **argv)
331 {
332 vector<Arg> args;
334 OptionSet optionset = {
335 Option("|device=",
336 [&](string s)
337 {
338 s_device_path = s;
339 }),
340 Option("c|connector=",
341 [&](string s)
342 {
343 args.push_back(Arg { ObjectType::Connector, s });
344 }),
345 Option("r|crtc=", [&](string s)
346 {
347 args.push_back(Arg { ObjectType::Crtc, s });
348 }),
349 Option("p|plane=", [&](string s)
350 {
351 args.push_back(Arg { ObjectType::Plane, s });
352 }),
353 Option("f|fb=", [&](string s)
354 {
355 args.push_back(Arg { ObjectType::Framebuffer, s });
356 }),
357 Option("|dmt", []()
358 {
359 s_use_dmt = true;
360 }),
361 Option("|cea", []()
362 {
363 s_use_cea = true;
364 }),
365 Option("h|help", [&]()
366 {
367 usage();
368 exit(-1);
369 }),
370 };
372 optionset.parse(argc, argv);
374 if (optionset.params().size() > 0) {
375 usage();
376 exit(-1);
377 }
379 return args;
380 }
382 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
383 {
384 vector<OutputInfo> outputs;
386 if (output_args.size() == 0) {
387 // no output args, show a pattern on all screens
388 for (auto& pipe : card.get_connected_pipelines()) {
389 OutputInfo output = { };
390 output.connector = pipe.connector;
391 output.crtc = pipe.crtc;
392 output.mode = output.connector->get_default_mode();
394 output.fb = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
396 outputs.push_back(output);
397 }
399 return outputs;
400 }
402 OutputInfo* current_output = 0;
403 PlaneInfo* current_plane = 0;
405 for (auto& arg : output_args) {
406 switch (arg.type) {
407 case ObjectType::Connector:
408 {
409 outputs.push_back(OutputInfo { });
410 current_output = &outputs.back();
412 parse_connector(card, arg.arg, *current_output);
413 current_plane = 0;
415 break;
416 }
418 case ObjectType::Crtc:
419 {
420 if (!current_output) {
421 outputs.push_back(OutputInfo { });
422 current_output = &outputs.back();
423 }
425 if (!current_output->connector)
426 get_default_connector(card, *current_output);
428 parse_crtc(card, arg.arg, *current_output);
430 current_output->user_set_crtc = true;
432 current_plane = 0;
434 break;
435 }
437 case ObjectType::Plane:
438 {
439 if (!current_output) {
440 outputs.push_back(OutputInfo { });
441 current_output = &outputs.back();
442 }
444 if (!current_output->connector)
445 get_default_connector(card, *current_output);
447 if (!current_output->crtc)
448 get_default_crtc(card, *current_output);
450 current_output->planes.push_back(PlaneInfo { });
451 current_plane = ¤t_output->planes.back();
453 parse_plane(card, arg.arg, *current_output, *current_plane);
455 break;
456 }
458 case ObjectType::Framebuffer:
459 {
460 if (!current_output) {
461 outputs.push_back(OutputInfo { });
462 current_output = &outputs.back();
463 }
465 if (!current_output->connector)
466 get_default_connector(card, *current_output);
468 if (!current_output->crtc)
469 get_default_crtc(card, *current_output);
471 int def_w, def_h;
473 if (current_plane) {
474 def_w = current_plane->w;
475 def_h = current_plane->h;
476 } else {
477 def_w = current_output->mode.hdisplay;
478 def_h = current_output->mode.vdisplay;
479 }
481 auto fb = parse_fb(card, arg.arg, def_w, def_h);
483 if (current_plane)
484 current_plane->fb = fb;
485 else
486 current_output->fb = fb;
488 break;
489 }
490 }
491 }
493 // create default framebuffers if needed
494 for (OutputInfo& o : outputs) {
495 if (!o.crtc) {
496 get_default_crtc(card, *current_output);
497 o.user_set_crtc = true;
498 }
500 if (!o.fb && o.user_set_crtc)
501 o.fb = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
503 for (PlaneInfo &p : o.planes) {
504 if (!p.fb)
505 p.fb = get_default_fb(card, p.w, p.h);
506 }
507 }
509 return outputs;
510 }
512 static std::string videomode_to_string(const Videomode& mode)
513 {
514 unsigned hfp = mode.hsync_start - mode.hdisplay;
515 unsigned hsw = mode.hsync_end - mode.hsync_start;
516 unsigned hbp = mode.htotal - mode.hsync_end;
518 unsigned vfp = mode.vsync_start - mode.vdisplay;
519 unsigned vsw = mode.vsync_end - mode.vsync_start;
520 unsigned vbp = mode.vtotal - mode.vsync_end;
522 float hz = (mode.clock * 1000.0) / (mode.htotal * mode.vtotal);
523 if (mode.flags & (1<<4)) // XXX interlace
524 hz *= 2;
526 char buf[256];
528 sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz (%.2fHz)",
529 mode.clock / 1000.0,
530 mode.hdisplay, hfp, hsw, hbp,
531 mode.vdisplay, vfp, vsw, vbp,
532 mode.vrefresh, hz);
534 return std::string(buf);
535 }
537 static void print_outputs(const vector<OutputInfo>& outputs)
538 {
539 for (unsigned i = 0; i < outputs.size(); ++i) {
540 const OutputInfo& o = outputs[i];
542 printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
543 o.connector->fullname().c_str());
544 printf(" Crtc %u/@%u: %ux%u-%u (%s)\n", o.crtc->id(), o.crtc->idx(),
545 o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
546 videomode_to_string(o.mode).c_str());
547 if (o.fb)
548 printf(" Fb %ux%u-%s\n", o.fb->width(), o.fb->height(),
549 PixelFormatToFourCC(o.fb->format()).c_str());
551 for (unsigned j = 0; j < o.planes.size(); ++j) {
552 const PlaneInfo& p = o.planes[j];
553 printf(" Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
554 p.x, p.y, p.w, p.h);
555 printf(" Fb %ux%u-%s\n", p.fb->width(), p.fb->height(),
556 PixelFormatToFourCC(p.fb->format()).c_str());
557 }
558 }
559 }
561 static void draw_test_patterns(const vector<OutputInfo>& outputs)
562 {
563 for (const OutputInfo& o : outputs) {
564 if (o.fb)
565 draw_test_pattern(*o.fb);
567 for (const PlaneInfo& p : o.planes)
568 draw_test_pattern(*p.fb);
569 }
570 }
572 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
573 {
574 for (const OutputInfo& o : outputs) {
575 auto conn = o.connector;
576 auto crtc = o.crtc;
578 if (o.fb) {
579 int r = crtc->set_mode(conn, *o.fb, o.mode);
580 if (r)
581 printf("crtc->set_mode() failed for crtc %u: %s\n",
582 crtc->id(), strerror(-r));
583 }
585 for (const PlaneInfo& p : o.planes) {
586 int r = crtc->set_plane(p.plane, *p.fb,
587 p.x, p.y, p.w, p.h,
588 0, 0, p.fb->width(), p.fb->height());
589 if (r)
590 printf("crtc->set_plane() failed for plane %u: %s\n",
591 p.plane->id(), strerror(-r));
592 }
593 }
594 }
596 int main(int argc, char **argv)
597 {
598 vector<Arg> output_args = parse_cmdline(argc, argv);
600 Card card(s_device_path);
602 vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
604 draw_test_patterns(outputs);
606 print_outputs(outputs);
608 set_crtcs_n_planes(card, outputs);
610 printf("press enter to exit\n");
612 getchar();
613 }