From 819060d54b19321f7f9f213e90c1b8d3dc87a45e Mon Sep 17 00:00:00 2001 From: zrj Date: Fri, 20 Nov 2015 13:59:41 +0200 Subject: [PATCH] drm: Implement simple broken EDID override by loading firmware (v16) Main differences between this variant and its gpl analogue: * permissive license; * no builtin generic EDID data blocks; Generic EDID firmware can be used from new dports/sysutils/devedid-data port. Still it is better to use monitor specific EDID block, either by modifying generic variants or repairing dumped one or even better extracting from working identical monitor if it is available (look for hex dump in Xorg.0.log). Make sure that used fw.bin at least has correct resolutions and valid checksum. This can be done with sysutils/edid-decode utility. Tested with broken lcd panel returning corrupted EDID block on dvi port: * radeonkms R7 240, dvi and dvi2hdmi cable adapter echo 'drm.edid_firmware="DVI-D-1:edidfw_repaired"' >> /boot/loader.conf * i915 Haswell i7-4712MQ laptop, dvi2hdmi cable adapter echo 'drm.edid_firmware="HDMI-A-1:edidfw_1920x1080"' >> /boot/loader.conf echo 'drm.video.hdmia1="e" # force enable on i915' >> /boot/loader.conf While here, document tunables: drm.video. and new drm.edid_firmware. --- share/man/man4/drm.4 | 52 ++++++++ sys/conf/files | 1 + sys/dev/drm/drm/Makefile | 1 + sys/dev/drm/drm_edid_load.c | 170 +++++++++++++++++++++++++ sys/dev/drm/include/drm/drm_os_linux.h | 3 + 5 files changed, 227 insertions(+) create mode 100644 sys/dev/drm/drm_edid_load.c diff --git a/share/man/man4/drm.4 b/share/man/man4/drm.4 index eb337550b7..d588252d2d 100644 --- a/share/man/man4/drm.4 +++ b/share/man/man4/drm.4 @@ -97,6 +97,58 @@ loader tunable. A read only .Xr sysctl 8 variable of the same name is provided for obtaining its current value. +.Sh LOADER TUNABLES +Tunables can be set at the +.Xr loader 8 +prompt before booting the kernel or stored in +.Xr loader.conf 5 . +.Bl -tag -width "xxxxxx" +.It Va drm.edid_firmware +Load external EDID binary monitor data from firmware module. +Useful if monitor is not sending appropiate EDID block, no video on +connected monitor or there is a need to enforce custom video modes. +Optionally, connector name can be specified before firmware name +followed by colon, for example: +.Pp +.Dl drm.edid_firmware="DVI-D-1:edidfw_repaired" +.Pp +Notes: +.Xr drm(4) +and +.Xr Xorg(1) +use different naming conventions for connector names. +This tunable only applies for KMS drivers. +.It Va drm.video. +Can be used to set framebuffer parameters for drm(4) connector +in lower case form and without dashes. +Mode specifier format: +.Pp +.Dl x[M][R][-][@][i][m][eDd] +.Bl -tag -width "" +.It +X resolution, e.g. 1920 +.It +Y resolution, e.g. 1080 +.It M +calculate timings using VESA CVT instead of table lookups +.It R +reduced blanking on digital monitors +.It +color depth +.It +refresh rate +.It i +calculate timings for interlaced mode +.It m +margins used in calculations +.It e +force enable output +.It D +same as e option, but force digital output on HDMI/DVI +.It d +force disable output +.El +.El .Sh SYSCTL VARIABLES A number of .Xr sysctl 8 diff --git a/sys/conf/files b/sys/conf/files index 06fd1c1bdc..1382fe55b1 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1987,6 +1987,7 @@ dev/drm/drm_dp_iic_helper.c optional drm dev/drm/drm_dragonfly.c optional drm dev/drm/drm_drv.c optional drm dev/drm/drm_edid.c optional drm +dev/drm/drm_edid_load.c optional drm dev/drm/drm_fb_helper.c optional drm dev/drm/drm_fops.c optional drm dev/drm/drm_gem.c optional drm diff --git a/sys/dev/drm/drm/Makefile b/sys/dev/drm/drm/Makefile index 9df50f3bb3..3ae648992a 100644 --- a/sys/dev/drm/drm/Makefile +++ b/sys/dev/drm/drm/Makefile @@ -15,6 +15,7 @@ SRCS = \ drm_dragonfly.c \ drm_drv.c \ drm_edid.c \ + drm_edid_load.c \ drm_fb_helper.c \ drm_fops.c \ drm_gem.c \ diff --git a/sys/dev/drm/drm_edid_load.c b/sys/dev/drm/drm_edid_load.c new file mode 100644 index 0000000000..f8551826ab --- /dev/null +++ b/sys/dev/drm/drm_edid_load.c @@ -0,0 +1,170 @@ +/* + * Copyright (c) 2015 Rimvydas Jasinskas + * + * Simple EDID firmware handling routines derived from + * drm_edid.c:drm_do_get_edid() + * + * Copyright (c) 2007-2008 Intel Corporation + * Jesse Barnes + * Copyright 2010 Red Hat, Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sub license, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the + * next paragraph) shall be included in all copies or substantial portions + * of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#include +#include +#include +#include + +#ifdef CONFIG_DRM_LOAD_EDID_FIRMWARE +static u8 *do_edid_fw_load(struct drm_connector *connector, const char *fwname, + const char *connector_name); + +static char edidfw_tun[256]; +TUNABLE_STR("drm.edid_firmware", edidfw_tun, sizeof(edidfw_tun)); + +int drm_load_edid_firmware(struct drm_connector *connector) +{ + const char *connector_name = connector->name; + char *cp, *fwname = edidfw_tun; + struct edid *fwedid; + int ret; + + if (*fwname == '\0') + return 0; + + /* Check for connector specifier presence */ + if ((cp = strchr(fwname, ':')) != NULL) { + /* if connector name doesn't match, we're done */ + if (strncmp(connector_name, fwname, cp - fwname)) + return 0; + fwname = cp + 1; + if (*fwname == '\0') + return 0; + } + + fwedid = (struct edid *)do_edid_fw_load(connector, fwname, connector_name); + if (fwedid == NULL) + return 0; + + drm_mode_connector_update_edid_property(connector, fwedid); + ret = drm_add_edid_modes(connector, fwedid); + kfree(fwedid); + + return ret; +} + +static u8 * +do_edid_fw_load(struct drm_connector *connector, const char *fwname, + const char *connector_name) +{ + const struct firmware *fw = NULL; + const u8 *fwdata; + u8 *block = NULL, *new = NULL; + int fwsize, expected; + int j, valid_extensions = 0; + bool print_bad_edid = !connector->bad_edid_counter || (drm_debug & DRM_UT_KMS); + + fw = firmware_get(fwname); + + if (fw == NULL) { + DRM_ERROR("Requesting EDID firmware %s failed\n", fwname); + return (NULL); + } + + fwdata = fw->data; + fwsize = fw->datasize; + + if (fwsize < EDID_LENGTH) + goto fw_out; + + expected = (fwdata[0x7e] + 1) * EDID_LENGTH; + if (expected != fwsize) { + DRM_ERROR("Size of EDID firmware %s is invalid: %d vs %d(got)\n", + fwname, expected, fwsize); + goto fw_out; + } + + block = kmalloc(fwsize, M_DRM, GFP_KERNEL); + if (block == NULL) { + goto fw_out; + } + memcpy(block, fwdata, fwsize); + + /* now it is safe to release the firmware */ +fw_out: + fwdata = NULL; + if (fw != NULL) { + firmware_put(fw, FIRMWARE_UNLOAD); + } + + if (block == NULL) + return (NULL); + + /* first check the base block */ + if (!drm_edid_block_valid(block, 0, print_bad_edid)) { + connector->bad_edid_counter++; + DRM_ERROR("EDID firmware %s base block is invalid ", fwname); + goto out; + } + + DRM_INFO("Got EDID base block from %s for connector %s\n", fwname, connector_name); + + /* if there's no extensions, we're done */ + if (block[0x7e] == 0) + return block; + + /* XXX then extension blocks */ + WARN(1, "Loading EDID firmware with extensions is untested!\n"); + + for (j = 1; j <= block[0x7e]; j++) { + /* if we skiped any extension block we have to shuffle good ones */ + if (j != valid_extensions + 1) { + memcpy(block + (valid_extensions + 1) * EDID_LENGTH, + block + (j * EDID_LENGTH), EDID_LENGTH); + } + if (drm_edid_block_valid(block + j * EDID_LENGTH, j, print_bad_edid)) { + valid_extensions++; + } + } + + if (valid_extensions != block[0x7e]) { + block[EDID_LENGTH-1] += block[0x7e] - valid_extensions; + block[0x7e] = valid_extensions; + new = krealloc(block, (valid_extensions + 1) * EDID_LENGTH, M_DRM, M_WAITOK); + if (new == NULL) + goto out; + block = new; + } + + if (valid_extensions > 0) { + DRM_INFO("Got %d extensions in EDID firmware from %s for connector %s\n", + valid_extensions, fwname, connector_name); + } + + /* if got to here return edid block */ + return block; + +out: + kfree(block); + return (NULL); +} + +#endif /* CONFIG_DRM_LOAD_EDID_FIRMWARE */ diff --git a/sys/dev/drm/include/drm/drm_os_linux.h b/sys/dev/drm/include/drm/drm_os_linux.h index 6adea578ef..4091d80514 100644 --- a/sys/dev/drm/include/drm/drm_os_linux.h +++ b/sys/dev/drm/include/drm/drm_os_linux.h @@ -129,3 +129,6 @@ do { \ if (drm_debug && drm_notyet_flag) \ kprintf("NOTYET: %s at %s:%d\n", __func__, __FILE__, __LINE__); \ } while (0) + +/* include code to override EDID blocks from external firmware modules */ +#define CONFIG_DRM_LOAD_EDID_FIRMWARE -- 2.41.0