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