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 "graphics_drm.h"
     18 
     19 #include <fcntl.h>
     20 #include <stdio.h>
     21 #include <stdlib.h>
     22 #include <sys/mman.h>
     23 #include <sys/types.h>
     24 #include <unistd.h>
     25 
     26 #include <drm_fourcc.h>
     27 #include <xf86drm.h>
     28 #include <xf86drmMode.h>
     29 
     30 #include "minui/minui.h"
     31 
     32 #define ARRAY_SIZE(A) (sizeof(A)/sizeof(*(A)))
     33 
     34 MinuiBackendDrm::MinuiBackendDrm()
     35     : GRSurfaceDrms(), main_monitor_crtc(nullptr), main_monitor_connector(nullptr), drm_fd(-1) {}
     36 
     37 void MinuiBackendDrm::DrmDisableCrtc(int drm_fd, drmModeCrtc* crtc) {
     38   if (crtc) {
     39     drmModeSetCrtc(drm_fd, crtc->crtc_id,
     40                    0,         // fb_id
     41                    0, 0,      // x,y
     42                    nullptr,   // connectors
     43                    0,         // connector_count
     44                    nullptr);  // mode
     45   }
     46 }
     47 
     48 void MinuiBackendDrm::DrmEnableCrtc(int drm_fd, drmModeCrtc* crtc, GRSurfaceDrm* surface) {
     49   int32_t ret = drmModeSetCrtc(drm_fd, crtc->crtc_id, surface->fb_id, 0, 0,  // x,y
     50                                &main_monitor_connector->connector_id,
     51                                1,  // connector_count
     52                                &main_monitor_crtc->mode);
     53 
     54   if (ret) {
     55     printf("drmModeSetCrtc failed ret=%d\n", ret);
     56   }
     57 }
     58 
     59 void MinuiBackendDrm::Blank(bool blank) {
     60   if (blank) {
     61     DrmDisableCrtc(drm_fd, main_monitor_crtc);
     62   } else {
     63     DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[current_buffer]);
     64   }
     65 }
     66 
     67 void MinuiBackendDrm::DrmDestroySurface(GRSurfaceDrm* surface) {
     68   if (!surface) return;
     69 
     70   if (surface->data) {
     71     munmap(surface->data, surface->row_bytes * surface->height);
     72   }
     73 
     74   if (surface->fb_id) {
     75     int ret = drmModeRmFB(drm_fd, surface->fb_id);
     76     if (ret) {
     77       printf("drmModeRmFB failed ret=%d\n", ret);
     78     }
     79   }
     80 
     81   if (surface->handle) {
     82     drm_gem_close gem_close = {};
     83     gem_close.handle = surface->handle;
     84 
     85     int ret = drmIoctl(drm_fd, DRM_IOCTL_GEM_CLOSE, &gem_close);
     86     if (ret) {
     87       printf("DRM_IOCTL_GEM_CLOSE failed ret=%d\n", ret);
     88     }
     89   }
     90 
     91   delete surface;
     92 }
     93 
     94 static int drm_format_to_bpp(uint32_t format) {
     95   switch (format) {
     96     case DRM_FORMAT_ABGR8888:
     97     case DRM_FORMAT_BGRA8888:
     98     case DRM_FORMAT_RGBX8888:
     99     case DRM_FORMAT_BGRX8888:
    100     case DRM_FORMAT_XBGR8888:
    101     case DRM_FORMAT_XRGB8888:
    102       return 32;
    103     case DRM_FORMAT_RGB565:
    104       return 16;
    105     default:
    106       printf("Unknown format %d\n", format);
    107       return 32;
    108   }
    109 }
    110 
    111 GRSurfaceDrm* MinuiBackendDrm::DrmCreateSurface(int width, int height) {
    112   GRSurfaceDrm* surface = new GRSurfaceDrm;
    113   *surface = {};
    114 
    115   uint32_t format;
    116 #if defined(RECOVERY_ABGR)
    117   format = DRM_FORMAT_RGBA8888;
    118 #elif defined(RECOVERY_BGRA)
    119   format = DRM_FORMAT_ARGB8888;
    120 #elif defined(RECOVERY_RGBX)
    121   format = DRM_FORMAT_XBGR8888;
    122 #else
    123   format = DRM_FORMAT_RGB565;
    124 #endif
    125 
    126   drm_mode_create_dumb create_dumb = {};
    127   create_dumb.height = height;
    128   create_dumb.width = width;
    129   create_dumb.bpp = drm_format_to_bpp(format);
    130   create_dumb.flags = 0;
    131 
    132   int ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb);
    133   if (ret) {
    134     printf("DRM_IOCTL_MODE_CREATE_DUMB failed ret=%d\n", ret);
    135     DrmDestroySurface(surface);
    136     return nullptr;
    137   }
    138   surface->handle = create_dumb.handle;
    139 
    140   uint32_t handles[4], pitches[4], offsets[4];
    141 
    142   handles[0] = surface->handle;
    143   pitches[0] = create_dumb.pitch;
    144   offsets[0] = 0;
    145 
    146   ret =
    147       drmModeAddFB2(drm_fd, width, height, format, handles, pitches, offsets, &(surface->fb_id), 0);
    148   if (ret) {
    149     printf("drmModeAddFB2 failed ret=%d\n", ret);
    150     DrmDestroySurface(surface);
    151     return nullptr;
    152   }
    153 
    154   drm_mode_map_dumb map_dumb = {};
    155   map_dumb.handle = create_dumb.handle;
    156   ret = drmIoctl(drm_fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb);
    157   if (ret) {
    158     printf("DRM_IOCTL_MODE_MAP_DUMB failed ret=%d\n", ret);
    159     DrmDestroySurface(surface);
    160     return nullptr;
    161   }
    162 
    163   surface->height = height;
    164   surface->width = width;
    165   surface->row_bytes = create_dumb.pitch;
    166   surface->pixel_bytes = create_dumb.bpp / 8;
    167   surface->data = static_cast<unsigned char*>(mmap(nullptr, surface->height * surface->row_bytes,
    168                                                    PROT_READ | PROT_WRITE, MAP_SHARED, drm_fd,
    169                                                    map_dumb.offset));
    170   if (surface->data == MAP_FAILED) {
    171     perror("mmap() failed");
    172     DrmDestroySurface(surface);
    173     return nullptr;
    174   }
    175 
    176   return surface;
    177 }
    178 
    179 static drmModeCrtc* find_crtc_for_connector(int fd, drmModeRes* resources,
    180                                             drmModeConnector* connector) {
    181   // Find the encoder. If we already have one, just use it.
    182   drmModeEncoder* encoder;
    183   if (connector->encoder_id) {
    184     encoder = drmModeGetEncoder(fd, connector->encoder_id);
    185   } else {
    186     encoder = nullptr;
    187   }
    188 
    189   int32_t crtc;
    190   if (encoder && encoder->crtc_id) {
    191     crtc = encoder->crtc_id;
    192     drmModeFreeEncoder(encoder);
    193     return drmModeGetCrtc(fd, crtc);
    194   }
    195 
    196   // Didn't find anything, try to find a crtc and encoder combo.
    197   crtc = -1;
    198   for (int i = 0; i < connector->count_encoders; i++) {
    199     encoder = drmModeGetEncoder(fd, connector->encoders[i]);
    200 
    201     if (encoder) {
    202       for (int j = 0; j < resources->count_crtcs; j++) {
    203         if (!(encoder->possible_crtcs & (1 << j))) continue;
    204         crtc = resources->crtcs[j];
    205         break;
    206       }
    207       if (crtc >= 0) {
    208         drmModeFreeEncoder(encoder);
    209         return drmModeGetCrtc(fd, crtc);
    210       }
    211     }
    212   }
    213 
    214   return nullptr;
    215 }
    216 
    217 static drmModeConnector* find_used_connector_by_type(int fd, drmModeRes* resources, unsigned type) {
    218   for (int i = 0; i < resources->count_connectors; i++) {
    219     drmModeConnector* connector = drmModeGetConnector(fd, resources->connectors[i]);
    220     if (connector) {
    221       if ((connector->connector_type == type) && (connector->connection == DRM_MODE_CONNECTED) &&
    222           (connector->count_modes > 0)) {
    223         return connector;
    224       }
    225       drmModeFreeConnector(connector);
    226     }
    227   }
    228   return nullptr;
    229 }
    230 
    231 static drmModeConnector* find_first_connected_connector(int fd, drmModeRes* resources) {
    232   for (int i = 0; i < resources->count_connectors; i++) {
    233     drmModeConnector* connector;
    234 
    235     connector = drmModeGetConnector(fd, resources->connectors[i]);
    236     if (connector) {
    237       if ((connector->count_modes > 0) && (connector->connection == DRM_MODE_CONNECTED))
    238         return connector;
    239 
    240       drmModeFreeConnector(connector);
    241     }
    242   }
    243   return nullptr;
    244 }
    245 
    246 drmModeConnector* MinuiBackendDrm::FindMainMonitor(int fd, drmModeRes* resources,
    247                                                    uint32_t* mode_index) {
    248   /* Look for LVDS/eDP/DSI connectors. Those are the main screens. */
    249   static constexpr unsigned kConnectorPriority[] = {
    250     DRM_MODE_CONNECTOR_LVDS,
    251     DRM_MODE_CONNECTOR_eDP,
    252     DRM_MODE_CONNECTOR_DSI,
    253   };
    254 
    255   drmModeConnector* main_monitor_connector = nullptr;
    256   unsigned i = 0;
    257   do {
    258     main_monitor_connector = find_used_connector_by_type(fd, resources, kConnectorPriority[i]);
    259     i++;
    260   } while (!main_monitor_connector && i < ARRAY_SIZE(kConnectorPriority));
    261 
    262   /* If we didn't find a connector, grab the first one that is connected. */
    263   if (!main_monitor_connector) {
    264     main_monitor_connector = find_first_connected_connector(fd, resources);
    265   }
    266 
    267   /* If we still didn't find a connector, give up and return. */
    268   if (!main_monitor_connector) return nullptr;
    269 
    270   *mode_index = 0;
    271   for (int modes = 0; modes < main_monitor_connector->count_modes; modes++) {
    272     if (main_monitor_connector->modes[modes].type & DRM_MODE_TYPE_PREFERRED) {
    273       *mode_index = modes;
    274       break;
    275     }
    276   }
    277 
    278   return main_monitor_connector;
    279 }
    280 
    281 void MinuiBackendDrm::DisableNonMainCrtcs(int fd, drmModeRes* resources, drmModeCrtc* main_crtc) {
    282   for (int i = 0; i < resources->count_connectors; i++) {
    283     drmModeConnector* connector = drmModeGetConnector(fd, resources->connectors[i]);
    284     drmModeCrtc* crtc = find_crtc_for_connector(fd, resources, connector);
    285     if (crtc->crtc_id != main_crtc->crtc_id) {
    286       DrmDisableCrtc(fd, crtc);
    287     }
    288     drmModeFreeCrtc(crtc);
    289   }
    290 }
    291 
    292 GRSurface* MinuiBackendDrm::Init() {
    293   drmModeRes* res = nullptr;
    294 
    295   /* Consider DRM devices in order. */
    296   for (int i = 0; i < DRM_MAX_MINOR; i++) {
    297     char* dev_name;
    298     int ret = asprintf(&dev_name, DRM_DEV_NAME, DRM_DIR_NAME, i);
    299     if (ret < 0) continue;
    300 
    301     drm_fd = open(dev_name, O_RDWR, 0);
    302     free(dev_name);
    303     if (drm_fd < 0) continue;
    304 
    305     uint64_t cap = 0;
    306     /* We need dumb buffers. */
    307     ret = drmGetCap(drm_fd, DRM_CAP_DUMB_BUFFER, &cap);
    308     if (ret || cap == 0) {
    309       close(drm_fd);
    310       continue;
    311     }
    312 
    313     res = drmModeGetResources(drm_fd);
    314     if (!res) {
    315       close(drm_fd);
    316       continue;
    317     }
    318 
    319     /* Use this device if it has at least one connected monitor. */
    320     if (res->count_crtcs > 0 && res->count_connectors > 0) {
    321       if (find_first_connected_connector(drm_fd, res)) break;
    322     }
    323 
    324     drmModeFreeResources(res);
    325     close(drm_fd);
    326     res = nullptr;
    327   }
    328 
    329   if (drm_fd < 0 || res == nullptr) {
    330     perror("cannot find/open a drm device");
    331     return nullptr;
    332   }
    333 
    334   uint32_t selected_mode;
    335   main_monitor_connector = FindMainMonitor(drm_fd, res, &selected_mode);
    336 
    337   if (!main_monitor_connector) {
    338     printf("main_monitor_connector not found\n");
    339     drmModeFreeResources(res);
    340     close(drm_fd);
    341     return nullptr;
    342   }
    343 
    344   main_monitor_crtc = find_crtc_for_connector(drm_fd, res, main_monitor_connector);
    345 
    346   if (!main_monitor_crtc) {
    347     printf("main_monitor_crtc not found\n");
    348     drmModeFreeResources(res);
    349     close(drm_fd);
    350     return nullptr;
    351   }
    352 
    353   DisableNonMainCrtcs(drm_fd, res, main_monitor_crtc);
    354 
    355   main_monitor_crtc->mode = main_monitor_connector->modes[selected_mode];
    356 
    357   int width = main_monitor_crtc->mode.hdisplay;
    358   int height = main_monitor_crtc->mode.vdisplay;
    359 
    360   drmModeFreeResources(res);
    361 
    362   GRSurfaceDrms[0] = DrmCreateSurface(width, height);
    363   GRSurfaceDrms[1] = DrmCreateSurface(width, height);
    364   if (!GRSurfaceDrms[0] || !GRSurfaceDrms[1]) {
    365     // GRSurfaceDrms and drm_fd should be freed in d'tor.
    366     return nullptr;
    367   }
    368 
    369   current_buffer = 0;
    370 
    371   DrmEnableCrtc(drm_fd, main_monitor_crtc, GRSurfaceDrms[1]);
    372 
    373   return GRSurfaceDrms[0];
    374 }
    375 
    376 GRSurface* MinuiBackendDrm::Flip() {
    377   int ret = drmModePageFlip(drm_fd, main_monitor_crtc->crtc_id,
    378                             GRSurfaceDrms[current_buffer]->fb_id, 0, nullptr);
    379   if (ret < 0) {
    380     printf("drmModePageFlip failed ret=%d\n", ret);
    381     return nullptr;
    382   }
    383   current_buffer = 1 - current_buffer;
    384   return GRSurfaceDrms[current_buffer];
    385 }
    386 
    387 MinuiBackendDrm::~MinuiBackendDrm() {
    388   DrmDisableCrtc(drm_fd, main_monitor_crtc);
    389   DrmDestroySurface(GRSurfaceDrms[0]);
    390   DrmDestroySurface(GRSurfaceDrms[1]);
    391   drmModeFreeCrtc(main_monitor_crtc);
    392   drmModeFreeConnector(main_monitor_connector);
    393   close(drm_fd);
    394   drm_fd = -1;
    395 }
    396