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 <kms++/kms++.h>
13 #include <kms++/modedb.h>
14 #include <kms++/mode_cvt.h>
15 
16 #include <kms++util/kms++util.h>
17 
18 using namespace std;
19 using namespace kms;
20 
21 struct PropInfo {
PropInfoPropInfo22 	PropInfo(string n, uint64_t v) : prop(NULL), name(n), val(v) {}
23 
24 	Property *prop;
25 	string name;
26 	uint64_t val;
27 };
28 
29 struct PlaneInfo
30 {
31 	Plane* plane;
32 
33 	unsigned x;
34 	unsigned y;
35 	unsigned w;
36 	unsigned h;
37 
38 	unsigned view_x;
39 	unsigned view_y;
40 	unsigned view_w;
41 	unsigned view_h;
42 
43 	vector<Framebuffer*> fbs;
44 
45 	vector<PropInfo> props;
46 };
47 
48 struct OutputInfo
49 {
50 	Connector* connector;
51 
52 	Crtc* crtc;
53 	Videomode mode;
54 	vector<Framebuffer*> legacy_fbs;
55 
56 	vector<PlaneInfo> planes;
57 
58 	vector<PropInfo> conn_props;
59 	vector<PropInfo> crtc_props;
60 };
61 
62 static bool s_use_dmt;
63 static bool s_use_cea;
64 static unsigned s_num_buffers = 1;
65 static bool s_flip_mode;
66 static bool s_flip_sync;
67 static bool s_cvt;
68 static bool s_cvt_v2;
69 static bool s_cvt_vid_opt;
70 static unsigned s_max_flips;
71 
72 __attribute__ ((unused))
print_regex_match(smatch sm)73 static void print_regex_match(smatch sm)
74 {
75 	for (unsigned i = 0; i < sm.size(); ++i) {
76 		string str = sm[i].str();
77 		printf("%u: %s\n", i, str.c_str());
78 	}
79 }
80 
get_connector(ResourceManager & resman,OutputInfo & output,const string & str="")81 static void get_connector(ResourceManager& resman, OutputInfo& output, const string& str = "")
82 {
83 	Connector* conn = resman.reserve_connector(str);
84 
85 	if (!conn)
86 		EXIT("No connector '%s'", str.c_str());
87 
88 	if (!conn->connected())
89 		EXIT("Connector '%s' not connected", conn->fullname().c_str());
90 
91 	output.connector = conn;
92 	output.mode = output.connector->get_default_mode();
93 }
94 
get_default_crtc(ResourceManager & resman,OutputInfo & output)95 static void get_default_crtc(ResourceManager& resman, OutputInfo& output)
96 {
97 	output.crtc = resman.reserve_crtc(output.connector);
98 
99 	if (!output.crtc)
100 		EXIT("Could not find available crtc");
101 }
102 
103 
add_default_planeinfo(OutputInfo * output)104 static PlaneInfo *add_default_planeinfo(OutputInfo* output)
105 {
106 	output->planes.push_back(PlaneInfo { });
107 	PlaneInfo *ret = &output->planes.back();
108 	ret->w = output->mode.hdisplay;
109 	ret->h = output->mode.vdisplay;
110 	return ret;
111 }
112 
parse_crtc(ResourceManager & resman,Card & card,const string & crtc_str,OutputInfo & output)113 static void parse_crtc(ResourceManager& resman, Card& card, const string& crtc_str, OutputInfo& output)
114 {
115 	// @12:1920x1200i@60
116 	// @12:33000000,800/210/30/16/-,480/22/13/10/-,i
117 
118 	const regex modename_re("(?:(@?)(\\d+):)?"	// @12:
119 				"(?:(\\d+)x(\\d+)(i)?)"	// 1920x1200i
120 				"(?:@([\\d\\.]+))?");	// @60
121 
122 	const regex modeline_re("(?:(@?)(\\d+):)?"			// @12:
123 				"(\\d+),"				// 33000000,
124 				"(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-]),"	// 800/210/30/16/-,
125 				"(\\d+)/(\\d+)/(\\d+)/(\\d+)/([+-])"	// 480/22/13/10/-
126 				"(?:,([i]+))?"				// ,i
127 				);
128 
129 	smatch sm;
130 	if (regex_match(crtc_str, sm, modename_re)) {
131 		if (sm[2].matched) {
132 			bool use_id = sm[1].length() == 1;
133 			unsigned num = stoul(sm[2].str());
134 
135 			if (use_id) {
136 				Crtc* c = card.get_crtc(num);
137 				if (!c)
138 					EXIT("Bad crtc id '%u'", num);
139 
140 				output.crtc = c;
141 			} else {
142 				auto crtcs = card.get_crtcs();
143 
144 				if (num >= crtcs.size())
145 					EXIT("Bad crtc number '%u'", num);
146 
147 				output.crtc = crtcs[num];
148 			}
149 		} else {
150 			output.crtc = output.connector->get_current_crtc();
151 		}
152 
153 		unsigned w = stoul(sm[3]);
154 		unsigned h = stoul(sm[4]);
155 		bool ilace = sm[5].matched ? true : false;
156 		float refresh = sm[6].matched ? stof(sm[6]) : 0;
157 
158 		if (s_cvt) {
159 			output.mode = videomode_from_cvt(w, h, refresh, ilace, s_cvt_v2, s_cvt_vid_opt);
160 		} else if (s_use_dmt) {
161 			try {
162 				output.mode = find_dmt(w, h, refresh, ilace);
163 			} catch (exception& e) {
164 				EXIT("Mode not found from DMT tables\n");
165 			}
166 		} else if (s_use_cea) {
167 			try {
168 				output.mode = find_cea(w, h, refresh, ilace);
169 			} catch (exception& e) {
170 				EXIT("Mode not found from CEA tables\n");
171 			}
172 		} else {
173 			try {
174 				output.mode = output.connector->get_mode(w, h, refresh, ilace);
175 			} catch (exception& e) {
176 				EXIT("Mode not found from the connector\n");
177 			}
178 		}
179 	} else if (regex_match(crtc_str, sm, modeline_re)) {
180 		if (sm[2].matched) {
181 			bool use_id = sm[1].length() == 1;
182 			unsigned num = stoul(sm[2].str());
183 
184 			if (use_id) {
185 				Crtc* c = card.get_crtc(num);
186 				if (!c)
187 					EXIT("Bad crtc id '%u'", num);
188 
189 				output.crtc = c;
190 			} else {
191 				auto crtcs = card.get_crtcs();
192 
193 				if (num >= crtcs.size())
194 					EXIT("Bad crtc number '%u'", num);
195 
196 				output.crtc = crtcs[num];
197 			}
198 		} else {
199 			output.crtc = output.connector->get_current_crtc();
200 		}
201 
202 		unsigned clock = stoul(sm[3]);
203 
204 		unsigned hact = stoul(sm[4]);
205 		unsigned hfp = stoul(sm[5]);
206 		unsigned hsw = stoul(sm[6]);
207 		unsigned hbp = stoul(sm[7]);
208 		bool h_pos_sync = sm[8] == "+" ? true : false;
209 
210 		unsigned vact = stoul(sm[9]);
211 		unsigned vfp = stoul(sm[10]);
212 		unsigned vsw = stoul(sm[11]);
213 		unsigned vbp = stoul(sm[12]);
214 		bool v_pos_sync = sm[13] == "+" ? true : false;
215 
216 		output.mode = videomode_from_timings(clock / 1000, hact, hfp, hsw, hbp, vact, vfp, vsw, vbp);
217 		output.mode.set_hsync(h_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
218 		output.mode.set_vsync(v_pos_sync ? SyncPolarity::Positive : SyncPolarity::Negative);
219 
220 		if (sm[14].matched) {
221 			for (int i = 0; i < sm[14].length(); ++i) {
222 				char f = string(sm[14])[i];
223 
224 				switch (f) {
225 				case 'i':
226 					output.mode.set_interlace(true);
227 					break;
228 				default:
229 					EXIT("Bad mode flag %c", f);
230 				}
231 			}
232 		}
233 	} else {
234 		EXIT("Failed to parse crtc option '%s'", crtc_str.c_str());
235 	}
236 
237 	if (!resman.reserve_crtc(output.crtc))
238 		EXIT("Could not find available crtc");
239 }
240 
parse_plane(ResourceManager & resman,Card & card,const string & plane_str,const OutputInfo & output,PlaneInfo & pinfo)241 static void parse_plane(ResourceManager& resman, Card& card, const string& plane_str, const OutputInfo& output, PlaneInfo& pinfo)
242 {
243 	// 3:400,400-400x400
244 	const regex plane_re("(?:(@?)(\\d+):)?"		// 3:
245 			     "(?:(\\d+),(\\d+)-)?"	// 400,400-
246 			     "(\\d+)x(\\d+)");		// 400x400
247 
248 	smatch sm;
249 	if (!regex_match(plane_str, sm, plane_re))
250 		EXIT("Failed to parse plane option '%s'", plane_str.c_str());
251 
252 	if (sm[2].matched) {
253 		bool use_id = sm[1].length() == 1;
254 		unsigned num = stoul(sm[2].str());
255 
256 		if (use_id) {
257 			Plane* p = card.get_plane(num);
258 			if (!p)
259 				EXIT("Bad plane id '%u'", num);
260 
261 			pinfo.plane = p;
262 		} else {
263 			auto planes = card.get_planes();
264 
265 			if (num >= planes.size())
266 				EXIT("Bad plane number '%u'", num);
267 
268 			pinfo.plane = planes[num];
269 		}
270 
271 		auto plane = resman.reserve_plane(pinfo.plane);
272 		if (!plane)
273 			EXIT("Plane id %u is not available", pinfo.plane->id());
274 	}
275 
276 	pinfo.w = stoul(sm[5]);
277 	pinfo.h = stoul(sm[6]);
278 
279 	if (sm[3].matched)
280 		pinfo.x = stoul(sm[3]);
281 	else
282 		pinfo.x = output.mode.hdisplay / 2 - pinfo.w / 2;
283 
284 	if (sm[4].matched)
285 		pinfo.y = stoul(sm[4]);
286 	else
287 		pinfo.y = output.mode.vdisplay / 2 - pinfo.h / 2;
288 }
289 
parse_prop(const string & prop_str,vector<PropInfo> & props)290 static void parse_prop(const string& prop_str, vector<PropInfo> &props)
291 {
292 	string name, val;
293 
294 	size_t split = prop_str.find("=");
295 
296 	if (split == string::npos)
297 		EXIT("Equal sign ('=') not found in %s", prop_str.c_str());
298 
299 	name = prop_str.substr(0, split);
300 	val = prop_str.substr(split+1);
301 
302 	props.push_back(PropInfo(name, stoull(val, 0, 0)));
303 }
304 
get_props(Card & card,vector<PropInfo> & props,const DrmPropObject * propobj)305 static void get_props(Card& card, vector<PropInfo> &props, const DrmPropObject* propobj)
306 {
307 	for (auto& pi : props)
308 		pi.prop = propobj->get_prop(pi.name);
309 }
310 
get_default_fb(Card & card,unsigned width,unsigned height)311 static vector<Framebuffer*> get_default_fb(Card& card, unsigned width, unsigned height)
312 {
313 	vector<Framebuffer*> v;
314 
315 	for (unsigned i = 0; i < s_num_buffers; ++i)
316 		v.push_back(new DumbFramebuffer(card, width, height, PixelFormat::XRGB8888));
317 
318 	return v;
319 }
320 
parse_fb(Card & card,const string & fb_str,OutputInfo * output,PlaneInfo * pinfo)321 static void parse_fb(Card& card, const string& fb_str, OutputInfo* output, PlaneInfo* pinfo)
322 {
323 	unsigned w, h;
324 	PixelFormat format = PixelFormat::XRGB8888;
325 
326 	if (pinfo) {
327 		w = pinfo->w;
328 		h = pinfo->h;
329 	} else {
330 		w = output->mode.hdisplay;
331 		h = output->mode.vdisplay;
332 	}
333 
334 	if (!fb_str.empty()) {
335 		// XXX the regexp is not quite correct
336 		// 400x400-NV12
337 		const regex fb_re("(?:(\\d+)x(\\d+))?"		// 400x400
338 				  "(?:-)?"			// -
339 				  "(\\w\\w\\w\\w)?");		// NV12
340 
341 		smatch sm;
342 		if (!regex_match(fb_str, sm, fb_re))
343 			EXIT("Failed to parse fb option '%s'", fb_str.c_str());
344 
345 		if (sm[1].matched)
346 			w = stoul(sm[1]);
347 		if (sm[2].matched)
348 			h = stoul(sm[2]);
349 		if (sm[3].matched)
350 			format = FourCCToPixelFormat(sm[3]);
351 	}
352 
353 	vector<Framebuffer*> v;
354 
355 	for (unsigned i = 0; i < s_num_buffers; ++i)
356 		v.push_back(new DumbFramebuffer(card, w, h, format));
357 
358 	if (pinfo)
359 		pinfo->fbs = v;
360 	else
361 		output->legacy_fbs = v;
362 }
363 
parse_view(const string & view_str,PlaneInfo & pinfo)364 static void parse_view(const string& view_str, PlaneInfo& pinfo)
365 {
366 	const regex view_re("(\\d+),(\\d+)-(\\d+)x(\\d+)");		// 400,400-400x400
367 
368 	smatch sm;
369 	if (!regex_match(view_str, sm, view_re))
370 		EXIT("Failed to parse view option '%s'", view_str.c_str());
371 
372 	pinfo.view_x = stoul(sm[1]);
373 	pinfo.view_y = stoul(sm[2]);
374 	pinfo.view_w = stoul(sm[3]);
375 	pinfo.view_h = stoul(sm[4]);
376 }
377 
378 static const char* usage_str =
379 		"Usage: kmstest [OPTION]...\n\n"
380 		"Show a test pattern on a display or plane\n\n"
381 		"Options:\n"
382 		"      --device=DEVICE       DEVICE is the path to DRM card to open\n"
383 		"  -c, --connector=CONN      CONN is <connector>\n"
384 		"  -r, --crtc=CRTC           CRTC is [<crtc>:]<w>x<h>[@<Hz>]\n"
385 		"                            or\n"
386 		"                            [<crtc>:]<pclk>,<hact>/<hfp>/<hsw>/<hbp>/<hsp>,<vact>/<vfp>/<vsw>/<vbp>/<vsp>[,i]\n"
387 		"  -p, --plane=PLANE         PLANE is [<plane>:][<x>,<y>-]<w>x<h>\n"
388 		"  -f, --fb=FB               FB is [<w>x<h>][-][<4cc>]\n"
389 		"  -v, --view=VIEW           VIEW is <x>,<y>-<w>x<h>\n"
390 		"  -P, --property=PROP=VAL   Set PROP to VAL in the previous DRM object\n"
391 		"      --dmt                 Search for the given mode from DMT tables\n"
392 		"      --cea                 Search for the given mode from CEA tables\n"
393 		"      --cvt=CVT             Create videomode with CVT. CVT is 'v1', 'v2' or 'v2o'\n"
394 		"      --flip[=max]          Do page flipping for each output with an optional maximum flips count\n"
395 		"      --sync                Synchronize page flipping\n"
396 		"\n"
397 		"<connector>, <crtc> and <plane> can be given by index (<idx>) or id (@<id>).\n"
398 		"<connector> can also be given by name.\n"
399 		"\n"
400 		"Options can be given multiple times to set up multiple displays or planes.\n"
401 		"Options may apply to previous options, e.g. a plane will be set on a crtc set in\n"
402 		"an earlier option.\n"
403 		"If you omit parameters, kmstest tries to guess what you mean\n"
404 		"\n"
405 		"Examples:\n"
406 		"\n"
407 		"Set eDP-1 mode to 1920x1080@60, show XR24 framebuffer on the crtc, and a 400x400 XB24 plane:\n"
408 		"    kmstest -c eDP-1 -r 1920x1080@60 -f XR24 -p 400x400 -f XB24\n\n"
409 		"XR24 framebuffer on first connected connector in the default mode:\n"
410 		"    kmstest -f XR24\n\n"
411 		"XR24 framebuffer on a 400x400 plane on the first connected connector in the default mode:\n"
412 		"    kmstest -p 400x400 -f XR24\n\n"
413 		"Test pattern on the second connector with default mode:\n"
414 		"    kmstest -c 1\n"
415 		"\n"
416 		"Environmental variables:\n"
417 		"    KMSXX_DISABLE_UNIVERSAL_PLANES    Don't enable universal planes even if available\n"
418 		"    KMSXX_DISABLE_ATOMIC              Don't enable atomic modesetting even if available\n"
419 		;
420 
usage()421 static void usage()
422 {
423 	puts(usage_str);
424 }
425 
426 enum class ArgType
427 {
428 	Connector,
429 	Crtc,
430 	Plane,
431 	Framebuffer,
432 	View,
433 	Property,
434 };
435 
436 struct Arg
437 {
438 	ArgType type;
439 	string arg;
440 };
441 
442 static string s_device_path = "/dev/dri/card0";
443 
parse_cmdline(int argc,char ** argv)444 static vector<Arg> parse_cmdline(int argc, char **argv)
445 {
446 	vector<Arg> args;
447 
448 	OptionSet optionset = {
449 		Option("|device=",
450 		[&](string s)
451 		{
452 			s_device_path = s;
453 		}),
454 		Option("c|connector=",
455 		[&](string s)
456 		{
457 			args.push_back(Arg { ArgType::Connector, s });
458 		}),
459 		Option("r|crtc=", [&](string s)
460 		{
461 			args.push_back(Arg { ArgType::Crtc, s });
462 		}),
463 		Option("p|plane=", [&](string s)
464 		{
465 			args.push_back(Arg { ArgType::Plane, s });
466 		}),
467 		Option("f|fb=", [&](string s)
468 		{
469 			args.push_back(Arg { ArgType::Framebuffer, s });
470 		}),
471 		Option("v|view=", [&](string s)
472 		{
473 			args.push_back(Arg { ArgType::View, s });
474 		}),
475 		Option("P|property=", [&](string s)
476 		{
477 			args.push_back(Arg { ArgType::Property, s });
478 		}),
479 		Option("|dmt", []()
480 		{
481 			s_use_dmt = true;
482 		}),
483 		Option("|cea", []()
484 		{
485 			s_use_cea = true;
486 		}),
487 		Option("|flip?", [&](string s)
488 		{
489 			s_flip_mode = true;
490 			s_num_buffers = 2;
491 			if (!s.empty())
492 				s_max_flips = stoi(s);
493 		}),
494 		Option("|sync", []()
495 		{
496 			s_flip_sync = true;
497 		}),
498 		Option("|cvt=", [&](string s)
499 		{
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("h|help", [&]()
512 		{
513 			usage();
514 			exit(-1);
515 		}),
516 	};
517 
518 	optionset.parse(argc, argv);
519 
520 	if (optionset.params().size() > 0) {
521 		usage();
522 		exit(-1);
523 	}
524 
525 	return args;
526 }
527 
setups_to_outputs(Card & card,ResourceManager & resman,const vector<Arg> & output_args)528 static vector<OutputInfo> setups_to_outputs(Card& card, ResourceManager& resman, const vector<Arg>& output_args)
529 {
530 	vector<OutputInfo> outputs;
531 
532 	OutputInfo* current_output = 0;
533 	PlaneInfo* current_plane = 0;
534 
535 	for (auto& arg : output_args) {
536 		switch (arg.type) {
537 		case ArgType::Connector:
538 		{
539 			outputs.push_back(OutputInfo { });
540 			current_output = &outputs.back();
541 
542 			get_connector(resman, *current_output, arg.arg);
543 			current_plane = 0;
544 
545 			break;
546 		}
547 
548 		case ArgType::Crtc:
549 		{
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 		{
567 			if (!current_output) {
568 				outputs.push_back(OutputInfo { });
569 				current_output = &outputs.back();
570 			}
571 
572 			if (!current_output->connector)
573 				get_connector(resman, *current_output);
574 
575 			if (!current_output->crtc)
576 				get_default_crtc(resman, *current_output);
577 
578 			current_plane = add_default_planeinfo(current_output);
579 
580 			parse_plane(resman, card, arg.arg, *current_output, *current_plane);
581 
582 			break;
583 		}
584 
585 		case ArgType::Framebuffer:
586 		{
587 			if (!current_output) {
588 				outputs.push_back(OutputInfo { });
589 				current_output = &outputs.back();
590 			}
591 
592 			if (!current_output->connector)
593 				get_connector(resman, *current_output);
594 
595 			if (!current_output->crtc)
596 				get_default_crtc(resman, *current_output);
597 
598 			if (!current_plane && card.has_atomic())
599 				current_plane = add_default_planeinfo(current_output);
600 
601 			parse_fb(card, arg.arg, current_output, current_plane);
602 
603 			break;
604 		}
605 
606 		case ArgType::View:
607 		{
608 			if (!current_plane || current_plane->fbs.empty())
609 				EXIT("'view' parameter requires a plane and a fb");
610 
611 			parse_view(arg.arg, *current_plane);
612 			break;
613 		}
614 
615 		case ArgType::Property:
616 		{
617 			if (!current_output)
618 				EXIT("No object to which set the property");
619 
620 			if (current_plane)
621 				parse_prop(arg.arg, current_plane->props);
622 			else if (current_output->crtc)
623 				parse_prop(arg.arg, current_output->crtc_props);
624 			else if (current_output->connector)
625 				parse_prop(arg.arg, current_output->conn_props);
626 			else
627 				EXIT("no object");
628 
629 			break;
630 		}
631 		}
632 	}
633 
634 	if (outputs.empty()) {
635 		// no outputs defined, show a pattern on all screens
636 		for (Connector* conn : card.get_connectors()) {
637 			if (!conn->connected())
638 				continue;
639 
640 			OutputInfo output = { };
641 			output.connector = resman.reserve_connector(conn);
642 			EXIT_IF(!output.connector, "Failed to reserve connector %s", conn->fullname().c_str());
643 			output.crtc = resman.reserve_crtc(conn);
644 			EXIT_IF(!output.crtc, "Failed to reserve crtc for %s", conn->fullname().c_str());
645 			output.mode = output.connector->get_default_mode();
646 
647 			outputs.push_back(output);
648 		}
649 	}
650 
651 	for (OutputInfo& o : outputs) {
652 		get_props(card, o.conn_props, o.connector);
653 
654 		if (!o.crtc)
655 			get_default_crtc(resman, o);
656 
657 		get_props(card, o.crtc_props, o.crtc);
658 
659 		if (card.has_atomic()) {
660 			if (o.planes.empty())
661 				add_default_planeinfo(&o);
662 		} else {
663 			if (o.legacy_fbs.empty())
664 				o.legacy_fbs = get_default_fb(card, o.mode.hdisplay, o.mode.vdisplay);
665 		}
666 
667 		for (PlaneInfo &p : o.planes) {
668 			if (p.fbs.empty())
669 				p.fbs = get_default_fb(card, p.w, p.h);
670 		}
671 
672 		for (PlaneInfo& p : o.planes) {
673 			if (!p.plane) {
674 				if (card.has_atomic())
675 					p.plane = resman.reserve_generic_plane(o.crtc, p.fbs[0]->format());
676 				else
677 					p.plane = resman.reserve_overlay_plane(o.crtc, p.fbs[0]->format());
678 
679 				if (!p.plane)
680 					EXIT("Failed to find available plane");
681 			}
682 			get_props(card, p.props, p.plane);
683 		}
684 	}
685 
686 	return outputs;
687 }
688 
videomode_to_string(const Videomode & m)689 static std::string videomode_to_string(const Videomode& m)
690 {
691 	string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
692 	string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
693 
694 	return sformat("%s %.3f %s %s %u (%.2f) %#x %#x",
695 		       m.name.c_str(),
696 		       m.clock / 1000.0,
697 		       h.c_str(), v.c_str(),
698 		       m.vrefresh, m.calculated_vrefresh(),
699 		       m.flags,
700 		       m.type);
701 }
702 
print_outputs(const vector<OutputInfo> & outputs)703 static void print_outputs(const vector<OutputInfo>& outputs)
704 {
705 	for (unsigned i = 0; i < outputs.size(); ++i) {
706 		const OutputInfo& o = outputs[i];
707 
708 		printf("Connector %u/@%u: %s", o.connector->idx(), o.connector->id(),
709 		       o.connector->fullname().c_str());
710 
711 		for (const PropInfo &prop: o.conn_props)
712 			printf(" %s=%" PRIu64, prop.prop->name().c_str(),
713 			       prop.val);
714 
715 		printf("\n  Crtc %u/@%u", o.crtc->idx(), o.crtc->id());
716 
717 		for (const PropInfo &prop: o.crtc_props)
718 			printf(" %s=%" PRIu64, prop.prop->name().c_str(),
719 			       prop.val);
720 
721 		printf(": %s\n", videomode_to_string(o.mode).c_str());
722 
723 		if (!o.legacy_fbs.empty()) {
724 			auto fb = o.legacy_fbs[0];
725 			printf(" (Fb %u %ux%u-%s)", fb->id(), fb->width(), fb->height(), PixelFormatToFourCC(fb->format()).c_str());
726 		}
727 
728 		for (unsigned j = 0; j < o.planes.size(); ++j) {
729 			const PlaneInfo& p = o.planes[j];
730 			auto fb = p.fbs[0];
731 			printf("  Plane %u/@%u: %u,%u-%ux%u", p.plane->idx(), p.plane->id(),
732 			       p.x, p.y, p.w, p.h);
733 			for (const PropInfo &prop: p.props)
734 				printf(" %s=%" PRIu64, prop.prop->name().c_str(),
735 				       prop.val);
736 			printf("\n");
737 
738 			printf("    Fb %u %ux%u-%s\n", fb->id(), fb->width(), fb->height(),
739 			       PixelFormatToFourCC(fb->format()).c_str());
740 		}
741 	}
742 }
743 
draw_test_patterns(const vector<OutputInfo> & outputs)744 static void draw_test_patterns(const vector<OutputInfo>& outputs)
745 {
746 	for (const OutputInfo& o : outputs) {
747 		for (auto fb : o.legacy_fbs)
748 			draw_test_pattern(*fb);
749 
750 		for (const PlaneInfo& p : o.planes)
751 			for (auto fb : p.fbs)
752 				draw_test_pattern(*fb);
753 	}
754 }
755 
set_crtcs_n_planes_legacy(Card & card,const vector<OutputInfo> & outputs)756 static void set_crtcs_n_planes_legacy(Card& card, const vector<OutputInfo>& outputs)
757 {
758 	// Disable unused crtcs
759 	for (Crtc* crtc : card.get_crtcs()) {
760 		if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
761 			continue;
762 
763 		crtc->disable_mode();
764 	}
765 
766 	for (const OutputInfo& o : outputs) {
767 		auto conn = o.connector;
768 		auto crtc = o.crtc;
769 
770 		if (!o.conn_props.empty() || !o.crtc_props.empty())
771 			printf("WARNING: properties not set without atomic modesetting");
772 
773 		if (!o.legacy_fbs.empty()) {
774 			auto fb = o.legacy_fbs[0];
775 			int r = crtc->set_mode(conn, *fb, o.mode);
776 			if (r)
777 				printf("crtc->set_mode() failed for crtc %u: %s\n",
778 				       crtc->id(), strerror(-r));
779 		}
780 
781 		for (const PlaneInfo& p : o.planes) {
782 			auto fb = p.fbs[0];
783 			int r = crtc->set_plane(p.plane, *fb,
784 						p.x, p.y, p.w, p.h,
785 						0, 0, fb->width(), fb->height());
786 			if (r)
787 				printf("crtc->set_plane() failed for plane %u: %s\n",
788 				       p.plane->id(), strerror(-r));
789 			if (!p.props.empty())
790 				printf("WARNING: properties not set without atomic modesetting");
791 		}
792 	}
793 }
794 
set_crtcs_n_planes_atomic(Card & card,const vector<OutputInfo> & outputs)795 static void set_crtcs_n_planes_atomic(Card& card, const vector<OutputInfo>& outputs)
796 {
797 	int r;
798 
799 	// XXX DRM framework doesn't allow moving an active plane from one crtc to another.
800 	// See drm_atomic.c::plane_switching_crtc().
801 	// For the time being, disable all crtcs and planes here.
802 
803 	AtomicReq disable_req(card);
804 
805 	// Disable unused crtcs
806 	for (Crtc* crtc : card.get_crtcs()) {
807 		//if (find_if(outputs.begin(), outputs.end(), [crtc](const OutputInfo& o) { return o.crtc == crtc; }) != outputs.end())
808 		//	continue;
809 
810 		disable_req.add(crtc, {
811 				{ "ACTIVE", 0 },
812 			});
813 	}
814 
815 	// Disable unused planes
816 	for (Plane* plane : card.get_planes())
817 		disable_req.add(plane, {
818 				{ "FB_ID", 0 },
819 				{ "CRTC_ID", 0 },
820 			});
821 
822 	r = disable_req.commit_sync(true);
823 	if (r)
824 		EXIT("Atomic commit failed when disabling: %d\n", r);
825 
826 
827 	// Keep blobs here so that we keep ref to them until we have committed the req
828 	vector<unique_ptr<Blob>> blobs;
829 
830 	AtomicReq req(card);
831 
832 	for (const OutputInfo& o : outputs) {
833 		auto conn = o.connector;
834 		auto crtc = o.crtc;
835 
836 		blobs.emplace_back(o.mode.to_blob(card));
837 		Blob* mode_blob = blobs.back().get();
838 
839 		req.add(conn, {
840 				{ "CRTC_ID", crtc->id() },
841 			});
842 
843 		for (const PropInfo &prop: o.conn_props)
844 			req.add(conn, prop.prop, prop.val);
845 
846 		req.add(crtc, {
847 				{ "ACTIVE", 1 },
848 				{ "MODE_ID", mode_blob->id() },
849 			});
850 
851 		for (const PropInfo &prop: o.crtc_props)
852 			req.add(crtc, prop.prop, prop.val);
853 
854 		for (const PlaneInfo& p : o.planes) {
855 			auto fb = p.fbs[0];
856 
857 			req.add(p.plane, {
858 					{ "FB_ID", fb->id() },
859 					{ "CRTC_ID", crtc->id() },
860 					{ "SRC_X", (p.view_x ?: 0) << 16 },
861 					{ "SRC_Y", (p.view_y ?: 0) << 16 },
862 					{ "SRC_W", (p.view_w ?: fb->width()) << 16 },
863 					{ "SRC_H", (p.view_h ?: fb->height()) << 16 },
864 					{ "CRTC_X", p.x },
865 					{ "CRTC_Y", p.y },
866 					{ "CRTC_W", p.w },
867 					{ "CRTC_H", p.h },
868 				});
869 
870 			for (const PropInfo &prop: p.props)
871 				req.add(p.plane, prop.prop, prop.val);
872 		}
873 	}
874 
875 	r = req.test(true);
876 	if (r)
877 		EXIT("Atomic test failed: %d\n", r);
878 
879 	r = req.commit_sync(true);
880 	if (r)
881 		EXIT("Atomic commit failed: %d\n", r);
882 }
883 
set_crtcs_n_planes(Card & card,const vector<OutputInfo> & outputs)884 static void set_crtcs_n_planes(Card& card, const vector<OutputInfo>& outputs)
885 {
886 	if (card.has_atomic())
887 		set_crtcs_n_planes_atomic(card, outputs);
888 	else
889 		set_crtcs_n_planes_legacy(card, outputs);
890 }
891 
892 static bool max_flips_reached;
893 
894 class FlipState : private PageFlipHandlerBase
895 {
896 public:
FlipState(Card & card,const string & name,vector<const OutputInfo * > outputs)897 	FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
898 		: m_card(card), m_name(name), m_outputs(outputs)
899 	{
900 	}
901 
start_flipping()902 	void start_flipping()
903 	{
904 		m_prev_frame = m_prev_print = std::chrono::steady_clock::now();
905 		m_slowest_frame = std::chrono::duration<float>::min();
906 		m_frame_num = 0;
907 		queue_next();
908 	}
909 
910 private:
handle_page_flip(uint32_t frame,double time)911 	void handle_page_flip(uint32_t frame, double time)
912 	{
913 		/*
914 		 * We get flip event for each crtc in this flipstate. We can commit the next frames
915 		 * only after we've gotten the flip event for all crtcs
916 		 */
917 		if (++m_flip_count < m_outputs.size())
918 			return;
919 
920 		m_frame_num++;
921 		if (s_max_flips && m_frame_num >= s_max_flips)
922 			max_flips_reached = true;
923 
924 		auto now = std::chrono::steady_clock::now();
925 
926 		std::chrono::duration<float> diff = now - m_prev_frame;
927 		if (diff > m_slowest_frame)
928 			m_slowest_frame = diff;
929 
930 		if (m_frame_num  % 100 == 0) {
931 			std::chrono::duration<float> fsec = now - m_prev_print;
932 			printf("Connector %s: fps %f, slowest %.2f ms\n",
933 			       m_name.c_str(),
934 			       100.0 / fsec.count(),
935 			       m_slowest_frame.count() * 1000);
936 			m_prev_print = now;
937 			m_slowest_frame = std::chrono::duration<float>::min();
938 		}
939 
940 		m_prev_frame = now;
941 
942 		queue_next();
943 	}
944 
get_bar_pos(Framebuffer * fb,unsigned frame_num)945 	static unsigned get_bar_pos(Framebuffer* fb, unsigned frame_num)
946 	{
947 		return (frame_num * bar_speed) % (fb->width() - bar_width + 1);
948 	}
949 
draw_bar(Framebuffer * fb,unsigned frame_num)950 	static void draw_bar(Framebuffer* fb, unsigned frame_num)
951 	{
952 		int old_xpos = frame_num < s_num_buffers ? -1 : get_bar_pos(fb, frame_num - s_num_buffers);
953 		int new_xpos = get_bar_pos(fb, frame_num);
954 
955 		draw_color_bar(*fb, old_xpos, new_xpos, bar_width);
956 		draw_text(*fb, fb->width() / 2, 0, to_string(frame_num), RGB(255, 255, 255));
957 	}
958 
do_flip_output(AtomicReq & req,unsigned frame_num,const OutputInfo & o)959 	static void do_flip_output(AtomicReq& req, unsigned frame_num, const OutputInfo& o)
960 	{
961 		unsigned cur = frame_num % s_num_buffers;
962 
963 		for (const PlaneInfo& p : o.planes) {
964 			auto fb = p.fbs[cur];
965 
966 			draw_bar(fb, frame_num);
967 
968 			req.add(p.plane, {
969 					{ "FB_ID", fb->id() },
970 				});
971 		}
972 	}
973 
do_flip_output_legacy(unsigned frame_num,const OutputInfo & o)974 	void do_flip_output_legacy(unsigned frame_num, const OutputInfo& o)
975 	{
976 		unsigned cur = frame_num % s_num_buffers;
977 
978 		if (!o.legacy_fbs.empty()) {
979 			auto fb = o.legacy_fbs[cur];
980 
981 			draw_bar(fb, frame_num);
982 
983 			int r = o.crtc->page_flip(*fb, this);
984 			ASSERT(r == 0);
985 		}
986 
987 		for (const PlaneInfo& p : o.planes) {
988 			auto fb = p.fbs[cur];
989 
990 			draw_bar(fb, frame_num);
991 
992 			int r = o.crtc->set_plane(p.plane, *fb,
993 						  p.x, p.y, p.w, p.h,
994 						  0, 0, fb->width(), fb->height());
995 			ASSERT(r == 0);
996 		}
997 	}
998 
queue_next()999 	void queue_next()
1000 	{
1001 		m_flip_count = 0;
1002 
1003 		if (m_card.has_atomic()) {
1004 			AtomicReq req(m_card);
1005 
1006 			for (auto o : m_outputs)
1007 				do_flip_output(req, m_frame_num, *o);
1008 
1009 			int r = req.commit(this);
1010 			if (r)
1011 				EXIT("Flip commit failed: %d\n", r);
1012 		} else {
1013 			ASSERT(m_outputs.size() == 1);
1014 			do_flip_output_legacy(m_frame_num, *m_outputs[0]);
1015 		}
1016 	}
1017 
1018 	Card& m_card;
1019 	string m_name;
1020 	vector<const OutputInfo*> m_outputs;
1021 	unsigned m_frame_num;
1022 	unsigned m_flip_count;
1023 
1024 	chrono::steady_clock::time_point m_prev_print;
1025 	chrono::steady_clock::time_point m_prev_frame;
1026 	chrono::duration<float> m_slowest_frame;
1027 
1028 	static const unsigned bar_width = 20;
1029 	static const unsigned bar_speed = 8;
1030 };
1031 
main_flip(Card & card,const vector<OutputInfo> & outputs)1032 static void main_flip(Card& card, const vector<OutputInfo>& outputs)
1033 {
1034 	fd_set fds;
1035 
1036 	FD_ZERO(&fds);
1037 
1038 	int fd = card.fd();
1039 
1040 	vector<unique_ptr<FlipState>> flipstates;
1041 
1042 	if (!s_flip_sync) {
1043 		for (const OutputInfo& o : outputs) {
1044 			auto fs = unique_ptr<FlipState>(new FlipState(card, to_string(o.connector->idx()), { &o }));
1045 			flipstates.push_back(move(fs));
1046 		}
1047 	} else {
1048 		vector<const OutputInfo*> ois;
1049 
1050 		string name;
1051 		for (const OutputInfo& o : outputs) {
1052 			name += to_string(o.connector->idx()) + ",";
1053 			ois.push_back(&o);
1054 		}
1055 
1056 		auto fs = unique_ptr<FlipState>(new FlipState(card, name, ois));
1057 		flipstates.push_back(move(fs));
1058 	}
1059 
1060 	for (unique_ptr<FlipState>& fs : flipstates)
1061 		fs->start_flipping();
1062 
1063 	while (!max_flips_reached) {
1064 		int r;
1065 
1066 		FD_SET(0, &fds);
1067 		FD_SET(fd, &fds);
1068 
1069 		r = select(fd + 1, &fds, NULL, NULL, NULL);
1070 		if (r < 0) {
1071 			fprintf(stderr, "select() failed with %d: %m\n", errno);
1072 			break;
1073 		} else if (FD_ISSET(0, &fds)) {
1074 			fprintf(stderr, "Exit due to user-input\n");
1075 			break;
1076 		} else if (FD_ISSET(fd, &fds)) {
1077 			card.call_page_flip_handlers();
1078 		}
1079 	}
1080 }
1081 
main(int argc,char ** argv)1082 int main(int argc, char **argv)
1083 {
1084 	vector<Arg> output_args = parse_cmdline(argc, argv);
1085 
1086 	Card card(s_device_path);
1087 
1088 	if (!card.has_atomic() && s_flip_sync)
1089 		EXIT("Synchronized flipping requires atomic modesetting");
1090 
1091 	ResourceManager resman(card);
1092 
1093 	vector<OutputInfo> outputs = setups_to_outputs(card, resman, output_args);
1094 
1095 	if (!s_flip_mode)
1096 		draw_test_patterns(outputs);
1097 
1098 	print_outputs(outputs);
1099 
1100 	set_crtcs_n_planes(card, outputs);
1101 
1102 	printf("press enter to exit\n");
1103 
1104 	if (s_flip_mode)
1105 		main_flip(card, outputs);
1106 	else
1107 		getchar();
1108 }
1109