1 #include <cstdio>
2 #include <algorithm>
3 #include <regex>
4 #include <set>
6 #include "kms++.h"
8 #include "test.h"
9 #include "opts.h"
11 using namespace std;
12 using namespace kms;
14 struct PlaneInfo
15 {
16 Plane* plane;
18 unsigned x;
19 unsigned y;
20 unsigned w;
21 unsigned h;
23 DumbFramebuffer* fb;
24 };
26 struct OutputInfo
27 {
28 Connector* connector;
30 Crtc* crtc;
31 Videomode mode;
32 bool user_set_crtc;
33 DumbFramebuffer* fb;
35 vector<PlaneInfo> planes;
36 };
38 static set<Crtc*> s_used_crtcs;
39 static set<Plane*> s_used_planes;
41 __attribute__ ((unused))
42 static void print_regex_match(smatch sm)
43 {
44 for (unsigned i = 0; i < sm.size(); ++i) {
45 string str = sm[i].str();
46 printf("%u: %s\n", i, str.c_str());
47 }
48 }
50 static void get_default_connector(Card& card, OutputInfo& output)
51 {
52 output.connector = card.get_first_connected_connector();
53 output.mode = output.connector->get_default_mode();
54 }
56 static void parse_connector(Card& card, const string& str, OutputInfo& output)
57 {
58 Connector* conn = nullptr;
60 auto connectors = card.get_connectors();
62 if (str[0] == '@') {
63 char* endptr;
64 unsigned idx = strtoul(str.c_str() + 1, &endptr, 10);
65 if (*endptr == 0) {
66 if (idx >= connectors.size())
67 EXIT("Bad connector number '%u'", idx);
69 conn = connectors[idx];
70 }
71 } else {
72 char* endptr;
73 unsigned id = strtoul(str.c_str(), &endptr, 10);
74 if (*endptr == 0) {
75 Connector* c = card.get_connector(id);
76 if (!c)
77 EXIT("Bad connector id '%u'", id);
79 conn = c;
80 }
81 }
83 if (!conn) {
84 auto iter = find_if(connectors.begin(), connectors.end(), [&str](Connector *c) { return c->fullname() == str; });
85 if (iter != connectors.end())
86 conn = *iter;
87 }
89 if (!conn)
90 EXIT("No connector '%s'", str.c_str());
92 if (!conn->connected())
93 EXIT("Connector '%s' not connected", conn->fullname().c_str());
95 output.connector = conn;
96 output.mode = output.connector->get_default_mode();
97 }
99 static void get_default_crtc(Card& card, OutputInfo& output)
100 {
101 Crtc* crtc = output.connector->get_current_crtc();
103 if (crtc && s_used_crtcs.find(crtc) == s_used_crtcs.end()) {
104 s_used_crtcs.insert(crtc);
105 output.crtc = crtc;
106 return;
107 }
109 for (const auto& possible : output.connector->get_possible_crtcs()) {
110 if (s_used_crtcs.find(possible) == s_used_crtcs.end()) {
111 s_used_crtcs.insert(possible);
112 output.crtc = possible;
113 return;
114 }
115 }
117 EXIT("Could not find available crtc");
118 }
120 static void parse_crtc(Card& card, const string& crtc_str, OutputInfo& output)
121 {
122 // @12:1920x1200-60
123 const regex mode_re("(?:(@?)(\\d+):)?(?:(\\d+)x(\\d+)(i)?)(?:-(\\d+))?");
125 smatch sm;
126 if (!regex_match(crtc_str, sm, mode_re))
127 EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
129 if (sm[2].matched) {
130 bool use_idx = sm[1].length() == 1;
131 unsigned num = stoul(sm[2].str());
133 if (use_idx) {
134 auto crtcs = card.get_crtcs();
136 if (num >= crtcs.size())
137 EXIT("Bad crtc number '%u'", num);
139 output.crtc = crtcs[num];
140 } else {
141 Crtc* c = card.get_crtc(num);
142 if (!c)
143 EXIT("Bad crtc id '%u'", num);
145 output.crtc = c;
146 }
147 } else {
148 output.crtc = output.connector->get_current_crtc();
149 }
151 unsigned w = stoul(sm[3]);
152 unsigned h = stoul(sm[4]);
153 bool ilace = sm[5].matched ? true : false;
154 unsigned refresh = sm[6].matched ? stoul(sm[6]) : 0;
156 output.mode = output.connector->get_mode(w, h, refresh, ilace);
157 }
159 static void parse_plane(Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
160 {
161 // 3:400,400-400x400
162 const regex plane_re("(?:(@?)(\\d+):)?(?:(\\d+),(\\d+)-)?(\\d+)x(\\d+)");
164 smatch sm;
165 if (!regex_match(plane_str, sm, plane_re))
166 EXIT("Failed to parse plane option '%s'", plane_str.c_str());
168 if (sm[2].matched) {
169 bool use_idx = sm[1].length() == 1;
170 unsigned num = stoul(sm[2].str());
172 if (use_idx) {
173 auto planes = card.get_planes();
175 if (num >= planes.size())
176 EXIT("Bad plane number '%u'", num);
178 pinfo.plane = planes[num];
179 } else {
180 Plane* p = card.get_plane(num);
181 if (!p)
182 EXIT("Bad plane id '%u'", num);
184 pinfo.plane = p;
185 }
186 } else {
187 for (Plane* p : output.crtc->get_possible_planes()) {
188 if (s_used_planes.find(p) != s_used_planes.end())
189 continue;
191 if (p->plane_type() != PlaneType::Overlay)
192 continue;
194 pinfo.plane = p;
195 }
197 if (!pinfo.plane)
198 EXIT("Failed to find available plane");
199 }
201 s_used_planes.insert(pinfo.plane);
203 pinfo.w = stoul(sm[5]);
204 pinfo.h = stoul(sm[6]);
206 if (sm[3].matched)
207 pinfo.x = stoul(sm[3]);
208 else
209 pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
211 if (sm[4].matched)
212 pinfo.y = stoul(sm[4]);
213 else
214 pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
215 }
217 static DumbFramebuffer* get_default_fb(Card& card, unsigned width, unsigned height)
218 {
219 auto fb = new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888);
220 draw_test_pattern(*fb);
221 return fb;
222 }
224 static DumbFramebuffer* parse_fb(Card& card, const string& fb_str, unsigned def_w, unsigned def_h)
225 {
226 unsigned w = def_w;
227 unsigned h = def_h;
228 PixelFormat format = PixelFormat::XRGB8888;
230 if (!fb_str.empty()) {
231 // XXX the regexp is not quite correct
232 // 400x400-NV12
233 const regex fb_re("(?:(\\d+)x(\\d+))?(?:-)?(\\w\\w\\w\\w)?");
235 smatch sm;
236 if (!regex_match(fb_str, sm, fb_re))
237 EXIT("Failed to parse fb option '%s'", fb_str.c_str());
239 if (sm[1].matched)
240 w = stoul(sm[1]);
241 if (sm[2].matched)
242 h = stoul(sm[2]);
243 if (sm[3].matched)
244 format = FourCCToPixelFormat(sm[3]);
245 }
247 auto fb = new DumbFramebuffer(card, w, h, format);
248 draw_test_pattern(*fb);
249 return fb;
250 }
252 static const char* usage_str =
253 "Usage: testpat [OPTION]...\n\n"
254 "Show a test pattern on a display or plane\n\n"
255 "Options:\n"
256 " --device=DEVICE DEVICE is the path to DRM card to open\n"
257 " -c, --connector=CONN CONN is <connector>\n"
258 " -r, --crtc=CRTC CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
259 " -p, --plane=PLANE PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
260 " -f, --fb=FB FB is [<w>x<h>][-][<4cc>]\n"
261 "\n"
262 "<connector>, <crtc> and <plane> can be given by id (<id>) or index (@<idx>).\n"
263 "<connector> can also be given by name.\n"
264 "\n"
265 "Options can be given multiple times to set up multiple displays or planes.\n"
266 "Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
267 "an earlier option.\n"
268 "If you omit parameters, testpat tries to guess what you mean\n"
269 "\n"
270 "Examples:\n"
271 "\n"
272 "Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
273 " testpat -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
274 "XR24 framebuffer on first connected connector in the default mode:\n"
275 " testpat -f XR24\n\n"
276 "XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
277 " testpat -p 400x400 -f XR24\n\n"
278 "Test pattern on the second connector with default mode:\n"
279 " testpat -c @1\n"
280 ;
282 static void usage()
283 {
284 puts(usage_str);
285 }
287 enum class ObjectType
288 {
289 Connector,
290 Crtc,
291 Plane,
292 Framebuffer,
293 };
295 struct Arg
296 {
297 ObjectType type;
298 string arg;
299 };
301 static string s_device_path = "/dev/dri/card0";
303 static vector<Arg> parse_cmdline(int argc, char **argv)
304 {
305 vector<Arg> args;
307 OptionSet optionset = {
308 Option("|device=",
309 [&](string s)
310 {
311 s_device_path = s;
312 }),
313 Option("c|connector=",
314 [&](string s)
315 {
316 args.push_back(Arg { ObjectType::Connector, s });
317 }),
318 Option("r|crtc=", [&](string s)
319 {
320 args.push_back(Arg { ObjectType::Crtc, s });
321 }),
322 Option("p|plane=", [&](string s)
323 {
324 args.push_back(Arg { ObjectType::Plane, s });
325 }),
326 Option("f|fb=", [&](string s)
327 {
328 args.push_back(Arg { ObjectType::Framebuffer, s });
329 }),
330 Option("h|help", [&]()
331 {
332 usage();
333 exit(-1);
334 }),
335 };
337 optionset.parse(argc, argv);
339 if (optionset.params().size() > 0) {
340 usage();
341 exit(-1);
342 }
344 return args;
345 }
347 static vector<OutputInfo> setups_to_outputs(Card& card, const vector<Arg>& output_args)
348 {
349 vector<OutputInfo> outputs;
351 if (output_args.size() == 0) {
352 // no output args, show a pattern on all screens
353 for (auto& pipe : card.get_connected_pipelines()) {
354 OutputInfo output = { };
355 output.connector = pipe.connector;
356 output.crtc = pipe.crtc;
357 output.mode = output.connector->get_default_mode();
359 output.fb = get_default_fb(card, output.mode.hdisplay, output.mode.vdisplay);
361 outputs.push_back(output);
362 }
364 return outputs;
365 }
367 OutputInfo* current_output = 0;
368 PlaneInfo* current_plane = 0;
370 for (auto& arg : output_args) {
371 switch (arg.type) {
372 case ObjectType::Connector:
373 {
374 outputs.push_back(OutputInfo { });
375 current_output = &outputs.back();
377 parse_connector(card, arg.arg, *current_output);
378 current_plane = 0;
380 break;
381 }
383 case ObjectType::Crtc:
384 {
385 if (!current_output) {
386 outputs.push_back(OutputInfo { });
387 current_output = &outputs.back();
388 }
390 if (!current_output->connector)
391 get_default_connector(card, *current_output);
393 parse_crtc(card, arg.arg, *current_output);
395 current_output->user_set_crtc = true;
397 current_plane = 0;
399 break;
400 }
402 case ObjectType::Plane:
403 {
404 if (!current_output) {
405 outputs.push_back(OutputInfo { });
406 current_output = &outputs.back();
407 }
409 if (!current_output->connector)
410 get_default_connector(card, *current_output);
412 if (!current_output->crtc)
413 get_default_crtc(card, *current_output);
415 current_output->planes.push_back(PlaneInfo { });
416 current_plane = ¤t_output->planes.back();
418 parse_plane(card, arg.arg, *current_output, *current_plane);
420 break;
421 }
423 case ObjectType::Framebuffer:
424 {
425 if (!current_output) {
426 outputs.push_back(OutputInfo { });
427 current_output = &outputs.back();
428 }
430 if (!current_output->connector)
431 get_default_connector(card, *current_output);
433 if (!current_output->crtc)
434 get_default_crtc(card, *current_output);
436 int def_w, def_h;
438 if (current_plane) {
439 def_w = current_plane->w;
440 def_h = current_plane->h;
441 } else {
442 def_w = current_output->mode.hdisplay;
443 def_h = current_output->mode.vdisplay;
444 }
446 auto fb = parse_fb(card, arg.arg, def_w, def_h);
448 if (current_plane)
449 current_plane->fb = fb;
450 else
451 current_output->fb = fb;
453 break;
454 }
455 }
456 }
458 // create default framebuffers if needed
459 for (OutputInfo& o : outputs) {
460 if (!o.crtc) {
461 get_default_crtc(card, *current_output);
462 o.user_set_crtc = true;
463 }
465 if (!o.fb && o.user_set_crtc)
466 o.fb = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
468 for (PlaneInfo &p : o.planes) {
469 if (!p.fb)
470 p.fb = get_default_fb(card, p.w, p.h);
471 }
472 }
474 return outputs;
475 }
477 static std::string videomode_to_string(const Videomode& mode)
478 {
479 unsigned hfp, hsw, hbp;
480 unsigned vfp, vsw, vbp;
482 hfp = mode.hsync_start - mode.hdisplay;
483 hsw = mode.hsync_end - mode.hsync_start;
484 hbp = mode.htotal - mode.hsync_end;
486 vfp = mode.vsync_start - mode.vdisplay;
487 vsw = mode.vsync_end - mode.vsync_start;
488 vbp = mode.vtotal - mode.vsync_end;
490 char buf[256];
492 sprintf(buf, "%.2f MHz %u/%u/%u/%u %u/%u/%u/%u %uHz",
493 mode.clock / 1000.0,
494 mode.hdisplay, hfp, hsw, hbp,
495 mode.vdisplay, vfp, vsw, vbp,
496 mode.vrefresh);
498 return std::string(buf);
499 }
501 static void print_outputs(const vector<OutputInfo>& outputs)
502 {
503 for (unsigned i = 0; i < outputs.size(); ++i) {
504 const OutputInfo& o = outputs[i];
506 printf("Connector %u/@%u: %s\n", o.connector->id(), o.connector->idx(),
507 o.connector->fullname().c_str());
508 printf(" Crtc %u/@%u: %ux%u-%u (%s)\n", o.crtc->id(), o.crtc->idx(),
509 o.mode.hdisplay, o.mode.vdisplay, o.mode.vrefresh,
510 videomode_to_string(o.mode).c_str());
511 if (o.fb)
512 printf(" Fb %ux%u-%s\n", o.fb->width(), o.fb->height(),
513 PixelFormatToFourCC(o.fb->format()).c_str());
515 for (unsigned j = 0; j < o.planes.size(); ++j) {
516 const PlaneInfo& p = o.planes[j];
517 printf(" Plane %u/@%u: %u,%u-%ux%u\n", p.plane->id(), p.plane->idx(),
518 p.x, p.y, p.w, p.h);
519 printf(" Fb %ux%u-%s\n", p.fb->width(), p.fb->height(),
520 PixelFormatToFourCC(p.fb->format()).c_str());
521 }
522 }
523 }
525 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
526 {
527 for (const OutputInfo& o : outputs) {
528 auto conn = o.connector;
529 auto crtc = o.crtc;
531 if (o.fb) {
532 int r = crtc->set_mode(conn, *o.fb, o.mode);
533 if (r)
534 printf("crtc->set_mode() failed for crtc %u: %s\n",
535 crtc->id(), strerror(-r));
536 }
538 for (const PlaneInfo& p : o.planes) {
539 int r = crtc->set_plane(p.plane, *p.fb,
540 p.x, p.y, p.w, p.h,
541 0, 0, p.fb->width(), p.fb->height());
542 if (r)
543 printf("crtc->set_plane() failed for plane %u: %s\n",
544 p.plane->id(), strerror(-r));
545 }
546 }
547 }
549 int main(int argc, char **argv)
550 {
551 vector<Arg> output_args = parse_cmdline(argc, argv);
553 Card card(s_device_path);
555 vector<OutputInfo> outputs = setups_to_outputs(card, output_args);
557 print_outputs(outputs);
559 set_crtcs_n_planes(card, outputs);
561 printf("press enter to exit\n");
563 getchar();
564 }