1 #include <cstdio>
2 #include <unistd.h>
3 #include <fcntl.h>
4 #include <utility>
5 #include <stdexcept>
6 #include <cstring>
7 #include <algorithm>
8 #include <cerrno>
9 #include <algorithm>
10 #include <glob.h>
11
12 #include <sys/stat.h>
13 #include <sys/sysmacros.h>
14 #include <sys/types.h>
15
16 #include <xf86drm.h>
17 #include <xf86drmMode.h>
18
19 #include <kms++/kms++.h>
20
21 using namespace std;
22
23 namespace kms
24 {
glob(const string & pattern)25 static vector<string> glob(const string& pattern)
26 {
27 glob_t glob_result;
28 memset(&glob_result, 0, sizeof(glob_result));
29
30 int r = glob(pattern.c_str(), 0, NULL, &glob_result);
31 if (r != 0) {
32 globfree(&glob_result);
33 throw runtime_error("failed to find DRM cards");
34 }
35
36 vector<string> filenames;
37 for (size_t i = 0; i < glob_result.gl_pathc; ++i)
38 filenames.push_back(string(glob_result.gl_pathv[i]));
39
40 globfree(&glob_result);
41
42 return filenames;
43 }
44
open_first_kms_device()45 static int open_first_kms_device()
46 {
47 vector<string> paths = glob("/dev/dri/card*");
48
49 for (const string& path : paths) {
50 int fd = open(path.c_str(), O_RDWR | O_CLOEXEC);
51 if (fd < 0)
52 throw invalid_argument(string(strerror(errno)) + " opening device " + path);
53
54 auto res = drmModeGetResources(fd);
55 if (!res) {
56 close(fd);
57 continue;
58 }
59
60 bool has_kms = res->count_crtcs > 0 && res->count_connectors > 0 && res->count_encoders > 0;
61
62 drmModeFreeResources(res);
63
64 if (has_kms)
65 return fd;
66
67 close(fd);
68 }
69
70 throw runtime_error("No modesetting DRM card found");
71 }
72
open_device_by_path(string path)73 static int open_device_by_path(string path)
74 {
75 int fd = open(path.c_str(), O_RDWR | O_CLOEXEC);
76 if (fd < 0)
77 throw invalid_argument(string(strerror(errno)) + " opening device " + path);
78 return fd;
79 }
80
81 // open Nth DRM card with the given driver name
open_device_by_driver(string name,uint32_t idx)82 static int open_device_by_driver(string name, uint32_t idx)
83 {
84 transform(name.begin(), name.end(), name.begin(), ::tolower);
85
86 uint32_t num_matches = 0;
87 vector<string> paths = glob("/dev/dri/card*");
88
89 for (const string& path : paths) {
90 int fd = open_device_by_path(path);
91
92 drmVersionPtr ver = drmGetVersion(fd);
93 string drv_name = string(ver->name, ver->name_len);
94 drmFreeVersion(ver);
95
96 transform(drv_name.begin(), drv_name.end(), drv_name.begin(), ::tolower);
97
98 if (name == drv_name) {
99 if (idx == num_matches)
100 return fd;
101 num_matches++;
102 }
103
104 close(fd);
105 }
106
107 throw invalid_argument("Failed to find a DRM device " + name + ":" + to_string(idx));
108 }
109
open_named_card(const std::string & name)110 std::unique_ptr<Card> Card::open_named_card(const std::string& name)
111 {
112 int fd = drmOpen(name.c_str(), 0);
113
114 if (fd < 0)
115 throw invalid_argument(string(strerror(errno)) + " opening card \"" + name + "\"");
116
117 return std::unique_ptr<Card>(new Card(fd, true));
118 }
119
Card(const std::string & dev_path)120 Card::Card(const std::string& dev_path)
121 {
122 const char* drv_p = getenv("KMSXX_DRIVER");
123 const char* dev_p = getenv("KMSXX_DEVICE");
124
125 if (!dev_path.empty()) {
126 m_fd = open_device_by_path(dev_path);
127 } else if (dev_p) {
128 string dev(dev_p);
129 m_fd = open_device_by_path(dev);
130 } else if (drv_p) {
131 string drv(drv_p);
132
133 auto isplit = find(drv.begin(), drv.end(), ':');
134
135 if (isplit == drv.begin())
136 throw runtime_error("Invalid KMSXX_DRIVER");
137
138 string name;
139 uint32_t num = 0;
140
141 if (isplit == drv.end()) {
142 name = drv;
143 } else {
144 name = string(drv.begin(), isplit);
145 string numstr = string(isplit + 1, drv.end());
146 num = stoul(numstr);
147 }
148
149 m_fd = open_device_by_driver(name, num);
150 } else {
151 m_fd = open_first_kms_device();
152 }
153
154 setup();
155 }
156
Card(const std::string & driver,uint32_t idx)157 Card::Card(const std::string& driver, uint32_t idx)
158 {
159 m_fd = open_device_by_driver(driver, idx);
160
161 setup();
162 }
163
Card(int fd,bool take_ownership)164 Card::Card(int fd, bool take_ownership)
165 {
166 if (take_ownership) {
167 m_fd = fd;
168 } else {
169 m_fd = fcntl(fd, F_DUPFD_CLOEXEC, 0);
170
171 if (m_fd < 0)
172 throw invalid_argument(string(strerror(errno)) + " duplicating fd");
173 }
174
175 setup();
176 }
177
setup()178 void Card::setup()
179 {
180 drmVersionPtr ver = drmGetVersion(m_fd);
181 m_version.major = ver->version_major;
182 m_version.minor = ver->version_minor;
183 m_version.patchlevel = ver->version_patchlevel;
184 m_version.name = string(ver->name, ver->name_len);
185 m_version.date = string(ver->date, ver->date_len);
186 m_version.desc = string(ver->desc, ver->desc_len);
187 drmFreeVersion(ver);
188
189 struct stat stats;
190 int r;
191
192 r = fstat(m_fd, &stats);
193 if (r < 0)
194 throw invalid_argument("Can't stat device (" + string(strerror(errno)) + ")");
195
196 m_minor = minor(stats.st_dev);
197
198 r = drmSetMaster(m_fd);
199 m_is_master = r == 0;
200
201 if (getenv("KMSXX_DISABLE_UNIVERSAL_PLANES") == 0) {
202 r = drmSetClientCap(m_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
203 m_has_universal_planes = r == 0;
204 } else {
205 m_has_universal_planes = false;
206 }
207
208 #ifdef DRM_CLIENT_CAP_ATOMIC
209 if (getenv("KMSXX_DISABLE_ATOMIC") == 0) {
210 r = drmSetClientCap(m_fd, DRM_CLIENT_CAP_ATOMIC, 1);
211 m_has_atomic = r == 0;
212 } else {
213 m_has_atomic = false;
214 }
215 #else
216 m_has_atomic = false;
217 #endif
218
219 uint64_t has_dumb;
220 r = drmGetCap(m_fd, DRM_CAP_DUMB_BUFFER, &has_dumb);
221 m_has_dumb = r == 0 && has_dumb;
222
223 auto res = drmModeGetResources(m_fd);
224 if (res) {
225 for (int i = 0; i < res->count_connectors; ++i) {
226 uint32_t id = res->connectors[i];
227 auto ob = new Connector(*this, id, i);
228 m_obmap[id] = ob;
229 m_connectors.push_back(ob);
230 }
231
232 for (int i = 0; i < res->count_crtcs; ++i) {
233 uint32_t id = res->crtcs[i];
234 auto ob = new Crtc(*this, id, i);
235 m_obmap[id] = ob;
236 m_crtcs.push_back(ob);
237 }
238
239 for (int i = 0; i < res->count_encoders; ++i) {
240 uint32_t id = res->encoders[i];
241 auto ob = new Encoder(*this, id, i);
242 m_obmap[id] = ob;
243 m_encoders.push_back(ob);
244 }
245
246 drmModeFreeResources(res);
247
248 auto planeRes = drmModeGetPlaneResources(m_fd);
249 if (planeRes) {
250 for (uint i = 0; i < planeRes->count_planes; ++i) {
251 uint32_t id = planeRes->planes[i];
252 auto ob = new Plane(*this, id, i);
253 m_obmap[id] = ob;
254 m_planes.push_back(ob);
255 }
256
257 drmModeFreePlaneResources(planeRes);
258 }
259 }
260
261 // collect all possible props
262 for (auto ob : get_objects()) {
263 auto props = drmModeObjectGetProperties(m_fd, ob->id(), ob->object_type());
264
265 if (props == nullptr)
266 continue;
267
268 for (unsigned i = 0; i < props->count_props; ++i) {
269 uint32_t prop_id = props->props[i];
270
271 if (m_obmap.find(prop_id) == m_obmap.end()) {
272 auto ob = new Property(*this, prop_id);
273 m_obmap[prop_id] = ob;
274 m_properties.push_back(ob);
275 }
276 }
277
278 drmModeFreeObjectProperties(props);
279 }
280
281 for (auto pair : m_obmap)
282 pair.second->setup();
283 }
284
~Card()285 Card::~Card()
286 {
287 restore_modes();
288
289 while (m_framebuffers.size() > 0)
290 delete m_framebuffers.back();
291
292 for (auto pair : m_obmap)
293 delete pair.second;
294
295 close(m_fd);
296 }
297
drop_master()298 void Card::drop_master()
299 {
300 drmDropMaster(fd());
301 m_is_master = false;
302 }
303
has_kms() const304 bool Card::has_kms() const
305 {
306 return m_connectors.size() > 0 && m_encoders.size() > 0 && m_crtcs.size() > 0;
307 }
308
restore_modes()309 void Card::restore_modes()
310 {
311 for (auto conn : get_connectors())
312 conn->restore_mode();
313 }
314
get_first_connected_connector() const315 Connector* Card::get_first_connected_connector() const
316 {
317 for (auto c : m_connectors) {
318 if (c->connected())
319 return c;
320 }
321
322 throw invalid_argument("no connected connectors");
323 }
324
get_object(uint32_t id) const325 DrmObject* Card::get_object(uint32_t id) const
326 {
327 auto iter = m_obmap.find(id);
328 if (iter != m_obmap.end())
329 return iter->second;
330 return nullptr;
331 }
332
get_objects() const333 std::vector<kms::DrmObject*> Card::get_objects() const
334 {
335 vector<DrmObject*> v;
336 for (auto pair : m_obmap)
337 v.push_back(pair.second);
338 return v;
339 }
340
get_connector(uint32_t id) const341 Connector* Card::get_connector(uint32_t id) const
342 {
343 return dynamic_cast<Connector*>(get_object(id));
344 }
get_crtc(uint32_t id) const345 Crtc* Card::get_crtc(uint32_t id) const
346 {
347 return dynamic_cast<Crtc*>(get_object(id));
348 }
get_encoder(uint32_t id) const349 Encoder* Card::get_encoder(uint32_t id) const
350 {
351 return dynamic_cast<Encoder*>(get_object(id));
352 }
get_prop(uint32_t id) const353 Property* Card::get_prop(uint32_t id) const
354 {
355 return dynamic_cast<Property*>(get_object(id));
356 }
get_plane(uint32_t id) const357 Plane* Card::get_plane(uint32_t id) const
358 {
359 return dynamic_cast<Plane*>(get_object(id));
360 }
361
get_connected_pipelines()362 std::vector<kms::Pipeline> Card::get_connected_pipelines()
363 {
364 vector<Pipeline> outputs;
365
366 for (auto conn : get_connectors()) {
367 if (conn->connected() == false)
368 continue;
369
370 Crtc* crtc = conn->get_current_crtc();
371
372 if (!crtc) {
373 for (auto possible : conn->get_possible_crtcs()) {
374 if (find_if(outputs.begin(), outputs.end(), [possible](Pipeline out) { return out.crtc == possible; }) == outputs.end()) {
375 crtc = possible;
376 break;
377 }
378 }
379 }
380
381 if (!crtc)
382 throw invalid_argument(string("Connector #") +
383 to_string(conn->idx()) +
384 " has no possible crtcs");
385
386 outputs.push_back(Pipeline{ crtc, conn });
387 }
388
389 return outputs;
390 }
391
page_flip_handler(int fd,unsigned int frame,unsigned int sec,unsigned int usec,void * data)392 static void page_flip_handler(int fd, unsigned int frame,
393 unsigned int sec, unsigned int usec,
394 void* data)
395 {
396 auto handler = (PageFlipHandlerBase*)data;
397 double time = sec + usec / 1000000.0;
398 handler->handle_page_flip(frame, time);
399 }
400
call_page_flip_handlers()401 void Card::call_page_flip_handlers()
402 {
403 drmEventContext ev{};
404 ev.version = DRM_EVENT_CONTEXT_VERSION;
405 ev.page_flip_handler = page_flip_handler;
406
407 drmHandleEvent(fd(), &ev);
408 }
409
disable_all()410 int Card::disable_all()
411 {
412 AtomicReq req(*this);
413
414 for (Crtc* c : m_crtcs) {
415 req.add(c, {
416 { "ACTIVE", 0 },
417 });
418 }
419
420 for (Plane* p : m_planes) {
421 req.add(p, {
422 { "FB_ID", 0 },
423 { "CRTC_ID", 0 },
424 });
425 }
426
427 return req.commit_sync(true);
428 }
429
430 } // namespace kms
431