blob: b442e86c150581efa25af1e8b43537a09a9243a2 [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]);
ravirajab837102018-07-03 13:25:29 +0530105 if (connector) {
106 crtc = find_crtc_for_connector(drm, connector);
107 if (crtc->crtc_id != drm->crtc->crtc_id)
108 drm_disable_crtc(drm, crtc);
109 drmModeFreeCrtc(crtc);
110 }
Dominik Behr83010f82016-03-18 18:43:08 -0700111 }
112}
113
114static int drm_is_primary_plane(drm_t* drm, uint32_t plane_id)
115{
116 uint32_t p;
117 bool found = false;
118 int ret = -1;
119
120 drmModeObjectPropertiesPtr props;
121 props = drmModeObjectGetProperties(drm->fd,
122 plane_id,
123 DRM_MODE_OBJECT_PLANE);
124 if (!props) {
125 LOG(ERROR, "Unable to get plane properties: %m");
126 return -1;
127 }
128
129 for (p = 0; p < props->count_props && !found; p++) {
130 drmModePropertyPtr prop;
131 prop = drmModeGetProperty(drm->fd, props->props[p]);
132 if (prop) {
133 if (strcmp("type", prop->name) == 0) {
134 found = true;
135 ret = (props->prop_values[p] == DRM_PLANE_TYPE_PRIMARY);
136 }
137 drmModeFreeProperty(prop);
138 }
139 }
140
141 drmModeFreeObjectProperties(props);
142
143 return ret;
144}
145
Dominik Behrb1abcba2016-04-14 14:57:21 -0700146/* Disable all planes except for primary on crtc we use. */
Dominik Behr83010f82016-03-18 18:43:08 -0700147static void drm_disable_non_primary_planes(drm_t* drm)
148{
149 int ret;
150
151 if (!drm->plane_resources)
152 return;
153
154 for (uint32_t p = 0; p < drm->plane_resources->count_planes; p++) {
155 drmModePlanePtr plane;
156 plane = drmModeGetPlane(drm->fd,
157 drm->plane_resources->planes[p]);
158 if (plane) {
159 int primary = drm_is_primary_plane(drm, plane->plane_id);
160 if (!(plane->crtc_id == drm->crtc->crtc_id && primary != 0)) {
161 ret = drmModeSetPlane(drm->fd, plane->plane_id, plane->crtc_id,
162 0, 0,
163 0, 0,
164 0, 0,
165 0, 0,
166 0, 0);
167 if (ret) {
168 LOG(WARNING, "Unable to disable plane: %m");
169 }
170 }
171 drmModeFreePlane(plane);
172 }
173 }
174}
175
176static bool drm_is_internal(unsigned type)
177{
178 unsigned t;
179 unsigned kInternalConnectors[] = {
180 DRM_MODE_CONNECTOR_LVDS,
181 DRM_MODE_CONNECTOR_eDP,
182 DRM_MODE_CONNECTOR_DSI,
183 };
184 for (t = 0; t < ARRAY_SIZE(kInternalConnectors); t++)
185 if (type == kInternalConnectors[t])
186 return true;
187 return false;
188}
189
190static drmModeConnector* find_first_connected_connector(drm_t* drm, bool internal, bool external)
191{
192 for (int i = 0; i < drm->resources->count_connectors; i++) {
193 drmModeConnector* connector;
194
195 connector = drmModeGetConnector(drm->fd, drm->resources->connectors[i]);
196 if (connector) {
197 bool is_internal = drm_is_internal(connector->connector_type);
198 if (!internal && is_internal)
199 continue;
200 if (!external && !is_internal)
201 continue;
202 if ((connector->count_modes > 0) &&
203 (connector->connection == DRM_MODE_CONNECTED))
204 return connector;
205
206 drmModeFreeConnector(connector);
207 }
208 }
209 return NULL;
210}
211
212static drmModeConnector* find_main_monitor(drm_t* drm, uint32_t* mode_index)
213{
214 int modes;
Dominik Behrd2cc4d22016-01-29 17:31:52 -0800215 int lid_state = input_check_lid_state();
Dominik Behr83010f82016-03-18 18:43:08 -0700216 drmModeConnector* main_monitor_connector = NULL;
217
218 /*
219 * Find the LVDS/eDP/DSI connectors. Those are the main screens.
220 */
Dominik Behrd2cc4d22016-01-29 17:31:52 -0800221 if (lid_state <= 0)
222 main_monitor_connector = find_first_connected_connector(drm, true, false);
Dominik Behr83010f82016-03-18 18:43:08 -0700223
224 /*
225 * Now try external connectors.
226 */
227 if (!main_monitor_connector)
228 main_monitor_connector =
229 find_first_connected_connector(drm, false, true);
230
231 /*
232 * If we still didn't find a connector, give up and return.
233 */
234 if (!main_monitor_connector)
235 return NULL;
236
237 *mode_index = 0;
238 for (modes = 0; modes < main_monitor_connector->count_modes; modes++) {
239 if (main_monitor_connector->modes[modes].type &
240 DRM_MODE_TYPE_PREFERRED) {
241 *mode_index = modes;
242 break;
243 }
244 }
245
246 return main_monitor_connector;
247}
248
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800249static void drm_clear_rmfb(drm_t* drm)
250{
251 if (drm->delayed_rmfb_fb_id) {
252 drmModeRmFB(drm->fd, drm->delayed_rmfb_fb_id);
253 drm->delayed_rmfb_fb_id = 0;
254 }
255}
256
Dominik Behr83010f82016-03-18 18:43:08 -0700257static void drm_fini(drm_t* drm)
258{
259 if (!drm)
260 return;
261
262 if (drm->fd >= 0) {
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800263 drm_clear_rmfb(drm);
264
Dominik Behr83010f82016-03-18 18:43:08 -0700265 if (drm->crtc) {
Dominik Behr83010f82016-03-18 18:43:08 -0700266 drmModeFreeCrtc(drm->crtc);
267 drm->crtc = NULL;
268 }
269
270 if (drm->main_monitor_connector) {
271 drmModeFreeConnector(drm->main_monitor_connector);
272 drm->main_monitor_connector = NULL;
273 }
274
275 if (drm->plane_resources) {
276 drmModeFreePlaneResources(drm->plane_resources);
277 drm->plane_resources = NULL;
278 }
279
280 if (drm->resources) {
281 drmModeFreeResources(drm->resources);
282 drm->resources = NULL;
283 }
284
285 drmClose(drm->fd);
286 drm->fd = -1;
287 }
288
289 free(drm);
290}
291
292static bool drm_equal(drm_t* l, drm_t* r)
293{
Dominik Behrd4d56272016-03-31 18:55:17 -0700294 if (!l && !r)
295 return true;
296 if ((!l && r) || (l && !r))
297 return false;
Dominik Behr83010f82016-03-18 18:43:08 -0700298 if (!l->crtc && r->crtc)
299 return false;
300 if (l->crtc && !r->crtc)
301 return false;
302 if (l->crtc && r->crtc)
303 if (l->crtc->crtc_id != r->crtc->crtc_id)
304 return false;
305
306 if (!l->main_monitor_connector && r->main_monitor_connector)
307 return false;
308 if (l->main_monitor_connector && !r->main_monitor_connector)
309 return false;
310 if (l->main_monitor_connector && r->main_monitor_connector)
311 if (l->main_monitor_connector->connector_id != r->main_monitor_connector->connector_id)
312 return false;
313 return true;
314}
315
316static int drm_score(drm_t* drm)
317{
318 drmVersionPtr version;
319 int score = 0;
320
321 if (!drm)
322 return -1000000000;
323
324 if (!drm->main_monitor_connector)
325 return -1000000000;
326
327 if (drm_is_internal(drm->main_monitor_connector->connector_type))
328 score++;
329
330 version = drmGetVersion(drm->fd);
331 if (version) {
Dominik Behrb1abcba2016-04-14 14:57:21 -0700332 /* We would rather use any driver besides UDL. */
Dominik Behr83010f82016-03-18 18:43:08 -0700333 if (strcmp("udl", version->name) == 0)
334 score--;
335 if (strcmp("evdi", version->name) == 0)
336 score--;
Dominik Behrb1abcba2016-04-14 14:57:21 -0700337 /* VGEM should be ignored because it has no displays, but lets make sure. */
Dominik Behr83010f82016-03-18 18:43:08 -0700338 if (strcmp("vgem", version->name) == 0)
339 score -= 1000000;
340 drmFreeVersion(version);
341 }
342 return score;
343}
344
Dominik Behrb1abcba2016-04-14 14:57:21 -0700345/*
346 * Scan and find best DRM object to display frecon on.
347 * This object should be created with DRM master, and we will keep master till
348 * first mode set or explicit drop master.
349 */
Dominik Behr83010f82016-03-18 18:43:08 -0700350drm_t* drm_scan(void)
351{
352 unsigned i;
353 char* dev_name;
354 int ret;
355 drm_t *best_drm = NULL;
356
357 for (i = 0; i < DRM_MAX_MINOR; i++) {
358 drm_t* drm = calloc(1, sizeof(drm_t));
359
360 if (!drm)
361 return NULL;
362
Dominik Behr815dc3d2016-04-19 16:59:39 -0700363try_open_again:
Dominik Behr83010f82016-03-18 18:43:08 -0700364 ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
365 if (ret < 0) {
366 drm_fini(drm);
367 continue;
368 }
Dominik Behr83010f82016-03-18 18:43:08 -0700369 drm->fd = open(dev_name, O_RDWR, 0);
370 free(dev_name);
371 if (drm->fd < 0) {
372 drm_fini(drm);
373 continue;
374 }
Dominik Behr815dc3d2016-04-19 16:59:39 -0700375 /* if we have master this should succeed */
376 ret = drmSetMaster(drm->fd);
377 if (ret != 0) {
378 drmClose(drm->fd);
379 drm->fd = -1;
380 usleep(100*1000);
381 goto try_open_again;
382 }
Dominik Behr83010f82016-03-18 18:43:08 -0700383
384 drm->resources = drmModeGetResources(drm->fd);
385 if (!drm->resources) {
386 drm_fini(drm);
387 continue;
388 }
389
Dominik Behrb1abcba2016-04-14 14:57:21 -0700390 /* Expect at least one crtc so we do not try to run on VGEM. */
Dominik Behr83010f82016-03-18 18:43:08 -0700391 if (drm->resources->count_crtcs == 0 || drm->resources->count_connectors == 0) {
392 drm_fini(drm);
393 continue;
394 }
395
396 drm->main_monitor_connector = find_main_monitor(drm, &drm->selected_mode);
397 if (!drm->main_monitor_connector) {
398 drm_fini(drm);
399 continue;
400 }
401
402 drm->crtc = find_crtc_for_connector(drm, drm->main_monitor_connector);
403 if (!drm->crtc) {
404 drm_fini(drm);
405 continue;
406 }
407
408 drm->crtc->mode = drm->main_monitor_connector->modes[drm->selected_mode];
409
410 drm->plane_resources = drmModeGetPlaneResources(drm->fd);
411 drm->refcount = 1;
412
413 if (drm_score(drm) > drm_score(best_drm)) {
414 drm_fini(best_drm);
415 best_drm = drm;
Dominik Behr45896022016-11-10 14:12:59 -0800416 } else {
417 drm_fini(drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700418 }
419 }
420
421 if (best_drm) {
422 drmVersionPtr version;
423 version = drmGetVersion(best_drm->fd);
424 if (version) {
425 LOG(INFO,
426 "Frecon using drm driver %s, version %d.%d, date(%s), desc(%s)",
427 version->name,
428 version->version_major,
429 version->version_minor,
430 version->date,
431 version->desc);
432 drmFreeVersion(version);
433 }
Dominik Behr83010f82016-03-18 18:43:08 -0700434 }
435
436 return best_drm;
437}
438
439void drm_set(drm_t* drm_)
440{
Dominik Behrda3c0102016-06-08 15:05:38 -0700441 if (g_drm) {
442 drm_delref(g_drm);
443 g_drm = NULL;
Dominik Behr83010f82016-03-18 18:43:08 -0700444 }
Dominik Behrda3c0102016-06-08 15:05:38 -0700445 g_drm = drm_;
Dominik Behr83010f82016-03-18 18:43:08 -0700446}
447
448void drm_close(void)
449{
Dominik Behrda3c0102016-06-08 15:05:38 -0700450 if (g_drm) {
451 drm_delref(g_drm);
452 g_drm = NULL;
Dominik Behr83010f82016-03-18 18:43:08 -0700453 }
454}
455
Dominik Behrb1abcba2016-04-14 14:57:21 -0700456void drm_delref(drm_t* drm)
Dominik Behr83010f82016-03-18 18:43:08 -0700457{
Dominik Behrd4d56272016-03-31 18:55:17 -0700458 if (!drm)
459 return;
Dominik Behr83010f82016-03-18 18:43:08 -0700460 if (drm->refcount) {
461 drm->refcount--;
462 } else {
463 LOG(ERROR, "Imbalanced drm_close()");
464 }
465 if (drm->refcount) {
466 return;
467 }
468
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800469 LOG(INFO, "Destroying drm device %p", drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700470 drm_fini(drm);
471}
472
473drm_t* drm_addref(void)
474{
Dominik Behrda3c0102016-06-08 15:05:38 -0700475 if (g_drm) {
476 g_drm->refcount++;
477 return g_drm;
Dominik Behr83010f82016-03-18 18:43:08 -0700478 }
479
480 return NULL;
481}
482
Dominik Behrda3c0102016-06-08 15:05:38 -0700483int drm_dropmaster(drm_t* drm)
Dominik Behrb1abcba2016-04-14 14:57:21 -0700484{
Dominik Behrda3c0102016-06-08 15:05:38 -0700485 int ret = 0;
486
487 if (!drm)
488 drm = g_drm;
489 if (drm)
490 ret = drmDropMaster(drm->fd);
491 return ret;
492}
493
494int drm_setmaster(drm_t* drm)
495{
496 int ret = 0;
497
498 if (!drm)
499 drm = g_drm;
500 if (drm)
501 ret = drmSetMaster(drm->fd);
502 return ret;
Dominik Behrb1abcba2016-04-14 14:57:21 -0700503}
504
Dominik Behr83010f82016-03-18 18:43:08 -0700505/*
Dominik Behrb1abcba2016-04-14 14:57:21 -0700506 * Returns true if connector/crtc/driver have changed and framebuffer object have to be re-created.
Dominik Behr83010f82016-03-18 18:43:08 -0700507 */
508bool drm_rescan(void)
509{
510 drm_t* ndrm;
511
Dominik Behrb1abcba2016-04-14 14:57:21 -0700512 /* In case we had master, drop master so the newly created object could have it. */
Dominik Behrda3c0102016-06-08 15:05:38 -0700513 drm_dropmaster(g_drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700514 ndrm = drm_scan();
515 if (ndrm) {
Dominik Behrda3c0102016-06-08 15:05:38 -0700516 if (drm_equal(ndrm, g_drm)) {
Dominik Behr83010f82016-03-18 18:43:08 -0700517 drm_fini(ndrm);
Dominik Behrda3c0102016-06-08 15:05:38 -0700518 /* Regain master we dropped. */
519 drm_setmaster(g_drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700520 } else {
Dominik Behrda3c0102016-06-08 15:05:38 -0700521 drm_delref(g_drm);
522 g_drm = ndrm;
Dominik Behr83010f82016-03-18 18:43:08 -0700523 return true;
524 }
525 } else {
Dominik Behrda3c0102016-06-08 15:05:38 -0700526 if (g_drm) {
527 drm_delref(g_drm); /* No usable monitor/drm object. */
528 g_drm = NULL;
Dominik Behr83010f82016-03-18 18:43:08 -0700529 return true;
530 }
531 }
532 return false;
533}
534
535bool drm_valid(drm_t* drm) {
536 return drm && drm->fd >= 0 && drm->resources && drm->main_monitor_connector && drm->crtc;
537}
538
539int32_t drm_setmode(drm_t* drm, uint32_t fb_id)
540{
541 int32_t ret;
Dominik Behr51a4da52016-07-28 14:18:48 -0700542
543 drm_disable_non_main_crtcs(drm);
544
Dominik Behr83010f82016-03-18 18:43:08 -0700545 ret = drmModeSetCrtc(drm->fd, drm->crtc->crtc_id,
546 fb_id,
547 0, 0, // x,y
548 &drm->main_monitor_connector->connector_id,
549 1, // connector_count
550 &drm->crtc->mode); // mode
551
552 if (ret) {
553 LOG(ERROR, "Unable to set crtc: %m");
Dominik Behr83010f82016-03-18 18:43:08 -0700554 return ret;
555 }
556
557 ret = drmModeSetCursor(drm->fd, drm->crtc->crtc_id,
558 0, 0, 0);
559
560 if (ret)
561 LOG(ERROR, "Unable to hide cursor");
562
563 drm_disable_non_primary_planes(drm);
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800564
565 drm_clear_rmfb(drm);
Dominik Behr83010f82016-03-18 18:43:08 -0700566 return ret;
567}
568
Dominik Behr6e0f6fd2016-12-02 17:54:08 -0800569/*
570 * Delayed rmfb(). We want to keep fb at least till after next modeset
571 * so our transitions are cleaner (e.g. when recreating term after exitin
572 * shell). Also it keeps fb around till Chrome starts.
573 */
574void drm_rmfb(drm_t* drm, uint32_t fb_id)
575{
576 drm_clear_rmfb(drm);
577 drm->delayed_rmfb_fb_id = fb_id;
578}
579
Dominik Behr83010f82016-03-18 18:43:08 -0700580bool drm_read_edid(drm_t* drm)
581{
582 if (drm->edid_found) {
583 return true;
584 }
585
586 for (int i = 0; i < drm->main_monitor_connector->count_props; i++) {
587 drmModePropertyPtr prop;
588 drmModePropertyBlobPtr blob_ptr;
589 prop = drmModeGetProperty(drm->fd, drm->main_monitor_connector->props[i]);
590 if (prop) {
591 if (strcmp(prop->name, "EDID") == 0) {
592 blob_ptr = drmModeGetPropertyBlob(drm->fd,
593 drm->main_monitor_connector->prop_values[i]);
594 if (blob_ptr) {
595 memcpy(&drm->edid, blob_ptr->data, EDID_SIZE);
596 drmModeFreePropertyBlob(blob_ptr);
597 return (drm->edid_found = true);
598 }
599 }
600 }
601 }
602
603 return false;
604}
605
606uint32_t drm_gethres(drm_t* drm)
607{
608 return drm->crtc->mode.hdisplay;
609}
610
611uint32_t drm_getvres(drm_t* drm)
612{
613 return drm->crtc->mode.vdisplay;
614}