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