blob: ddda187218adebb9cfe2553166c2118855653522 [file] [log] [blame]
Stéphane Marchesin1a92c442015-06-29 20:05:48 -07001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include <drm_fourcc.h>
18#include <fcntl.h>
19#include <stdbool.h>
20#include <stdio.h>
21#include <stdlib.h>
22#include <string.h>
23#include <sys/cdefs.h>
24#include <sys/ioctl.h>
25#include <sys/mman.h>
26#include <sys/types.h>
27#include <unistd.h>
28#include <xf86drm.h>
29#include <xf86drmMode.h>
30
31#include "minui.h"
32#include "graphics.h"
33
34#define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A)))
35
36struct drm_surface {
37 GRSurface base;
38 uint32_t fb_id;
39 uint32_t handle;
40};
41
42static drm_surface *drm_surfaces[2];
43static int current_buffer;
44
45static drmModeCrtc *main_monitor_crtc;
46static drmModeConnector *main_monitor_connector;
47
48static int drm_fd = -1;
49
50static void drm_disable_crtc(int drm_fd, drmModeCrtc *crtc) {
51 if (crtc) {
52 drmModeSetCrtc(drm_fd, crtc->crtc_id,
53 0, // fb_id
54 0, 0, // x,y
55 NULL, // connectors
56 0, // connector_count
57 NULL); // mode
58 }
59}
60
61static void drm_enable_crtc(int drm_fd, drmModeCrtc *crtc,
62 struct drm_surface *surface) {
63 int32_t ret;
64
65 ret = drmModeSetCrtc(drm_fd, crtc->crtc_id,
66 surface->fb_id,
67 0, 0, // x,y
68 &main_monitor_connector->connector_id,
69 1, // connector_count
70 &main_monitor_crtc->mode);
71
72 if (ret)
73 printf("drmModeSetCrtc failed ret=%d\n", ret);
74}
75
76static void drm_blank(minui_backend* backend __unused, bool blank) {
77 if (blank)
78 drm_disable_crtc(drm_fd, main_monitor_crtc);
79 else
80 drm_enable_crtc(drm_fd, main_monitor_crtc,
81 drm_surfaces[current_buffer]);
82}
83
84static void drm_destroy_surface(struct drm_surface *surface) {
85 struct drm_gem_close gem_close;
86 int ret;
87
88 if(!surface)
89 return;
90
91 if (surface->base.data)
92 munmap(surface->base.data,
93 surface->base.row_bytes * surface->base.height);
94
95 if (surface->fb_id) {
96 ret = drmModeRmFB(drm_fd, surface->fb_id);
97 if (ret)
98 printf("drmModeRmFB failed ret=%d\n", ret);
99 }
100
101 if (surface->handle) {
102 memset(&gem_close, 0, sizeof(gem_close));
103 gem_close.handle = surface->handle;
104
105 ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
106 if (ret)
107 printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret);
108 }
109
110 free(surface);
111}
112
113static int drm_format_to_bpp(uint32_t format) {
114 switch(format) {
115 case DRM_FORMAT_ABGR8888:
116 case DRM_FORMAT_BGRA8888:
117 case DRM_FORMAT_RGBX8888:
118 case DRM_FORMAT_BGRX8888:
119 case DRM_FORMAT_XBGR8888:
Kra1o577568592015-10-14 18:09:54 +0200120 case DRM_FORMAT_ARGB8888:
Stéphane Marchesin1a92c442015-06-29 20:05:48 -0700121 case DRM_FORMAT_XRGB8888:
122 return 32;
123 case DRM_FORMAT_RGB565:
124 return 16;
125 default:
126 printf("Unknown format %d\n", format);
127 return 32;
128 }
129}
130
131static drm_surface *drm_create_surface(int width, int height) {
132 struct drm_surface *surface;
133 struct drm_mode_create_dumb create_dumb;
134 uint32_t format;
135 int ret;
136
137 surface = (struct drm_surface*)calloc(1, sizeof(*surface));
138 if (!surface) {
139 printf("Can't allocate memory\n");
140 return NULL;
141 }
142
143#if defined(RECOVERY_ABGR)
144 format = DRM_FORMAT_RGBA8888;
145#elif defined(RECOVERY_BGRA)
146 format = DRM_FORMAT_ARGB8888;
Kra1o577568592015-10-14 18:09:54 +0200147#elif defined(RECOVERY_RGBA)
148 format = DRM_FORMAT_ABGR8888;
Stéphane Marchesin1a92c442015-06-29 20:05:48 -0700149#elif defined(RECOVERY_RGBX)
150 format = DRM_FORMAT_XBGR8888;
151#else
152 format = DRM_FORMAT_RGB565;
153#endif
154
155 memset(&create_dumb, 0, sizeof(create_dumb));
156 create_dumb.height = height;
157 create_dumb.width = width;
158 create_dumb.bpp = drm_format_to_bpp(format);
159 create_dumb.flags = 0;
160
161 ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
162 if (ret) {
163 printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n",ret);
164 drm_destroy_surface(surface);
165 return NULL;
166 }
167 surface->handle = create_dumb.handle;
168
169 uint32_t handles[4], pitches[4], offsets[4];
170
171 handles[0] = surface->handle;
172 pitches[0] = create_dumb.pitch;
173 offsets[0] = 0;
174
175 ret = drmModeAddFB2(drm_fd, width, height,
176 format, handles, pitches, offsets,
177 &(surface->fb_id), 0);
178 if (ret) {
179 printf("drmModeAddFB2 failed ret=%d\n", ret);
180 drm_destroy_surface(surface);
181 return NULL;
182 }
183
184 struct drm_mode_map_dumb map_dumb;
185 memset(&map_dumb, 0, sizeof(map_dumb));
186 map_dumb.handle = create_dumb.handle;
187 ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
188 if (ret) {
189 printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n",ret);
190 drm_destroy_surface(surface);
191 return NULL;;
192 }
193
194 surface->base.height = height;
195 surface->base.width = width;
196 surface->base.row_bytes = create_dumb.pitch;
197 surface->base.pixel_bytes = create_dumb.bpp / 8;
198 surface->base.data = (unsigned char*)
199 mmap(NULL,
200 surface->base.height * surface->base.row_bytes,
201 PROT_READ | PROT_WRITE, MAP_SHARED,
202 drm_fd, map_dumb.offset);
203 if (surface->base.data == MAP_FAILED) {
204 perror("mmap() failed");
205 drm_destroy_surface(surface);
206 return NULL;
207 }
208
209 return surface;
210}
211
212static drmModeCrtc *find_crtc_for_connector(int fd,
213 drmModeRes *resources,
214 drmModeConnector *connector) {
215 int i, j;
216 drmModeEncoder *encoder;
217 int32_t crtc;
218
219 /*
220 * Find the encoder. If we already have one, just use it.
221 */
222 if (connector->encoder_id)
223 encoder = drmModeGetEncoder(fd, connector->encoder_id);
224 else
225 encoder = NULL;
226
227 if (encoder && encoder->crtc_id) {
228 crtc = encoder->crtc_id;
229 drmModeFreeEncoder(encoder);
230 return drmModeGetCrtc(fd, crtc);
231 }
232
233 /*
234 * Didn't find anything, try to find a crtc and encoder combo.
235 */
236 crtc = -1;
237 for (i = 0; i < connector->count_encoders; i++) {
238 encoder = drmModeGetEncoder(fd, connector->encoders[i]);
239
240 if (encoder) {
241 for (j = 0; j < resources->count_crtcs; j++) {
242 if (!(encoder->possible_crtcs & (1 << j)))
243 continue;
244 crtc = resources->crtcs[j];
245 break;
246 }
247 if (crtc >= 0) {
248 drmModeFreeEncoder(encoder);
249 return drmModeGetCrtc(fd, crtc);
250 }
251 }
252 }
253
254 return NULL;
255}
256
257static drmModeConnector *find_used_connector_by_type(int fd,
258 drmModeRes *resources,
259 unsigned type) {
260 int i;
261 for (i = 0; i < resources->count_connectors; i++) {
262 drmModeConnector *connector;
263
264 connector = drmModeGetConnector(fd, resources->connectors[i]);
265 if (connector) {
266 if ((connector->connector_type == type) &&
267 (connector->connection == DRM_MODE_CONNECTED) &&
268 (connector->count_modes > 0))
269 return connector;
270
271 drmModeFreeConnector(connector);
272 }
273 }
274 return NULL;
275}
276
277static drmModeConnector *find_first_connected_connector(int fd,
278 drmModeRes *resources) {
279 int i;
280 for (i = 0; i < resources->count_connectors; i++) {
281 drmModeConnector *connector;
282
283 connector = drmModeGetConnector(fd, resources->connectors[i]);
284 if (connector) {
285 if ((connector->count_modes > 0) &&
286 (connector->connection == DRM_MODE_CONNECTED))
287 return connector;
288
289 drmModeFreeConnector(connector);
290 }
291 }
292 return NULL;
293}
294
295static drmModeConnector *find_main_monitor(int fd, drmModeRes *resources,
296 uint32_t *mode_index) {
297 unsigned i = 0;
298 int modes;
299 /* Look for LVDS/eDP/DSI connectors. Those are the main screens. */
300 unsigned kConnectorPriority[] = {
301 DRM_MODE_CONNECTOR_LVDS,
302 DRM_MODE_CONNECTOR_eDP,
303 DRM_MODE_CONNECTOR_DSI,
304 };
305
306 drmModeConnector *main_monitor_connector = NULL;
307 do {
308 main_monitor_connector = find_used_connector_by_type(fd,
309 resources,
310 kConnectorPriority[i]);
311 i++;
312 } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority));
313
314 /* If we didn't find a connector, grab the first one that is connected. */
315 if (!main_monitor_connector)
316 main_monitor_connector =
317 find_first_connected_connector(fd, resources);
318
319 /* If we still didn't find a connector, give up and return. */
320 if (!main_monitor_connector)
321 return NULL;
322
323 *mode_index = 0;
324 for (modes = 0; modes < main_monitor_connector->count_modes; modes++) {
325 if (main_monitor_connector->modes[modes].type &
326 DRM_MODE_TYPE_PREFERRED) {
327 *mode_index = modes;
328 break;
329 }
330 }
331
332 return main_monitor_connector;
333}
334
335static void disable_non_main_crtcs(int fd,
336 drmModeRes *resources,
337 drmModeCrtc* main_crtc) {
338 int i;
339 drmModeCrtc* crtc;
340
341 for (i = 0; i < resources->count_connectors; i++) {
342 drmModeConnector *connector;
343
344 connector = drmModeGetConnector(fd, resources->connectors[i]);
345 crtc = find_crtc_for_connector(fd, resources, connector);
346 if (crtc->crtc_id != main_crtc->crtc_id)
347 drm_disable_crtc(fd, crtc);
348 drmModeFreeCrtc(crtc);
349 }
350}
351
352static GRSurface* drm_init(minui_backend* backend __unused) {
353 drmModeRes *res = NULL;
354 uint32_t selected_mode;
355 char *dev_name;
356 int width, height;
357 int ret, i;
358
359 /* Consider DRM devices in order. */
360 for (i = 0; i < DRM_MAX_MINOR; i++) {
361 uint64_t cap = 0;
362
363 ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
364 if (ret < 0)
365 continue;
366
367 drm_fd = open(dev_name, O_RDWR, 0);
368 free(dev_name);
369 if (drm_fd < 0)
370 continue;
371
372 /* We need dumb buffers. */
373 ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap);
374 if (ret || cap == 0) {
375 close(drm_fd);
376 continue;
377 }
378
379 res = drmModeGetResources(drm_fd);
380 if (!res) {
381 close(drm_fd);
382 continue;
383 }
384
385 /* Use this device if it has at least one connected monitor. */
386 if (res->count_crtcs > 0 && res->count_connectors > 0)
387 if (find_first_connected_connector(drm_fd, res))
388 break;
389
390 drmModeFreeResources(res);
391 close(drm_fd);
392 res = NULL;
393 }
394
395 if (drm_fd < 0 || res == NULL) {
396 perror("cannot find/open a drm device");
397 return NULL;
398 }
399
400 main_monitor_connector = find_main_monitor(drm_fd,
401 res, &selected_mode);
402
403 if (!main_monitor_connector) {
404 printf("main_monitor_connector not found\n");
405 drmModeFreeResources(res);
406 close(drm_fd);
407 return NULL;
408 }
409
410 main_monitor_crtc = find_crtc_for_connector(drm_fd, res,
411 main_monitor_connector);
412
413 if (!main_monitor_crtc) {
414 printf("main_monitor_crtc not found\n");
415 drmModeFreeResources(res);
416 close(drm_fd);
417 return NULL;
418 }
419
420 disable_non_main_crtcs(drm_fd,
421 res, main_monitor_crtc);
422
423 main_monitor_crtc->mode = main_monitor_connector->modes[selected_mode];
424
425 width = main_monitor_crtc->mode.hdisplay;
426 height = main_monitor_crtc->mode.vdisplay;
427
428 drmModeFreeResources(res);
429
430 drm_surfaces[0] = drm_create_surface(width, height);
431 drm_surfaces[1] = drm_create_surface(width, height);
432 if (!drm_surfaces[0] || !drm_surfaces[1]) {
433 drm_destroy_surface(drm_surfaces[0]);
434 drm_destroy_surface(drm_surfaces[1]);
435 drmModeFreeResources(res);
436 close(drm_fd);
437 return NULL;
438 }
439
440 current_buffer = 0;
441
442 drm_enable_crtc(drm_fd, main_monitor_crtc, drm_surfaces[1]);
443
444 return &(drm_surfaces[0]->base);
445}
446
447static GRSurface* drm_flip(minui_backend* backend __unused) {
448 int ret;
449
450 ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id,
451 drm_surfaces[current_buffer]->fb_id, 0, NULL);
452 if (ret < 0) {
453 printf("drmModePageFlip failed ret=%d\n", ret);
454 return NULL;
455 }
456 current_buffer = 1 - current_buffer;
457 return &(drm_surfaces[current_buffer]->base);
458}
459
460static void drm_exit(minui_backend* backend __unused) {
461 drm_disable_crtc(drm_fd, main_monitor_crtc);
462 drm_destroy_surface(drm_surfaces[0]);
463 drm_destroy_surface(drm_surfaces[1]);
464 drmModeFreeCrtc(main_monitor_crtc);
465 drmModeFreeConnector(main_monitor_connector);
466 close(drm_fd);
467 drm_fd = -1;
468}
469
470static minui_backend drm_backend = {
471 .init = drm_init,
472 .flip = drm_flip,
473 .blank = drm_blank,
474 .exit = drm_exit,
475};
476
477minui_backend* open_drm() {
478 return &drm_backend;
479}