diff --git a/lib/igt_kms.c b/lib/igt_kms.c
index 064b741..9e1fca7 100644
--- a/lib/igt_kms.c
+++ b/lib/igt_kms.c
@@ -730,7 +730,15 @@ static uint32_t igt_plane_get_fb_gem_handle(igt_plane_t *plane)
 		return 0;
 }
 
-static int igt_cursor_commit(igt_plane_t *plane, igt_output_t *output)
+#define CHECK_RETURN(r, fail) {	\
+	if (r && !fail)		\
+		return r;	\
+	igt_assert(r == 0);	\
+}
+
+static int igt_cursor_commit(igt_plane_t *plane,
+			     igt_output_t *output,
+			     bool fail_on_error)
 {
 	igt_display_t *display = output->display;
 	uint32_t crtc_id = output->config.crtc->crtc_id;
@@ -747,8 +755,7 @@ static int igt_cursor_commit(igt_plane_t *plane, igt_output_t *output)
 		    x, y);
 
 		ret = drmModeMoveCursor(display->drm_fd, crtc_id, x, y);
-
-		igt_assert(ret == 0);
+		CHECK_RETURN(ret, fail_on_error);
 
 		plane->position_changed = false;
 	}
@@ -756,7 +763,15 @@ static int igt_cursor_commit(igt_plane_t *plane, igt_output_t *output)
 	return 0;
 }
 
-static int igt_drm_plane_commit(igt_plane_t *plane, igt_output_t *output)
+/*
+ * Commit position and fb changes to a DRM plane via the SetPlane ioctl; if the
+ * DRM call to program the plane fails, we'll either fail immediately (for
+ * tests that expect the commit to succeed) or return the failure code (for
+ * tests that expect a specific error code).
+ */
+static int igt_drm_plane_commit(igt_plane_t *plane,
+				igt_output_t *output,
+				bool fail_on_error)
 {
 	igt_display_t *display = output->display;
 	uint32_t fb_id, crtc_id;
@@ -783,8 +798,7 @@ static int igt_drm_plane_commit(igt_plane_t *plane, igt_output_t *output)
 				      IGT_FIXED(0,0), /* src_y */
 				      IGT_FIXED(0,0), /* src_w */
 				      IGT_FIXED(0,0) /* src_h */);
-
-		igt_assert(ret == 0);
+		CHECK_RETURN(ret, fail_on_error);
 
 		plane->fb_changed = false;
 	} else if (plane->fb_changed || plane->position_changed) {
@@ -807,8 +821,7 @@ static int igt_drm_plane_commit(igt_plane_t *plane, igt_output_t *output)
 				      IGT_FIXED(0,0), /* src_y */
 				      IGT_FIXED(plane->fb->width,0), /* src_w */
 				      IGT_FIXED(plane->fb->height,0) /* src_h */);
-
-		igt_assert(ret == 0);
+		CHECK_RETURN(ret, fail_on_error);
 
 		plane->fb_changed = false;
 		plane->position_changed = false;
@@ -817,20 +830,22 @@ static int igt_drm_plane_commit(igt_plane_t *plane, igt_output_t *output)
 	return 0;
 }
 
-static int igt_plane_commit(igt_plane_t *plane, igt_output_t *output)
+static int igt_plane_commit(igt_plane_t *plane,
+			    igt_output_t *output,
+			    bool fail_on_error)
 {
 	if (plane->is_cursor) {
-		igt_cursor_commit(plane, output);
+		igt_cursor_commit(plane, output, fail_on_error);
 	} else if (plane->is_primary) {
 		/* state updated by SetCrtc */
 	} else {
-		igt_drm_plane_commit(plane, output);
+		igt_drm_plane_commit(plane, output, fail_on_error);
 	}
 
 	return 0;
 }
 
-static int igt_output_commit(igt_output_t *output)
+static int igt_output_commit(igt_output_t *output, bool fail_on_error)
 {
 	igt_display_t *display = output->display;
 	igt_pipe_t *pipe;
@@ -885,8 +900,7 @@ static int igt_output_commit(igt_output_t *output)
 					     0,    /* n_connectors */
 					     NULL  /* mode */);
 		}
-
-		igt_assert(ret == 0);
+		CHECK_RETURN(ret, fail_on_error);
 
 		primary->fb_changed = false;
 	}
@@ -919,8 +933,7 @@ static int igt_output_commit(igt_output_t *output)
 			ret = drmModeSetCursor(display->drm_fd, crtc_id,
 					       0, 0, 0);
 		}
-
-		igt_assert(ret == 0);
+		CHECK_RETURN(ret, fail_on_error);
 
 		cursor->fb_changed = false;
 		need_wait_for_vblank = true;
