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