diff mbox

[2/3] drm/i915: Attach a fb to the load-detect pipe

Message ID 1303245123-27172-3-git-send-email-chris@chris-wilson.co.uk (mailing list archive)
State New, archived
Headers show

Commit Message

Chris Wilson April 19, 2011, 8:32 p.m. UTC
We need to ensure that we feed valid memory into the display plane
attached to the pipe when switching the pipe on. Otherwise, the display
engine may read through an invalid PTE and so throw an PGTBL_ER
exception.

For bonus amusement value, we perform the first load detect before even
establishing our fbdev.

Reported-by: Knut Petersen <Knut_Petersen@t-online.de>
References: https://bugs.freedesktop.org/show_bug.cgi?id=36246
Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
---
 drivers/gpu/drm/i915/intel_crt.c     |   24 ++---
 drivers/gpu/drm/i915/intel_display.c |  182 +++++++++++++++++++---------------
 drivers/gpu/drm/i915/intel_drv.h     |   25 +++--
 drivers/gpu/drm/i915/intel_tv.c      |   11 +--
 4 files changed, 132 insertions(+), 110 deletions(-)

Comments

Keith Packard April 19, 2011, 9:26 p.m. UTC | #1
On Tue, 19 Apr 2011 21:32:02 +0100, Chris Wilson <chris@chris-wilson.co.uk> wrote:

> We need to ensure that we feed valid memory into the display plane
> attached to the pipe when switching the pipe on. Otherwise, the display
> engine may read through an invalid PTE and so throw an PGTBL_ER
> exception.

This seems to rewrite most of the load detection code, but doesn't
justify that rewrite in comments (either commit or in-line). You'll need
to break this up so that we can actually see what is changing.
Chris Wilson April 20, 2011, 9:25 a.m. UTC | #2
On Tue, 19 Apr 2011 14:26:04 -0700, Keith Packard <keithp@keithp.com> wrote:
> On Tue, 19 Apr 2011 21:32:02 +0100, Chris Wilson <chris@chris-wilson.co.uk> wrote:
> 
> > We need to ensure that we feed valid memory into the display plane
> > attached to the pipe when switching the pipe on. Otherwise, the display
> > engine may read through an invalid PTE and so throw an PGTBL_ER
> > exception.
> 
> This seems to rewrite most of the load detection code, but doesn't
> justify that rewrite in comments (either commit or in-line). You'll need
> to break this up so that we can actually see what is changing.

I created a fresh series of patches that hopefully clarifies what needs to
be corrected in intel_get_load_detect_pipe(). It was meant to be attached
here, but I failed to drive git send-email correctly. :(
-Chris
diff mbox

Patch

diff --git a/drivers/gpu/drm/i915/intel_crt.c b/drivers/gpu/drm/i915/intel_crt.c
index d03fc05..2a32405 100644
--- a/drivers/gpu/drm/i915/intel_crt.c
+++ b/drivers/gpu/drm/i915/intel_crt.c
@@ -305,13 +305,11 @@  static bool intel_crt_detect_ddc(struct drm_connector *connector)
 }
 
 static enum drm_connector_status
