blob: a039b0ae5114080b27b8cbdb96f1cccd89379986 [file] [log] [blame]
Dominik Behr83010f82016-03-18 18:43:08 -07001/*
2 * Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6
7#include <errno.h>
8#include <fcntl.h>
9#include <stdbool.h>
10#include <stddef.h>
11#include <stdio.h>
12#include <stdlib.h>
13#include <string.h>
14#include <sys/mman.h>
15#include <time.h>
16#include <unistd.h>
17
18#include "drm.h"
19#include "util.h"
20
21static drm_t* drm = NULL;
22
23static void drm_disable_crtc(drm_t* drm, drmModeCrtc* crtc)
24{
25 if (crtc) {
26 drmModeSetCrtc(drm->fd, crtc->crtc_id, 0, // buffer_id
27 0, 0, // x,y
28 NULL, // connectors
29 0, // connector_count
30 NULL); // mode
31 }
32}
33
34static drmModeCrtc* find_crtc_for_connector(drm_t* drm, drmModeConnector* connector)
35{
36 int i, j;
37 drmModeEncoder* encoder;
38 int32_t crtc_id;
39
40 if (connector->encoder_id)
41 encoder = drmModeGetEncoder(drm->fd, connector->encoder_id);
42 else
43 encoder = NULL;
44
45 if (encoder && encoder->crtc_id) {
46 crtc_id = encoder->crtc_id;
47 drmModeFreeEncoder(encoder);
48 return drmModeGetCrtc(drm->fd, crtc_id);
49 }
50
51 crtc_id = -1;
52 for (i = 0; i < connector->count_encoders; i++) {
53 encoder = drmModeGetEncoder(drm->fd, connector->encoders[i]);
54
55 if (encoder) {
56 for (j = 0; j < drm->resources->count_crtcs; j++) {
57 if (!(encoder->possible_crtcs & (1 << j)))
58 continue;
59 crtc_id = drm->resources->crtcs[j];
60 break;
61 }
62 if (crtc_id >= 0) {
63 drmModeFreeEncoder(encoder);
64 return drmModeGetCrtc(drm->fd, crtc_id);
65 }
66 }
67 }
68
69 return NULL;
70}
71
72static void drm_disable_non_main_crtcs(drm_t* drm)
73{
74 int i;
75 drmModeCrtc* crtc;
76
77 for (i = 0; i < drm->resources->count_connectors; i++) {
78 drmModeConnector* connector;
79
80 connector = drmModeGetConnector(drm->fd, drm->resources->connectors[i]);
81 crtc = find_crtc_for_connector(drm, connector);
82 if (crtc->crtc_id != drm->crtc->crtc_id)
83 drm_disable_crtc(drm, crtc);
84 drmModeFreeCrtc(crtc);
85 }
86}
87
88static int drm_is_primary_plane(drm_t* drm, uint32_t plane_id)
89{
90 uint32_t p;
91 bool found = false;
92 int ret = -1;
93
94 drmModeObjectPropertiesPtr props;
95 props = drmModeObjectGetProperties(drm->fd,
96 plane_id,
97 DRM_MODE_OBJECT_PLANE);
98 if (!props) {
99 LOG(ERROR, "Unable to get plane properties: %m");
100 return -1;
101 }
102
103 for (p = 0; p < props->count_props && !found; p++) {
104 drmModePropertyPtr prop;
105 prop = drmModeGetProperty(drm->fd, props->props[p]);
106 if (prop) {
107 if (strcmp("type", prop->name) == 0) {
108 found = true;
109 ret = (props->prop_values[p] == DRM_PLANE_TYPE_PRIMARY);
110 }
111 drmModeFreeProperty(prop);
112 }
113 }
114
115 drmModeFreeObjectProperties(props);
116
117 return ret;
118}
119
120/* disable all planes except for primary on crtc we use */
121static void drm_disable_non_primary_planes(drm_t* drm)
122{
123 int ret;
124
125 if (!drm->plane_resources)
126 return;
127
128 for (uint32_t p = 0; p < drm->plane_resources->count_planes; p++) {
129 drmModePlanePtr plane;
130 plane = drmModeGetPlane(drm->fd,
131 drm->plane_resources->planes[p]);
132 if (plane) {
133 int primary = drm_is_primary_plane(drm, plane->plane_id);
134 if (!(plane->crtc_id == drm->crtc->crtc_id && primary != 0)) {
135 ret = drmModeSetPlane(drm->fd, plane->plane_id, plane->crtc_id,
136 0, 0,
137 0, 0,
138 0, 0,
139 0, 0,
140 0, 0);
141 if (ret) {
142 LOG(WARNING, "Unable to disable plane: %m");
143 }
144 }
145 drmModeFreePlane(plane);
146 }
147 }
148}
149
150static bool drm_is_internal(unsigned type)
151{
152 unsigned t;
153 unsigned kInternalConnectors[] = {
154 DRM_MODE_CONNECTOR_LVDS,
155 DRM_MODE_CONNECTOR_eDP,
156 DRM_MODE_CONNECTOR_DSI,
157 };
158 for (t = 0; t < ARRAY_SIZE(kInternalConnectors); t++)
159 if (type == kInternalConnectors[t])
160 return true;
161 return false;
162}
163
164static drmModeConnector* find_first_connected_connector(drm_t* drm, bool internal, bool external)
165{
166 for (int i = 0; i < drm->resources->count_connectors; i++) {
167 drmModeConnector* connector;
168
169 connector = drmModeGetConnector(drm->fd, drm->resources->connectors[i]);
170 if (connector) {
171 bool is_internal = drm_is_internal(connector->connector_type);
172 if (!internal && is_internal)
173 continue;
174 if (!external && !is_internal)
175 continue;
176 if ((connector->count_modes > 0) &&
177 (connector->connection == DRM_MODE_CONNECTED))
178 return connector;
179
180 drmModeFreeConnector(connector);
181 }
182 }
183 return NULL;
184}
185
186static drmModeConnector* find_main_monitor(drm_t* drm, uint32_t* mode_index)
187{
188 int modes;
189 drmModeConnector* main_monitor_connector = NULL;
190
191 /*
192 * Find the LVDS/eDP/DSI connectors. Those are the main screens.
193 */
194 main_monitor_connector = find_first_connected_connector(drm, true, false);
195
196 /*
197 * Now try external connectors.
198 */
199 if (!main_monitor_connector)
200 main_monitor_connector =
201 find_first_connected_connector(drm, false, true);
202
203 /*
204 * If we still didn't find a connector, give up and return.
205 */
206 if (!main_monitor_connector)
207 return NULL;
208
209 *mode_index = 0;
210 for (modes = 0; modes < main_monitor_connector->count_modes; modes++) {
211 if (main_monitor_connector->modes[modes].type &
212 DRM_MODE_TYPE_PREFERRED) {
213 *mode_index = modes;
214 break;
215 }
216 }
217
218 return main_monitor_connector;
219}
220
221static void drm_fini(drm_t* drm)
222{
223 if (!drm)
224 return;
225
226 if (drm->fd >= 0) {
227 if (drm->crtc) {
228 int32_t ret;
229
230 ret = drmSetMaster(drm->fd);
231 if (ret)
232 LOG(ERROR, "drmSetMaster in fini failed: %m");
233 drm_disable_crtc(drm, drm->crtc);
234 drmModeFreeCrtc(drm->crtc);
235 drm->crtc = NULL;
236 }
237
238 if (drm->main_monitor_connector) {
239 drmModeFreeConnector(drm->main_monitor_connector);
240 drm->main_monitor_connector = NULL;
241 }
242
243 if (drm->plane_resources) {
244 drmModeFreePlaneResources(drm->plane_resources);
245 drm->plane_resources = NULL;
246 }
247
248 if (drm->resources) {
249 drmModeFreeResources(drm->resources);
250 drm->resources = NULL;
251 }
252
253 drmClose(drm->fd);
254 drm->fd = -1;
255 }
256
257 free(drm);
258}
259
260static bool drm_equal(drm_t* l, drm_t* r)
261{
262 if (!l->crtc && r->crtc)
263 return false;
264 if (l->crtc && !r->crtc)
265 return false;
266 if (l->crtc && r->crtc)
267 if (l->crtc->crtc_id != r->crtc->crtc_id)
268 return false;
269
270 if (!l->main_monitor_connector && r->main_monitor_connector)
271 return false;
272 if (l->main_monitor_connector && !r->main_monitor_connector)
273 return false;
274 if (l->main_monitor_connector && r->main_monitor_connector)
275 if (l->main_monitor_connector->connector_id != r->main_monitor_connector->connector_id)
276 return false;
277 return true;
278}
279
280static int drm_score(drm_t* drm)
281{
282 drmVersionPtr version;
283 int score = 0;
284
285 if (!drm)
286 return -1000000000;
287
288 if (!drm->main_monitor_connector)
289 return -1000000000;
290
291 if (drm_is_internal(drm->main_monitor_connector->connector_type))
292 score++;
293
294 version = drmGetVersion(drm->fd);
295 if (version) {
296 /* we would rather use any driver besides UDL */
297 if (strcmp("udl", version->name) == 0)
298 score--;
299 if (strcmp("evdi", version->name) == 0)
300 score--;
301 /* VGEM should be ignored because it has no displays, but lets make sure */
302 if (strcmp("vgem", version->name) == 0)
303 score -= 1000000;
304 drmFreeVersion(version);
305 }
306 return score;
307}
308
309drm_t* drm_scan(void)
310{
311 unsigned i;
312 char* dev_name;
313 int ret;
314 drm_t *best_drm = NULL;
315
316 for (i = 0; i < DRM_MAX_MINOR; i++) {
317 drm_t* drm = calloc(1, sizeof(drm_t));
318
319 if (!drm)
320 return NULL;
321
322 ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
323 if (ret < 0) {
324 drm_fini(drm);
325 continue;
326 }
327
328 drm->fd = open(dev_name, O_RDWR, 0);
329 free(dev_name);
330 if (drm->fd < 0) {
331 drm_fini(drm);
332 continue;
333 }
334
335 drm->resources = drmModeGetResources(drm->fd);
336 if (!drm->resources) {
337 drm_fini(drm);
338 continue;
339 }
340
341 /* expect at least one crtc so we do not try to run on VGEM */
342 if (drm->resources->count_crtcs == 0 || drm->resources->count_connectors == 0) {
343 drm_fini(drm);
344 continue;
345 }
346
347 drm->main_monitor_connector = find_main_monitor(drm, &drm->selected_mode);
348 if (!drm->main_monitor_connector) {
349 drm_fini(drm);
350 continue;
351 }
352
353 drm->crtc = find_crtc_for_connector(drm, drm->main_monitor_connector);
354 if (!drm->crtc) {
355 drm_fini(drm);
356 continue;
357 }
358
359 drm->crtc->mode = drm->main_monitor_connector->modes[drm->selected_mode];
360
361 drm->plane_resources = drmModeGetPlaneResources(drm->fd);
362 drm->refcount = 1;
363
364 if (drm_score(drm) > drm_score(best_drm)) {
365 drm_fini(best_drm);
366 best_drm = drm;
367 }
368 }
369
370 if (best_drm) {
371 drmVersionPtr version;
372 version = drmGetVersion(best_drm->fd);
373 if (version) {
374 LOG(INFO,
375 "Frecon using drm driver %s, version %d.%d, date(%s), desc(%s)",
376 version->name,
377 version->version_major,
378 version->version_minor,
379 version->date,
380 version->desc);
381 drmFreeVersion(version);
382 }
383 drmDropMaster(best_drm->fd);
384 }
385
386 return best_drm;
387}
388
389void drm_set(drm_t* drm_)
390{
391 if (drm) {
392 drm_delref(drm);
393 drm = NULL;
394 }
395 drm = drm_;
396}
397
398void drm_close(void)
399{
400 if (drm) {
401 drm_delref(drm);
402 drm = NULL;
403 }
404}
405
406void drm_delref(drm_t *drm)
407{
408 if (drm->refcount) {
409 drm->refcount--;
410 } else {
411 LOG(ERROR, "Imbalanced drm_close()");
412 }
413 if (drm->refcount) {
414 return;
415 }
416
417 drm_fini(drm);
418}
419
420drm_t* drm_addref(void)
421{
422 if (drm) {
423 drm->refcount++;
424 return drm;
425 }
426
427 return NULL;
428}
429
430/*
431 * returns true if connector/crtc/driver have changed and framebuffer object have to be re-create
432 */
433bool drm_rescan(void)
434{
435 drm_t* ndrm;
436
437 ndrm = drm_scan();
438 if (ndrm) {
439 if (drm_equal(ndrm, drm)) {
440 drm_fini(ndrm);
441 } else {
442 drm_delref(drm);
443 drm = ndrm;
444 return true;
445 }
446 } else {
447 if (drm) {
448 drm_delref(drm); /* no usable monitor/drm object */
449 drm = NULL;
450 return true;
451 }
452 }
453 return false;
454}
455
456bool drm_valid(drm_t* drm) {
457 return drm && drm->fd >= 0 && drm->resources && drm->main_monitor_connector && drm->crtc;
458}
459
460int32_t drm_setmode(drm_t* drm, uint32_t fb_id)
461{
462 int32_t ret;
463
464 ret = drmSetMaster(drm->fd);
465 if (ret)
466 LOG(ERROR, "drmSetMaster failed: %m");
467
468 ret = drmModeSetCrtc(drm->fd, drm->crtc->crtc_id,
469 fb_id,
470 0, 0, // x,y
471 &drm->main_monitor_connector->connector_id,
472 1, // connector_count
473 &drm->crtc->mode); // mode
474
475 if (ret) {
476 LOG(ERROR, "Unable to set crtc: %m");
477 drmDropMaster(drm->fd);
478 return ret;
479 }
480
481 ret = drmModeSetCursor(drm->fd, drm->crtc->crtc_id,
482 0, 0, 0);
483
484 if (ret)
485 LOG(ERROR, "Unable to hide cursor");
486
487 drm_disable_non_primary_planes(drm);
488 drm_disable_non_main_crtcs(drm);
489 drmDropMaster(drm->fd);
490 return ret;
491}
492
493bool drm_read_edid(drm_t* drm)
494{
495 if (drm->edid_found) {
496 return true;
497 }
498
499 for (int i = 0; i < drm->main_monitor_connector->count_props; i++) {
500 drmModePropertyPtr prop;
501 drmModePropertyBlobPtr blob_ptr;
502 prop = drmModeGetProperty(drm->fd, drm->main_monitor_connector->props[i]);
503 if (prop) {
504 if (strcmp(prop->name, "EDID") == 0) {
505 blob_ptr = drmModeGetPropertyBlob(drm->fd,
506 drm->main_monitor_connector->prop_values[i]);
507 if (blob_ptr) {
508 memcpy(&drm->edid, blob_ptr->data, EDID_SIZE);
509 drmModeFreePropertyBlob(blob_ptr);
510 return (drm->edid_found = true);
511 }
512 }
513 }
514 }
515
516 return false;
517}
518
519uint32_t drm_gethres(drm_t* drm)
520{
521 return drm->crtc->mode.hdisplay;
522}
523
524uint32_t drm_getvres(drm_t* drm)
525{
526 return drm->crtc->mode.vdisplay;
527}