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