@@ -932,7 +945,7 @@ static int igt_output_commit(igt_output_t *output)
 		if (plane->fb_changed || plane->position_changed)
 			need_wait_for_vblank = true;
 
-		igt_plane_commit(plane, output);
+		igt_plane_commit(plane, output, fail_on_error);
 	}
 
 	/*
@@ -946,23 +959,20 @@ static int igt_output_commit(igt_output_t *output)
 	return 0;
 }
 
-/**
- * igt_display_commit2:
- * @display: DRM device handle
- * @s: Commit style
- *
- * Commits framebuffer and positioning changes to all planes of each display
- * pipe, using a specific API to perform the programming.  This function should
- * be used to exercise a specific driver programming API; igt_display_commit
- * should be used instead if the API used is unimportant to the test being run.
+/*
+ * Commit all plane changes across all outputs of the display.
  *
- * Returns: 0 upon success.  This function will never return upon failure
- * since igt_fail() at lower levels will longjmp out of it.
+ * If @fail_on_error is true, any failure to commit plane state will lead
+ * to subtest failure in the specific function where the failure occurs.
+ * Otherwise, the first error code encountered will be returned and no
+ * further programming will take place, which may result in some changes
+ * taking effect and others not taking effect.
  */
-int igt_display_commit2(igt_display_t *display,
-		       enum igt_commit_style s)
+static int do_display_commit(igt_display_t *display,
+			     enum igt_commit_style s,
+			     bool fail_on_error)
 {
-	int i;
+	int i, ret;
 
 	LOG_INDENT(display, "commit");
 
@@ -974,7 +984,8 @@ int igt_display_commit2(igt_display_t *display,
 		if (!output->valid)
 			continue;
 
-		igt_output_commit(output);
+		ret = igt_output_commit(output, fail_on_error);
+		CHECK_RETURN(ret, fail_on_error);
 	}
 
 	LOG_UNINDENT(display);
@@ -986,10 +997,57 @@ int igt_display_commit2(igt_display_t *display,
 }
 
 /**
- * igt_display_commit:
+ * igt_display_commit2:
+ * @display: DRM device handle
+ * @s: Commit style
+ *
+ * Commits framebuffer and positioning changes to all planes of each display
+ * pipe, using a specific API to perform the programming.  This function should
+ * be used to exercise a specific driver programming API; igt_display_commit
+ * should be used instead if the API used is unimportant to the test being run.
+ *
+ * This function should only be used to commit changes that are expected to
+ * succeed, since any failure during the commit process will cause the IGT
+ * subtest to fail.  To commit changes that are expected to fail, use
+ * @igt_try_display_commit2 instead.
+ *
+ * Returns: 0 upon success.  This function will never return upon failure
+ * since igt_fail() at lower levels will longjmp out of it.
+ */
+int igt_display_commit2(igt_display_t *display,
+		       enum igt_commit_style s)
+{
+	return do_display_commit(display, s, true);
+}
+
+/**
+ * igt_display_try_commit2:
  * @display: DRM device handle
  * @s: Commit style
  *
+ * Attempts to commit framebuffer and positioning changes to all planes of each
+ * display pipe.  This function should be used to commit changes that are
+ * expected to fail, so that the error code can be checked for correctness.
+ * For changes that are expected to succeed, use @igt_display_commit instead.
+ *
+ * Note that in non-atomic commit styles, no display programming will be
+ * performed after the first failure is encountered, so only some of the
+ * operations requested by a test may have been completed.  Tests that catch
+ * errors returned by this function should take care to restore the display to
+ * a sane state after a failure is detected.
+ *
+ * Returns: 0 upon success, otherwise the error code of the first error
+ * encountered.
+ */
+int igt_display_try_commit2(igt_display_t *display, enum igt_commit_style s)
+{
+       return do_display_commit(display, s, false);
+}
+
+/**
+ * igt_display_commit:
+ * @display: DRM device handle
+ *
  * Commits framebuffer and positioning changes to all planes of each display
  * pipe.
  *
diff --git a/lib/igt_kms.h b/lib/igt_kms.h
index 62dc73c..e238523 100644
--- a/lib/igt_kms.h
+++ b/lib/igt_kms.h
@@ -158,6 +158,7 @@ void igt_display_init(igt_display_t *display, int drm_fd);
 void igt_display_fini(igt_display_t *display);
 int  igt_display_commit2(igt_display_t *display, enum igt_commit_style s);
 int  igt_display_commit(igt_display_t *display);
+int  igt_display_try_commit2(igt_display_t *display, enum igt_commit_style s);
 int  igt_display_get_n_pipes(igt_display_t *display);
 
 const char *igt_output_name(igt_output_t *output);