-intel_crt_load_detect(struct drm_crtc *crtc, struct intel_crt *crt)
+intel_crt_load_detect(struct intel_crt *crt)
 {
-	struct drm_encoder *encoder = &crt->base.base;
-	struct drm_device *dev = encoder->dev;
+	struct drm_device *dev = crt->base.base.dev;
 	struct drm_i915_private *dev_priv = dev->dev_private;
-	struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
-	uint32_t pipe = intel_crtc->pipe;
+	uint32_t pipe = to_intel_crtc(crt->base.base.crtc)->pipe;
 	uint32_t save_bclrpat;
 	uint32_t save_vtotal;
 	uint32_t vtotal, vactive;
@@ -432,7 +430,6 @@  intel_crt_detect(struct drm_connector *connector, bool force)
 	struct drm_device *dev = connector->dev;
 	struct intel_crt *crt = intel_attached_crt(connector);
 	struct drm_crtc *crtc;
-	int dpms_mode;
 	enum drm_connector_status status;
 
 	if (I915_HAS_HOTPLUG(dev)) {
@@ -454,17 +451,18 @@  intel_crt_detect(struct drm_connector *connector, bool force)
 	/* for pre-945g platforms use load detect */
 	crtc = crt->base.base.crtc;
 	if (crtc && crtc->enabled) {
-		status = intel_crt_load_detect(crtc, crt);
+		status = intel_crt_load_detect(crt);
 	} else {
-		crtc = intel_get_load_detect_pipe(&crt->base, connector,
-						  NULL, &dpms_mode);
-		if (crtc) {
+		struct intel_load_detect_pipe tmp;
+
+		if (intel_get_load_detect_pipe(&crt->base,
+					       connector, NULL,
+					       &tmp)) {
 			if (intel_crt_detect_ddc(connector))
 				status = connector_status_connected;
 			else
-				status = intel_crt_load_detect(crtc, crt);
-			intel_release_load_detect_pipe(&crt->base,
-						       connector, dpms_mode);
+				status = intel_crt_load_detect(crt);
+			intel_release_load_detect_pipe(&tmp);
 		} else
 			status = connector_status_unknown;
 	}
diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
index b7d63a5..b9bb20d 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -5541,115 +5541,133 @@  static struct drm_display_mode load_detect_mode = {
 		 704, 832, 0, 480, 489, 491, 520, 0, DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC),
 };
 
-struct drm_crtc *intel_get_load_detect_pipe(struct intel_encoder *intel_encoder,
-					    struct drm_connector *connector,
-					    struct drm_display_mode *mode,
-					    int *dpms_mode)
+bool intel_get_load_detect_pipe(struct intel_encoder *encoder,
+				struct drm_connector *connector,
+				struct drm_display_mode *mode,
+				struct intel_load_detect_pipe *old)
 {
-	struct intel_crtc *intel_crtc;
-	struct drm_crtc *possible_crtc;
-	struct drm_crtc *supported_crtc =NULL;
-	struct drm_encoder *encoder = &intel_encoder->base;
-	struct drm_crtc *crtc = NULL;
-	struct drm_device *dev = encoder->dev;
-	struct drm_encoder_helper_funcs *encoder_funcs = encoder->helper_private;
-	struct drm_crtc_helper_funcs *crtc_funcs;
-	int i = -1;
-
-	/*
-	 * Algorithm gets a little messy:
-	 *   - if the connector already has an assigned crtc, use it (but make
-	 *     sure it's on first)
-	 *   - try to find the first unused crtc that can drive this connector,
-	 *     and use that if we find one
-	 *   - if there are no unused crtcs available, try to use the first
-	 *     one we found that supports the connector
-	 */
+	struct drm_device *dev = encoder->base.dev;
+	struct drm_crtc *crtc;
+	struct drm_framebuffer *fb, *old_fb;
+	int x, y;
 
 	/* See if we already have a CRTC for this connector */
-	if (encoder->crtc) {
-		crtc = encoder->crtc;
-		/* Make sure the crtc and connector are running */
-		intel_crtc = to_intel_crtc(crtc);
-		*dpms_mode = intel_crtc->dpms_mode;
-		if (intel_crtc->dpms_mode != DRM_MODE_DPMS_ON) {
-			crtc_funcs = crtc->helper_private;
-			crtc_funcs->dpms(crtc, DRM_MODE_DPMS_ON);
-			encoder_funcs->dpms(encoder, DRM_MODE_DPMS_ON);
-		}
-		return crtc;
-	}
+	crtc = NULL;
+	if (encoder->base.crtc)
+		crtc = encoder->base.crtc;
 
-	/* Find an unused one (if possible) */
-	list_for_each_entry(possible_crtc, &dev->mode_config.crtc_list, head) {
-		i++;
-		if (!(encoder->possible_crtcs & (1 << i)))
-			continue;
-		if (!possible_crtc->enabled) {
-			crtc = possible_crtc;
-			break;
+	if (crtc == NULL) {
+		struct drm_crtc *pos;
+		int i = 0;
+
+		/* Find an unused one (if possible) */
+		list_for_each_entry(pos, &dev->mode_config.crtc_list, head) {
+			if (!(encoder->base.possible_crtcs & (1 << i++)))
+				continue;
+
+			if (!pos->enabled) {
+				crtc = pos;
+				break;
+			}
 		}
-		if (!supported_crtc)
-			supported_crtc = possible_crtc;
 	}
 
 	/*
 	 * If we didn't find an unused CRTC, don't use any.
 	 */
-	if (!crtc) {
-		return NULL;
-	}
+	if (crtc == NULL)
+		return false;
 
-	encoder->crtc = crtc;
-	connector->encoder = encoder;
-	intel_encoder->load_detect_temp = true;
+	/* Reuse the existing crtc->fb unless absent */
+	old_fb = crtc->fb;
+	if (old_fb == NULL || !crtc->enabled) {
+		struct drm_i915_private *dev_priv = dev->dev_private;
+		struct intel_fbdev *ifbdev = dev_priv->fbdev;
 
-	intel_crtc = to_intel_crtc(crtc);
-	*dpms_mode = intel_crtc->dpms_mode;
+		fb = &ifbdev->ifb.base;
+		x = y = 0;
 
-	if (!crtc->enabled) {
 		if (!mode)
 			mode = &load_detect_mode;
-		drm_crtc_helper_set_mode(crtc, mode, 0, 0, crtc->fb);
 	} else {
-		if (intel_crtc->dpms_mode != DRM_MODE_DPMS_ON) {
-			crtc_funcs = crtc->helper_private;
-			crtc_funcs->dpms(crtc, DRM_MODE_DPMS_ON);
-		}
+		fb = crtc->fb;
+		x = crtc->x;
+		y = crtc->y;
+		mode = &crtc->mode;
+	}
+
+	/* We may be called during ifbdev init, before the fb is bound */
+	if (fb == NULL || to_intel_framebuffer(fb)->obj == NULL)
+		return false;
+
+	/* Save the current configuration to be restored after load-detect */
+	old->connector = connector;
+	old->encoder = connector->encoder;
+	old->crtc = connector->encoder ? connector->encoder->crtc : NULL;
+	if (old->crtc) {
+		struct intel_crtc *intel_crtc;
 
-		/* Add this connector to the crtc */
-		encoder_funcs->mode_set(encoder, &crtc->mode, &crtc->mode);
-		encoder_funcs->commit(encoder);
+		intel_crtc = to_intel_crtc(connector->encoder->crtc);
+		old->crtc = &intel_crtc->base;
+		old->dpms_mode = intel_crtc->dpms_mode;
+		old->fb = intel_crtc->base.fb;
+		old->mode = intel_crtc->base.mode;
+		old->x = intel_crtc->base.x;
+		old->y = intel_crtc->base.y;
 	}
+
+	/* Set the new connector->encoder->crtc->fb configuration */
+	crtc->fb = fb;
+	encoder->base.crtc = crtc;
+	connector->encoder = &encoder->base;
+
+	if (!drm_crtc_helper_set_mode(crtc, mode, x, y, old_fb)) {
+		DRM_DEBUG_KMS("failed to set mode for load-detect\n");
+
+		old->connector->encoder = old->encoder;
+		old->encoder->crtc = old->crtc;
+		old->crtc->fb = old->fb;
+
+		crtc->fb = old_fb;
+
+		drm_helper_disable_unused_functions(dev);
+		return false;
+	}
+	connector->dpms = DRM_MODE_DPMS_ON;
+
 	/* let the connector get through one full cycle before testing */
-	intel_wait_for_vblank(dev, intel_crtc->pipe);
+	intel_wait_for_vblank(dev, to_intel_crtc(crtc)->pipe);
 
-	return crtc;
+	return true;
 }
 
-void intel_release_load_detect_pipe(struct intel_encoder *intel_encoder,
-				    struct drm_connector *connector, int dpms_mode)
+void intel_release_load_detect_pipe(struct intel_load_detect_pipe *old)
 {
-	struct drm_encoder *encoder = &intel_encoder->base;
-	struct drm_device *dev = encoder->dev;
-	struct drm_crtc *crtc = encoder->crtc;
-	struct drm_encoder_helper_funcs *encoder_funcs = encoder->helper_private;
-	struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
+	struct drm_crtc *crtc = old->crtc;
+
+	old->connector->encoder = old->encoder;
+	if (old->encoder != NULL)
+		old->encoder->crtc = crtc;
+	if (crtc != NULL) {
+		struct drm_framebuffer *fb;
+
+		fb = crtc->fb;
+		crtc->fb = old->fb;
 
-	if (intel_encoder->load_detect_temp) {
-		encoder->crtc = NULL;
-		connector->encoder = NULL;
-		intel_encoder->load_detect_temp = false;
 		crtc->enabled = drm_helper_crtc_in_use(crtc);
-		drm_helper_disable_unused_functions(dev);
+		if (crtc->enabled) {
+			if (!drm_crtc_helper_set_mode(crtc, &old->mode,
+						      old->x, old->y, fb))
+				DRM_ERROR("failed to restore mode after load-detect\n");
+
+			old->connector = DRM_MODE_DPMS_ON;
+			if (old->dpms_mode != DRM_MODE_DPMS_ON)
+				drm_helper_connector_dpms(old->connector,
+							  old->dpms_mode);
+		}
 	}
 
-	/* Switch crtc and encoder back off if necessary */
-	if (crtc->enabled && dpms_mode != DRM_MODE_DPMS_ON) {
-		if (encoder->crtc == crtc)
-			encoder_funcs->dpms(encoder, dpms_mode);
-		crtc_funcs->dpms(crtc, dpms_mode);
-	}
+	drm_helper_disable_unused_functions(crtc->dev);
 }
 
 /* Returns the clock of the currently programmed mode of the given pipe. */
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index aeb1b98..64cfc88 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -140,7 +140,6 @@  struct intel_fbdev {
 struct intel_encoder {
 	struct drm_encoder base;
 	int type;
-	bool load_detect_temp;
 	bool needs_tv_clock;
 	void (*hot_plug)(struct intel_encoder *);
 	int crtc_mask;
@@ -298,13 +297,23 @@  static inline void intel_wait_for_crtc_vblank_safe(struct drm_crtc *crtc)
 		msleep(50);
 }
 extern void intel_wait_for_pipe_off(struct drm_device *dev, int pipe);
-extern struct drm_crtc *intel_get_load_detect_pipe(struct intel_encoder *intel_encoder,
-						   struct drm_connector *connector,
-						   struct drm_display_mode *mode,
-						   int *dpms_mode);
-extern void intel_release_load_detect_pipe(struct intel_encoder *intel_encoder,
-					   struct drm_connector *connector,
-					   int dpms_mode);
+
+struct intel_load_detect_pipe {
+	struct drm_connector *connector;
+	struct drm_encoder *encoder;
+	struct drm_crtc *crtc;
+
+	int dpms_mode;
+	struct drm_framebuffer *fb;
+	struct drm_display_mode mode;
+	int x, y;
+};
+
+extern bool intel_get_load_detect_pipe(struct intel_encoder *intel_encoder,
+				       struct drm_connector *connector,
+				       struct drm_display_mode *mode,
+				       struct intel_load_detect_pipe *tmp);
+extern void intel_release_load_detect_pipe(struct intel_load_detect_pipe *tmp);
 
 extern struct drm_connector* intel_sdvo_find(struct drm_device *dev, int sdvoB);
 extern int intel_sdvo_supports_hotplug(struct drm_connector *connector);
diff --git a/drivers/gpu/drm/i915/intel_tv.c b/drivers/gpu/drm/i915/intel_tv.c
index 3047a66..1352acf 100644
--- a/drivers/gpu/drm/i915/intel_tv.c
+++ b/drivers/gpu/drm/i915/intel_tv.c
@@ -1370,15 +1370,12 @@  intel_tv_detect(struct drm_connector *connector, bool force)
 	if (intel_tv->base.base.crtc && intel_tv->base.base.crtc->enabled) {
 		type = intel_tv_detect_type(intel_tv, connector);
 	} else if (force) {
-		struct drm_crtc *crtc;
-		int dpms_mode;
+		struct intel_load_detect_pipe tmp;
 
-		crtc = intel_get_load_detect_pipe(&intel_tv->base, connector,
-						  &mode, &dpms_mode);
-		if (crtc) {
+		if (intel_get_load_detect_pipe(&intel_tv->base, connector,
+					       &mode, &tmp)) {
 			type = intel_tv_detect_type(intel_tv, connector);
-			intel_release_load_detect_pipe(&intel_tv->base, connector,
-						       dpms_mode);
+			intel_release_load_detect_pipe(&tmp);
 		} else
 			return connector_status_unknown;
 	} else