Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2014 The Chromium OS Authors. All rights reserved. |
| 3 | * Use of this source code is governed by a BSD-style license that can be |
| 4 | * found in the LICENSE file. |
| 5 | */ |
| 6 | |
| 7 | #include <drm_fourcc.h> |
| 8 | #include <errno.h> |
| 9 | #include <fcntl.h> |
| 10 | #include <stdbool.h> |
| 11 | #include <stddef.h> |
| 12 | #include <stdio.h> |
| 13 | #include <stdlib.h> |
| 14 | #include <string.h> |
| 15 | #include <sys/mman.h> |
| 16 | #include <time.h> |
| 17 | #include <unistd.h> |
| 18 | |
| 19 | #include "util.h" |
| 20 | #include "fb.h" |
| 21 | |
| 22 | static int fb_buffer_create(fb_t* fb, |
| 23 | int* pitch) |
| 24 | { |
| 25 | struct drm_mode_create_dumb create_dumb; |
| 26 | struct drm_mode_destroy_dumb destroy_dumb; |
Drew Davenport | f6d4ab0 | 2018-04-30 16:38:11 -0600 | [diff] [blame] | 27 | uint32_t* fb_buffer; |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 28 | int ret; |
| 29 | |
| 30 | memset(&create_dumb, 0, sizeof (create_dumb)); |
| 31 | create_dumb.bpp = 32; |
Dominik Behr | 57b5c72 | 2018-08-08 18:52:31 -0700 | [diff] [blame] | 32 | create_dumb.width = fb->drm->console_mode_info.hdisplay; |
| 33 | create_dumb.height = fb->drm->console_mode_info.vdisplay; |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 34 | |
| 35 | ret = drmIoctl(fb->drm->fd, DRM_IOCTL_MODE_CREATE_DUMB, &create_dumb); |
| 36 | if (ret) { |
| 37 | LOG(ERROR, "CREATE_DUMB failed"); |
| 38 | return ret; |
| 39 | } |
| 40 | |
| 41 | fb->buffer_properties.size = create_dumb.size; |
| 42 | fb->buffer_handle = create_dumb.handle; |
| 43 | |
| 44 | struct drm_mode_map_dumb map_dumb; |
| 45 | map_dumb.handle = create_dumb.handle; |
| 46 | ret = drmIoctl(fb->drm->fd, DRM_IOCTL_MODE_MAP_DUMB, &map_dumb); |
| 47 | if (ret) { |
| 48 | LOG(ERROR, "MAP_DUMB failed"); |
| 49 | goto destroy_buffer; |
| 50 | } |
| 51 | |
| 52 | fb->lock.map_offset = map_dumb.offset; |
| 53 | |
| 54 | uint32_t offset = 0; |
Dominik Behr | 57b5c72 | 2018-08-08 18:52:31 -0700 | [diff] [blame] | 55 | ret = drmModeAddFB2(fb->drm->fd, fb->drm->console_mode_info.hdisplay, fb->drm->console_mode_info.vdisplay, |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 56 | DRM_FORMAT_XRGB8888, &create_dumb.handle, |
| 57 | &create_dumb.pitch, &offset, &fb->fb_id, 0); |
| 58 | if (ret) { |
| 59 | LOG(ERROR, "drmModeAddFB2 failed"); |
| 60 | goto destroy_buffer; |
| 61 | } |
| 62 | |
| 63 | *pitch = create_dumb.pitch; |
| 64 | |
Drew Davenport | f6d4ab0 | 2018-04-30 16:38:11 -0600 | [diff] [blame] | 65 | fb_buffer = fb_lock(fb); |
| 66 | if (fb_buffer) { |
| 67 | memset(fb_buffer, 0, fb->buffer_properties.size); |
| 68 | fb_unlock(fb); |
| 69 | } |
| 70 | |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 71 | return 0; |
| 72 | |
| 73 | destroy_buffer: |
| 74 | destroy_dumb.handle = create_dumb.handle; |
| 75 | |
| 76 | drmIoctl(fb->drm->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb); |
| 77 | |
| 78 | return ret; |
| 79 | } |
| 80 | |
| 81 | void fb_buffer_destroy(fb_t* fb) |
| 82 | { |
| 83 | struct drm_mode_destroy_dumb destroy_dumb; |
| 84 | |
| 85 | if (fb->buffer_handle <= 0) |
Dominik Behr | 6e0f6fd | 2016-12-02 17:54:08 -0800 | [diff] [blame] | 86 | goto unref_drm; |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 87 | |
Dominik Behr | 6e0f6fd | 2016-12-02 17:54:08 -0800 | [diff] [blame] | 88 | drm_rmfb(fb->drm, fb->fb_id); |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 89 | fb->fb_id = 0; |
| 90 | destroy_dumb.handle = fb->buffer_handle; |
| 91 | drmIoctl(fb->drm->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb); |
| 92 | fb->buffer_handle = 0; |
| 93 | fb->lock.map = NULL; |
zhuo-hao | e998090 | 2016-07-28 12:05:36 +0800 | [diff] [blame] | 94 | fb->lock.count = 0; |
Dominik Behr | 6e0f6fd | 2016-12-02 17:54:08 -0800 | [diff] [blame] | 95 | unref_drm: |
| 96 | if (fb->drm) { |
| 97 | drm_delref(fb->drm); |
| 98 | fb->drm = NULL; |
| 99 | } |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 100 | } |
| 101 | |
| 102 | static bool parse_edid_dtd(uint8_t* dtd, drmModeModeInfo* mode, |
| 103 | int32_t* hdisplay_size, int32_t* vdisplay_size) { |
| 104 | int32_t clock; |
| 105 | int32_t hactive, hbl, hso, hsw, hsize; |
| 106 | int32_t vactive, vbl, vso, vsw, vsize; |
| 107 | |
| 108 | clock = ((int32_t)dtd[DTD_PCLK_HI] << 8) | dtd[DTD_PCLK_LO]; |
| 109 | if (!clock) |
| 110 | return false; |
| 111 | |
| 112 | hactive = ((int32_t)(dtd[DTD_HABL_HI] & 0xf0) << 4) + dtd[DTD_HA_LO]; |
| 113 | vactive = ((int32_t)(dtd[DTD_VABL_HI] & 0xf0) << 4) + dtd[DTD_VA_LO]; |
| 114 | hbl = ((int32_t)(dtd[DTD_HABL_HI] & 0x0f) << 8) + dtd[DTD_HBL_LO]; |
| 115 | vbl = ((int32_t)(dtd[DTD_VABL_HI] & 0x0f) << 8) + dtd[DTD_VBL_LO]; |
| 116 | hso = ((int32_t)(dtd[DTD_HVSX_HI] & 0xc0) << 2) + dtd[DTD_HSO_LO]; |
| 117 | vso = ((int32_t)(dtd[DTD_HVSX_HI] & 0x0c) << 2) + (dtd[DTD_VSX_LO] >> 4); |
| 118 | hsw = ((int32_t)(dtd[DTD_HVSX_HI] & 0x30) << 4) + dtd[DTD_HSW_LO]; |
| 119 | vsw = ((int32_t)(dtd[DTD_HVSX_HI] & 0x03) << 4) + (dtd[DTD_VSX_LO] & 0xf); |
| 120 | hsize = ((int32_t)(dtd[DTD_HVSIZE_HI] & 0xf0) << 4) + dtd[DTD_HSIZE_LO]; |
| 121 | vsize = ((int32_t)(dtd[DTD_HVSIZE_HI] & 0x0f) << 8) + dtd[DTD_VSIZE_LO]; |
| 122 | |
| 123 | mode->clock = clock * 10; |
| 124 | mode->hdisplay = hactive; |
| 125 | mode->vdisplay = vactive; |
| 126 | mode->hsync_start = hactive + hso; |
| 127 | mode->vsync_start = vactive + vso; |
| 128 | mode->hsync_end = mode->hsync_start + hsw; |
| 129 | mode->vsync_end = mode->vsync_start + vsw; |
| 130 | mode->htotal = hactive + hbl; |
| 131 | mode->vtotal = vactive + vbl; |
| 132 | *hdisplay_size = hsize; |
| 133 | *vdisplay_size = vsize; |
| 134 | return true; |
| 135 | } |
| 136 | |
| 137 | static bool parse_edid_dtd_display_size(drm_t* drm, int32_t* hsize_mm, int32_t* vsize_mm) { |
Dominik Behr | 57b5c72 | 2018-08-08 18:52:31 -0700 | [diff] [blame] | 138 | drmModeModeInfo* mode = &drm->console_mode_info; |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 139 | |
| 140 | for (int i = 0; i < EDID_N_DTDS; i++) { |
| 141 | uint8_t* dtd = (uint8_t*)&drm->edid[EDID_DTD_BASE + i * DTD_SIZE]; |
| 142 | drmModeModeInfo dtd_mode; |
| 143 | int32_t hdisplay_size, vdisplay_size; |
| 144 | if (!parse_edid_dtd(dtd, &dtd_mode, &hdisplay_size, &vdisplay_size) || |
| 145 | mode->clock != dtd_mode.clock || |
| 146 | mode->hdisplay != dtd_mode.hdisplay || |
| 147 | mode->vdisplay != dtd_mode.vdisplay || |
| 148 | mode->hsync_start != dtd_mode.hsync_start || |
| 149 | mode->vsync_start != dtd_mode.vsync_start || |
| 150 | mode->hsync_end != dtd_mode.hsync_end || |
| 151 | mode->vsync_end != dtd_mode.vsync_end || |
| 152 | mode->htotal != dtd_mode.htotal || |
| 153 | mode->vtotal != dtd_mode.vtotal) |
| 154 | continue; |
| 155 | *hsize_mm = hdisplay_size; |
| 156 | *vsize_mm = vdisplay_size; |
| 157 | return true; |
| 158 | } |
| 159 | return false; |
| 160 | } |
| 161 | |
| 162 | int fb_buffer_init(fb_t* fb) |
| 163 | { |
| 164 | int32_t width, height, pitch; |
| 165 | int32_t hsize_mm, vsize_mm; |
| 166 | int r; |
| 167 | |
zhuo-hao | 8f99f43 | 2016-08-02 17:58:26 +0800 | [diff] [blame] | 168 | /* reuse the buffer_properties if it was set before */ |
| 169 | if (!fb->buffer_properties.width || !fb->buffer_properties.height || |
| 170 | !fb->buffer_properties.pitch || !fb->buffer_properties.scaling) { |
| 171 | /* some reasonable defaults */ |
| 172 | fb->buffer_properties.width = 640; |
| 173 | fb->buffer_properties.height = 480; |
| 174 | fb->buffer_properties.pitch = 640 * 4; |
| 175 | fb->buffer_properties.scaling = 1; |
| 176 | } |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 177 | |
| 178 | fb->drm = drm_addref(); |
| 179 | |
| 180 | if (!fb->drm) { |
| 181 | LOG(WARNING, "No monitor available, running headless!"); |
| 182 | return -ENODEV; |
| 183 | } |
| 184 | |
Dominik Behr | 57b5c72 | 2018-08-08 18:52:31 -0700 | [diff] [blame] | 185 | width = fb->drm->console_mode_info.hdisplay; |
| 186 | height = fb->drm->console_mode_info.vdisplay; |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 187 | |
| 188 | r = fb_buffer_create(fb, &pitch); |
| 189 | if (r < 0) { |
| 190 | LOG(ERROR, "fb_buffer_create failed"); |
| 191 | return r; |
| 192 | } |
| 193 | |
| 194 | fb->buffer_properties.width = width; |
| 195 | fb->buffer_properties.height = height; |
| 196 | fb->buffer_properties.pitch = pitch; |
| 197 | |
Dominik Behr | bb728f3 | 2019-09-03 17:52:13 -0700 | [diff] [blame] | 198 | /* |
| 199 | for reference, since it is not available in headers right now |
| 200 | DRM_MODE_PANEL_ORIENTATION_UNKNOWN = -1, |
| 201 | DRM_MODE_PANEL_ORIENTATION_NORMAL = 0, |
| 202 | DRM_MODE_PANEL_ORIENTATION_BOTTOM_UP, |
| 203 | DRM_MODE_PANEL_ORIENTATION_LEFT_UP, |
| 204 | DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, |
| 205 | */ |
| 206 | switch (fb->drm->panel_orientation) { |
| 207 | case 1: |
| 208 | fb->buffer_properties.rotation = DRM_MODE_ROTATE_180; |
| 209 | break; |
| 210 | case 2: |
| 211 | fb->buffer_properties.rotation = DRM_MODE_ROTATE_270; |
| 212 | break; |
| 213 | case 3: |
| 214 | fb->buffer_properties.rotation = DRM_MODE_ROTATE_90; |
| 215 | break; |
| 216 | default: |
| 217 | fb->buffer_properties.rotation = DRM_MODE_ROTATE_0; |
| 218 | } |
| 219 | |
Dominik Behr | 57b5c72 | 2018-08-08 18:52:31 -0700 | [diff] [blame] | 220 | hsize_mm = fb->drm->console_mmWidth; |
| 221 | vsize_mm = fb->drm->console_mmHeight; |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 222 | if (drm_read_edid(fb->drm)) |
| 223 | parse_edid_dtd_display_size(fb->drm, &hsize_mm, &vsize_mm); |
| 224 | |
| 225 | if (hsize_mm) { |
| 226 | int dots_per_cm = width * 10 / hsize_mm; |
| 227 | if (dots_per_cm > 133) |
| 228 | fb->buffer_properties.scaling = 4; |
Brian Norris | a2af9ca | 2018-02-23 15:03:23 -0800 | [diff] [blame] | 229 | else if (dots_per_cm > 105) |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 230 | fb->buffer_properties.scaling = 3; |
| 231 | else if (dots_per_cm > 67) |
| 232 | fb->buffer_properties.scaling = 2; |
| 233 | } |
| 234 | |
| 235 | return 0; |
| 236 | } |
| 237 | |
| 238 | fb_t* fb_init(void) |
| 239 | { |
| 240 | fb_t* fb; |
| 241 | |
| 242 | fb = (fb_t*)calloc(1, sizeof(fb_t)); |
| 243 | if (!fb) |
| 244 | return NULL; |
| 245 | |
| 246 | fb_buffer_init(fb); |
| 247 | |
| 248 | return fb; |
| 249 | } |
| 250 | |
| 251 | void fb_close(fb_t* fb) |
| 252 | { |
| 253 | if (!fb) |
| 254 | return; |
| 255 | |
| 256 | fb_buffer_destroy(fb); |
| 257 | |
| 258 | free(fb); |
| 259 | } |
| 260 | |
| 261 | int32_t fb_setmode(fb_t* fb) |
| 262 | { |
| 263 | /* headless mode */ |
| 264 | if (!drm_valid(fb->drm)) |
| 265 | return 0; |
| 266 | |
| 267 | return drm_setmode(fb->drm, fb->fb_id); |
| 268 | } |
| 269 | |
| 270 | uint32_t* fb_lock(fb_t* fb) |
| 271 | { |
| 272 | if (fb->lock.count == 0 && fb->buffer_handle > 0) { |
| 273 | fb->lock.map = |
| 274 | mmap(0, fb->buffer_properties.size, PROT_READ | PROT_WRITE, |
| 275 | MAP_SHARED, fb->drm->fd, fb->lock.map_offset); |
| 276 | if (fb->lock.map == MAP_FAILED) { |
| 277 | LOG(ERROR, "mmap failed"); |
| 278 | return NULL; |
| 279 | } |
| 280 | } |
zhuo-hao | e998090 | 2016-07-28 12:05:36 +0800 | [diff] [blame] | 281 | |
| 282 | if (fb->lock.map) |
| 283 | fb->lock.count++; |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 284 | |
| 285 | return fb->lock.map; |
| 286 | } |
| 287 | |
| 288 | void fb_unlock(fb_t* fb) |
| 289 | { |
| 290 | if (fb->lock.count > 0) |
| 291 | fb->lock.count--; |
| 292 | else |
| 293 | LOG(ERROR, "fb locking unbalanced"); |
| 294 | |
| 295 | if (fb->lock.count == 0 && fb->buffer_handle > 0) { |
zhuo-hao | 150ce79 | 2016-05-26 14:42:22 +0800 | [diff] [blame] | 296 | int32_t ret; |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 297 | struct drm_clip_rect clip_rect = { |
| 298 | 0, 0, fb->buffer_properties.width, fb->buffer_properties.height |
| 299 | }; |
| 300 | munmap(fb->lock.map, fb->buffer_properties.size); |
zhuo-hao | 150ce79 | 2016-05-26 14:42:22 +0800 | [diff] [blame] | 301 | ret = drmModeDirtyFB(fb->drm->fd, fb->fb_id, &clip_rect, 1); |
Daniel Kurtz | e6d4280 | 2016-07-14 13:01:30 +0800 | [diff] [blame] | 302 | if (ret && errno != ENOSYS) |
zhuo-hao | 150ce79 | 2016-05-26 14:42:22 +0800 | [diff] [blame] | 303 | LOG(ERROR, "drmModeDirtyFB failed: %m"); |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 304 | } |
| 305 | } |
| 306 | |
| 307 | int32_t fb_getwidth(fb_t* fb) |
| 308 | { |
Dominik Behr | bb728f3 | 2019-09-03 17:52:13 -0700 | [diff] [blame] | 309 | switch (fb->buffer_properties.rotation) { |
| 310 | case DRM_MODE_ROTATE_90: |
| 311 | case DRM_MODE_ROTATE_270: |
| 312 | return fb->buffer_properties.height; |
| 313 | break; |
| 314 | case DRM_MODE_ROTATE_0: |
| 315 | case DRM_MODE_ROTATE_180: |
| 316 | default: |
| 317 | return fb->buffer_properties.width; |
| 318 | } |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 319 | } |
| 320 | |
| 321 | int32_t fb_getheight(fb_t* fb) |
| 322 | { |
Dominik Behr | bb728f3 | 2019-09-03 17:52:13 -0700 | [diff] [blame] | 323 | switch (fb->buffer_properties.rotation) { |
| 324 | case DRM_MODE_ROTATE_90: |
| 325 | case DRM_MODE_ROTATE_270: |
| 326 | return fb->buffer_properties.width; |
| 327 | break; |
| 328 | case DRM_MODE_ROTATE_0: |
| 329 | case DRM_MODE_ROTATE_180: |
| 330 | default: |
| 331 | return fb->buffer_properties.height; |
| 332 | } |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 333 | } |
| 334 | |
| 335 | int32_t fb_getscaling(fb_t* fb) |
| 336 | { |
| 337 | return fb->buffer_properties.scaling; |
| 338 | } |
Dominik Behr | bb728f3 | 2019-09-03 17:52:13 -0700 | [diff] [blame] | 339 | |
| 340 | bool |
| 341 | fb_stepper_init(fb_stepper_t *s, fb_t *fb, int32_t x, int32_t y, uint32_t width, uint32_t height) |
| 342 | { |
| 343 | s->fb = fb; |
| 344 | s->start_x = x; |
| 345 | s->start_y = y; |
| 346 | s->w = width; |
| 347 | s->h = height; |
| 348 | s->x = 0; |
| 349 | s->y = 0; |
| 350 | s->pitch_div_4 = s->fb->buffer_properties.pitch >> 2; |
| 351 | |
| 352 | /* quick check if whole rect is outside fb */ |
| 353 | if (x + width <= 0 || y + height <= 0) |
| 354 | return false; |
| 355 | |
| 356 | switch (s->fb->buffer_properties.rotation) { |
| 357 | case DRM_MODE_ROTATE_90: |
| 358 | case DRM_MODE_ROTATE_270: |
| 359 | s->max_x = s->fb->buffer_properties.height; |
| 360 | s->max_y = s->fb->buffer_properties.width; |
| 361 | break; |
| 362 | case DRM_MODE_ROTATE_180: |
| 363 | case DRM_MODE_ROTATE_0: |
| 364 | default: |
| 365 | s->max_x = s->fb->buffer_properties.width; |
| 366 | s->max_y = s->fb->buffer_properties.height; |
| 367 | } |
| 368 | |
| 369 | if (x >= s->max_x |
| 370 | || y >= s->max_y) |
| 371 | return false; |
| 372 | |
| 373 | switch (s->fb->buffer_properties.rotation) { |
| 374 | case DRM_MODE_ROTATE_90: |
| 375 | s->m[0][0] = 0; |
| 376 | s->m[0][1] = -1; |
| 377 | s->m[0][2] = s->fb->buffer_properties.width - 1; |
| 378 | |
| 379 | s->m[1][0] = 1; |
| 380 | s->m[1][1] = 0; |
| 381 | s->m[1][2] = 0; |
| 382 | break; |
| 383 | case DRM_MODE_ROTATE_270: |
| 384 | s->m[0][0] = 0; |
| 385 | s->m[0][1] = 1; |
| 386 | s->m[0][2] = 0; |
| 387 | |
| 388 | s->m[1][0] = -1; |
| 389 | s->m[1][1] = 0; |
| 390 | s->m[1][2] = s->fb->buffer_properties.height - 1; |
| 391 | break; |
| 392 | case DRM_MODE_ROTATE_180: |
| 393 | s->m[0][0] = -1; |
| 394 | s->m[0][1] = 0; |
| 395 | s->m[0][2] = s->fb->buffer_properties.width - 1; |
| 396 | |
| 397 | s->m[1][0] = 0; |
| 398 | s->m[1][1] = -1; |
| 399 | s->m[1][2] = s->fb->buffer_properties.height - 1; |
| 400 | break; |
| 401 | case DRM_MODE_ROTATE_0: |
| 402 | default: |
| 403 | s->m[0][0] = 1; |
| 404 | s->m[0][1] = 0; |
| 405 | s->m[0][2] = 0; |
| 406 | |
| 407 | s->m[1][0] = 0; |
| 408 | s->m[1][1] = 1; |
| 409 | s->m[1][2] = 0; |
| 410 | } |
| 411 | |
| 412 | return true; |
| 413 | } |