1 /*
2 * Copyright © 2008-2011 Kristian Høgsberg
3 * Copyright © 2011 Intel Corporation
4 * Copyright © 2017, 2018 Collabora, Ltd.
5 * Copyright © 2017, 2018 General Electric Company
6 * Copyright (c) 2018 DisplayLink (UK) Ltd.
7 *
8 * Permission is hereby granted, free of charge, to any person obtaining
9 * a copy of this software and associated documentation files (the
10 * "Software"), to deal in the Software without restriction, including
11 * without limitation the rights to use, copy, modify, merge, publish,
12 * distribute, sublicense, and/or sell copies of the Software, and to
13 * permit persons to whom the Software is furnished to do so, subject to
14 * the following conditions:
15 *
16 * The above copyright notice and this permission notice (including the
17 * next paragraph) shall be included in all copies or substantial
18 * portions of the Software.
19 *
20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
24 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
25 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
26 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 * SOFTWARE.
28 */
29
30 #include "config.h"
31
32 #include <xf86drm.h>
33 #include <xf86drmMode.h>
34 #include <drm_fourcc.h>
35
36 #include "drm-internal.h"
37
38 static const char *const aspect_ratio_as_string[] = {
39 [WESTON_MODE_PIC_AR_NONE] = "",
40 [WESTON_MODE_PIC_AR_4_3] = " 4:3",
41 [WESTON_MODE_PIC_AR_16_9] = " 16:9",
42 [WESTON_MODE_PIC_AR_64_27] = " 64:27",
43 [WESTON_MODE_PIC_AR_256_135] = " 256:135",
44 };
45
46 /*
47 * Get the aspect-ratio from drmModeModeInfo mode flags.
48 *
49 * @param drm_mode_flags- flags from drmModeModeInfo structure.
50 * @returns aspect-ratio as encoded in enum 'weston_mode_aspect_ratio'.
51 */
52 static enum weston_mode_aspect_ratio
drm_to_weston_mode_aspect_ratio(uint32_t drm_mode_flags)53 drm_to_weston_mode_aspect_ratio(uint32_t drm_mode_flags)
54 {
55 return (drm_mode_flags & DRM_MODE_FLAG_PIC_AR_MASK) >>
56 DRM_MODE_FLAG_PIC_AR_BITS_POS;
57 }
58
59 static const char *
aspect_ratio_to_string(enum weston_mode_aspect_ratio ratio)60 aspect_ratio_to_string(enum weston_mode_aspect_ratio ratio)
61 {
62 if (ratio < 0 || ratio >= ARRAY_LENGTH(aspect_ratio_as_string) ||
63 !aspect_ratio_as_string[ratio])
64 return " (unknown aspect ratio)";
65
66 return aspect_ratio_as_string[ratio];
67 }
68
69 static int
drm_subpixel_to_wayland(int drm_value)70 drm_subpixel_to_wayland(int drm_value)
71 {
72 switch (drm_value) {
73 default:
74 case DRM_MODE_SUBPIXEL_UNKNOWN:
75 return WL_OUTPUT_SUBPIXEL_UNKNOWN;
76 case DRM_MODE_SUBPIXEL_NONE:
77 return WL_OUTPUT_SUBPIXEL_NONE;
78 case DRM_MODE_SUBPIXEL_HORIZONTAL_RGB:
79 return WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB;
80 case DRM_MODE_SUBPIXEL_HORIZONTAL_BGR:
81 return WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR;
82 case DRM_MODE_SUBPIXEL_VERTICAL_RGB:
83 return WL_OUTPUT_SUBPIXEL_VERTICAL_RGB;
84 case DRM_MODE_SUBPIXEL_VERTICAL_BGR:
85 return WL_OUTPUT_SUBPIXEL_VERTICAL_BGR;
86 }
87 }
88
89 int
drm_mode_ensure_blob(struct drm_backend * backend,struct drm_mode * mode)90 drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode)
91 {
92 int ret;
93
94 if (mode->blob_id)
95 return 0;
96
97 ret = drmModeCreatePropertyBlob(backend->drm.fd,
98 &mode->mode_info,
99 sizeof(mode->mode_info),
100 &mode->blob_id);
101 if (ret != 0)
102 weston_log("failed to create mode property blob: %s\n",
103 strerror(errno));
104
105 drm_debug(backend, "\t\t\t[atomic] created new mode blob %lu for %s\n",
106 (unsigned long) mode->blob_id, mode->mode_info.name);
107
108 return ret;
109 }
110
111 static bool
check_non_desktop(struct drm_head * head,drmModeObjectPropertiesPtr props)112 check_non_desktop(struct drm_head *head, drmModeObjectPropertiesPtr props)
113 {
114 struct drm_property_info *non_desktop_info =
115 &head->props_conn[WDRM_CONNECTOR_NON_DESKTOP];
116
117 return drm_property_get_value(non_desktop_info, props, 0);
118 }
119
120 static int
parse_modeline(const char * s,drmModeModeInfo * mode)121 parse_modeline(const char *s, drmModeModeInfo *mode)
122 {
123 char hsync[16];
124 char vsync[16];
125 float fclock;
126
127 memset(mode, 0, sizeof *mode);
128
129 mode->type = DRM_MODE_TYPE_USERDEF;
130 mode->hskew = 0;
131 mode->vscan = 0;
132 mode->vrefresh = 0;
133 mode->flags = 0;
134
135 if (sscanf(s, "%f %hd %hd %hd %hd %hd %hd %hd %hd %15s %15s",
136 &fclock,
137 &mode->hdisplay,
138 &mode->hsync_start,
139 &mode->hsync_end,
140 &mode->htotal,
141 &mode->vdisplay,
142 &mode->vsync_start,
143 &mode->vsync_end,
144 &mode->vtotal, hsync, vsync) != 11)
145 return -1;
146
147 mode->clock = fclock * 1000;
148 if (strcasecmp(hsync, "+hsync") == 0)
149 mode->flags |= DRM_MODE_FLAG_PHSYNC;
150 else if (strcasecmp(hsync, "-hsync") == 0)
151 mode->flags |= DRM_MODE_FLAG_NHSYNC;
152 else
153 return -1;
154
155 if (strcasecmp(vsync, "+vsync") == 0)
156 mode->flags |= DRM_MODE_FLAG_PVSYNC;
157 else if (strcasecmp(vsync, "-vsync") == 0)
158 mode->flags |= DRM_MODE_FLAG_NVSYNC;
159 else
160 return -1;
161
162 snprintf(mode->name, sizeof mode->name, "%dx%d@%.3f",
163 mode->hdisplay, mode->vdisplay, fclock);
164
165 return 0;
166 }
167
168 static void
edid_parse_string(const uint8_t * data,char text[])169 edid_parse_string(const uint8_t *data, char text[])
170 {
171 int i;
172 int replaced = 0;
173
174 /* this is always 12 bytes, but we can't guarantee it's null
175 * terminated or not junk. */
176 strncpy(text, (const char *) data, 12);
177
178 /* guarantee our new string is null-terminated */
179 text[12] = '\0';
180
181 /* remove insane chars */
182 for (i = 0; text[i] != '\0'; i++) {
183 if (text[i] == '\n' ||
184 text[i] == '\r') {
185 text[i] = '\0';
186 break;
187 }
188 }
189
190 /* ensure string is printable */
191 for (i = 0; text[i] != '\0'; i++) {
192 if (!isprint(text[i])) {
193 text[i] = '-';
194 replaced++;
195 }
196 }
197
198 /* if the string is random junk, ignore the string */
199 if (replaced > 4)
200 text[0] = '\0';
201 }
202
203 #define EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING 0xfe
204 #define EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME 0xfc
205 #define EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER 0xff
206 #define EDID_OFFSET_DATA_BLOCKS 0x36
207 #define EDID_OFFSET_LAST_BLOCK 0x6c
208 #define EDID_OFFSET_PNPID 0x08
209 #define EDID_OFFSET_SERIAL 0x0c
210
211 static int
edid_parse(struct drm_edid * edid,const uint8_t * data,size_t length)212 edid_parse(struct drm_edid *edid, const uint8_t *data, size_t length)
213 {
214 int i;
215 uint32_t serial_number;
216
217 /* check header */
218 if (length < 128)
219 return -1;
220 if (data[0] != 0x00 || data[1] != 0xff)
221 return -1;
222
223 /* decode the PNP ID from three 5 bit words packed into 2 bytes
224 * /--08--\/--09--\
225 * 7654321076543210
226 * |\---/\---/\---/
227 * R C1 C2 C3 */
228 edid->pnp_id[0] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x7c) / 4) - 1;
229 edid->pnp_id[1] = 'A' + ((data[EDID_OFFSET_PNPID + 0] & 0x3) * 8) + ((data[EDID_OFFSET_PNPID + 1] & 0xe0) / 32) - 1;
230 edid->pnp_id[2] = 'A' + (data[EDID_OFFSET_PNPID + 1] & 0x1f) - 1;
231 edid->pnp_id[3] = '\0';
232
233 /* maybe there isn't a ASCII serial number descriptor, so use this instead */
234 serial_number = (uint32_t) data[EDID_OFFSET_SERIAL + 0];
235 serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 1] * 0x100;
236 serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 2] * 0x10000;
237 serial_number += (uint32_t) data[EDID_OFFSET_SERIAL + 3] * 0x1000000;
238 if (serial_number > 0)
239 sprintf(edid->serial_number, "%lu", (unsigned long) serial_number);
240
241 /* parse EDID data */
242 for (i = EDID_OFFSET_DATA_BLOCKS;
243 i <= EDID_OFFSET_LAST_BLOCK;
244 i += 18) {
245 /* ignore pixel clock data */
246 if (data[i] != 0)
247 continue;
248 if (data[i+2] != 0)
249 continue;
250
251 /* any useful blocks? */
252 if (data[i+3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_NAME) {
253 edid_parse_string(&data[i+5],
254 edid->monitor_name);
255 } else if (data[i+3] == EDID_DESCRIPTOR_DISPLAY_PRODUCT_SERIAL_NUMBER) {
256 edid_parse_string(&data[i+5],
257 edid->serial_number);
258 } else if (data[i+3] == EDID_DESCRIPTOR_ALPHANUMERIC_DATA_STRING) {
259 edid_parse_string(&data[i+5],
260 edid->eisa_id);
261 }
262 }
263 return 0;
264 }
265
266 /** Parse monitor make, model and serial from EDID
267 *
268 * \param head The head whose \c drm_edid to fill in.
269 * \param props The DRM connector properties to get the EDID from.
270 * \param[out] make The monitor make (PNP ID).
271 * \param[out] model The monitor model (name).
272 * \param[out] serial_number The monitor serial number.
273 *
274 * Each of \c *make, \c *model and \c *serial_number are set only if the
275 * information is found in the EDID. The pointers they are set to must not
276 * be free()'d explicitly, instead they get implicitly freed when the
277 * \c drm_head is destroyed.
278 */
279 static void
find_and_parse_output_edid(struct drm_head * head,drmModeObjectPropertiesPtr props,const char ** make,const char ** model,const char ** serial_number)280 find_and_parse_output_edid(struct drm_head *head,
281 drmModeObjectPropertiesPtr props,
282 const char **make,
283 const char **model,
284 const char **serial_number)
285 {
286 drmModePropertyBlobPtr edid_blob = NULL;
287 uint32_t blob_id;
288 int rc;
289
290 blob_id =
291 drm_property_get_value(&head->props_conn[WDRM_CONNECTOR_EDID],
292 props, 0);
293 if (!blob_id)
294 return;
295
296 edid_blob = drmModeGetPropertyBlob(head->backend->drm.fd, blob_id);
297 if (!edid_blob)
298 return;
299
300 rc = edid_parse(&head->edid,
301 edid_blob->data,
302 edid_blob->length);
303 if (!rc) {
304 if (head->edid.pnp_id[0] != '\0')
305 *make = head->edid.pnp_id;
306 if (head->edid.monitor_name[0] != '\0')
307 *model = head->edid.monitor_name;
308 if (head->edid.serial_number[0] != '\0')
309 *serial_number = head->edid.serial_number;
310 }
311 drmModeFreePropertyBlob(edid_blob);
312 }
313
314 static uint32_t
drm_refresh_rate_mHz(const drmModeModeInfo * info)315 drm_refresh_rate_mHz(const drmModeModeInfo *info)
316 {
317 uint64_t refresh;
318
319 /* Calculate higher precision (mHz) refresh rate */
320 refresh = (info->clock * 1000000LL / info->htotal +
321 info->vtotal / 2) / info->vtotal;
322
323 if (info->flags & DRM_MODE_FLAG_INTERLACE)
324 refresh *= 2;
325 if (info->flags & DRM_MODE_FLAG_DBLSCAN)
326 refresh /= 2;
327 if (info->vscan > 1)
328 refresh /= info->vscan;
329
330 return refresh;
331 }
332
333 /**
334 * Add a mode to output's mode list
335 *
336 * Copy the supplied DRM mode into a Weston mode structure, and add it to the
337 * output's mode list.
338 *
339 * @param output DRM output to add mode to
340 * @param info DRM mode structure to add
341 * @returns Newly-allocated Weston/DRM mode structure
342 */
343 static struct drm_mode *
drm_output_add_mode(struct drm_output * output,const drmModeModeInfo * info)344 drm_output_add_mode(struct drm_output *output, const drmModeModeInfo *info)
345 {
346 struct drm_mode *mode;
347
348 mode = malloc(sizeof *mode);
349 if (mode == NULL)
350 return NULL;
351
352 mode->base.flags = 0;
353 mode->base.width = info->hdisplay;
354 mode->base.height = info->vdisplay;
355
356 mode->base.refresh = drm_refresh_rate_mHz(info);
357 mode->mode_info = *info;
358 mode->blob_id = 0;
359
360 if (info->type & DRM_MODE_TYPE_PREFERRED)
361 mode->base.flags |= WL_OUTPUT_MODE_PREFERRED;
362
363 mode->base.aspect_ratio = drm_to_weston_mode_aspect_ratio(info->flags);
364
365 wl_list_insert(output->base.mode_list.prev, &mode->base.link);
366
367 return mode;
368 }
369
370 /**
371 * Destroys a mode, and removes it from the list.
372 */
373 static void
drm_output_destroy_mode(struct drm_backend * backend,struct drm_mode * mode)374 drm_output_destroy_mode(struct drm_backend *backend, struct drm_mode *mode)
375 {
376 if (mode->blob_id)
377 drmModeDestroyPropertyBlob(backend->drm.fd, mode->blob_id);
378 wl_list_remove(&mode->base.link);
379 free(mode);
380 }
381
382 /** Destroy a list of drm_modes
383 *
384 * @param backend The backend for releasing mode property blobs.
385 * @param mode_list The list linked by drm_mode::base.link.
386 */
387 void
drm_mode_list_destroy(struct drm_backend * backend,struct wl_list * mode_list)388 drm_mode_list_destroy(struct drm_backend *backend, struct wl_list *mode_list)
389 {
390 struct drm_mode *mode, *next;
391
392 wl_list_for_each_safe(mode, next, mode_list, base.link)
393 drm_output_destroy_mode(backend, mode);
394 }
395
396 void
drm_output_print_modes(struct drm_output * output)397 drm_output_print_modes(struct drm_output *output)
398 {
399 struct weston_mode *m;
400 struct drm_mode *dm;
401 const char *aspect_ratio;
402
403 wl_list_for_each(m, &output->base.mode_list, link) {
404 dm = to_drm_mode(m);
405
406 aspect_ratio = aspect_ratio_to_string(m->aspect_ratio);
407 weston_log_continue(STAMP_SPACE "%dx%d@%.1f%s%s%s, %.1f MHz\n",
408 m->width, m->height, m->refresh / 1000.0,
409 aspect_ratio,
410 m->flags & WL_OUTPUT_MODE_PREFERRED ?
411 ", preferred" : "",
412 m->flags & WL_OUTPUT_MODE_CURRENT ?
413 ", current" : "",
414 dm->mode_info.clock / 1000.0);
415 }
416 }
417
418
419 /**
420 * Find the closest-matching mode for a given target
421 *
422 * Given a target mode, find the most suitable mode amongst the output's
423 * current mode list to use, preferring the current mode if possible, to
424 * avoid an expensive mode switch.
425 *
426 * @param output DRM output
427 * @param target_mode Mode to attempt to match
428 * @returns Pointer to a mode from the output's mode list
429 */
430 struct drm_mode *
drm_output_choose_mode(struct drm_output * output,struct weston_mode * target_mode)431 drm_output_choose_mode(struct drm_output *output,
432 struct weston_mode *target_mode)
433 {
434 struct drm_mode *tmp_mode = NULL, *mode_fall_back = NULL, *mode;
435 enum weston_mode_aspect_ratio src_aspect = WESTON_MODE_PIC_AR_NONE;
436 enum weston_mode_aspect_ratio target_aspect = WESTON_MODE_PIC_AR_NONE;
437 struct drm_backend *b;
438
439 b = to_drm_backend(output->base.compositor);
440 target_aspect = target_mode->aspect_ratio;
441 src_aspect = output->base.current_mode->aspect_ratio;
442 if (output->base.current_mode->width == target_mode->width &&
443 output->base.current_mode->height == target_mode->height &&
444 (output->base.current_mode->refresh == target_mode->refresh ||
445 target_mode->refresh == 0)) {
446 if (!b->aspect_ratio_supported || src_aspect == target_aspect)
447 return to_drm_mode(output->base.current_mode);
448 }
449
450 wl_list_for_each(mode, &output->base.mode_list, base.link) {
451
452 src_aspect = mode->base.aspect_ratio;
453 if (mode->mode_info.hdisplay == target_mode->width &&
454 mode->mode_info.vdisplay == target_mode->height) {
455 if (mode->base.refresh == target_mode->refresh ||
456 target_mode->refresh == 0) {
457 if (!b->aspect_ratio_supported ||
458 src_aspect == target_aspect)
459 return mode;
460 else if (!mode_fall_back)
461 mode_fall_back = mode;
462 } else if (!tmp_mode) {
463 tmp_mode = mode;
464 }
465 }
466 }
467
468 if (mode_fall_back)
469 return mode_fall_back;
470
471 return tmp_mode;
472 }
473
474 void
update_head_from_connector(struct drm_head * head,drmModeObjectProperties * props)475 update_head_from_connector(struct drm_head *head,
476 drmModeObjectProperties *props)
477 {
478 const char *make = "unknown";
479 const char *model = "unknown";
480 const char *serial_number = "unknown";
481
482 find_and_parse_output_edid(head, props, &make, &model, &serial_number);
483 weston_head_set_monitor_strings(&head->base, make, model, serial_number);
484 weston_head_set_non_desktop(&head->base,
485 check_non_desktop(head, props));
486 weston_head_set_subpixel(&head->base,
487 drm_subpixel_to_wayland(head->connector->subpixel));
488
489 weston_head_set_physical_size(&head->base, head->connector->mmWidth,
490 head->connector->mmHeight);
491
492 /* Unknown connection status is assumed disconnected. */
493 weston_head_set_connection_status(&head->base,
494 head->connector->connection == DRM_MODE_CONNECTED);
495 }
496
497 /**
498 * Choose suitable mode for an output
499 *
500 * Find the most suitable mode to use for initial setup (or reconfiguration on
501 * hotplug etc) for a DRM output.
502 *
503 * @param backend the DRM backend
504 * @param output DRM output to choose mode for
505 * @param mode Strategy and preference to use when choosing mode
506 * @param modeline Manually-entered mode (may be NULL)
507 * @param current_mode Mode currently being displayed on this output
508 * @returns A mode from the output's mode list, or NULL if none available
509 */
510 static struct drm_mode *
drm_output_choose_initial_mode(struct drm_backend * backend,struct drm_output * output,enum weston_drm_backend_output_mode mode,const char * modeline,const drmModeModeInfo * current_mode)511 drm_output_choose_initial_mode(struct drm_backend *backend,
512 struct drm_output *output,
513 enum weston_drm_backend_output_mode mode,
514 const char *modeline,
515 const drmModeModeInfo *current_mode)
516 {
517 struct drm_mode *preferred = NULL;
518 struct drm_mode *current = NULL;
519 struct drm_mode *configured = NULL;
520 struct drm_mode *config_fall_back = NULL;
521 struct drm_mode *best = NULL;
522 struct drm_mode *drm_mode;
523 drmModeModeInfo drm_modeline;
524 int32_t width = 0;
525 int32_t height = 0;
526 uint32_t refresh = 0;
527 uint32_t aspect_width = 0;
528 uint32_t aspect_height = 0;
529 enum weston_mode_aspect_ratio aspect_ratio = WESTON_MODE_PIC_AR_NONE;
530 int n;
531
532 if (mode == WESTON_DRM_BACKEND_OUTPUT_PREFERRED && modeline) {
533 n = sscanf(modeline, "%dx%d@%d %u:%u", &width, &height,
534 &refresh, &aspect_width, &aspect_height);
535 if (backend->aspect_ratio_supported && n == 5) {
536 if (aspect_width == 4 && aspect_height == 3)
537 aspect_ratio = WESTON_MODE_PIC_AR_4_3;
538 else if (aspect_width == 16 && aspect_height == 9)
539 aspect_ratio = WESTON_MODE_PIC_AR_16_9;
540 else if (aspect_width == 64 && aspect_height == 27)
541 aspect_ratio = WESTON_MODE_PIC_AR_64_27;
542 else if (aspect_width == 256 && aspect_height == 135)
543 aspect_ratio = WESTON_MODE_PIC_AR_256_135;
544 else
545 weston_log("Invalid modeline \"%s\" for output %s\n",
546 modeline, output->base.name);
547 }
548 if (n != 2 && n != 3 && n != 5) {
549 width = -1;
550
551 if (parse_modeline(modeline, &drm_modeline) == 0) {
552 configured = drm_output_add_mode(output, &drm_modeline);
553 if (!configured)
554 return NULL;
555 } else {
556 weston_log("Invalid modeline \"%s\" for output %s\n",
557 modeline, output->base.name);
558 }
559 }
560 }
561
562 wl_list_for_each_reverse(drm_mode, &output->base.mode_list, base.link) {
563 if (width == drm_mode->base.width &&
564 height == drm_mode->base.height &&
565 (refresh == 0 || refresh == drm_mode->mode_info.vrefresh)) {
566 if (!backend->aspect_ratio_supported ||
567 aspect_ratio == drm_mode->base.aspect_ratio)
568 configured = drm_mode;
569 else
570 config_fall_back = drm_mode;
571 }
572
573 if (memcmp(current_mode, &drm_mode->mode_info,
574 sizeof *current_mode) == 0)
575 current = drm_mode;
576
577 if (drm_mode->base.flags & WL_OUTPUT_MODE_PREFERRED)
578 preferred = drm_mode;
579
580 best = drm_mode;
581 }
582
583 if (current == NULL && current_mode->clock != 0) {
584 current = drm_output_add_mode(output, current_mode);
585 if (!current)
586 return NULL;
587 }
588
589 if (mode == WESTON_DRM_BACKEND_OUTPUT_CURRENT)
590 configured = current;
591
592 if (configured)
593 return configured;
594
595 if (config_fall_back)
596 return config_fall_back;
597
598 if (preferred)
599 return preferred;
600
601 if (current)
602 return current;
603
604 if (best)
605 return best;
606
607 weston_log("no available modes for %s\n", output->base.name);
608 return NULL;
609 }
610
611 static uint32_t
u32distance(uint32_t a,uint32_t b)612 u32distance(uint32_t a, uint32_t b)
613 {
614 if (a < b)
615 return b - a;
616 else
617 return a - b;
618 }
619
620 /** Choose equivalent mode
621 *
622 * If the two modes are not equivalent, return NULL.
623 * Otherwise return the mode that is more likely to work in place of both.
624 *
625 * None of the fuzzy matching criteria in this function have any justification.
626 *
627 * typedef struct _drmModeModeInfo {
628 * uint32_t clock;
629 * uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew;
630 * uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan;
631 *
632 * uint32_t vrefresh;
633 *
634 * uint32_t flags;
635 * uint32_t type;
636 * char name[DRM_DISPLAY_MODE_LEN];
637 * } drmModeModeInfo, *drmModeModeInfoPtr;
638 */
639 static const drmModeModeInfo *
drm_mode_pick_equivalent(const drmModeModeInfo * a,const drmModeModeInfo * b)640 drm_mode_pick_equivalent(const drmModeModeInfo *a, const drmModeModeInfo *b)
641 {
642 uint32_t refresh_a, refresh_b;
643
644 if (a->hdisplay != b->hdisplay || a->vdisplay != b->vdisplay)
645 return NULL;
646
647 if (a->flags != b->flags)
648 return NULL;
649
650 /* kHz */
651 if (u32distance(a->clock, b->clock) > 500)
652 return NULL;
653
654 refresh_a = drm_refresh_rate_mHz(a);
655 refresh_b = drm_refresh_rate_mHz(b);
656 if (u32distance(refresh_a, refresh_b) > 50)
657 return NULL;
658
659 if ((a->type ^ b->type) & DRM_MODE_TYPE_PREFERRED) {
660 if (a->type & DRM_MODE_TYPE_PREFERRED)
661 return a;
662 else
663 return b;
664 }
665
666 return a;
667 }
668
669 /* If the given mode info is not already in the list, add it.
670 * If it is in the list, either keep the existing or replace it,
671 * depending on which one is "better".
672 */
673 static int
drm_output_try_add_mode(struct drm_output * output,const drmModeModeInfo * info)674 drm_output_try_add_mode(struct drm_output *output, const drmModeModeInfo *info)
675 {
676 struct weston_mode *base;
677 struct drm_mode *mode;
678 struct drm_backend *backend;
679 const drmModeModeInfo *chosen = NULL;
680
681 assert(info);
682
683 wl_list_for_each(base, &output->base.mode_list, link) {
684 mode = to_drm_mode(base);
685 chosen = drm_mode_pick_equivalent(&mode->mode_info, info);
686 if (chosen)
687 break;
688 }
689
690 if (chosen == info) {
691 backend = to_drm_backend(output->base.compositor);
692 drm_output_destroy_mode(backend, mode);
693 chosen = NULL;
694 }
695
696 if (!chosen) {
697 mode = drm_output_add_mode(output, info);
698 if (!mode)
699 return -1;
700 }
701 /* else { the equivalent mode is already in the list } */
702
703 return 0;
704 }
705
706 /** Rewrite the output's mode list
707 *
708 * @param output The output.
709 * @return 0 on success, -1 on failure.
710 *
711 * Destroy all existing modes in the list, and reconstruct a new list from
712 * scratch, based on the currently attached heads.
713 *
714 * On failure the output's mode list may contain some modes.
715 */
716 static int
drm_output_update_modelist_from_heads(struct drm_output * output)717 drm_output_update_modelist_from_heads(struct drm_output *output)
718 {
719 struct drm_backend *backend = to_drm_backend(output->base.compositor);
720 struct weston_head *head_base;
721 struct drm_head *head;
722 int i;
723 int ret;
724
725 assert(!output->base.enabled);
726
727 drm_mode_list_destroy(backend, &output->base.mode_list);
728
729 wl_list_for_each(head_base, &output->base.head_list, output_link) {
730 head = to_drm_head(head_base);
731 for (i = 0; i < head->connector->count_modes; i++) {
732 ret = drm_output_try_add_mode(output,
733 &head->connector->modes[i]);
734 if (ret < 0)
735 return -1;
736 }
737 }
738
739 return 0;
740 }
741
742 int
drm_output_set_mode(struct weston_output * base,enum weston_drm_backend_output_mode mode,const char * modeline)743 drm_output_set_mode(struct weston_output *base,
744 enum weston_drm_backend_output_mode mode,
745 const char *modeline)
746 {
747 struct drm_output *output = to_drm_output(base);
748 struct drm_backend *b = to_drm_backend(base->compositor);
749 struct drm_head *head = to_drm_head(weston_output_get_first_head(base));
750
751 struct drm_mode *current;
752
753 if (output->virtual)
754 return -1;
755
756 if (drm_output_update_modelist_from_heads(output) < 0)
757 return -1;
758
759 current = drm_output_choose_initial_mode(b, output, mode, modeline,
760 &head->inherited_mode);
761 if (!current)
762 return -1;
763
764 output->base.current_mode = ¤t->base;
765 output->base.current_mode->flags |= WL_OUTPUT_MODE_CURRENT;
766
767 /* Set native_ fields, so weston_output_mode_switch_to_native() works */
768 output->base.native_mode = output->base.current_mode;
769 output->base.native_scale = output->base.current_scale;
770
771 return 0;
772 }
773