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; |
| 32 | create_dumb.width = fb->drm->crtc->mode.hdisplay; |
| 33 | create_dumb.height = fb->drm->crtc->mode.vdisplay; |
| 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; |
| 55 | ret = drmModeAddFB2(fb->drm->fd, fb->drm->crtc->mode.hdisplay, fb->drm->crtc->mode.vdisplay, |
| 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 | |
| 66 | fb_buffer = fb_lock(fb); |
| 67 | if (fb_buffer) { |
| 68 | memset(fb_buffer, 0, fb->buffer_properties.size); |
| 69 | fb_unlock(fb); |
| 70 | } |
| 71 | |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 72 | return 0; |
| 73 | |
| 74 | destroy_buffer: |
| 75 | destroy_dumb.handle = create_dumb.handle; |
| 76 | |
| 77 | drmIoctl(fb->drm->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb); |
| 78 | |
| 79 | return ret; |
| 80 | } |
| 81 | |
| 82 | void fb_buffer_destroy(fb_t* fb) |
| 83 | { |
| 84 | struct drm_mode_destroy_dumb destroy_dumb; |
| 85 | |
| 86 | if (fb->buffer_handle <= 0) |
Dominik Behr | 6e0f6fd | 2016-12-02 17:54:08 -0800 | [diff] [blame] | 87 | goto unref_drm; |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 88 | |
Dominik Behr | 6e0f6fd | 2016-12-02 17:54:08 -0800 | [diff] [blame] | 89 | drm_rmfb(fb->drm, fb->fb_id); |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 90 | fb->fb_id = 0; |
| 91 | destroy_dumb.handle = fb->buffer_handle; |
| 92 | drmIoctl(fb->drm->fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy_dumb); |
| 93 | fb->buffer_handle = 0; |
| 94 | fb->lock.map = NULL; |
zhuo-hao | e998090 | 2016-07-28 12:05:36 +0800 | [diff] [blame] | 95 | fb->lock.count = 0; |
Dominik Behr | 6e0f6fd | 2016-12-02 17:54:08 -0800 | [diff] [blame] | 96 | unref_drm: |
| 97 | if (fb->drm) { |
| 98 | drm_delref(fb->drm); |
| 99 | fb->drm = NULL; |
| 100 | } |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 101 | } |
| 102 | |
| 103 | static bool parse_edid_dtd(uint8_t* dtd, drmModeModeInfo* mode, |
| 104 | int32_t* hdisplay_size, int32_t* vdisplay_size) { |
| 105 | int32_t clock; |
| 106 | int32_t hactive, hbl, hso, hsw, hsize; |
| 107 | int32_t vactive, vbl, vso, vsw, vsize; |
| 108 | |
| 109 | clock = ((int32_t)dtd[DTD_PCLK_HI] << 8) | dtd[DTD_PCLK_LO]; |
| 110 | if (!clock) |
| 111 | return false; |
| 112 | |
| 113 | hactive = ((int32_t)(dtd[DTD_HABL_HI] & 0xf0) << 4) + dtd[DTD_HA_LO]; |
| 114 | vactive = ((int32_t)(dtd[DTD_VABL_HI] & 0xf0) << 4) + dtd[DTD_VA_LO]; |
| 115 | hbl = ((int32_t)(dtd[DTD_HABL_HI] & 0x0f) << 8) + dtd[DTD_HBL_LO]; |
| 116 | vbl = ((int32_t)(dtd[DTD_VABL_HI] & 0x0f) << 8) + dtd[DTD_VBL_LO]; |
| 117 | hso = ((int32_t)(dtd[DTD_HVSX_HI] & 0xc0) << 2) + dtd[DTD_HSO_LO]; |
| 118 | vso = ((int32_t)(dtd[DTD_HVSX_HI] & 0x0c) << 2) + (dtd[DTD_VSX_LO] >> 4); |
| 119 | hsw = ((int32_t)(dtd[DTD_HVSX_HI] & 0x30) << 4) + dtd[DTD_HSW_LO]; |
| 120 | vsw = ((int32_t)(dtd[DTD_HVSX_HI] & 0x03) << 4) + (dtd[DTD_VSX_LO] & 0xf); |
| 121 | hsize = ((int32_t)(dtd[DTD_HVSIZE_HI] & 0xf0) << 4) + dtd[DTD_HSIZE_LO]; |
| 122 | vsize = ((int32_t)(dtd[DTD_HVSIZE_HI] & 0x0f) << 8) + dtd[DTD_VSIZE_LO]; |
| 123 | |
| 124 | mode->clock = clock * 10; |
| 125 | mode->hdisplay = hactive; |
| 126 | mode->vdisplay = vactive; |
| 127 | mode->hsync_start = hactive + hso; |
| 128 | mode->vsync_start = vactive + vso; |
| 129 | mode->hsync_end = mode->hsync_start + hsw; |
| 130 | mode->vsync_end = mode->vsync_start + vsw; |
| 131 | mode->htotal = hactive + hbl; |
| 132 | mode->vtotal = vactive + vbl; |
| 133 | *hdisplay_size = hsize; |
| 134 | *vdisplay_size = vsize; |
| 135 | return true; |
| 136 | } |
| 137 | |
| 138 | static bool parse_edid_dtd_display_size(drm_t* drm, int32_t* hsize_mm, int32_t* vsize_mm) { |
| 139 | drmModeModeInfo* mode = &drm->crtc->mode; |
| 140 | |
| 141 | for (int i = 0; i < EDID_N_DTDS; i++) { |
| 142 | uint8_t* dtd = (uint8_t*)&drm->edid[EDID_DTD_BASE + i * DTD_SIZE]; |
| 143 | drmModeModeInfo dtd_mode; |
| 144 | int32_t hdisplay_size, vdisplay_size; |
| 145 | if (!parse_edid_dtd(dtd, &dtd_mode, &hdisplay_size, &vdisplay_size) || |
| 146 | mode->clock != dtd_mode.clock || |
| 147 | mode->hdisplay != dtd_mode.hdisplay || |
| 148 | mode->vdisplay != dtd_mode.vdisplay || |
| 149 | mode->hsync_start != dtd_mode.hsync_start || |
| 150 | mode->vsync_start != dtd_mode.vsync_start || |
| 151 | mode->hsync_end != dtd_mode.hsync_end || |
| 152 | mode->vsync_end != dtd_mode.vsync_end || |
| 153 | mode->htotal != dtd_mode.htotal || |
| 154 | mode->vtotal != dtd_mode.vtotal) |
| 155 | continue; |
| 156 | *hsize_mm = hdisplay_size; |
| 157 | *vsize_mm = vdisplay_size; |
| 158 | return true; |
| 159 | } |
| 160 | return false; |
| 161 | } |
| 162 | |
| 163 | int fb_buffer_init(fb_t* fb) |
| 164 | { |
| 165 | int32_t width, height, pitch; |
| 166 | int32_t hsize_mm, vsize_mm; |
| 167 | int r; |
| 168 | |
zhuo-hao | 8f99f43 | 2016-08-02 17:58:26 +0800 | [diff] [blame] | 169 | /* reuse the buffer_properties if it was set before */ |
| 170 | if (!fb->buffer_properties.width || !fb->buffer_properties.height || |
| 171 | !fb->buffer_properties.pitch || !fb->buffer_properties.scaling) { |
| 172 | /* some reasonable defaults */ |
| 173 | fb->buffer_properties.width = 640; |
| 174 | fb->buffer_properties.height = 480; |
| 175 | fb->buffer_properties.pitch = 640 * 4; |
| 176 | fb->buffer_properties.scaling = 1; |
| 177 | } |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 178 | |
| 179 | fb->drm = drm_addref(); |
| 180 | |
| 181 | if (!fb->drm) { |
| 182 | LOG(WARNING, "No monitor available, running headless!"); |
| 183 | return -ENODEV; |
| 184 | } |
| 185 | |
| 186 | width = fb->drm->crtc->mode.hdisplay; |
| 187 | height = fb->drm->crtc->mode.vdisplay; |
| 188 | |
| 189 | r = fb_buffer_create(fb, &pitch); |
| 190 | if (r < 0) { |
| 191 | LOG(ERROR, "fb_buffer_create failed"); |
| 192 | return r; |
| 193 | } |
| 194 | |
| 195 | fb->buffer_properties.width = width; |
| 196 | fb->buffer_properties.height = height; |
| 197 | fb->buffer_properties.pitch = pitch; |
| 198 | |
| 199 | hsize_mm = fb->drm->main_monitor_connector->mmWidth; |
| 200 | vsize_mm = fb->drm->main_monitor_connector->mmHeight; |
| 201 | if (drm_read_edid(fb->drm)) |
| 202 | parse_edid_dtd_display_size(fb->drm, &hsize_mm, &vsize_mm); |
| 203 | |
| 204 | if (hsize_mm) { |
| 205 | int dots_per_cm = width * 10 / hsize_mm; |
| 206 | if (dots_per_cm > 133) |
| 207 | fb->buffer_properties.scaling = 4; |
Brian Norris | a2af9ca | 2018-02-23 15:03:23 -0800 | [diff] [blame] | 208 | else if (dots_per_cm > 105) |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 209 | fb->buffer_properties.scaling = 3; |
| 210 | else if (dots_per_cm > 67) |
| 211 | fb->buffer_properties.scaling = 2; |
| 212 | } |
| 213 | |
| 214 | return 0; |
| 215 | } |
| 216 | |
| 217 | fb_t* fb_init(void) |
| 218 | { |
| 219 | fb_t* fb; |
| 220 | |
| 221 | fb = (fb_t*)calloc(1, sizeof(fb_t)); |
| 222 | if (!fb) |
| 223 | return NULL; |
| 224 | |
| 225 | fb_buffer_init(fb); |
| 226 | |
| 227 | return fb; |
| 228 | } |
| 229 | |
| 230 | void fb_close(fb_t* fb) |
| 231 | { |
| 232 | if (!fb) |
| 233 | return; |
| 234 | |
| 235 | fb_buffer_destroy(fb); |
| 236 | |
| 237 | free(fb); |
| 238 | } |
| 239 | |
| 240 | int32_t fb_setmode(fb_t* fb) |
| 241 | { |
| 242 | /* headless mode */ |
| 243 | if (!drm_valid(fb->drm)) |
| 244 | return 0; |
| 245 | |
| 246 | return drm_setmode(fb->drm, fb->fb_id); |
| 247 | } |
| 248 | |
| 249 | uint32_t* fb_lock(fb_t* fb) |
| 250 | { |
| 251 | if (fb->lock.count == 0 && fb->buffer_handle > 0) { |
| 252 | fb->lock.map = |
| 253 | mmap(0, fb->buffer_properties.size, PROT_READ | PROT_WRITE, |
| 254 | MAP_SHARED, fb->drm->fd, fb->lock.map_offset); |
| 255 | if (fb->lock.map == MAP_FAILED) { |
| 256 | LOG(ERROR, "mmap failed"); |
| 257 | return NULL; |
| 258 | } |
| 259 | } |
zhuo-hao | e998090 | 2016-07-28 12:05:36 +0800 | [diff] [blame] | 260 | |
| 261 | if (fb->lock.map) |
| 262 | fb->lock.count++; |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 263 | |
| 264 | return fb->lock.map; |
| 265 | } |
| 266 | |
| 267 | void fb_unlock(fb_t* fb) |
| 268 | { |
| 269 | if (fb->lock.count > 0) |
| 270 | fb->lock.count--; |
| 271 | else |
| 272 | LOG(ERROR, "fb locking unbalanced"); |
| 273 | |
| 274 | if (fb->lock.count == 0 && fb->buffer_handle > 0) { |
zhuo-hao | 150ce79 | 2016-05-26 14:42:22 +0800 | [diff] [blame] | 275 | int32_t ret; |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 276 | struct drm_clip_rect clip_rect = { |
| 277 | 0, 0, fb->buffer_properties.width, fb->buffer_properties.height |
| 278 | }; |
| 279 | munmap(fb->lock.map, fb->buffer_properties.size); |
zhuo-hao | 150ce79 | 2016-05-26 14:42:22 +0800 | [diff] [blame] | 280 | ret = drmModeDirtyFB(fb->drm->fd, fb->fb_id, &clip_rect, 1); |
Daniel Kurtz | e6d4280 | 2016-07-14 13:01:30 +0800 | [diff] [blame] | 281 | if (ret && errno != ENOSYS) |
zhuo-hao | 150ce79 | 2016-05-26 14:42:22 +0800 | [diff] [blame] | 282 | LOG(ERROR, "drmModeDirtyFB failed: %m"); |
Dominik Behr | 83010f8 | 2016-03-18 18:43:08 -0700 | [diff] [blame] | 283 | } |
| 284 | } |
| 285 | |
| 286 | int32_t fb_getwidth(fb_t* fb) |
| 287 | { |
| 288 | return fb->buffer_properties.width; |
| 289 | } |
| 290 | |
| 291 | int32_t fb_getheight(fb_t* fb) |
| 292 | { |
| 293 | return fb->buffer_properties.height; |
| 294 | } |
| 295 | |
| 296 | int32_t fb_getpitch(fb_t* fb) |
| 297 | { |
| 298 | return fb->buffer_properties.pitch; |
| 299 | } |
| 300 | |
| 301 | int32_t fb_getscaling(fb_t* fb) |
| 302 | { |
| 303 | return fb->buffer_properties.scaling; |
| 304 | } |