1 #include <algorithm>
2 #include <cinttypes>
3 #include <cstdio>
4 #include <iostream>
5 #include <string>
6 #include <unistd.h>
7
8 #include <kms++/kms++.h>
9 #include <kms++util/kms++util.h>
10
11 using namespace std;
12 using namespace kms;
13
14 static struct {
15 bool print_props;
16 bool print_modes;
17 bool print_list;
18 bool x_modeline;
19 } s_opts;
20
format_mode(const Videomode & m,unsigned idx)21 static string format_mode(const Videomode& m, unsigned idx)
22 {
23 string str;
24
25 str = sformat(" %2u ", idx);
26
27 if (s_opts.x_modeline) {
28 str += sformat("%12s %6u %4u %4u %4u %4u %4u %4u %4u %4u %2u %#x %#x",
29 m.name.c_str(),
30 m.clock,
31 m.hdisplay, m.hsync_start, m.hsync_end, m.htotal,
32 m.vdisplay, m.vsync_start, m.vsync_end, m.vtotal,
33 m.vrefresh,
34 m.flags,
35 m.type);
36 } else {
37 string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
38 string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
39
40 str += sformat("%-12s %7.3f %-16s %-16s %2u (%.2f) %#10x %#6x",
41 m.name.c_str(),
42 m.clock / 1000.0,
43 h.c_str(), v.c_str(),
44 m.vrefresh, m.calculated_vrefresh(),
45 m.flags,
46 m.type);
47 }
48
49 return str;
50 }
51
format_mode_short(const Videomode & m)52 static string format_mode_short(const Videomode& m)
53 {
54 string h = sformat("%u/%u/%u/%u", m.hdisplay, m.hfp(), m.hsw(), m.hbp());
55 string v = sformat("%u/%u/%u/%u", m.vdisplay, m.vfp(), m.vsw(), m.vbp());
56
57 return sformat("%s %.3f %s %s %u (%.2f)",
58 m.name.c_str(),
59 m.clock / 1000.0,
60 h.c_str(), v.c_str(),
61 m.vrefresh, m.calculated_vrefresh());
62 }
63
format_connector(Connector & c)64 static string format_connector(Connector& c)
65 {
66 string str;
67
68 str = sformat("Connector %u (%u) %s",
69 c.idx(), c.id(), c.fullname().c_str());
70
71 switch (c.connector_status()) {
72 case ConnectorStatus::Connected:
73 str += " (connected)";
74 break;
75 case ConnectorStatus::Disconnected:
76 str += " (disconnected)";
77 break;
78 default:
79 str += " (unknown)";
80 break;
81 }
82
83 return str;
84 }
85
format_encoder(Encoder & e)86 static string format_encoder(Encoder& e)
87 {
88 return sformat("Encoder %u (%u) %s",
89 e.idx(), e.id(), e.get_encoder_type().c_str());
90 }
91
format_crtc(Crtc & c)92 static string format_crtc(Crtc& c)
93 {
94 string str;
95
96 str = sformat("Crtc %u (%u)", c.idx(), c.id());
97
98 if (c.mode_valid())
99 str += " " + format_mode_short(c.mode());
100
101 return str;
102 }
103
format_plane(Plane & p)104 static string format_plane(Plane& p)
105 {
106 string str;
107
108 str = sformat("Plane %u (%u)", p.idx(), p.id());
109
110 if (p.fb_id())
111 str += sformat(" fb-id: %u", p.fb_id());
112
113 string crtcs = join<Crtc*>(p.get_possible_crtcs(), " ", [](Crtc* crtc) { return to_string(crtc->idx()); });
114
115 str += sformat(" (crtcs: %s)", crtcs.c_str());
116
117 if (p.card().has_atomic()) {
118 str += sformat(" %u,%u %ux%u -> %u,%u %ux%u",
119 (uint32_t)p.get_prop_value("SRC_X") >> 16,
120 (uint32_t)p.get_prop_value("SRC_Y") >> 16,
121 (uint32_t)p.get_prop_value("SRC_W") >> 16,
122 (uint32_t)p.get_prop_value("SRC_H") >> 16,
123 (uint32_t)p.get_prop_value("CRTC_X"),
124 (uint32_t)p.get_prop_value("CRTC_Y"),
125 (uint32_t)p.get_prop_value("CRTC_W"),
126 (uint32_t)p.get_prop_value("CRTC_H"));
127 }
128
129 string fmts = join<PixelFormat>(p.get_formats(), " ", [](PixelFormat fmt) { return PixelFormatToFourCC(fmt); });
130
131 str += sformat(" (%s)", fmts.c_str());
132
133 return str;
134 }
135
format_fb(Framebuffer & fb)136 static string format_fb(Framebuffer& fb)
137 {
138 return sformat("FB %u %ux%u",
139 fb.id(), fb.width(), fb.height());
140 }
141
format_property(const Property * prop,uint64_t val)142 static string format_property(const Property* prop, uint64_t val)
143 {
144 string ret = sformat("%s (%u) = ", prop->name().c_str(), prop->id());
145
146 switch (prop->type()) {
147 case PropertyType::Bitmask:
148 {
149 vector<string> v, vall;
150
151 for (auto kvp : prop->get_enums()) {
152 if (val & (1 << kvp.first))
153 v.push_back(kvp.second);
154 vall.push_back(sformat("%s=0x%x", kvp.second.c_str(), 1 << kvp.first));
155 }
156
157 ret += sformat("0x%" PRIx64 " (%s) [%s]", val, join(v, "|").c_str(), join(vall, "|").c_str());
158
159 break;
160 }
161
162 case PropertyType::Blob:
163 {
164 uint32_t blob_id = (uint32_t)val;
165
166 if (blob_id) {
167 Blob blob(prop->card(), blob_id);
168 auto data = blob.data();
169
170 ret += sformat("blob-id %u len %zu", blob_id, data.size());
171 } else {
172 ret += sformat("blob-id %u", blob_id);
173 }
174
175 break;
176 }
177
178 case PropertyType::Enum:
179 {
180 string cur;
181 vector<string> vall;
182
183 for (auto kvp : prop->get_enums()) {
184 if (val == kvp.first)
185 cur = kvp.second;
186 vall.push_back(sformat("%s=%" PRIu64, kvp.second.c_str(), kvp.first));
187 }
188
189 ret += sformat("%" PRIu64 " (%s) [%s]", val, cur.c_str(), join(vall, "|").c_str());
190
191 break;
192 }
193
194 case PropertyType::Object:
195 {
196 ret += sformat("object id %u", (uint32_t)val);
197 break;
198 }
199
200 case PropertyType::Range:
201 {
202 auto values = prop->get_values();
203
204 ret += sformat("%" PRIu64 " [%" PRIu64 " - %" PRIu64 "]",
205 val, values[0], values[1]);
206
207 break;
208 }
209
210 case PropertyType::SignedRange:
211 {
212 auto values = prop->get_values();
213
214 ret += sformat("%" PRIi64 " [%" PRIi64 " - %" PRIi64 "]",
215 (int64_t)val, (int64_t)values[0], (int64_t)values[1]);
216
217 break;
218 }
219
220 }
221
222 if (prop->is_pending())
223 ret += " (pending)";
224 if (prop->is_immutable())
225 ret += " (immutable)";
226
227 return ret;
228 }
229
format_props(DrmPropObject * o)230 static vector<string> format_props(DrmPropObject* o)
231 {
232 vector<string> lines;
233
234 auto pmap = o->get_prop_map();
235 for (auto pp : pmap) {
236 const Property* p = o->card().get_prop(pp.first);
237 lines.push_back(format_property(p, pp.second));
238 }
239
240 return lines;
241 }
242
format_ob(DrmObject * ob)243 static string format_ob(DrmObject* ob)
244 {
245 if (auto o = dynamic_cast<Connector*>(ob))
246 return format_connector(*o);
247 else if (auto o = dynamic_cast<Encoder*>(ob))
248 return format_encoder(*o);
249 else if (auto o = dynamic_cast<Crtc*>(ob))
250 return format_crtc(*o);
251 else if (auto o = dynamic_cast<Plane*>(ob))
252 return format_plane(*o);
253 else if (auto o = dynamic_cast<Framebuffer*>(ob))
254 return format_fb(*o);
255 else
256 EXIT("Unkown DRM Object type\n");
257 }
258
259 template<class T>
filter(const vector<T> & sequence,function<bool (T)> predicate)260 vector<T> filter(const vector<T>& sequence, function<bool(T)> predicate)
261 {
262 vector<T> result;
263
264 for(auto it = sequence.begin(); it != sequence.end(); ++it)
265 if(predicate(*it))
266 result.push_back(*it);
267
268 return result;
269 }
270
271 struct Entry
272 {
273 string title;
274 vector<string> lines;
275 vector<Entry> children;
276 };
277
add_entry(vector<Entry> & entries)278 static Entry& add_entry(vector<Entry>& entries)
279 {
280 entries.emplace_back();
281 return entries.back();
282 }
283 /*
284 static bool on_tty()
285 {
286 return isatty(STDOUT_FILENO) > 0;
287 }
288 */
289 enum class TreeGlyphMode {
290 None,
291 ASCII,
292 UTF8,
293 };
294
295 static TreeGlyphMode s_glyph_mode = TreeGlyphMode::None;
296
297 enum class TreeGlyph {
298 Vertical,
299 Branch,
300 Right,
301 Space,
302 };
303
304 static const map<TreeGlyph, string> glyphs_utf8 = {
305 { TreeGlyph::Vertical, "│ " },
306 { TreeGlyph::Branch, "├─" },
307 { TreeGlyph::Right, "└─" },
308 { TreeGlyph::Space, " " },
309
310 };
311
312 static const map<TreeGlyph, string> glyphs_ascii = {
313 { TreeGlyph::Vertical, "| " },
314 { TreeGlyph::Branch, "|-" },
315 { TreeGlyph::Right, "`-" },
316 { TreeGlyph::Space, " " },
317
318 };
319
get_glyph(TreeGlyph glyph)320 const char* get_glyph(TreeGlyph glyph)
321 {
322 if (s_glyph_mode == TreeGlyphMode::None)
323 return " ";
324
325 const map<TreeGlyph, string>& glyphs = s_glyph_mode == TreeGlyphMode::UTF8 ? glyphs_utf8 : glyphs_ascii;
326
327 return glyphs.at(glyph).c_str();
328 }
329
print_entry(const Entry & e,const string & prefix,bool is_child,bool is_last)330 static void print_entry(const Entry& e, const string& prefix, bool is_child, bool is_last)
331 {
332 string prefix1;
333 string prefix2;
334
335 if (is_child) {
336 prefix1 = prefix + (is_last ? get_glyph(TreeGlyph::Right) : get_glyph(TreeGlyph::Branch));
337 prefix2 = prefix + (is_last ? get_glyph(TreeGlyph::Space) : get_glyph(TreeGlyph::Vertical));
338 }
339
340 printf("%s%s\n", prefix1.c_str(), e.title.c_str());
341
342 bool has_children = e.children.size() > 0;
343
344 string data_prefix = prefix2 + (has_children ? get_glyph(TreeGlyph::Vertical) : get_glyph(TreeGlyph::Space));
345
346 for (const string& str : e.lines) {
347 string p = data_prefix + get_glyph(TreeGlyph::Space);
348 printf("%s%s\n", p.c_str(), str.c_str());
349 }
350
351 for (const Entry& child : e.children) {
352 bool is_last = &child == &e.children.back();
353
354 print_entry(child, prefix2, true, is_last);
355 }
356 }
357
print_entries(const vector<Entry> & entries,const string & prefix)358 static void print_entries(const vector<Entry>& entries, const string& prefix)
359 {
360 for (const Entry& e: entries) {
361 print_entry(e, "", false, false);
362 }
363 }
364
365 template<class T>
append(vector<DrmObject * > & dst,const vector<T * > & src)366 static void append(vector<DrmObject*>& dst, const vector<T*>& src)
367 {
368 dst.insert(dst.end(), src.begin(), src.end());
369 }
370
371
print_as_list(Card & card)372 static void print_as_list(Card& card)
373 {
374 vector<DrmPropObject*> obs;
375 vector<Framebuffer*> fbs;
376
377 for (Connector* conn : card.get_connectors()) {
378 obs.push_back(conn);
379 }
380
381 for (Encoder* enc : card.get_encoders()) {
382 obs.push_back(enc);
383 }
384
385 for (Crtc* crtc : card.get_crtcs()) {
386 obs.push_back(crtc);
387 if (crtc->buffer_id() && !card.has_has_universal_planes()) {
388 Framebuffer* fb = new Framebuffer(card, crtc->buffer_id());
389 fbs.push_back(fb);
390 }
391 }
392
393 for (Plane* plane : card.get_planes()) {
394 obs.push_back(plane);
395 if (plane->fb_id()) {
396 Framebuffer* fb = new Framebuffer(card, plane->fb_id());
397 fbs.push_back(fb);
398 }
399 }
400
401 for (DrmPropObject* ob: obs) {
402 printf("%s\n", format_ob(ob).c_str());
403
404 if (s_opts.print_props) {
405 for (string str : format_props(ob))
406 printf(" %s\n", str.c_str());
407 }
408 }
409
410 for (Framebuffer* fb: fbs) {
411 printf("%s\n", format_ob(fb).c_str());
412 }
413 }
414
print_as_tree(Card & card)415 static void print_as_tree(Card& card)
416 {
417 vector<Entry> entries;
418
419 for (Connector* conn : card.get_connectors()) {
420 Entry& e1 = add_entry(entries);
421 e1.title = format_ob(conn);
422 if (s_opts.print_props)
423 e1.lines = format_props(conn);
424
425 for (Encoder* enc : conn->get_encoders()) {
426
427 Entry& e2 = add_entry(e1.children);
428 e2.title = format_ob(enc);
429 if (s_opts.print_props)
430 e2.lines = format_props(enc);
431
432 if (Crtc* crtc = enc->get_crtc()) {
433 Entry& e3 = add_entry(e2.children);
434 e3.title = format_ob(crtc);
435 if (s_opts.print_props)
436 e3.lines = format_props(crtc);
437
438 if (crtc->buffer_id() && !card.has_has_universal_planes()) {
439 Framebuffer fb(card, crtc->buffer_id());
440 Entry& e5 = add_entry(e3.children);
441
442 e5.title = format_ob(&fb);
443 }
444
445 for (Plane* plane : card.get_planes()) {
446 if (plane->crtc_id() != crtc->id())
447 continue;
448
449 Entry& e4 = add_entry(e3.children);
450 e4.title = format_ob(plane);
451 if (s_opts.print_props)
452 e4.lines = format_props(plane);
453
454 uint32_t fb_id = plane->fb_id();
455 if (fb_id) {
456 Framebuffer fb(card, fb_id);
457
458 Entry& e5 = add_entry(e4.children);
459
460 e5.title = format_ob(&fb);
461 }
462 }
463 }
464 }
465 }
466
467 print_entries(entries, "");
468 }
469
print_modes(Card & card)470 static void print_modes(Card& card)
471 {
472 for (Connector* conn : card.get_connectors()) {
473 if (!conn->connected())
474 continue;
475
476 printf("%s\n", format_ob(conn).c_str());
477
478 auto modes = conn->get_modes();
479 for (unsigned i = 0; i < modes.size(); ++i)
480 printf("%s\n", format_mode(modes[i], i).c_str());
481 }
482 }
483
484 static const char* usage_str =
485 "Usage: kmsprint [OPTIONS]\n\n"
486 "Options:\n"
487 " --device=DEVICE DEVICE is the path to DRM card to open\n"
488 " -l, --list Print list instead of tree\n"
489 " -m, --modes Print modes\n"
490 " --xmode Print modes using X modeline\n"
491 " -p, --props Print properties\n"
492 ;
493
usage()494 static void usage()
495 {
496 puts(usage_str);
497 }
498
main(int argc,char ** argv)499 int main(int argc, char **argv)
500 {
501 string dev_path;
502
503 OptionSet optionset = {
504 Option("|device=", [&dev_path](string s)
505 {
506 dev_path = s;
507 }),
508 Option("l|list", []()
509 {
510 s_opts.print_list = true;
511 }),
512 Option("m|modes", []()
513 {
514 s_opts.print_modes = true;
515 }),
516 Option("p|props", []()
517 {
518 s_opts.print_props = true;
519 }),
520 Option("|xmode", []() {
521 s_opts.x_modeline = true;
522 }),
523 Option("h|help", []()
524 {
525 usage();
526 exit(-1);
527 }),
528 };
529
530 optionset.parse(argc, argv);
531
532 if (optionset.params().size() > 0) {
533 usage();
534 exit(-1);
535 }
536
537 Card card(dev_path);
538
539 if (s_opts.print_modes) {
540 print_modes(card);
541 return 0;
542 }
543
544 if (s_opts.print_list)
545 print_as_list(card);
546 else
547 print_as_tree(card);
548 }
549