1 /*
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 
36 struct drm_surface {
37     GRSurface base;
38     uint32_t fb_id;
39     uint32_t handle;
40 };
41 
42 static drm_surface *drm_surfaces[2];
43 static int current_buffer;
44 
45 static drmModeCrtc *main_monitor_crtc;
46 static drmModeConnector *main_monitor_connector;
47 
48 static int drm_fd = -1;
49 
drm_disable_crtc(int drm_fd,drmModeCrtc * crtc)50 static 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 
drm_enable_crtc(int drm_fd,drmModeCrtc * crtc,struct drm_surface * surface)61 static 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 
drm_blank(minui_backend * backend __unused,bool blank)76 static 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 
drm_destroy_surface(struct drm_surface * surface)84 static 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 
drm_format_to_bpp(uint32_t format)113 static 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:
120         case DRM_FORMAT_XRGB8888:
121             return 32;
122         case DRM_FORMAT_RGB565:
123             return 16;
124         default:
125             printf("Unknown format %d\n", format);
126             return 32;
127     }
128 }
129 
drm_create_surface(int width,int height)130 static drm_surface *drm_create_surface(int width, int height) {
131     struct drm_surface *surface;
132     struct drm_mode_create_dumb create_dumb;
133     uint32_t format;
134     int ret;
135 
136     surface = (struct drm_surface*)calloc(1, sizeof(*surface));
137     if (!surface) {
138         printf("Can't allocate memory\n");
139         return NULL;
140     }
141 
142 #if defined(RECOVERY_ABGR)
143     format = DRM_FORMAT_RGBA8888;
144 #elif defined(RECOVERY_BGRA)
145     format = DRM_FORMAT_ARGB8888;
146 #elif defined(RECOVERY_RGBX)
147     format = DRM_FORMAT_XBGR8888;
148 #else
149     format = DRM_FORMAT_RGB565;
150 #endif
151 
152     memset(&create_dumb, 0, sizeof(create_dumb));
153     create_dumb.height = height;
154     create_dumb.width = width;
155     create_dumb.bpp = drm_format_to_bpp(format);
156     create_dumb.flags = 0;
157 
158     ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
159     if (ret) {
160         printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n",ret);
161         drm_destroy_surface(surface);
162         return NULL;
163     }
164     surface->handle = create_dumb.handle;
165 
166     uint32_t handles[4], pitches[4], offsets[4];
167 
168     handles[0] = surface->handle;
169     pitches[0] = create_dumb.pitch;
170     offsets[0] = 0;
171 
172     ret = drmModeAddFB2(drm_fd, width, height,
173             format, handles, pitches, offsets,
174             &(surface->fb_id), 0);
175     if (ret) {
176         printf("drmModeAddFB2 failed ret=%d\n", ret);
177         drm_destroy_surface(surface);
178         return NULL;
179     }
180 
181     struct drm_mode_map_dumb map_dumb;
182     memset(&map_dumb, 0, sizeof(map_dumb));
183     map_dumb.handle = create_dumb.handle;
184     ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
185     if (ret) {
186         printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n",ret);
187         drm_destroy_surface(surface);
188         return NULL;;
189     }
190 
191     surface->base.height = height;
192     surface->base.width = width;
193     surface->base.row_bytes = create_dumb.pitch;
194     surface->base.pixel_bytes = create_dumb.bpp / 8;
195     surface->base.data = (unsigned char*)
196                          mmap(NULL,
197                               surface->base.height * surface->base.row_bytes,
198                               PROT_READ | PROT_WRITE, MAP_SHARED,
199                               drm_fd, map_dumb.offset);
200     if (surface->base.data == MAP_FAILED) {
201         perror("mmap() failed");
202         drm_destroy_surface(surface);
203         return NULL;
204     }
205 
206     return surface;
207 }
208 
find_crtc_for_connector(int fd,drmModeRes * resources,drmModeConnector * connector)209 static drmModeCrtc *find_crtc_for_connector(int fd,
210                             drmModeRes *resources,
211                             drmModeConnector *connector) {
212     int i, j;
213     drmModeEncoder *encoder;
214     int32_t crtc;
215 
216     /*
217      * Find the encoder. If we already have one, just use it.
218      */
219     if (connector->encoder_id)
220         encoder = drmModeGetEncoder(fd, connector->encoder_id);
221     else
222         encoder = NULL;
223 
224     if (encoder && encoder->crtc_id) {
225         crtc = encoder->crtc_id;
226         drmModeFreeEncoder(encoder);
227         return drmModeGetCrtc(fd, crtc);
228     }
229 
230     /*
231      * Didn't find anything, try to find a crtc and encoder combo.
232      */
233     crtc = -1;
234     for (i = 0; i < connector->count_encoders; i++) {
235         encoder = drmModeGetEncoder(fd, connector->encoders[i]);
236 
237         if (encoder) {
238             for (j = 0; j < resources->count_crtcs; j++) {
239                 if (!(encoder->possible_crtcs & (1 << j)))
240                     continue;
241                 crtc = resources->crtcs[j];
242                 break;
243             }
244             if (crtc >= 0) {
245                 drmModeFreeEncoder(encoder);
246                 return drmModeGetCrtc(fd, crtc);
247             }
248         }
249     }
250 
251     return NULL;
252 }
253 
find_used_connector_by_type(int fd,drmModeRes * resources,unsigned type)254 static drmModeConnector *find_used_connector_by_type(int fd,
255                                  drmModeRes *resources,
256                                  unsigned type) {
257     int i;
258     for (i = 0; i < resources->count_connectors; i++) {
259         drmModeConnector *connector;
260 
261         connector = drmModeGetConnector(fd, resources->connectors[i]);
262         if (connector) {
263             if ((connector->connector_type == type) &&
264                     (connector->connection == DRM_MODE_CONNECTED) &&
265                     (connector->count_modes > 0))
266                 return connector;
267 
268             drmModeFreeConnector(connector);
269         }
270     }
271     return NULL;
272 }
273 
find_first_connected_connector(int fd,drmModeRes * resources)274 static drmModeConnector *find_first_connected_connector(int fd,
275                              drmModeRes *resources) {
276     int i;
277     for (i = 0; i < resources->count_connectors; i++) {
278         drmModeConnector *connector;
279 
280         connector = drmModeGetConnector(fd, resources->connectors[i]);
281         if (connector) {
282             if ((connector->count_modes > 0) &&
283                     (connector->connection == DRM_MODE_CONNECTED))
284                 return connector;
285 
286             drmModeFreeConnector(connector);
287         }
288     }
289     return NULL;
290 }
291 
find_main_monitor(int fd,drmModeRes * resources,uint32_t * mode_index)292 static drmModeConnector *find_main_monitor(int fd, drmModeRes *resources,
293         uint32_t *mode_index) {
294     unsigned i = 0;
295     int modes;
296     /* Look for LVDS/eDP/DSI connectors. Those are the main screens. */
297     unsigned kConnectorPriority[] = {
298         DRM_MODE_CONNECTOR_LVDS,
299         DRM_MODE_CONNECTOR_eDP,
300         DRM_MODE_CONNECTOR_DSI,
301     };
302 
303     drmModeConnector *main_monitor_connector = NULL;
304     do {
305         main_monitor_connector = find_used_connector_by_type(fd,
306                                          resources,
307                                          kConnectorPriority[i]);
308         i++;
309     } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority));
310 
311     /* If we didn't find a connector, grab the first one that is connected. */
312     if (!main_monitor_connector)
313         main_monitor_connector =
314                 find_first_connected_connector(fd, resources);
315 
316     /* If we still didn't find a connector, give up and return. */
317     if (!main_monitor_connector)
318         return NULL;
319 
320     *mode_index = 0;
321     for (modes = 0; modes < main_monitor_connector->count_modes; modes++) {
322         if (main_monitor_connector->modes[modes].type &
323                 DRM_MODE_TYPE_PREFERRED) {
324             *mode_index = modes;
325             break;
326         }
327     }
328 
329     return main_monitor_connector;
330 }
331 
disable_non_main_crtcs(int fd,drmModeRes * resources,drmModeCrtc * main_crtc)332 static void disable_non_main_crtcs(int fd,
333                     drmModeRes *resources,
334                     drmModeCrtc* main_crtc) {
335     int i;
336     drmModeCrtc* crtc;
337 
338     for (i = 0; i < resources->count_connectors; i++) {
339         drmModeConnector *connector;
340 
341         connector = drmModeGetConnector(fd, resources->connectors[i]);
342         crtc = find_crtc_for_connector(fd, resources, connector);
343         if (crtc->crtc_id != main_crtc->crtc_id)
344             drm_disable_crtc(fd, crtc);
345         drmModeFreeCrtc(crtc);
346     }
347 }
348 
drm_init(minui_backend * backend __unused)349 static GRSurface* drm_init(minui_backend* backend __unused) {
350     drmModeRes *res = NULL;
351     uint32_t selected_mode;
352     char *dev_name;
353     int width, height;
354     int ret, i;
355 
356     /* Consider DRM devices in order. */
357     for (i = 0; i < DRM_MAX_MINOR; i++) {
358         uint64_t cap = 0;
359 
360         ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
361         if (ret < 0)
362             continue;
363 
364         drm_fd = open(dev_name, O_RDWR, 0);
365         free(dev_name);
366         if (drm_fd < 0)
367             continue;
368 
369         /* We need dumb buffers. */
370         ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap);
371         if (ret || cap == 0) {
372             close(drm_fd);
373             continue;
374         }
375 
376         res = drmModeGetResources(drm_fd);
377         if (!res) {
378             close(drm_fd);
379             continue;
380         }
381 
382         /* Use this device if it has at least one connected monitor. */
383         if (res->count_crtcs > 0 && res->count_connectors > 0)
384             if (find_first_connected_connector(drm_fd, res))
385                 break;
386 
387         drmModeFreeResources(res);
388         close(drm_fd);
389         res = NULL;
390     }
391 
392     if (drm_fd < 0 || res == NULL) {
393         perror("cannot find/open a drm device");
394         return NULL;
395     }
396 
397     main_monitor_connector = find_main_monitor(drm_fd,
398             res, &selected_mode);
399 
400     if (!main_monitor_connector) {
401         printf("main_monitor_connector not found\n");
402         drmModeFreeResources(res);
403         close(drm_fd);
404         return NULL;
405     }
406 
407     main_monitor_crtc = find_crtc_for_connector(drm_fd, res,
408                                                 main_monitor_connector);
409 
410     if (!main_monitor_crtc) {
411         printf("main_monitor_crtc not found\n");
412         drmModeFreeResources(res);
413         close(drm_fd);
414         return NULL;
415     }
416 
417     disable_non_main_crtcs(drm_fd,
418                            res, main_monitor_crtc);
419 
420     main_monitor_crtc->mode = main_monitor_connector->modes[selected_mode];
421 
422     width = main_monitor_crtc->mode.hdisplay;
423     height = main_monitor_crtc->mode.vdisplay;
424 
425     drmModeFreeResources(res);
426 
427     drm_surfaces[0] = drm_create_surface(width, height);
428     drm_surfaces[1] = drm_create_surface(width, height);
429     if (!drm_surfaces[0] || !drm_surfaces[1]) {
430         drm_destroy_surface(drm_surfaces[0]);
431         drm_destroy_surface(drm_surfaces[1]);
432         drmModeFreeResources(res);
433         close(drm_fd);
434         return NULL;
435     }
436 
437     current_buffer = 0;
438 
439     drm_enable_crtc(drm_fd, main_monitor_crtc, drm_surfaces[1]);
440 
441     return &(drm_surfaces[0]->base);
442 }
443 
drm_flip(minui_backend * backend __unused)444 static GRSurface* drm_flip(minui_backend* backend __unused) {
445     int ret;
446 
447     ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id,
448                           drm_surfaces[current_buffer]->fb_id, 0, NULL);
449     if (ret < 0) {
450         printf("drmModePageFlip failed ret=%d\n", ret);
451         return NULL;
452     }
453     current_buffer = 1 - current_buffer;
454     return &(drm_surfaces[current_buffer]->base);
455 }
456 
drm_exit(minui_backend * backend __unused)457 static void drm_exit(minui_backend* backend __unused) {
458     drm_disable_crtc(drm_fd, main_monitor_crtc);
459     drm_destroy_surface(drm_surfaces[0]);
460     drm_destroy_surface(drm_surfaces[1]);
461     drmModeFreeCrtc(main_monitor_crtc);
462     drmModeFreeConnector(main_monitor_connector);
463     close(drm_fd);
464     drm_fd = -1;
465 }
466 
467 static minui_backend drm_backend = {
468     .init = drm_init,
469     .flip = drm_flip,
470     .blank = drm_blank,
471     .exit = drm_exit,
472 };
473 
open_drm()474 minui_backend* open_drm() {
475     return &drm_backend;
476 }
477