blob: 0ca40affb429eb2b1e0d8e56789c10936c6d1f55 [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"
Dominik Behrd2cc4d22016-01-29 17:31:52 -080019#include "input.h"
Dominik Behr83010f82016-03-18 18:43:08 -070020#include "util.h"
21
Dominik Behrda3c0102016-06-08 15:05:38 -070022static drm_t* g_drm = NULL;
Dominik Behr83010f82016-03-18 18:43:08 -070023
24static void drm_disable_crtc(drm_t* drm, drmModeCrtc* crtc)
25{
26 if (crtc) {
27 drmModeSetCrtc(drm->fd, crtc->crtc_id, 0, // buffer_id
28 0, 0, // x,y
29 NULL, // connectors
30 0, // connector_count
31 NULL); // mode
32 }
33}
34
35static drmModeCrtc* find_crtc_for_connector(drm_t* drm, drmModeConnector* connector)
36{
37 int i, j;
38 drmModeEncoder* encoder;
39 int32_t crtc_id;
40
41 if (connector->encoder_id)
42 encoder = drmModeGetEncoder(drm->fd, connector->encoder_id);
43 else
44 encoder = NULL;
45
46 if (encoder && encoder->crtc_id) {
47 crtc_id = encoder->crtc_id;
48 drmModeFreeEncoder(encoder);
49 return drmModeGetCrtc(drm->fd, crtc_id);
50 }
51
52 crtc_id = -1;
53 for (i = 0; i < connector->count_encoders; i++) {
54 encoder = drmModeGetEncoder(drm->fd, connector->encoders[i]);
55
56 if (encoder) {
57 for (j = 0; j < drm->resources->count_crtcs; j++) {
58 if (!(encoder->possible_crtcs & (1 << j)))
59 continue;
60 crtc_id = drm->resources->crtcs[j];
61 break;
62 }
63 if (crtc_id >= 0) {
64 drmModeFreeEncoder(encoder);
65 return drmModeGetCrtc(drm->fd, crtc_id);
66 }
67 }
68 }
69
70 return NULL;
71}
72
73static void drm_disable_non_main_crtcs(drm_t* drm)
74{
75 int i;
76 drmModeCrtc* crtc;
77
78 for (i = 0; i < drm->resources->count_connectors; i++) {
79 drmModeConnector* connector;
80
81 connector = drmModeGetConnector(drm->fd, drm->resources->connectors[i]);
82 crtc = find_crtc_for_connector(drm, connector);
83 if (crtc->crtc_id != drm->crtc->crtc_id)
84 drm_disable_crtc(drm, crtc);
85 drmModeFreeCrtc(crtc);
86 }
87}
88
89static int drm_is_primary_plane(drm_t* drm, uint32_t plane_id)
90{
91 uint32_t p;
92 bool found = false;
93 int ret = -1;
94
95 drmModeObjectPropertiesPtr props;
96 props = drmModeObjectGetProperties(drm->fd,
97 plane_id,
98 DRM_MODE_OBJECT_PLANE);
99 if (!props) {
100 LOG(ERROR, "Unable to get plane properties: %m");
101 return -1;
102 }
103
104 for (p = 0; p < props->count_props && !found; p++) {
105 drmModePropertyPtr prop;
106 prop = drmModeGetProperty(drm->fd, props->props[p]);
107 if (prop) {
108 if (strcmp("type", prop->name) == 0) {
109 found = true;
110 ret = (props->prop_values[p] == DRM_PLANE_TYPE_PRIMARY);
111 }
112 drmModeFreeProperty(prop);
113 }
114 }
115
116 drmModeFreeObjectProperties(props);
117
118 return ret;
119}
120
Dominik Behrb1abcba2016-04-14 14:57:21 -0700121/* Disable all planes except for primary on crtc we use. */
Dominik Behr83010f82016-03-18 18:43:08 -0700122static void drm_disable_non_primary_planes(drm_t* drm)
123{
124 int ret;
125
126 if (!drm->plane_resources)
127 return;
128
129 for (uint32_t p = 0; p < drm->plane_resources->count_planes; p++) {
130 drmModePlanePtr plane;
131 plane = drmModeGetPlane(drm->fd,
132 drm->plane_resources->planes[p]);
133 if (plane) {
134 int primary = drm_is_primary_plane(drm, plane->plane_id);
135 if (!(plane->crtc_id == drm->crtc->crtc_id && primary != 0)) {
136 ret = drmModeSetPlane(drm->fd, plane->plane_id, plane->crtc_id,
137 0, 0,
138 0, 0,
139 0, 0,
140 0, 0,
141 0, 0);
142 if (ret) {
143 LOG(WARNING, "Unable to disable plane: %m");
144 }
145 }
146 drmModeFreePlane(plane);
147 }
148 }
149}
150
151static bool drm_is_internal(unsigned type)
152{
153 unsigned t;
154 unsigned kInternalConnectors[] = {
155 DRM_MODE_CONNECTOR_LVDS,
156 DRM_MODE_CONNECTOR_eDP,
157 DRM_MODE_CONNECTOR_DSI,
158 };
159 for (t = 0; t < ARRAY_SIZE(kInternalConnectors); t++)
160 if (type == kInternalConnectors[t])
161 return true;
162 return false;
163}
164
165static drmModeConnector* find_first_connected_connector(drm_t* drm, bool internal, bool external)
166{
167 for (int i = 0; i < drm->resources->count_connectors; i++) {
168 drmModeConnector* connector;
169
170 connector = drmModeGetConnector(drm->fd, drm->resources->connectors[i]);
171 if (connector) {
172 bool is_internal = drm_is_internal(connector->connector_type);
173 if (!internal && is_internal)
174 continue;
175 if (!external && !is_internal)
176 continue;
177 if ((connector->count_modes > 0) &&
178 (connector->connection == DRM_MODE_CONNECTED))
179 return connector;
180
181 drmModeFreeConnector(connector);
182 }
183 }
184 return NULL;
185}
186
187static drmModeConnector* find_main_monitor(drm_t* drm, uint32_t* mode_index)
188{
189 int modes;
Dominik Behrd2cc4d22016-01-29 17:31:52 -0800190 int lid_state = input_check_lid_state();
Dominik Behr83010f82016-03-18 18:43:08 -0700191 drmModeConnector* main_monitor_connector = NULL;
192
193 /*
194 * Find the LVDS/eDP/DSI connectors. Those are the main screens.
195 */
Dominik Behrd2cc4d22016-01-29 17:31:52 -0800196 if (lid_state <= 0)
197 main_monitor_connector = find_first_connected_connector(drm, true, false);
Dominik Behr83010f82016-03-18 18:43:08 -0700198
199 /*
200 * Now try external connectors.
201 */
202 if (!main_monitor_connector)
203 main_monitor_connector =
204 find_first_connected_connector(drm, false, true);
205
206 /*
207 * If we still didn't find a connector, give up and return.
208 */
209 if (!main_monitor_connector)
210 return NULL;
211
212 *mode_index = 0;
213 for (modes = 0; modes < main_monitor_connector->count_modes; modes++) {
214 if (main_monitor_connector->modes[modes].type &
215 DRM_MODE_TYPE_PREFERRED) {
216 *mode_index = modes;
217 break;
218 }
219 }
220
221 return main_monitor_connector;
222}
223
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800224static void drm_clear_rmfb(drm_t* drm)
225{
226 if (drm->delayed_rmfb_fb_id) {
227 drmModeRmFB(drm->fd, drm->delayed_rmfb_fb_id);
228 drm->delayed_rmfb_fb_id = 0;
229 }
230}
231
Dominik Behr83010f82016-03-18 18:43:08 -0700232static void drm_fini(drm_t* drm)
233{
234 if (!drm)
235 return;
236
237 if (drm->fd >= 0) {
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800238 drm_clear_rmfb(drm);
239
Dominik Behr83010f82016-03-18 18:43:08 -0700240 if (drm->crtc) {
Dominik Behr83010f82016-03-18 18:43:08 -0700241 drmModeFreeCrtc(drm->crtc);
242 drm->crtc = NULL;
243 }
244
245 if (drm->main_monitor_connector) {
246 drmModeFreeConnector(drm->main_monitor_connector);
247 drm->main_monitor_connector = NULL;
248 }
249
250 if (drm->plane_resources) {
251 drmModeFreePlaneResources(drm->plane_resources);
252 drm->plane_resources = NULL;
253 }
254
255 if (drm->resources) {
256 drmModeFreeResources(drm->resources);
257 drm->resources = NULL;
258 }
259
260 drmClose(drm->fd);
261 drm->fd = -1;
262 }
263
264 free(drm);
265}
266
267static bool drm_equal(drm_t* l, drm_t* r)
268{
Dominik Behrd4d56272016-03-31 18:55:17 -0700269 if (!l && !r)
270 return true;
271 if ((!l && r) || (l && !r))
272 return false;
Dominik Behr83010f82016-03-18 18:43:08 -0700273 if (!l->crtc && r->crtc)
274 return false;
275 if (l->crtc && !r->crtc)
276 return false;
277 if (l->crtc && r->crtc)
278 if (l->crtc->crtc_id != r->crtc->crtc_id)
279 return false;
280
281 if (!l->main_monitor_connector && r->main_monitor_connector)
282 return false;
283 if (l->main_monitor_connector && !r->main_monitor_connector)
284 return false;
285 if (l->main_monitor_connector && r->main_monitor_connector)
286 if (l->main_monitor_connector->connector_id != r->main_monitor_connector->connector_id)
287 return false;
288 return true;
289}
290
291static int drm_score(drm_t* drm)
292{
293 drmVersionPtr version;
294 int score = 0;
295
296 if (!drm)
297 return -1000000000;
298
299 if (!drm->main_monitor_connector)
300 return -1000000000;
301
302 if (drm_is_internal(drm->main_monitor_connector->connector_type))
303 score++;
304
305 version = drmGetVersion(drm->fd);
306 if (version) {
Dominik Behrb1abcba2016-04-14 14:57:21 -0700307 /* We would rather use any driver besides UDL. */
Dominik Behr83010f82016-03-18 18:43:08 -0700308 if (strcmp("udl", version->name) == 0)
309 score--;
310 if (strcmp("evdi", version->name) == 0)
311 score--;
Dominik Behrb1abcba2016-04-14 14:57:21 -0700312 /* VGEM should be ignored because it has no displays, but lets make sure. */
Dominik Behr83010f82016-03-18 18:43:08 -0700313 if (strcmp("vgem", version->name) == 0)
314 score -= 1000000;
315 drmFreeVersion(version);
316 }
317 return score;
318}
319
Dominik Behrb1abcba2016-04-14 14:57:21 -0700320/*
321 * Scan and find best DRM object to display frecon on.
322 * This object should be created with DRM master, and we will keep master till
323 * first mode set or explicit drop master.
324 */
Dominik Behr83010f82016-03-18 18:43:08 -0700325drm_t* drm_scan(void)
326{
327 unsigned i;
328 char* dev_name;
329 int ret;
330 drm_t *best_drm = NULL;
331
332 for (i = 0; i < DRM_MAX_MINOR; i++) {
333 drm_t* drm = calloc(1, sizeof(drm_t));
334
335 if (!drm)
336 return NULL;
337
Dominik Behr815dc3d2016-04-19 16:59:39 -0700338try_open_again:
Dominik Behr83010f82016-03-18 18:43:08 -0700339 ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
340 if (ret < 0) {
341 drm_fini(drm);
342 continue;
343 }
Dominik Behr83010f82016-03-18 18:43:08 -0700344 drm->fd = open(dev_name, O_RDWR, 0);
345 free(dev_name);
346 if (drm->fd < 0) {
347 drm_fini(drm);
348 continue;
349 }
Dominik Behr815dc3d2016-04-19 16:59:39 -0700350 /* if we have master this should succeed */
351 ret = drmSetMaster(drm->fd);
352 if (ret != 0) {
353 drmClose(drm->fd);
354 drm->fd = -1;
355 usleep(100*1000);
356 goto try_open_again;
357 }
Dominik Behr83010f82016-03-18 18:43:08 -0700358
359 drm->resources = drmModeGetResources(drm->fd);
360 if (!drm->resources) {
361 drm_fini(drm);
362 continue;
363 }
364
Dominik Behrb1abcba2016-04-14 14:57:21 -0700365 /* Expect at least one crtc so we do not try to run on VGEM. */
Dominik Behr83010f82016-03-18 18:43:08 -0700366 if (drm->resources->count_crtcs == 0 || drm->resources->count_connectors == 0) {
367 drm_fini(drm);
368 continue;
369 }
370
371 drm->main_monitor_connector = find_main_monitor(drm, &drm->selected_mode);
372 if (!drm->main_monitor_connector) {
373 drm_fini(drm);
374 continue;
375 }
376
377 drm->crtc = find_crtc_for_connector(drm, drm->main_monitor_connector);
378 if (!drm->crtc) {
379 drm_fini(drm);
380 continue;
381 }
382
383 drm->crtc->mode = drm->main_monitor_connector->modes[drm->selected_mode];
384
385 drm->plane_resources = drmModeGetPlaneResources(drm->fd);
386 drm->refcount = 1;
387
388 if (drm_score(drm) > drm_score(best_drm)) {
389 drm_fini(best_drm);
390 best_drm = drm;
Dominik Behr45896022016-11-10 14:12:59 -0800391 } else {
392 drm_fini(drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700393 }
394 }
395
396 if (best_drm) {
397 drmVersionPtr version;
398 version = drmGetVersion(best_drm->fd);
399 if (version) {
400 LOG(INFO,
401 "Frecon using drm driver %s, version %d.%d, date(%s), desc(%s)",
402 version->name,
403 version->version_major,
404 version->version_minor,
405 version->date,
406 version->desc);
407 drmFreeVersion(version);
408 }
Dominik Behr83010f82016-03-18 18:43:08 -0700409 }
410
411 return best_drm;
412}
413
414void drm_set(drm_t* drm_)
415{
Dominik Behrda3c0102016-06-08 15:05:38 -0700416 if (g_drm) {
417 drm_delref(g_drm);
418 g_drm = NULL;
Dominik Behr83010f82016-03-18 18:43:08 -0700419 }
Dominik Behrda3c0102016-06-08 15:05:38 -0700420 g_drm = drm_;
Dominik Behr83010f82016-03-18 18:43:08 -0700421}
422
423void drm_close(void)
424{
Dominik Behrda3c0102016-06-08 15:05:38 -0700425 if (g_drm) {
426 drm_delref(g_drm);
427 g_drm = NULL;
Dominik Behr83010f82016-03-18 18:43:08 -0700428 }
429}
430
Dominik Behrb1abcba2016-04-14 14:57:21 -0700431void drm_delref(drm_t* drm)
Dominik Behr83010f82016-03-18 18:43:08 -0700432{
Dominik Behrd4d56272016-03-31 18:55:17 -0700433 if (!drm)
434 return;
Dominik Behr83010f82016-03-18 18:43:08 -0700435 if (drm->refcount) {
436 drm->refcount--;
437 } else {
438 LOG(ERROR, "Imbalanced drm_close()");
439 }
440 if (drm->refcount) {
441 return;
442 }
443
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800444 LOG(INFO, "Destroying drm device %p", drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700445 drm_fini(drm);
446}
447
448drm_t* drm_addref(void)
449{
Dominik Behrda3c0102016-06-08 15:05:38 -0700450 if (g_drm) {
451 g_drm->refcount++;
452 return g_drm;
Dominik Behr83010f82016-03-18 18:43:08 -0700453 }
454
455 return NULL;
456}
457
Dominik Behrda3c0102016-06-08 15:05:38 -0700458int drm_dropmaster(drm_t* drm)
Dominik Behrb1abcba2016-04-14 14:57:21 -0700459{
Dominik Behrda3c0102016-06-08 15:05:38 -0700460 int ret = 0;
461
462 if (!drm)
463 drm = g_drm;
464 if (drm)
465 ret = drmDropMaster(drm->fd);
466 return ret;
467}
468
469int drm_setmaster(drm_t* drm)
470{
471 int ret = 0;
472
473 if (!drm)
474 drm = g_drm;
475 if (drm)
476 ret = drmSetMaster(drm->fd);
477 return ret;
Dominik Behrb1abcba2016-04-14 14:57:21 -0700478}
479
Dominik Behr83010f82016-03-18 18:43:08 -0700480/*
Dominik Behrb1abcba2016-04-14 14:57:21 -0700481 * Returns true if connector/crtc/driver have changed and framebuffer object have to be re-created.
Dominik Behr83010f82016-03-18 18:43:08 -0700482 */
483bool drm_rescan(void)
484{
485 drm_t* ndrm;
486
Dominik Behrb1abcba2016-04-14 14:57:21 -0700487 /* In case we had master, drop master so the newly created object could have it. */
Dominik Behrda3c0102016-06-08 15:05:38 -0700488 drm_dropmaster(g_drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700489 ndrm = drm_scan();
490 if (ndrm) {
Dominik Behrda3c0102016-06-08 15:05:38 -0700491 if (drm_equal(ndrm, g_drm)) {
Dominik Behr83010f82016-03-18 18:43:08 -0700492 drm_fini(ndrm);
Dominik Behrda3c0102016-06-08 15:05:38 -0700493 /* Regain master we dropped. */
494 drm_setmaster(g_drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700495 } else {
Dominik Behrda3c0102016-06-08 15:05:38 -0700496 drm_delref(g_drm);
497 g_drm = ndrm;
Dominik Behr83010f82016-03-18 18:43:08 -0700498 return true;
499 }
500 } else {
Dominik Behrda3c0102016-06-08 15:05:38 -0700501 if (g_drm) {
502 drm_delref(g_drm); /* No usable monitor/drm object. */
503 g_drm = NULL;
Dominik Behr83010f82016-03-18 18:43:08 -0700504 return true;
505 }
506 }
507 return false;
508}
509
510bool drm_valid(drm_t* drm) {
511 return drm && drm->fd >= 0 && drm->resources && drm->main_monitor_connector && drm->crtc;
512}
513
514int32_t drm_setmode(drm_t* drm, uint32_t fb_id)
515{
516 int32_t ret;
Dominik Behr51a4da52016-07-28 14:18:48 -0700517
518 drm_disable_non_main_crtcs(drm);
519
Dominik Behr83010f82016-03-18 18:43:08 -0700520 ret = drmModeSetCrtc(drm->fd, drm->crtc->crtc_id,
521 fb_id,
522 0, 0, // x,y
523 &drm->main_monitor_connector->connector_id,
524 1, // connector_count
525 &drm->crtc->mode); // mode
526
527 if (ret) {
528 LOG(ERROR, "Unable to set crtc: %m");
Dominik Behr83010f82016-03-18 18:43:08 -0700529 return ret;
530 }
531
532 ret = drmModeSetCursor(drm->fd, drm->crtc->crtc_id,
533 0, 0, 0);
534
535 if (ret)
536 LOG(ERROR, "Unable to hide cursor");
537
538 drm_disable_non_primary_planes(drm);
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800539
540 drm_clear_rmfb(drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700541 return ret;
542}
543
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800544/*
545 * Delayed rmfb(). We want to keep fb at least till after next modeset
546 * so our transitions are cleaner (e.g. when recreating term after exitin
547 * shell). Also it keeps fb around till Chrome starts.
548 */
549void drm_rmfb(drm_t* drm, uint32_t fb_id)
550{
551 drm_clear_rmfb(drm);
552 drm->delayed_rmfb_fb_id = fb_id;
553}
554
Dominik Behr83010f82016-03-18 18:43:08 -0700555bool drm_read_edid(drm_t* drm)
556{
557 if (drm->edid_found) {
558 return true;
559 }
560
561 for (int i = 0; i < drm->main_monitor_connector->count_props; i++) {
562 drmModePropertyPtr prop;
563 drmModePropertyBlobPtr blob_ptr;
564 prop = drmModeGetProperty(drm->fd, drm->main_monitor_connector->props[i]);
565 if (prop) {
566 if (strcmp(prop->name, "EDID") == 0) {
567 blob_ptr = drmModeGetPropertyBlob(drm->fd,
568 drm->main_monitor_connector->prop_values[i]);
569 if (blob_ptr) {
570 memcpy(&drm->edid, blob_ptr->data, EDID_SIZE);
571 drmModeFreePropertyBlob(blob_ptr);
572 return (drm->edid_found = true);
573 }
574 }
575 }
576 }
577
578 return false;
579}
580
581uint32_t drm_gethres(drm_t* drm)
582{
583 return drm->crtc->mode.hdisplay;
584}
585
586uint32_t drm_getvres(drm_t* drm)
587{
588 return drm->crtc->mode.vdisplay;
589}