From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 5567C278161 for ; Fri, 21 Aug 2026 22:19:52 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787350794; cv=none; b=noteidz+gQpm0RGNNl2uXzbRaM8iauEWOkuLky/em8+fE1TU486ng6mKNxixoHCwrauKK/wzfMqlB6GVtPgnzsAqUCH7TavjCXlwouxHwXOb0M1JT7AgAOq/KoF9f+pwQWCBzwD0tjpsNgYan+XDKQU3UUdQNTEN20xsIwTXDdM= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787350794; c=relaxed/simple; bh=Stv40hM310lQudrO9BRpcRVGym+WZ1bGmSKpJseadso=; h=Message-ID:Subject:From:To:Cc:Date:In-Reply-To:References: Content-Type:MIME-Version; b=hJcjGIArbi+1sOOpG58g4BUzTpF/ctQhvyfOzZMAIH35y7ZYpWEHPG7e8IG6kKVjoHMNZ3/MAqyYViZ6QcraURcY6/lUoCKbF9ud3t/ytMnmfu/1qWaFk+szxNkcUQZTpMGdHiU1rJ9MTy8Ot9QjTO5Wc3uwnRZ3wBkXuiULjaw= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=OuJKecpN; dkim=pass (2048-bit key) header.d=redhat.com header.i=@redhat.com header.b=VDXD9vzk; arc=none smtp.client-ip=170.10.133.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="OuJKecpN"; dkim=pass (2048-bit key) header.d=redhat.com header.i=@redhat.com header.b="VDXD9vzk" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1787350791; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=DQkr9w10nlQ8gm/DKrubBOOVTdxyxtdDUmfOTZG3zRo=; b=OuJKecpN9gJvp2LckQNVUW2ccT9o70Xc28oO+HI6dKISp0tboVhVOa9L5kDhLdhvzKs6gj luIc4/0meEfssqwkOqQcsk6KNAhjWHjuC1uGrkXN93NLZdQgxS2j4iUrHvfSrL4rGTtJIr rsGRBNFTzEFfGBJxp33RnxgpCri3AJM= Received: from mail-qv1-f71.google.com (mail-qv1-f71.google.com [209.85.219.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-16-ArbSFY7_Oz-4pxUBK1n4pg-1; Fri, 21 Aug 2026 18:19:49 -0400 X-MC-Unique: ArbSFY7_Oz-4pxUBK1n4pg-1 X-Mimecast-MFC-AGG-ID: ArbSFY7_Oz-4pxUBK1n4pg_1787350789 Received: by mail-qv1-f71.google.com with SMTP id 6a1803df08f44-90c914d88c6so955386d6.1 for ; Fri, 21 Aug 2026 15:19:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=google; t=1787350789; x=1787955589; darn=vger.kernel.org; h=mime-version:user-agent:content-transfer-encoding:content-type :references:in-reply-to:date:cc:to:from:subject:message-id:from:to :cc:subject:date:message-id:reply-to:content-type; bh=DQkr9w10nlQ8gm/DKrubBOOVTdxyxtdDUmfOTZG3zRo=; b=VDXD9vzkG2mIS5CLIJsgh3otOGsfOStb5ba0Gxi//9166SxjpqNtQYKIFHYUSR/Tnw Gg51D+o/cm2yImJbxWXTQyczIkqYnVHmq8lVWMVGfF4p7hQW6vVfPtf8UWCygAcc7NS9 Sab0qHFJuINQLUpMdOOI+l9zjRSteTrLVxeyfIKOu/Y4XdD3hUWDNl/fGW8H5hgnYrcc ehuSjQsLJ3yHw0u+BZmGHW5x+is7Y5LOstzmqiZI73qbAOae1QiB8iua2+A9Dc3r//RW Dx3DKpTjRg7XDg8N4XbuPO2l8sm+K/zem7LxjQtMkX/edFWOBNMO+Wpc+ewYhsVSdILN Ir2g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787350789; x=1787955589; h=mime-version:user-agent:content-transfer-encoding:content-type :references:in-reply-to:date:cc:to:from:subject:message-id:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=DQkr9w10nlQ8gm/DKrubBOOVTdxyxtdDUmfOTZG3zRo=; b=PZz5NhPNN6+xEQgwRs0dWyYtEDeXIoHmM/8vroBkHlUSxYQqSoGb6tNSXwieCxQap4 cg7kWXfFhqojK1rsKVZwDAGliYLtkZwyXPwRmX7mNAPqnd+VGVHf/ZayTJ4XHfR5uHS3 qU/IHGI1KdQSYhmrupx1jpHgJ+icMX+AVMRtQM9T20fqLIrDowJx0PmbK01w5Z1Km1J2 JYomOPUSSMql4kP6foWlKaHOkSbzMY2sIp6drWdLnLOswSp5jG2EoS3ncHud2Xg0vlyl b7JpgVUksmR5otq8xzA9vRzTfb34uw7FhI85mmbm0u6eWCT+O2w+S3NwDbtfOlGx1IhV LL6w== X-Forwarded-Encrypted: i=1; AHgh+RqB8IdHX6DLdu9jzYRnbKSKjnWEfAeC8/CSXDLArGfAvJBLVi8ChhPfANDei5F79BE+Xt5qOhr4h/4PO20=@vger.kernel.org X-Gm-Message-State: AFuF++nDOVmHU1YjsBHfDKJFc1rYGQA90FnjiMhzRqmtjeAsuZZqO5nm 7zwGs2fSgTuCwuhPJl8+++Xqmdfx7D5fB1AjvUbtT1sHhcnEXIMsM8c7EezgG7HAFGPtz2+mZoV Nio/SKQ2n2rvTqUUDzABBE63swVoZ9KbXlfAc3WCiAfy9Rf+j5W0F/F4hNQd5CY2W6g== X-Gm-Gg: AR+sD13RGRnmysjyOpPA9Plp7rPXuMiy6lgDyHyvg40iMa4vJw0IZ2kQMmm0qszC0gR SdGMV0XGhDRMIPKG/gFUyEWTj36+y+kEZvIr+4yDMHllXeGzzN6s4zoLxawBy+/W7o60EBYZTmJ UbqhdkvPknHjybQ5SpZy+tadOFoAaH4SvwiZ9jXIQapaZ+QVrfxduJSUBBPdfaFDUzFtSVNEVVc h497DJ7HoX1w9HB0u6mAnmFk1MWpm8ow5OK2Te9Zo5MQiS+mZiAI7czqGyBkheISgjoKEqTYAO8 7qqhVv5gaPtTNHXmK9PVs2+ulQbsFH93zoCUN2qHKJf/+g8MNTlfDNcjkpL+WzAxA2STsv/G X-Received: by 2002:a05:6214:21e6:b0:908:488b:14f with SMTP id 6a1803df08f44-90c6ab306bbmr157986726d6.5.1787350789139; Fri, 21 Aug 2026 15:19:49 -0700 (PDT) X-Received: by 2002:a05:6214:21e6:b0:908:488b:14f with SMTP id 6a1803df08f44-90c6ab306bbmr157986286d6.5.1787350788559; Fri, 21 Aug 2026 15:19:48 -0700 (PDT) Received: from [192.168.8.4] ([100.0.180.93]) by smtp.gmail.com with ESMTPSA id 6a1803df08f44-90c5f2b2b38sm72754756d6.43.2026.08.21.15.19.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 21 Aug 2026 15:19:47 -0700 (PDT) Message-ID: <95f6f723028a7b402dfa5ba62672a5c18be3521a.camel@redhat.com> Subject: Re: [PATCH v2 10/10] drm/nouveau: honor HF-EEODB EDIDs by converting to struct drm_edid From: lyude@redhat.com To: Mohamed Ahmed , linux-kernel@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, Danilo Krummrich , Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Simona Vetter , Mary Guillemard , nouveau@lists.freedesktop.org Date: Fri, 21 Aug 2026 18:19:46 -0400 In-Reply-To: <20260820164929.17117-11-mohamedahmedegypt2001@gmail.com> References: <20260820164929.17117-1-mohamedahmedegypt2001@gmail.com> <20260820164929.17117-11-mohamedahmedegypt2001@gmail.com> Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable User-Agent: Evolution 3.58.3 (3.58.3-1.fc43) Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Reviewed-by: Lyude Paul On Thu, 2026-08-20 at 20:49 +0400, Mohamed Ahmed wrote: > HDMI 2.1 sinks ship HF-EEODB EDIDs. Byte 126 deliberately claims one > extension block for legacy sources, and the true count lives in the > CTA > block's first data block. Their high-refresh timings sit in DisplayID > extension blocks 2+. >=20 > The kernel already reads these EDIDs whole, and on GSP boards RM > returns > the full EDID with its true size. However, nouveau then hands the > buffer > to the legacy API which sizes the EDID from byte 126 so the DisplayID > blocks are not parsed/exposed and the sysfs blob truncates to 256 > bytes. > The mode list then tops out at the CTA VICs. >=20 > Convert to the struct drm_edid API, which carries the real size. > Read via drm_edid_read_ddc()/drm_edid_read_switcheroo() where an > adapter exists, and serve RM's buffer through drm_edid_read_custom() > with a block-reading callback where not, which also routes the GSP > path through drm's block validation and the debugfs EDID override. >=20 > A failed RM read now takes the same "no EDID" path as an empty DDC > read > which stops it from leaking the runtime-PM reference detect() holds. > The > property, display_info, and mode list go through > drm_edid_connector_update()/_add_modes() and every EDID source is > funneled through nouveau_connector_set_edid() so all of them reach > the > property that drm_edid_connector_add_modes() reads from. >=20 > nouveau_acpi_edid() now returns a drm_edid sized from the EDID's > extension count but never past the length _DDC actually returned, so > padded buffers pass and truncated ones are rejected, and every > firmware-provided EDID (OF, ACPI, VBIOS-embedded) is validated with > drm_edid_valid(). An invalid one is treated as no EDID. get_modes() > re-syncs the property from the connector's copy when the probe helper > has cleared it for a forced-off connector, before adding modes. >=20 > The few raw EDID readers are converted along with it, so no raw > struct > edid pointer is kept. Ownership and freeing move to the drm_edid. MST > connectors keep their separate legacy path. >=20 > Signed-off-by: Mohamed Ahmed > --- > =C2=A0drivers/gpu/drm/nouveau/dispnv04/dfp.c=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0 |=C2=A0=C2=A0 5 +- > =C2=A0drivers/gpu/drm/nouveau/dispnv50/disp.c=C2=A0=C2=A0=C2=A0=C2=A0 |= =C2=A0=C2=A0 4 +- > =C2=A0drivers/gpu/drm/nouveau/nouveau_acpi.c=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0 |=C2=A0 21 ++- > =C2=A0drivers/gpu/drm/nouveau/nouveau_acpi.h=C2=A0=C2=A0=C2=A0=C2=A0=C2= =A0 |=C2=A0 10 +- > =C2=A0drivers/gpu/drm/nouveau/nouveau_connector.c | 147 +++++++++++++++--= - > -- > =C2=A0drivers/gpu/drm/nouveau/nouveau_connector.h |=C2=A0 12 +- > =C2=A06 files changed, 151 insertions(+), 48 deletions(-) >=20 > diff --git a/drivers/gpu/drm/nouveau/dispnv04/dfp.c > b/drivers/gpu/drm/nouveau/dispnv04/dfp.c > index c9f96ec8455d..00eb2aacbe93 100644 > --- a/drivers/gpu/drm/nouveau/dispnv04/dfp.c > +++ b/drivers/gpu/drm/nouveau/dispnv04/dfp.c > @@ -344,9 +344,8 @@ static void nv04_dfp_mode_set(struct drm_encoder > *encoder, > =C2=A0 regp->fp_control |=3D (2 << 24); > =C2=A0 if (nv_encoder->dcb->type =3D=3D DCB_OUTPUT_LVDS) { > =C2=A0 bool duallink =3D false, dummy; > - if (nv_connector->edid && > - =C2=A0=C2=A0=C2=A0 nv_connector->type =3D=3D DCB_CONNECTOR_LVDS_SPWG) = { > - duallink =3D (((u8 *)nv_connector->edid)[121] > =3D=3D 2); > + if (nv_connector->spwg_links) { > + duallink =3D nv_connector->spwg_links =3D=3D 2; > =C2=A0 } else { > =C2=A0 nouveau_bios_parse_lvds_table(dev, > output_mode->clock, > =C2=A0 =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 &duallink, > &dummy); > diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c > b/drivers/gpu/drm/nouveau/dispnv50/disp.c > index 2c66e480b511..add19d479ebe 100644 > --- a/drivers/gpu/drm/nouveau/dispnv50/disp.c > +++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c > @@ -1803,8 +1803,8 @@ nv50_sor_atomic_enable(struct drm_encoder > *encoder, struct drm_atomic_commit *st > =C2=A0 lvds_dual =3D bios->fp.dual_link; > =C2=A0 lvds_8bpc =3D bios->fp.if_is_24bit; > =C2=A0 } else { > - if (nv_connector->type =3D=3D > DCB_CONNECTOR_LVDS_SPWG) { > - if (((u8 *)nv_connector->edid)[121] > =3D=3D 2) > + if (nv_connector->spwg_links) { > + if (nv_connector->spwg_links =3D=3D 2) > =C2=A0 lvds_dual =3D true; > =C2=A0 } else > =C2=A0 if (mode->clock >=3D bios- > >fp.duallink_transition_clk) { > diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c > b/drivers/gpu/drm/nouveau/nouveau_acpi.c > index 21b56cc7605c..ab2868edd42b 100644 > --- a/drivers/gpu/drm/nouveau/nouveau_acpi.c > +++ b/drivers/gpu/drm/nouveau/nouveau_acpi.c > @@ -360,9 +360,10 @@ void nouveau_unregister_dsm_handler(void) {} > =C2=A0void nouveau_switcheroo_optimus_dsm(void) {} > =C2=A0#endif > =C2=A0 > -void * > +const struct drm_edid * > =C2=A0nouveau_acpi_edid(struct drm_device *dev, struct drm_connector > *connector) > =C2=A0{ > + const struct drm_edid *drm_edid; > =C2=A0 struct acpi_device *acpidev; > =C2=A0 int type, ret; > =C2=A0 void *edid; > @@ -384,7 +385,23 @@ nouveau_acpi_edid(struct drm_device *dev, struct > drm_connector *connector) > =C2=A0 if (ret < 0) > =C2=A0 return NULL; > =C2=A0 > - return edid; > + /* Never let the EDID's own extension count reach past what > _DDC > + * actually returned. Drop the padding some firmware appends > so the > + * container is exactly the EDID, then validate it like the > DDC > + * readers would. > + */ > + if (ret >=3D EDID_LENGTH) > + ret =3D min_t(int, ret, EDID_LENGTH * > + =C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 (1 + ((const struct edid > *)edid)->extensions)); > + drm_edid =3D drm_edid_alloc(edid, ret); > + kfree(edid); > + > + if (drm_edid && !drm_edid_valid(drm_edid)) { > + drm_dbg_kms(dev, "Invalid EDID from ACPI _DDC\n"); > + drm_edid_free(drm_edid); > + drm_edid =3D NULL; > + } > + return drm_edid; > =C2=A0} > =C2=A0 > =C2=A0bool nouveau_acpi_video_backlight_use_native(void) > diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.h > b/drivers/gpu/drm/nouveau/nouveau_acpi.h > index e39dd8b94b8b..bc3ccca7b338 100644 > --- a/drivers/gpu/drm/nouveau/nouveau_acpi.h > +++ b/drivers/gpu/drm/nouveau/nouveau_acpi.h > @@ -4,13 +4,15 @@ > =C2=A0 > =C2=A0#define ROM_BIOS_PAGE 4096 > =C2=A0 > +struct drm_edid; > + > =C2=A0#if defined(CONFIG_ACPI) && defined(CONFIG_X86) > =C2=A0bool nouveau_is_optimus(void); > =C2=A0bool nouveau_is_v1_dsm(void); > =C2=A0void nouveau_register_dsm_handler(void); > =C2=A0void nouveau_unregister_dsm_handler(void); > =C2=A0void nouveau_switcheroo_optimus_dsm(void); > -void *nouveau_acpi_edid(struct drm_device *, struct drm_connector > *); > +const struct drm_edid *nouveau_acpi_edid(struct drm_device *, struct > drm_connector *); > =C2=A0bool nouveau_acpi_video_backlight_use_native(void); > =C2=A0void nouveau_acpi_video_register_backlight(void); > =C2=A0#else > @@ -19,7 +21,11 @@ static inline bool nouveau_is_v1_dsm(void) { > return false; }; > =C2=A0static inline void nouveau_register_dsm_handler(void) {} > =C2=A0static inline void nouveau_unregister_dsm_handler(void) {} > =C2=A0static inline void nouveau_switcheroo_optimus_dsm(void) {} > -static inline void *nouveau_acpi_edid(struct drm_device *dev, struct > drm_connector *connector) { return NULL; } > +static inline const struct drm_edid * > +nouveau_acpi_edid(struct drm_device *dev, struct drm_connector > *connector) > +{ > + return NULL; > +} > =C2=A0static inline bool nouveau_acpi_video_backlight_use_native(void) { > return true; } > =C2=A0static inline void nouveau_acpi_video_register_backlight(void) {} > =C2=A0#endif > diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c > b/drivers/gpu/drm/nouveau/nouveau_connector.c > index b0b0ad9a0c24..55912da83cd0 100644 > --- a/drivers/gpu/drm/nouveau/nouveau_connector.c > +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c > @@ -397,7 +397,7 @@ nouveau_connector_destroy(struct drm_connector > *connector) > =C2=A0 struct nouveau_connector *nv_connector =3D > nouveau_connector(connector); > =C2=A0 nvif_event_dtor(&nv_connector->irq); > =C2=A0 nvif_event_dtor(&nv_connector->hpd); > - kfree(nv_connector->edid); > + drm_edid_free(nv_connector->drm_edid); > =C2=A0 drm_connector_unregister(connector); > =C2=A0 drm_connector_cleanup(connector); > =C2=A0 if (nv_connector->aux.transfer) > @@ -469,6 +469,36 @@ nouveau_connector_ddc_detect(struct > drm_connector *connector) > =C2=A0 return found; > =C2=A0} > =C2=A0 > +static void > +nouveau_connector_set_edid(struct nouveau_connector *nv_connector, > + =C2=A0=C2=A0 const struct drm_edid *drm_edid) > +{ > + if (nv_connector->drm_edid =3D=3D drm_edid) > + return; > + > + /* Updates the EDID property and display_info with HF-EEODB- > aware > + * sizing. The legacy helpers truncate both to what EDID > byte 126 > + * admits, hiding the DisplayID extension blocks that carry > the > + * high-refresh timings. > + */ > + drm_edid_connector_update(&nv_connector->base, drm_edid); > + > + drm_edid_free(nv_connector->drm_edid); > + nv_connector->drm_edid =3D drm_edid; > + > + /* The SPWG link-count byte lives in a vendor descriptor drm > has no > + * accessor for. Peek at it once here so nothing else needs > the raw > + * EDID. > + */ > + nv_connector->spwg_links =3D 0; > + if (nv_connector->type =3D=3D DCB_CONNECTOR_LVDS_SPWG) { > + const u8 *raw =3D (const u8 *)drm_edid_raw(drm_edid); > + > + if (raw) > + nv_connector->spwg_links =3D raw[121] =3D=3D 2 ? 2 > : 1; > + } > +} > + > =C2=A0static struct nouveau_encoder * > =C2=A0nouveau_connector_of_detect(struct drm_connector *connector) > =C2=A0{ > @@ -490,8 +520,17 @@ nouveau_connector_of_detect(struct drm_connector > *connector) > =C2=A0 int idx =3D name ? name[strlen(name) - 1] - 'A' : 0; > =C2=A0 > =C2=A0 if (nv_encoder->dcb->i2c_index =3D=3D idx && edid) { > - nv_connector->edid =3D > - kmemdup(edid, EDID_LENGTH, > GFP_KERNEL); > + const struct drm_edid *drm_edid =3D > + drm_edid_alloc(edid, EDID_LENGTH); > + > + /* Firmware-provided, so validate it like > the DDC > + * readers would. > + */ > + if (drm_edid && !drm_edid_valid(drm_edid)) { > + drm_edid_free(drm_edid); > + drm_edid =3D NULL; > + } > + nouveau_connector_set_edid(nv_connector, > drm_edid); > =C2=A0 return nv_encoder; > =C2=A0 } > =C2=A0 } > @@ -546,17 +585,23 @@ nouveau_connector_set_encoder(struct > drm_connector *connector, > =C2=A0 } > =C2=A0} > =C2=A0 > -static void > -nouveau_connector_set_edid(struct nouveau_connector *nv_connector, > - =C2=A0=C2=A0 struct edid *edid) > +struct nouveau_rm_edid { > + u8 *data; > + size_t size; > +}; > + > +static int > +nouveau_connector_rm_edid_block(void *context, u8 *buf, unsigned int > block, > + size_t len) > =C2=A0{ > - if (nv_connector->edid !=3D edid) { > - struct edid *old_edid =3D nv_connector->edid; > + struct nouveau_rm_edid *rm =3D context; > + size_t offset =3D (size_t)block * EDID_LENGTH; > =C2=A0 > - drm_connector_update_edid_property(&nv_connector- > >base, edid); > - kfree(old_edid); > - nv_connector->edid =3D edid; > - } > + if (offset + len > rm->size) > + return -EINVAL; > + > + memcpy(buf, rm->data + offset, len); > + return 0; > =C2=A0} > =C2=A0 > =C2=A0static enum drm_connector_status > @@ -590,22 +635,37 @@ nouveau_connector_detect(struct drm_connector > *connector, bool force) > =C2=A0 > =C2=A0 nv_encoder =3D nouveau_connector_ddc_detect(connector); > =C2=A0 if (nv_encoder) { > - struct edid *new_edid =3D NULL; > + const struct drm_edid *new_edid =3D NULL; > =C2=A0 > =C2=A0 if (nv_encoder->i2c) { > =C2=A0 if ((vga_switcheroo_handler_flags() & > VGA_SWITCHEROO_CAN_SWITCH_DDC) && > =C2=A0 =C2=A0=C2=A0=C2=A0 nv_connector->type =3D=3D > DCB_CONNECTOR_LVDS) > - new_edid =3D > drm_get_edid_switcheroo(connector, nv_encoder->i2c); > + new_edid =3D > drm_edid_read_switcheroo(connector, nv_encoder->i2c); > =C2=A0 else > - new_edid =3D drm_get_edid(connector, > nv_encoder->i2c); > + new_edid =3D > drm_edid_read_ddc(connector, nv_encoder->i2c); > =C2=A0 } else { > - ret =3D nvif_outp_edid_get(&nv_encoder->outp, > (u8 **)&new_edid); > - if (ret < 0) > - return > connector_status_disconnected; > + struct nouveau_rm_edid rm =3D {}; > + > + /* RM (which owns the DDC pads on GSP > boards) reads the > + * EDID whole and returns its true size, > which for an > + * HF-EEODB EDID exceeds what byte 126 > admits. Serve it > + * through drm's block reader so EEODB > sizing, block > + * validation, and the debugfs EDID override > all apply. > + * A failed read is treated like an empty > DDC read, > + * which releases the runtime-PM reference. > + */ > + ret =3D nvif_outp_edid_get(&nv_encoder->outp, > &rm.data); > + if (ret >=3D 0) { > + rm.size =3D ret; > + new_edid =3D > drm_edid_read_custom(connector, > + nouv > eau_connector_rm_edid_block, > + &rm) > ; > + kfree(rm.data); > + } > =C2=A0 } > =C2=A0 > =C2=A0 nouveau_connector_set_edid(nv_connector, new_edid); > - if (!nv_connector->edid) { > + if (!nv_connector->drm_edid) { > =C2=A0 NV_ERROR(drm, "DDC responded, but no EDID > for %s\n", > =C2=A0 connector->name); > =C2=A0 goto detect_analog; > @@ -626,7 +686,7 @@ nouveau_connector_detect(struct drm_connector > *connector, bool force) > =C2=A0 =C2=A0=C2=A0=C2=A0 nv_partner->dcb->type =3D=3D > DCB_OUTPUT_TMDS) || > =C2=A0 =C2=A0=C2=A0 (nv_encoder->dcb->type =3D=3D > DCB_OUTPUT_TMDS && > =C2=A0 =C2=A0=C2=A0=C2=A0 nv_partner->dcb->type =3D=3D > DCB_OUTPUT_ANALOG))) { > - if (nv_connector->edid->input & > DRM_EDID_INPUT_DIGITAL) > + if (drm_edid_is_digital(nv_connector- > >drm_edid)) > =C2=A0 type =3D DCB_OUTPUT_TMDS; > =C2=A0 else > =C2=A0 type =3D DCB_OUTPUT_ANALOG; > @@ -638,7 +698,8 @@ nouveau_connector_detect(struct drm_connector > *connector, bool force) > =C2=A0 conn_status =3D connector_status_connected; > =C2=A0 > =C2=A0 if (nv_encoder->dcb->type =3D=3D DCB_OUTPUT_DP) > - drm_dp_cec_set_edid(&nv_connector->aux, > nv_connector->edid); > + drm_dp_cec_attach(&nv_connector->aux, > + =C2=A0 connector- > >display_info.source_physical_address); > =C2=A0 > =C2=A0 goto out; > =C2=A0 } else { > @@ -670,7 +731,7 @@ nouveau_connector_detect(struct drm_connector > *connector, bool force) > =C2=A0 } > =C2=A0 > =C2=A0 out: > - if (!nv_connector->edid) > + if (!nv_connector->drm_edid) > =C2=A0 drm_dp_cec_unset_edid(&nv_connector->aux); > =C2=A0 > =C2=A0 pm_runtime_mark_last_busy(dev->dev); > @@ -686,7 +747,7 @@ nouveau_connector_detect_lvds(struct > drm_connector *connector, bool force) > =C2=A0 struct nouveau_drm *drm =3D nouveau_drm(dev); > =C2=A0 struct nouveau_connector *nv_connector =3D > nouveau_connector(connector); > =C2=A0 struct nouveau_encoder *nv_encoder =3D NULL; > - struct edid *edid =3D NULL; > + const struct drm_edid *edid =3D NULL; > =C2=A0 enum drm_connector_status status =3D > connector_status_disconnected; > =C2=A0 > =C2=A0 nv_encoder =3D find_encoder(connector, DCB_OUTPUT_LVDS); > @@ -697,7 +758,7 @@ nouveau_connector_detect_lvds(struct > drm_connector *connector, bool force) > =C2=A0 if (!drm->vbios.fp_no_ddc) { > =C2=A0 status =3D nouveau_connector_detect(connector, force); > =C2=A0 if (status =3D=3D connector_status_connected) { > - edid =3D nv_connector->edid; > + edid =3D nv_connector->drm_edid; > =C2=A0 goto out; > =C2=A0 } > =C2=A0 } > @@ -733,9 +794,17 @@ nouveau_connector_detect_lvds(struct > drm_connector *connector, bool force) > =C2=A0 * stored for the panel stored in them. > =C2=A0 */ > =C2=A0 if (!drm->vbios.fp_no_ddc) { > - edid =3D (struct edid > *)nouveau_bios_embedded_edid(dev); > - if (edid) { > - edid =3D kmemdup(edid, EDID_LENGTH, > GFP_KERNEL); > + const void *embedded =3D > nouveau_bios_embedded_edid(dev); > + > + if (embedded) { > + edid =3D drm_edid_alloc(embedded, > EDID_LENGTH); > + /* Firmware-provided, so validate it like > the DDC > + * readers would. > + */ > + if (edid && !drm_edid_valid(edid)) { > + drm_edid_free(edid); > + edid =3D NULL; > + } > =C2=A0 if (edid) > =C2=A0 status =3D connector_status_connected; > =C2=A0 } > @@ -886,7 +955,7 @@ nouveau_connector_detect_depth(struct > drm_connector *connector) > =C2=A0 bool duallink; > =C2=A0 > =C2=A0 /* if the edid is feeling nice enough to provide this info, > use it */ > - if (nv_connector->edid && connector->display_info.bpc) > + if (nv_connector->drm_edid && connector->display_info.bpc) > =C2=A0 return; > =C2=A0 > =C2=A0 /* EDID 1.4 is *supposed* to be supported on eDP, but, > Apple... */ > @@ -913,9 +982,8 @@ nouveau_connector_detect_depth(struct > drm_connector *connector) > =C2=A0 /* LVDS: DDC panel, need to first determine the number of > links to > =C2=A0 * know which if_is_24bit flag to check... > =C2=A0 */ > - if (nv_connector->edid && > - =C2=A0=C2=A0=C2=A0 nv_connector->type =3D=3D DCB_CONNECTOR_LVDS_SPWG) > - duallink =3D ((u8 *)nv_connector->edid)[121] =3D=3D 2; > + if (nv_connector->spwg_links) > + duallink =3D nv_connector->spwg_links =3D=3D 2; > =C2=A0 else > =C2=A0 duallink =3D mode->clock >=3D bios- > >fp.duallink_transition_clk; > =C2=A0 > @@ -973,12 +1041,17 @@ nouveau_connector_get_modes(struct > drm_connector *connector) > =C2=A0 nv_connector->native_mode =3D NULL; > =C2=A0 } > =C2=A0 > - if (nv_connector->edid) > - ret =3D drm_add_edid_modes(connector, nv_connector- > >edid); > - else > - if (nv_encoder->dcb->type =3D=3D DCB_OUTPUT_LVDS && > - =C2=A0=C2=A0=C2=A0 (nv_encoder->dcb->lvdsconf.use_straps_for_mode || > - =C2=A0=C2=A0=C2=A0=C2=A0 drm->vbios.fp_no_ddc) && nouveau_bios_fp_mode(= dev, > NULL)) { > + if (nv_connector->drm_edid) { > + /* The probe helper clears the property and > display_info for > + * a forced-off connector without calling detect(). > Re-sync > + * from our copy then, since add_modes() reads the > property. > + */ > + if (!connector->edid_blob_ptr) > + drm_edid_connector_update(connector, > nv_connector->drm_edid); > + ret =3D drm_edid_connector_add_modes(connector); > + } else if (nv_encoder->dcb->type =3D=3D DCB_OUTPUT_LVDS && > + =C2=A0=C2=A0 (nv_encoder->dcb->lvdsconf.use_straps_for_mode || > + =C2=A0=C2=A0=C2=A0 drm->vbios.fp_no_ddc) && > nouveau_bios_fp_mode(dev, NULL)) { > =C2=A0 struct drm_display_mode mode; > =C2=A0 > =C2=A0 nouveau_bios_fp_mode(dev, &mode); > diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.h > b/drivers/gpu/drm/nouveau/nouveau_connector.h > index 0608cabed058..eb292d2ba4bc 100644 > --- a/drivers/gpu/drm/nouveau/nouveau_connector.h > +++ b/drivers/gpu/drm/nouveau/nouveau_connector.h > @@ -43,7 +43,7 @@ > =C2=A0 > =C2=A0struct nvkm_i2c_port; > =C2=A0struct dcb_output; > -struct edid; > +struct drm_edid; > =C2=A0 > =C2=A0#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT > =C2=A0struct nouveau_backlight { > @@ -121,6 +121,13 @@ struct nouveau_connector { > =C2=A0 struct drm_connector base; > =C2=A0 enum dcb_connector_type type; > =C2=A0 u8 index; > + /* LVDS_SPWG panels state their link count in EDID > descriptor 4 (SPWG > + * byte 0x79), cached by nouveau_connector_set_edid() so > nothing else > + * needs the raw EDID. 0 =3D unknown (not an SPWG panel, or no > EDID) and > + * callers fall back to their transition-clock/VBIOS > heuristics; > + * 1 =3D single link; 2 =3D dual link. > + */ > + u8 spwg_links; > =C2=A0 > =C2=A0 struct nvif_conn conn; > =C2=A0 u64 hpd_pending; > @@ -137,7 +144,8 @@ struct nouveau_connector { > =C2=A0 int scaling_mode; > =C2=A0 > =C2=A0 struct nouveau_encoder *detected_encoder; > - struct edid *edid; > + /* Owner of the sink's EDID, HF-EEODB-complete. */ > + const struct drm_edid *drm_edid; > =C2=A0 struct drm_display_mode *native_mode; > =C2=A0#ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT > =C2=A0 struct nouveau_backlight *backlight;