drm/i915: Reject modeset when the same digital port is used more than once
authorVille Syrjälä <ville.syrjala@linux.intel.com>
Tue, 2 Dec 2014 12:10:46 +0000 (14:10 +0200)
committerDaniel Vetter <daniel.vetter@ffwll.ch>
Wed, 3 Dec 2014 08:31:53 +0000 (09:31 +0100)
On pre-HSW we have two encoders per digital port: one HDMI, one DP.
However they are the same physical port in hardware and we can't enable
both at the same time. Reject the modeset if the user attempts this.

So far we've been saved by the fact that we never see both HDMI and DP
connectors as connected. But if the user decides to force a mode anyway,
all kinds of funny stuff might happen.

Unfortunately we don't seem to have any way to inform userspace that
such configurations are invalid except by returning an error from
setcrtc. possible_clones only covers real cloning situations, and
looking at the connector names doesn't work either since we don't
always register both connectors for the same port. I suppose the
only way to fix that would be to expose only a single encoder per
digital port like we do on HSW+ but that would be a fairly large
undertaking for little gain.

kms_setmode hits this since it forces modes on non-connected VGA and
HDMI connectors. Previosuly it just resulted in weirdness such as
failed link training. With this patch it will now get an error back
from the kernel and will die with an assert since it thinks that the
configuration should be fine.

v2: Deal with INTEL_OUTPUT_UNKNOWN (Paulo)

Cc: Paulo Zanoni <przanoni@gmail.com>
Reviewed-by: Paulo Zanoni <paulo.r.zanoni@intel.com>
Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com>
Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
drivers/gpu/drm/i915/intel_display.c

index 910df02840d62f21afb29d3e9ec9cf685e19c851..6289babd03b05ae9e459813fd6c64a7dd7403189 100644 (file)
@@ -10153,6 +10153,48 @@ static bool check_encoder_cloning(struct intel_crtc *crtc)
        return true;
 }
 
+static bool check_digital_port_conflicts(struct drm_device *dev)
+{
+       struct intel_connector *connector;
+       unsigned int used_ports = 0;
+
+       /*
+        * Walk the connector list instead of the encoder
+        * list to detect the problem on ddi platforms
+        * where there's just one encoder per digital port.
+        */
+       list_for_each_entry(connector,
+                           &dev->mode_config.connector_list, base.head) {
+               struct intel_encoder *encoder = connector->new_encoder;
+
+               if (!encoder)
+                       continue;
+
+               WARN_ON(!encoder->new_crtc);
+
+               switch (encoder->type) {
+                       unsigned int port_mask;
+               case INTEL_OUTPUT_UNKNOWN:
+                       if (WARN_ON(!HAS_DDI(dev)))
+                               break;
+               case INTEL_OUTPUT_DISPLAYPORT:
+               case INTEL_OUTPUT_HDMI:
+               case INTEL_OUTPUT_EDP:
+                       port_mask = 1 << enc_to_dig_port(&encoder->base)->port;
+
+                       /* the same port mustn't appear more than once */
+                       if (used_ports & port_mask)
+                               return false;
+
+                       used_ports |= port_mask;
+               default:
+                       break;
+               }
+       }
+
+       return true;
+}
+
 static struct intel_crtc_config *
 intel_modeset_pipe_config(struct drm_crtc *crtc,
                          struct drm_framebuffer *fb,
@@ -10169,6 +10211,11 @@ intel_modeset_pipe_config(struct drm_crtc *crtc,
                return ERR_PTR(-EINVAL);
        }
 
+       if (!check_digital_port_conflicts(dev)) {
+               DRM_DEBUG_KMS("rejecting conflicting digital port configuration\n");
+               return ERR_PTR(-EINVAL);
+       }
+
        pipe_config = kzalloc(sizeof(*pipe_config), GFP_KERNEL);
        if (!pipe_config)
                return ERR_PTR(-ENOMEM);