From 9faf80cdc1b48e4809da1e4a1637bccfa880e130 Mon Sep 17 00:00:00 2001 From: harryankers Date: Thu, 11 Jun 2026 20:03:39 +0100 Subject: [PATCH] dp: assume sink capabilities on force-enabled connectors without DPCD When a DP connector is force-enabled with an EDID override (video=DP-n:e + drm.edid_firmware) and no physical sink is attached, no HPD event ever reaches the DP library, so no device is created. Mode validation then runs without sink DSC/FEC capabilities and HDR support checks fail outright, capping such virtual displays at modes that fit the bare link bandwidth (4K60) with no HDR. Address this in four steps, all gated on the existing dpcdOffline state so connectors with a responsive sink are unaffected: - nvDpyGetDynamicData(): when a dpy is force-connected on a DP connector that the DP library considers unplugged, simulate a long pulse so the library performs discovery and creates a device. - DPCDHALImpl::parseAndReadCaps(): when the caps read fails, fall back to populateFakeDpcd() instead of leaving revision 0, which previously made notifyLongPulseInternal() bail out before discovery. Also raise the faked DPCD revision to 1.4, since HDR requires DPRX 1.3+. - DeviceImpl::getDSCSupport() / readAndParseDSCCaps() / getFECSupport(): when the AUX read fails and DPCD is offline, synthesize DSC decoder capabilities modeled on a typical DSC 1.2 4K high-refresh monitor, and claim FEC, so high-bandwidth modes can validate through the DSC path. - DPCDHALImpl::getSDPExtnForColorimetry(): claim VSC SDP colorimetry support when DPCD is offline so HDR signaling can be enabled. Tested on RTX 3060 Ti (610.43.02, KDE Wayland): a force-enabled DP connector with a 4K120 EDID now validates and sets 3840x2160@120 with DSC single mode, and HDR enables end to end. --- src/common/displayport/src/dp_configcaps.cpp | 16 +++++++- src/common/displayport/src/dp_deviceimpl.cpp | 38 +++++++++++++++++++ .../include/dp/nvdp-connector.h | 2 + src/nvidia-modeset/src/dp/nvdp-connector.cpp | 6 +++ src/nvidia-modeset/src/nvkms-dpy.c | 10 +++++ 5 files changed, 71 insertions(+), 1 deletion(-) diff --git a/src/common/displayport/src/dp_configcaps.cpp b/src/common/displayport/src/dp_configcaps.cpp index 340a3d9b51..dd90f8af3a 100644 --- a/src/common/displayport/src/dp_configcaps.cpp +++ b/src/common/displayport/src/dp_configcaps.cpp @@ -185,6 +185,12 @@ void DPCDHALImpl::parseAndReadCaps() // Set an invalid state here and make sure we REMEMBER we couldn't get the caps caps.revisionMajor = 0; dpcdOffline = true; + + // + // Forced/sink-less connector (EDID override, no AUX): fall back to + // fake caps so discovery can proceed and create a device. + // + populateFakeDpcd(); return; } @@ -634,6 +640,14 @@ bool DPCDHALImpl::getSDPExtnForColorimetry() { bool bSDPExtnForColorimetry = false; NvU8 byte = 0; + if (dpcdOffline) + { + // + // Forced/sink-less connector: claim VSC SDP colorimetry support so + // HDR signaling can be enabled. No real sink interprets the SDP. + // + return true; + } if (caps.extendedRxCapsPresent) { if (AuxRetry::ack == bus.read(NV_DPCD14_EXTENDED_DPRX_FEATURE_ENUM_LIST, &byte, sizeof byte)) @@ -996,7 +1010,7 @@ void DPCDHALImpl::populateFakeDpcd() // this should be extended in for more dpcd offsets in future. // caps.revisionMajor = 0x1; - caps.revisionMinor = 0x1; + caps.revisionMinor = 0x4; caps.supportsESI = false; caps.maxLinkRate = dp2LinkRate_8_10Gbps; caps.maxLaneCount = 4; diff --git a/src/common/displayport/src/dp_deviceimpl.cpp b/src/common/displayport/src/dp_deviceimpl.cpp index 9d1313e5c0..117a12cd48 100644 --- a/src/common/displayport/src/dp_deviceimpl.cpp +++ b/src/common/displayport/src/dp_deviceimpl.cpp @@ -1732,6 +1732,15 @@ NvBool DeviceImpl::getDSCSupport() } } + else if (hal->isDpcdOffline()) + { + // + // Forced/sink-less connector (EDID override, no AUX): assume the + // virtual sink can decompress DSC so that high-bandwidth modes pass + // validation. The stream is never decoded by a real device. + // + dscCaps.bDSCDecompressionSupported = true; + } else { DP_PRINTF(DP_ERROR, "DP-DEV> DSC Support AUX READ failed for %s!", address.toString(sb)); @@ -2308,6 +2317,11 @@ bool DeviceImpl::getFECSupport() bFECSupported = this->bandwidth.enum_path.bPathFECCapable; } + else if (hal->isDpcdOffline()) + { + // Forced/sink-less connector: claim FEC so the DSC path is usable. + bFECSupported = true; + } else if (AuxBus::success == this->getDpcdData(NV_DPCD14_FEC_CAPABILITY, &byte, sizeof(byte), &size, &nakReason)) { @@ -2560,6 +2574,30 @@ bool DeviceImpl::readAndParseDSCCaps() if(AuxBus::success != this->getDpcdData(NV_DPCD14_DSC_SUPPORT, &rawDscCaps[0], sizeof(rawDscCaps), &sizeCompleted, &nakReason)) { + if (hal->isDpcdOffline()) + { + // + // Forced/sink-less connector: synthesize DSC decoder caps + // modeled on a typical DSC 1.2 4K high-refresh monitor. + // + rawDscCaps[0x0] = 0x01; // decompression supported + rawDscCaps[0x1] = 0x21; // DSC algorithm revision 1.2 + rawDscCaps[0x2] = 0x00; // RC buffer block size + rawDscCaps[0x3] = 0x0F; // RC buffer size + rawDscCaps[0x4] = 0xFF; // slice caps 1: up to 12 slices + rawDscCaps[0x5] = 0x03; // line buffer bit depth: 12 + rawDscCaps[0x6] = 0x01; // block prediction supported + rawDscCaps[0x7] = 0xFF; // max bits per pixel LSB + rawDscCaps[0x8] = 0x03; // max bits per pixel MSB (1023/16 bpp) + rawDscCaps[0x9] = 0x1F; // color formats: RGB + all YCbCr + rawDscCaps[0xA] = 0x0E; // color depth: 8/10/12 bpc + rawDscCaps[0xB] = 0xEE; // peak throughput 1000 MP/s both modes + rawDscCaps[0xC] = 0x10; // max slice width 5120 px + rawDscCaps[0xD] = 0x00; // slice caps 2 + rawDscCaps[0xE] = 0x00; // branch throughput + rawDscCaps[0xF] = 0x00; // bpp increment: 1/16 + return parseDscCaps(&rawDscCaps[0], sizeof(rawDscCaps)); + } DP_PRINTF(DP_ERROR, "DP-DEV> Error querying DSC Caps on %s!", this->address.toString(sb)); return false; } diff --git a/src/nvidia-modeset/include/dp/nvdp-connector.h b/src/nvidia-modeset/include/dp/nvdp-connector.h index 27304b9152..3996c7b93b 100644 --- a/src/nvidia-modeset/include/dp/nvdp-connector.h +++ b/src/nvidia-modeset/include/dp/nvdp-connector.h @@ -37,6 +37,8 @@ void nvDPNotifyLongPulse(NVConnectorEvoPtr pConnectorEvo, void nvDPNotifyShortPulse(NVDPLibConnectorPtr pNVDpLibConnector); +NvBool nvDPConnectorIsPlugged(const NVConnectorEvoRec *pConnectorEvo); + void nvDPDestroyConnector(NVDPLibConnectorPtr pNVDpLibConnector); NVDPLibModesetStatePtr nvDPLibCreateModesetState( diff --git a/src/nvidia-modeset/src/dp/nvdp-connector.cpp b/src/nvidia-modeset/src/dp/nvdp-connector.cpp index 61cb523b18..a8a552a4a3 100644 --- a/src/nvidia-modeset/src/dp/nvdp-connector.cpp +++ b/src/nvidia-modeset/src/dp/nvdp-connector.cpp @@ -121,6 +121,12 @@ void nvDPNotifyLongPulse(NVConnectorEvoPtr pConnectorEvo, } +NvBool nvDPConnectorIsPlugged(const NVConnectorEvoRec *pConnectorEvo) +{ + return (pConnectorEvo->pDpLibConnector != NULL) && + pConnectorEvo->pDpLibConnector->plugged; +} + void nvDPNotifyShortPulse(NVDPLibConnectorPtr pNVDpLibConnector) { DisplayPort::Connector *c = pNVDpLibConnector->connector; diff --git a/src/nvidia-modeset/src/nvkms-dpy.c b/src/nvidia-modeset/src/nvkms-dpy.c index 7fc92fcfd7..c9f7ae66bf 100644 --- a/src/nvidia-modeset/src/nvkms-dpy.c +++ b/src/nvidia-modeset/src/nvkms-dpy.c @@ -22,6 +22,7 @@ */ #include "dp/nvdp-device.h" +#include "dp/nvdp-connector.h" #include "dp/nvdp-connector-event-sink.h" #include "nvkms-api-types.h" @@ -3127,6 +3128,15 @@ NvBool nvDpyGetDynamicData( connectedList = nvDPLibDpyIsConnected(pDpyEvo) ? oneDpyIdList : nvEmptyDpyIdList(); } else if (pRequest->forceConnected) { + if (nvConnectorUsesDPLib(pConnectorEvo) && + !nvDPConnectorIsPlugged(pConnectorEvo)) { + /* + * Forced sink-less DP connector (EDID override): simulate a + * plug so the DP library performs discovery and creates a + * device, enabling DSC/HDR capability paths to be consulted. + */ + nvDPNotifyLongPulse(pConnectorEvo, TRUE); + } connectedList = oneDpyIdList; } else if (pRequest->forceDisconnected) { connectedList = nvEmptyDpyIdList();