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 = &current->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