blob: 04532d976f0e1012b2014182c7bd4968ae10cddc [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
Daniele Castagna3ccfd382017-02-02 19:51:00 -050035static int32_t crtc_planes_num(drm_t* drm, int32_t crtc_index)
36{
37 drmModePlanePtr plane;
38 int32_t planes_num = 0;
39 drmModePlaneResPtr plane_resources = drmModeGetPlaneResources(drm->fd);
40 for (uint32_t p = 0; p < plane_resources->count_planes; p++) {
41 plane = drmModeGetPlane(drm->fd, plane_resources->planes[p]);
42
43 if (plane->possible_crtcs & (1 << crtc_index))
44 planes_num++;
45
46 drmModeFreePlane(plane);
47 }
48 drmModeFreePlaneResources(plane_resources);
49 return planes_num;
50}
51
Dominik Behr83010f82016-03-18 18:43:08 -070052static drmModeCrtc* find_crtc_for_connector(drm_t* drm, drmModeConnector* connector)
53{
54 int i, j;
55 drmModeEncoder* encoder;
56 int32_t crtc_id;
Daniele Castagna3ccfd382017-02-02 19:51:00 -050057 int32_t crtc_planes;
58 int32_t max_crtc_planes;
Dominik Behr83010f82016-03-18 18:43:08 -070059
60 if (connector->encoder_id)
61 encoder = drmModeGetEncoder(drm->fd, connector->encoder_id);
62 else
63 encoder = NULL;
64
65 if (encoder && encoder->crtc_id) {
66 crtc_id = encoder->crtc_id;
67 drmModeFreeEncoder(encoder);
68 return drmModeGetCrtc(drm->fd, crtc_id);
69 }
70
71 crtc_id = -1;
Daniele Castagna3ccfd382017-02-02 19:51:00 -050072 max_crtc_planes = -1;
Dominik Behr83010f82016-03-18 18:43:08 -070073 for (i = 0; i < connector->count_encoders; i++) {
74 encoder = drmModeGetEncoder(drm->fd, connector->encoders[i]);
75
76 if (encoder) {
77 for (j = 0; j < drm->resources->count_crtcs; j++) {
78 if (!(encoder->possible_crtcs & (1 << j)))
79 continue;
Daniele Castagna3ccfd382017-02-02 19:51:00 -050080
81 crtc_planes = crtc_planes_num(drm, j);
82 if (max_crtc_planes < crtc_planes) {
83 crtc_id = drm->resources->crtcs[j];
84 max_crtc_planes = crtc_planes;
85 }
Dominik Behr83010f82016-03-18 18:43:08 -070086 }
Daniele Castagna3ccfd382017-02-02 19:51:00 -050087 drmModeFreeEncoder(encoder);
88 if (crtc_id != -1)
Dominik Behr83010f82016-03-18 18:43:08 -070089 return drmModeGetCrtc(drm->fd, crtc_id);
Dominik Behr83010f82016-03-18 18:43:08 -070090 }
91 }
92
93 return NULL;
94}
95
96static void drm_disable_non_main_crtcs(drm_t* drm)
97{
98 int i;
99 drmModeCrtc* crtc;
100
101 for (i = 0; i < drm->resources->count_connectors; i++) {
102 drmModeConnector* connector;
103
104 connector = drmModeGetConnector(drm->fd, drm->resources->connectors[i]);
105 crtc = find_crtc_for_connector(drm, connector);
106 if (crtc->crtc_id != drm->crtc->crtc_id)
107 drm_disable_crtc(drm, crtc);
108 drmModeFreeCrtc(crtc);
109 }
110}
111
112static int drm_is_primary_plane(drm_t* drm, uint32_t plane_id)
113{
114 uint32_t p;
115 bool found = false;
116 int ret = -1;
117
118 drmModeObjectPropertiesPtr props;
119 props = drmModeObjectGetProperties(drm->fd,
120 plane_id,
121 DRM_MODE_OBJECT_PLANE);
122 if (!props) {
123 LOG(ERROR, "Unable to get plane properties: %m");
124 return -1;
125 }
126
127 for (p = 0; p < props->count_props && !found; p++) {
128 drmModePropertyPtr prop;
129 prop = drmModeGetProperty(drm->fd, props->props[p]);
130 if (prop) {
131 if (strcmp("type", prop->name) == 0) {
132 found = true;
133 ret = (props->prop_values[p] == DRM_PLANE_TYPE_PRIMARY);
134 }
135 drmModeFreeProperty(prop);
136 }
137 }
138
139 drmModeFreeObjectProperties(props);
140
141 return ret;
142}
143
Dominik Behrb1abcba2016-04-14 14:57:21 -0700144/* Disable all planes except for primary on crtc we use. */
Dominik Behr83010f82016-03-18 18:43:08 -0700145static void drm_disable_non_primary_planes(drm_t* drm)
146{
147 int ret;
148
149 if (!drm->plane_resources)
150 return;
151
152 for (uint32_t p = 0; p < drm->plane_resources->count_planes; p++) {
153 drmModePlanePtr plane;
154 plane = drmModeGetPlane(drm->fd,
155 drm->plane_resources->planes[p]);
156 if (plane) {
157 int primary = drm_is_primary_plane(drm, plane->plane_id);
158 if (!(plane->crtc_id == drm->crtc->crtc_id && primary != 0)) {
159 ret = drmModeSetPlane(drm->fd, plane->plane_id, plane->crtc_id,
160 0, 0,
161 0, 0,
162 0, 0,
163 0, 0,
164 0, 0);
165 if (ret) {
166 LOG(WARNING, "Unable to disable plane: %m");
167 }
168 }
169 drmModeFreePlane(plane);
170 }
171 }
172}
173
174static bool drm_is_internal(unsigned type)
175{
176 unsigned t;
177 unsigned kInternalConnectors[] = {
178 DRM_MODE_CONNECTOR_LVDS,
179 DRM_MODE_CONNECTOR_eDP,
180 DRM_MODE_CONNECTOR_DSI,
181 };
182 for (t = 0; t < ARRAY_SIZE(kInternalConnectors); t++)
183 if (type == kInternalConnectors[t])
184 return true;
185 return false;
186}
187
188static drmModeConnector* find_first_connected_connector(drm_t* drm, bool internal, bool external)
189{
190 for (int i = 0; i < drm->resources->count_connectors; i++) {
191 drmModeConnector* connector;
192
193 connector = drmModeGetConnector(drm->fd, drm->resources->connectors[i]);
194 if (connector) {
195 bool is_internal = drm_is_internal(connector->connector_type);
196 if (!internal && is_internal)
197 continue;
198 if (!external && !is_internal)
199 continue;
200 if ((connector->count_modes > 0) &&
201 (connector->connection == DRM_MODE_CONNECTED))
202 return connector;
203
204 drmModeFreeConnector(connector);
205 }
206 }
207 return NULL;
208}
209
210static drmModeConnector* find_main_monitor(drm_t* drm, uint32_t* mode_index)
211{
212 int modes;
Dominik Behrd2cc4d22016-01-29 17:31:52 -0800213 int lid_state = input_check_lid_state();
Dominik Behr83010f82016-03-18 18:43:08 -0700214 drmModeConnector* main_monitor_connector = NULL;
215
216 /*
217 * Find the LVDS/eDP/DSI connectors. Those are the main screens.
218 */
Dominik Behrd2cc4d22016-01-29 17:31:52 -0800219 if (lid_state <= 0)
220 main_monitor_connector = find_first_connected_connector(drm, true, false);
Dominik Behr83010f82016-03-18 18:43:08 -0700221
222 /*
223 * Now try external connectors.
224 */
225 if (!main_monitor_connector)
226 main_monitor_connector =
227 find_first_connected_connector(drm, false, true);
228
229 /*
230 * If we still didn't find a connector, give up and return.
231 */
232 if (!main_monitor_connector)
233 return NULL;
234
235 *mode_index = 0;
236 for (modes = 0; modes < main_monitor_connector->count_modes; modes++) {
237 if (main_monitor_connector->modes[modes].type &
238 DRM_MODE_TYPE_PREFERRED) {
239 *mode_index = modes;
240 break;
241 }
242 }
243
244 return main_monitor_connector;
245}
246
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800247static void drm_clear_rmfb(drm_t* drm)
248{
249 if (drm->delayed_rmfb_fb_id) {
250 drmModeRmFB(drm->fd, drm->delayed_rmfb_fb_id);
251 drm->delayed_rmfb_fb_id = 0;
252 }
253}
254
Dominik Behr83010f82016-03-18 18:43:08 -0700255static void drm_fini(drm_t* drm)
256{
257 if (!drm)
258 return;
259
260 if (drm->fd >= 0) {
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800261 drm_clear_rmfb(drm);
262
Dominik Behr83010f82016-03-18 18:43:08 -0700263 if (drm->crtc) {
Dominik Behr83010f82016-03-18 18:43:08 -0700264 drmModeFreeCrtc(drm->crtc);
265 drm->crtc = NULL;
266 }
267
268 if (drm->main_monitor_connector) {
269 drmModeFreeConnector(drm->main_monitor_connector);
270 drm->main_monitor_connector = NULL;
271 }
272
273 if (drm->plane_resources) {
274 drmModeFreePlaneResources(drm->plane_resources);
275 drm->plane_resources = NULL;
276 }
277
278 if (drm->resources) {
279 drmModeFreeResources(drm->resources);
280 drm->resources = NULL;
281 }
282
283 drmClose(drm->fd);
284 drm->fd = -1;
285 }
286
287 free(drm);
288}
289
290static bool drm_equal(drm_t* l, drm_t* r)
291{
Dominik Behrd4d56272016-03-31 18:55:17 -0700292 if (!l && !r)
293 return true;
294 if ((!l && r) || (l && !r))
295 return false;
Dominik Behr83010f82016-03-18 18:43:08 -0700296 if (!l->crtc && r->crtc)
297 return false;
298 if (l->crtc && !r->crtc)
299 return false;
300 if (l->crtc && r->crtc)
301 if (l->crtc->crtc_id != r->crtc->crtc_id)
302 return false;
303
304 if (!l->main_monitor_connector && r->main_monitor_connector)
305 return false;
306 if (l->main_monitor_connector && !r->main_monitor_connector)
307 return false;
308 if (l->main_monitor_connector && r->main_monitor_connector)
309 if (l->main_monitor_connector->connector_id != r->main_monitor_connector->connector_id)
310 return false;
311 return true;
312}
313
314static int drm_score(drm_t* drm)
315{
316 drmVersionPtr version;
317 int score = 0;
318
319 if (!drm)
320 return -1000000000;
321
322 if (!drm->main_monitor_connector)
323 return -1000000000;
324
325 if (drm_is_internal(drm->main_monitor_connector->connector_type))
326 score++;
327
328 version = drmGetVersion(drm->fd);
329 if (version) {
Dominik Behrb1abcba2016-04-14 14:57:21 -0700330 /* We would rather use any driver besides UDL. */
Dominik Behr83010f82016-03-18 18:43:08 -0700331 if (strcmp("udl", version->name) == 0)
332 score--;
333 if (strcmp("evdi", version->name) == 0)
334 score--;
Dominik Behrb1abcba2016-04-14 14:57:21 -0700335 /* VGEM should be ignored because it has no displays, but lets make sure. */
Dominik Behr83010f82016-03-18 18:43:08 -0700336 if (strcmp("vgem", version->name) == 0)
337 score -= 1000000;
338 drmFreeVersion(version);
339 }
340 return score;
341}
342
Dominik Behrb1abcba2016-04-14 14:57:21 -0700343/*
344 * Scan and find best DRM object to display frecon on.
345 * This object should be created with DRM master, and we will keep master till
346 * first mode set or explicit drop master.
347 */
Dominik Behr83010f82016-03-18 18:43:08 -0700348drm_t* drm_scan(void)
349{
350 unsigned i;
351 char* dev_name;
352 int ret;
353 drm_t *best_drm = NULL;
354
355 for (i = 0; i < DRM_MAX_MINOR; i++) {
356 drm_t* drm = calloc(1, sizeof(drm_t));
357
358 if (!drm)
359 return NULL;
360
Dominik Behr815dc3d2016-04-19 16:59:39 -0700361try_open_again:
Dominik Behr83010f82016-03-18 18:43:08 -0700362 ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
363 if (ret < 0) {
364 drm_fini(drm);
365 continue;
366 }
Dominik Behr83010f82016-03-18 18:43:08 -0700367 drm->fd = open(dev_name, O_RDWR, 0);
368 free(dev_name);
369 if (drm->fd < 0) {
370 drm_fini(drm);
371 continue;
372 }
Dominik Behr815dc3d2016-04-19 16:59:39 -0700373 /* if we have master this should succeed */
374 ret = drmSetMaster(drm->fd);
375 if (ret != 0) {
376 drmClose(drm->fd);
377 drm->fd = -1;
378 usleep(100*1000);
379 goto try_open_again;
380 }
Dominik Behr83010f82016-03-18 18:43:08 -0700381
382 drm->resources = drmModeGetResources(drm->fd);
383 if (!drm->resources) {
384 drm_fini(drm);
385 continue;
386 }
387
Dominik Behrb1abcba2016-04-14 14:57:21 -0700388 /* Expect at least one crtc so we do not try to run on VGEM. */
Dominik Behr83010f82016-03-18 18:43:08 -0700389 if (drm->resources->count_crtcs == 0 || drm->resources->count_connectors == 0) {
390 drm_fini(drm);
391 continue;
392 }
393
394 drm->main_monitor_connector = find_main_monitor(drm, &drm->selected_mode);
395 if (!drm->main_monitor_connector) {
396 drm_fini(drm);
397 continue;
398 }
399
400 drm->crtc = find_crtc_for_connector(drm, drm->main_monitor_connector);
401 if (!drm->crtc) {
402 drm_fini(drm);
403 continue;
404 }
405
406 drm->crtc->mode = drm->main_monitor_connector->modes[drm->selected_mode];
407
408 drm->plane_resources = drmModeGetPlaneResources(drm->fd);
409 drm->refcount = 1;
410
411 if (drm_score(drm) > drm_score(best_drm)) {
412 drm_fini(best_drm);
413 best_drm = drm;
Dominik Behr45896022016-11-10 14:12:59 -0800414 } else {
415 drm_fini(drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700416 }
417 }
418
419 if (best_drm) {
420 drmVersionPtr version;
421 version = drmGetVersion(best_drm->fd);
422 if (version) {
423 LOG(INFO,
424 "Frecon using drm driver %s, version %d.%d, date(%s), desc(%s)",
425 version->name,
426 version->version_major,
427 version->version_minor,
428 version->date,
429 version->desc);
430 drmFreeVersion(version);
431 }
Dominik Behr83010f82016-03-18 18:43:08 -0700432 }
433
434 return best_drm;
435}
436
437void drm_set(drm_t* drm_)
438{
Dominik Behrda3c0102016-06-08 15:05:38 -0700439 if (g_drm) {
440 drm_delref(g_drm);
441 g_drm = NULL;
Dominik Behr83010f82016-03-18 18:43:08 -0700442 }
Dominik Behrda3c0102016-06-08 15:05:38 -0700443 g_drm = drm_;
Dominik Behr83010f82016-03-18 18:43:08 -0700444}
445
446void drm_close(void)
447{
Dominik Behrda3c0102016-06-08 15:05:38 -0700448 if (g_drm) {
449 drm_delref(g_drm);
450 g_drm = NULL;
Dominik Behr83010f82016-03-18 18:43:08 -0700451 }
452}
453
Dominik Behrb1abcba2016-04-14 14:57:21 -0700454void drm_delref(drm_t* drm)
Dominik Behr83010f82016-03-18 18:43:08 -0700455{
Dominik Behrd4d56272016-03-31 18:55:17 -0700456 if (!drm)
457 return;
Dominik Behr83010f82016-03-18 18:43:08 -0700458 if (drm->refcount) {
459 drm->refcount--;
460 } else {
461 LOG(ERROR, "Imbalanced drm_close()");
462 }
463 if (drm->refcount) {
464 return;
465 }
466
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800467 LOG(INFO, "Destroying drm device %p", drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700468 drm_fini(drm);
469}
470
471drm_t* drm_addref(void)
472{
Dominik Behrda3c0102016-06-08 15:05:38 -0700473 if (g_drm) {
474 g_drm->refcount++;
475 return g_drm;
Dominik Behr83010f82016-03-18 18:43:08 -0700476 }
477
478 return NULL;
479}
480
Dominik Behrda3c0102016-06-08 15:05:38 -0700481int drm_dropmaster(drm_t* drm)
Dominik Behrb1abcba2016-04-14 14:57:21 -0700482{
Dominik Behrda3c0102016-06-08 15:05:38 -0700483 int ret = 0;
484
485 if (!drm)
486 drm = g_drm;
487 if (drm)
488 ret = drmDropMaster(drm->fd);
489 return ret;
490}
491
492int drm_setmaster(drm_t* drm)
493{
494 int ret = 0;
495
496 if (!drm)
497 drm = g_drm;
498 if (drm)
499 ret = drmSetMaster(drm->fd);
500 return ret;
Dominik Behrb1abcba2016-04-14 14:57:21 -0700501}
502
Dominik Behr83010f82016-03-18 18:43:08 -0700503/*
Dominik Behrb1abcba2016-04-14 14:57:21 -0700504 * Returns true if connector/crtc/driver have changed and framebuffer object have to be re-created.
Dominik Behr83010f82016-03-18 18:43:08 -0700505 */
506bool drm_rescan(void)
507{
508 drm_t* ndrm;
509
Dominik Behrb1abcba2016-04-14 14:57:21 -0700510 /* In case we had master, drop master so the newly created object could have it. */
Dominik Behrda3c0102016-06-08 15:05:38 -0700511 drm_dropmaster(g_drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700512 ndrm = drm_scan();
513 if (ndrm) {
Dominik Behrda3c0102016-06-08 15:05:38 -0700514 if (drm_equal(ndrm, g_drm)) {
Dominik Behr83010f82016-03-18 18:43:08 -0700515 drm_fini(ndrm);
Dominik Behrda3c0102016-06-08 15:05:38 -0700516 /* Regain master we dropped. */
517 drm_setmaster(g_drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700518 } else {
Dominik Behrda3c0102016-06-08 15:05:38 -0700519 drm_delref(g_drm);
520 g_drm = ndrm;
Dominik Behr83010f82016-03-18 18:43:08 -0700521 return true;
522 }
523 } else {
Dominik Behrda3c0102016-06-08 15:05:38 -0700524 if (g_drm) {
525 drm_delref(g_drm); /* No usable monitor/drm object. */
526 g_drm = NULL;
Dominik Behr83010f82016-03-18 18:43:08 -0700527 return true;
528 }
529 }
530 return false;
531}
532
533bool drm_valid(drm_t* drm) {
534 return drm && drm->fd >= 0 && drm->resources && drm->main_monitor_connector && drm->crtc;
535}
536
537int32_t drm_setmode(drm_t* drm, uint32_t fb_id)
538{
539 int32_t ret;
Dominik Behr51a4da52016-07-28 14:18:48 -0700540
541 drm_disable_non_main_crtcs(drm);
542
Dominik Behr83010f82016-03-18 18:43:08 -0700543 ret = drmModeSetCrtc(drm->fd, drm->crtc->crtc_id,
544 fb_id,
545 0, 0, // x,y
546 &drm->main_monitor_connector->connector_id,
547 1, // connector_count
548 &drm->crtc->mode); // mode
549
550 if (ret) {
551 LOG(ERROR, "Unable to set crtc: %m");
Dominik Behr83010f82016-03-18 18:43:08 -0700552 return ret;
553 }
554
555 ret = drmModeSetCursor(drm->fd, drm->crtc->crtc_id,
556 0, 0, 0);
557
558 if (ret)
559 LOG(ERROR, "Unable to hide cursor");
560
561 drm_disable_non_primary_planes(drm);
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800562
563 drm_clear_rmfb(drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700564 return ret;
565}
566
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800567/*
568 * Delayed rmfb(). We want to keep fb at least till after next modeset
569 * so our transitions are cleaner (e.g. when recreating term after exitin
570 * shell). Also it keeps fb around till Chrome starts.
571 */
572void drm_rmfb(drm_t* drm, uint32_t fb_id)
573{
574 drm_clear_rmfb(drm);
575 drm->delayed_rmfb_fb_id = fb_id;
576}
577
Dominik Behr83010f82016-03-18 18:43:08 -0700578bool drm_read_edid(drm_t* drm)
579{
580 if (drm->edid_found) {
581 return true;
582 }
583
584 for (int i = 0; i < drm->main_monitor_connector->count_props; i++) {
585 drmModePropertyPtr prop;
586 drmModePropertyBlobPtr blob_ptr;
587 prop = drmModeGetProperty(drm->fd, drm->main_monitor_connector->props[i]);
588 if (prop) {
589 if (strcmp(prop->name, "EDID") == 0) {
590 blob_ptr = drmModeGetPropertyBlob(drm->fd,
591 drm->main_monitor_connector->prop_values[i]);
592 if (blob_ptr) {
593 memcpy(&drm->edid, blob_ptr->data, EDID_SIZE);
594 drmModeFreePropertyBlob(blob_ptr);
595 return (drm->edid_found = true);
596 }
597 }
598 }
599 }
600
601 return false;
602}
603
604uint32_t drm_gethres(drm_t* drm)
605{
606 return drm->crtc->mode.hdisplay;
607}
608
609uint32_t drm_getvres(drm_t* drm)
610{
611 return drm->crtc->mode.vdisplay;
612}