mirror of
https://github.com/torvalds/linux.git
synced 2024-11-01 13:03:25 +01:00
89ac522d45
The EDID firmware loading mechanism introduced a few built-in EDIDs that could be forced on any connector, bypassing the EDIDs it exposes. While convenient, this limited set of EDIDs doesn't take into account the connector type, and we can end up with an EDID that is completely invalid for a given connector. For example, the edid/800x600.bin file matches the following EDID: edid-decode (hex): 00 ff ff ff ff ff ff 00 31 d8 00 00 00 00 00 00 05 16 01 03 6d 1b 14 78 ea 5e c0 a4 59 4a 98 25 20 50 54 01 00 00 45 40 01 01 01 01 01 01 01 01 01 01 01 01 01 01 a0 0f 20 00 31 58 1c 20 28 80 14 00 15 d0 10 00 00 1e 00 00 00 ff 00 4c 69 6e 75 78 20 23 30 0a 20 20 20 20 00 00 00 fd 00 3b 3d 24 26 05 00 0a 20 20 20 20 20 20 00 00 00 fc 00 4c 69 6e 75 78 20 53 56 47 41 0a 20 20 00 c2 ---------------- Block 0, Base EDID: EDID Structure Version & Revision: 1.3 Vendor & Product Identification: Manufacturer: LNX Model: 0 Made in: week 5 of 2012 Basic Display Parameters & Features: Analog display Signal Level Standard: 0.700 : 0.000 : 0.700 V p-p Blank level equals black level Sync: Separate Composite Serration Maximum image size: 27 cm x 20 cm Gamma: 2.20 DPMS levels: Standby Suspend Off RGB color display First detailed timing is the preferred timing Color Characteristics: Red : 0.6416, 0.3486 Green: 0.2919, 0.5957 Blue : 0.1474, 0.1250 White: 0.3125, 0.3281 Established Timings I & II: DMT 0x09: 800x600 60.316541 Hz 4:3 37.879 kHz 40.000000 MHz Standard Timings: DMT 0x09: 800x600 60.316541 Hz 4:3 37.879 kHz 40.000000 MHz Detailed Timing Descriptors: DTD 1: 800x600 60.316541 Hz 4:3 37.879 kHz 40.000000 MHz (277 mm x 208 mm) Hfront 40 Hsync 128 Hback 88 Hpol P Vfront 1 Vsync 4 Vback 23 Vpol P Display Product Serial Number: 'Linux #0' Display Range Limits: Monitor ranges (GTF): 59-61 Hz V, 36-38 kHz H, max dotclock 50 MHz Display Product Name: 'Linux SVGA' Checksum: 0xc2 So, an analog monitor EDID. However, if the connector was an HDMI monitor for example, it breaks the HDMI specification that requires, among other things, a digital display, the VIC 1 mode and an HDMI Forum Vendor Specific Data Block in an CTA-861 extension. We thus end up with a completely invalid EDID, which thus might confuse HDMI-related code that could parse it. After some discussions on IRC, we identified mainly two ways to fix this: - We can either create more EDIDs for each connector type to provide a built-in EDID that matches the resolution passed in the name, and still be a sensible EDID for that connector type; - Or we can just prevent the EDID to be exposed to userspace if it's built-in. Or possibly both. However, the conclusion was that maybe we just don't need the built-in EDIDs at all and we should just get rid of them. So here we are. Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch> Acked-by: Jani Nikula <jani.nikula@intel.com> Acked-by: Pekka Paalanen <pekka.paalanen@collabora.com> Acked-by: Thomas Zimmermann <tzimmermann@suse.de> Signed-off-by: Maxime Ripard <mripard@kernel.org> Link: https://patchwork.freedesktop.org/patch/msgid/20240221092636.691701-1-mripard@kernel.org
107 lines
2.6 KiB
C
107 lines
2.6 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
/*
|
|
drm_edid_load.c: use a built-in EDID data set or load it via the firmware
|
|
interface
|
|
|
|
Copyright (C) 2012 Carsten Emde <C.Emde@osadl.org>
|
|
|
|
*/
|
|
|
|
#include <linux/firmware.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
|
|
#include <drm/drm_connector.h>
|
|
#include <drm/drm_drv.h>
|
|
#include <drm/drm_edid.h>
|
|
#include <drm/drm_print.h>
|
|
|
|
#include "drm_crtc_internal.h"
|
|
|
|
static char edid_firmware[PATH_MAX];
|
|
module_param_string(edid_firmware, edid_firmware, sizeof(edid_firmware), 0644);
|
|
MODULE_PARM_DESC(edid_firmware,
|
|
"Do not probe monitor, use specified EDID blob from /lib/firmware instead.");
|
|
|
|
static const struct drm_edid *edid_load(struct drm_connector *connector, const char *name)
|
|
{
|
|
const struct firmware *fw = NULL;
|
|
const struct drm_edid *drm_edid;
|
|
int err;
|
|
|
|
err = request_firmware(&fw, name, connector->dev->dev);
|
|
if (err) {
|
|
drm_err(connector->dev,
|
|
"[CONNECTOR:%d:%s] Requesting EDID firmware \"%s\" failed (err=%d)\n",
|
|
connector->base.id, connector->name,
|
|
name, err);
|
|
return ERR_PTR(err);
|
|
}
|
|
|
|
drm_dbg_kms(connector->dev, "[CONNECTOR:%d:%s] Loaded external firmware EDID \"%s\"\n",
|
|
connector->base.id, connector->name, name);
|
|
|
|
drm_edid = drm_edid_alloc(fw->data, fw->size);
|
|
if (!drm_edid_valid(drm_edid)) {
|
|
drm_err(connector->dev, "Invalid firmware EDID \"%s\"\n", name);
|
|
drm_edid_free(drm_edid);
|
|
drm_edid = ERR_PTR(-EINVAL);
|
|
}
|
|
|
|
release_firmware(fw);
|
|
|
|
return drm_edid;
|
|
}
|
|
|
|
const struct drm_edid *drm_edid_load_firmware(struct drm_connector *connector)
|
|
{
|
|
char *edidname, *last, *colon, *fwstr, *edidstr, *fallback = NULL;
|
|
const struct drm_edid *drm_edid;
|
|
|
|
if (edid_firmware[0] == '\0')
|
|
return ERR_PTR(-ENOENT);
|
|
|
|
/*
|
|
* If there are multiple edid files specified and separated
|
|
* by commas, search through the list looking for one that
|
|
* matches the connector.
|
|
*
|
|
* If there's one or more that doesn't specify a connector, keep
|
|
* the last one found one as a fallback.
|
|
*/
|
|
fwstr = kstrdup(edid_firmware, GFP_KERNEL);
|
|
if (!fwstr)
|
|
return ERR_PTR(-ENOMEM);
|
|
edidstr = fwstr;
|
|
|
|
while ((edidname = strsep(&edidstr, ","))) {
|
|
colon = strchr(edidname, ':');
|
|
if (colon != NULL) {
|
|
if (strncmp(connector->name, edidname, colon - edidname))
|
|
continue;
|
|
edidname = colon + 1;
|
|
break;
|
|
}
|
|
|
|
if (*edidname != '\0') /* corner case: multiple ',' */
|
|
fallback = edidname;
|
|
}
|
|
|
|
if (!edidname) {
|
|
if (!fallback) {
|
|
kfree(fwstr);
|
|
return ERR_PTR(-ENOENT);
|
|
}
|
|
edidname = fallback;
|
|
}
|
|
|
|
last = edidname + strlen(edidname) - 1;
|
|
if (*last == '\n')
|
|
*last = '\0';
|
|
|
|
drm_edid = edid_load(connector, edidname);
|
|
|
|
kfree(fwstr);
|
|
|
|
return drm_edid;
|
|
}
|