diff --git a/src/common/displayport/src/dp_connectorimpl.cpp b/src/common/displayport/src/dp_connectorimpl.cpp index f3267bc57..0ba86c2aa 100644 --- a/src/common/displayport/src/dp_connectorimpl.cpp +++ b/src/common/displayport/src/dp_connectorimpl.cpp @@ -1395,6 +1395,125 @@ bool ConnectorImpl::compoundQueryAttachMST(Group * target, if (compoundQueryAttachMSTIsDscPossible(target, modesetParams, pDscParams)) { + // + // Before entering the DSC path, check if the mode can be supported + // without compression. The SST path (compoundQueryAttachSST) already + // does this correctly: it only enables DSC when willLinkSupportModeSST + // fails. For MST, perform an equivalent pre-check using PBN to avoid + // unnecessary DSC on links with sufficient bandwidth. + // + // Unnecessary DSC adds link training complexity and can cause + // instability through MST hubs and USB-C docks, manifesting as + // spurious HPD short pulses and DPCD AUX channel failures during + // DSC capability negotiation. + // + bool bForceDsc = pDscParams && + (pDscParams->forceDsc == DSC_FORCE_ENABLE); + bool bDscDualRequested = + (modesetParams.modesetInfo.mode == DSC_DUAL); + + if (!bForceDsc && !bDscDualRequested) + { + unsigned base_pbn, slots, slots_pbn; + localInfo.lc.pbnRequired(localInfo.localModesetInfo, + base_pbn, slots, slots_pbn); + if (compoundQueryLocalLinkPBN + slots_pbn <= + localInfo.lc.pbnTotal()) + { + // + // The uncompressed mode fits within available local link PBN. + // Speculatively try the full generic validation (watermark, + // per-device bandwidth at intermediate MST branches). If it + // succeeds, DSC is unnecessary. + // + // compoundQueryAttachMSTGeneric mutates connector and + // per-device state (compoundQueryResult, compoundQueryLocalLinkPBN, + // and device compound_query_state). Since this is a speculative + // check, save the state before and restore it on failure so the + // DSC path starts from a clean slate. + // + bool savedCompoundQueryResult = compoundQueryResult; + + // + // Save per-device compound_query_state for all devices in this + // group's MST path. On failure, intermediate branch devices may + // retain stale timeslot allocations from the uncompressed attempt + // which would cause the DSC path to either skip them (via + // bandwidthAllocatedForIndex) or over-count their timeslots. + // + struct { + DeviceImpl *dev; + unsigned timeslots_used_by_query; + unsigned bandwidthAllocatedForIndex; + } savedDevState[63]; + unsigned savedDevCount = 0; + bool savedDevOverflow = false; + + for (Device * d = target->enumDevices(0); + d && !savedDevOverflow; + d = target->enumDevices(d)) + { + DeviceImpl * tail = (DeviceImpl *)d; + while (tail && tail->getParent()) + { + if (savedDevCount >= 63) + { + savedDevOverflow = true; + break; + } + savedDevState[savedDevCount].dev = tail; + savedDevState[savedDevCount].timeslots_used_by_query = + tail->bandwidth.compound_query_state.timeslots_used_by_query; + savedDevState[savedDevCount].bandwidthAllocatedForIndex = + tail->bandwidth.compound_query_state.bandwidthAllocatedForIndex; + savedDevCount++; + tail = (DeviceImpl*)tail->getParent(); + } + } + + // + // If we couldn't save all device state (extremely deep MST + // topology), skip the speculative pre-check to avoid partial + // state restoration on failure. + // + if (savedDevOverflow) + goto skipPreCheck; + + if (compoundQueryAttachMSTGeneric(target, modesetParams, + &localInfo, pDscParams, + pErrorCode)) + { + return true; + } + + // + // Generic validation failed — restore all state so the DSC + // path runs on a clean slate. + // + // compoundQueryLocalLinkPBN is already rolled back by + // compoundQueryAttachMSTGeneric on failure. + // + compoundQueryResult = savedCompoundQueryResult; + for (unsigned idx = 0; idx < savedDevCount; idx++) + { + savedDevState[idx].dev->bandwidth.compound_query_state + .timeslots_used_by_query = + savedDevState[idx].timeslots_used_by_query; + savedDevState[idx].dev->bandwidth.compound_query_state + .bandwidthAllocatedForIndex = + savedDevState[idx].bandwidthAllocatedForIndex; + } + + if (pErrorCode) + *pErrorCode = DP_IMP_ERROR_NONE; + skipPreCheck: + } + } + + // + // DSC is required: either forced by client, DSC_DUAL requested, + // or local link bandwidth is insufficient for uncompressed mode. + // unsigned int forceDscBitsPerPixelX16 = pDscParams->bitsPerPixelX16; result = compoundQueryAttachMSTDsc(target, modesetParams, &localInfo, pDscParams, pErrorCode); @@ -1406,13 +1525,12 @@ bool ConnectorImpl::compoundQueryAttachMST(Group * target, compoundQueryResult = compoundQueryAttachMSTGeneric(target, modesetParams, &localInfo, pDscParams, pErrorCode); // - // compoundQueryAttachMST Generic might fail due to the insufficient bandwidth , - // We only check whether bpp can be fit in the available bandwidth based on the tranied link config in compoundQueryAttachMSTDsc function. - // There might be cases where the default 10 bpp might fit in the available bandwidth based on the trained link config, - // however, the bandwidth might be insufficient at the actual bottleneck link between source and sink to drive the mode, causing CompoundQueryAttachMSTGeneric to fail. - // Incase of CompoundQueryAttachMSTGeneric failure, instead of returning false, check whether the mode can be supported with the max dsc compression bpp - // and return true if it can be supported. - + // compoundQueryAttachMSTGeneric might fail due to insufficient bandwidth + // at a bottleneck link between source and sink. The default 10 bpp might + // fit based on the trained link config, but the actual available bandwidth + // at intermediate MST branches may be lower. If so, retry with max DSC + // compression (8 bpp) to check if that can support the mode. + // if (!compoundQueryResult && forceDscBitsPerPixelX16 == 0U) { pDscParams->bitsPerPixelX16 = MAX_DSC_COMPRESSION_BPPX16;