Home | History | Annotate | Download | only in utils
      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 {
     22 	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))
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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:
    897 	FlipState(Card& card, const string& name, vector<const OutputInfo*> outputs)
    898 		: m_card(card), m_name(name), m_outputs(outputs)
    899 	{
    900 	}
    901 
    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:
    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 
    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 
    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 
    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 
    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 
    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 
   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 
   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