• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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