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