Home | History | Annotate | Download | only in minui
      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 
     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 
     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 
     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 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    474 minui_backend* open_drm() {
    475     return &drm_backend;
    476 }
    477