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