[PATCH weston v10 36/61] compositor-drm: Atomic modesetting support

Daniel Stone daniels at collabora.com
Tue Apr 4 16:54:54 UTC 2017


Add support for using the atomic-modesetting API to apply output state.
Unlike previous series, this commit does not unflip sprites_are_broken,
until further work has been done with assign_planes to make it reliable.

Differential Revision: https://phabricator.freedesktop.org/D1507

Signed-off-by: Daniel Stone <daniels at collabora.com>
Co-authored-by: Pekka Paalanen <pekka.paalanen at collabora.co.uk>
Co-authored-by: Louis-Francis Ratté-Boulianne <louis-francis.ratte-boulianne at collabora.com>
Co-authored-by: Derek Foreman <derek.foreman at collabora.co.uk>
---
 configure.ac               |   3 +-
 libweston/compositor-drm.c | 298 +++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 292 insertions(+), 9 deletions(-)

v10: Use page_flip_handler2 so we can issue a single atomic request covering
     multiple CRTCs.

diff --git a/configure.ac b/configure.ac
index d3ef8a99..83918ac1 100644
--- a/configure.ac
+++ b/configure.ac
@@ -206,7 +206,8 @@ AM_CONDITIONAL(ENABLE_DRM_COMPOSITOR, test x$enable_drm_compositor = xyes)
 if test x$enable_drm_compositor = xyes; then
   AC_DEFINE([BUILD_DRM_COMPOSITOR], [1], [Build the DRM compositor])
   PKG_CHECK_MODULES(DRM_COMPOSITOR, [libudev >= 136 libdrm >= 2.4.30 gbm mtdev >= 1.1.0])
-  PKG_CHECK_MODULES(DRM_COMPOSITOR_ATOMIC, [libdrm >= 2.4.62],
+  # XXX: Will be 2.4.76 ... ? Need page_flip_handler2
+  PKG_CHECK_MODULES(DRM_COMPOSITOR_ATOMIC, [libdrm >= 2.4.75],
 		    [AC_DEFINE([HAVE_DRM_ATOMIC], 1, [libdrm supports atomic API])],
 		    [AC_MSG_WARN([libdrm does not support atomic modesetting, will omit that capability])])
   PKG_CHECK_MODULES(DRM_COMPOSITOR_GBM, [gbm >= 10.2],
diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c
index 8d56d513..f46bdcd4 100644
--- a/libweston/compositor-drm.c
+++ b/libweston/compositor-drm.c
@@ -381,6 +381,7 @@ struct drm_output {
 
 	int vblank_pending;
 	int page_flip_pending;
+	int atomic_complete_pending;
 	int destroy_pending;
 	int disable_pending;
 	int dpms_off_pending;
@@ -1435,6 +1436,7 @@ drm_output_assign_state(struct drm_output_state *state,
 			enum drm_output_state_update_mode mode)
 {
 	struct drm_output *output = state->output;
+	struct drm_backend *b = to_drm_backend(output->base.compositor);
 	struct drm_plane_state *plane_state;
 
 	assert(!output->state_last);
@@ -1448,6 +1450,9 @@ drm_output_assign_state(struct drm_output_state *state,
 	wl_list_init(&state->link);
 	output->state_cur = state;
 
+	if (b->atomic_modeset && mode == DRM_OUTPUT_STATE_UPDATE_ASYNCHRONOUS)
+		output->atomic_complete_pending = 1;
+
 	/* Replace state_cur on each affected plane with the new state, being
 	 * careful to dispose of orphaned (but only orphaned) previous state.
 	 * If the previous state is not orphaned (still has an output_state
@@ -1459,7 +1464,8 @@ drm_output_assign_state(struct drm_output_state *state,
 			drm_plane_state_free(plane->state_cur, true);
 		plane->state_cur = plane_state;
 
-		if (mode != DRM_OUTPUT_STATE_UPDATE_ASYNCHRONOUS)
+		if (mode != DRM_OUTPUT_STATE_UPDATE_ASYNCHRONOUS ||
+		    b->atomic_modeset)
 			continue;
 
 		if (plane->type == WDRM_PLANE_TYPE_OVERLAY)
@@ -1750,7 +1756,7 @@ drm_waitvblank_pipe(struct drm_output *output)
 }
 
 static int
-drm_output_apply_state(struct drm_output_state *state)
+drm_output_apply_state_legacy(struct drm_output_state *state)
 {
 	struct drm_output *output = state->output;
 	struct drm_backend *backend = to_drm_backend(output->base.compositor);
@@ -1916,6 +1922,236 @@ err:
 	return -1;
 }
 
+#ifdef HAVE_DRM_ATOMIC
+static int
+crtc_add_prop(drmModeAtomicReq *req, struct drm_output *output,
+	      enum wdrm_crtc_property prop, uint64_t val)
+{
+	struct drm_backend *b = to_drm_backend(output->base.compositor);
+	struct drm_property_info *info = &b->props_crtc[prop];
+	int ret;
+
+	if (!info)
+		return -1;
+
+	ret = drmModeAtomicAddProperty(req, output->crtc_id, info->prop_id,
+				       val);
+	return (ret <= 0) ? -1 : 0;
+}
+
+static int
+connector_add_prop(drmModeAtomicReq *req, struct drm_output *output,
+		   enum wdrm_connector_property prop, uint64_t val)
+{
+	struct drm_backend *b = to_drm_backend(output->base.compositor);
+	struct drm_property_info *info = &b->props_conn[prop];
+	int ret;
+
+	if (!info)
+		return -1;
+
+	ret = drmModeAtomicAddProperty(req, output->connector_id,
+				       info->prop_id, val);
+	return (ret <= 0) ? -1 : 0;
+}
+
+static int
+plane_add_prop(drmModeAtomicReq *req, struct drm_plane *plane,
+	       enum wdrm_plane_property prop, uint64_t val)
+{
+	struct drm_backend *b = to_drm_backend(plane->base.compositor);
+	struct drm_property_info *info = &b->props_plane[prop];
+	int ret;
+
+	if (!info)
+		return -1;
+
+	ret = drmModeAtomicAddProperty(req, plane->plane_id, info->prop_id,
+				       val);
+	return (ret <= 0) ? -1 : 0;
+}
+
+static int
+drm_mode_ensure_blob(struct drm_backend *backend, struct drm_mode *mode)
+{
+	int ret;
+
+	if (mode->blob_id)
+		return 0;
+
+	ret = drmModeCreatePropertyBlob(backend->drm.fd,
+					&mode->mode_info,
+					sizeof(mode->mode_info),
+					&mode->blob_id);
+	if (ret != 0)
+		weston_log("failed to create mode property blob: %m\n");
+
+	return ret;
+}
+
+static int
+drm_output_apply_state_atomic(struct drm_output_state *state,
+			      drmModeAtomicReq *req,
+			      uint32_t *flags)
+{
+	struct drm_output *output = state->output;
+	struct drm_backend *backend = to_drm_backend(output->base.compositor);
+	struct drm_plane_state *plane_state;
+	struct drm_mode *current_mode = to_drm_mode(output->base.current_mode);
+	int ret = 0;
+
+	if (state->dpms != output->state_cur->dpms)
+		*flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
+
+	if (state->dpms == WESTON_DPMS_ON) {
+		ret = drm_mode_ensure_blob(backend, current_mode);
+		if (ret != 0)
+			goto err;
+
+		ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID,
+				     current_mode->blob_id);
+		ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 1);
+		ret |= connector_add_prop(req, output, WDRM_CONNECTOR_CRTC_ID,
+					  output->crtc_id);
+	} else {
+		ret |= crtc_add_prop(req, output, WDRM_CRTC_MODE_ID, 0);
+		ret |= crtc_add_prop(req, output, WDRM_CRTC_ACTIVE, 0);
+		ret |= connector_add_prop(req, output, WDRM_CONNECTOR_CRTC_ID,
+					  0);
+	}
+
+	if (ret != 0) {
+		weston_log("couldn't set atomic CRTC/connector state\n");
+		goto err;
+	}
+
+	wl_list_for_each(plane_state, &state->plane_list, link) {
+		struct drm_plane *plane = plane_state->plane;
+
+		ret |= plane_add_prop(req, plane, WDRM_PLANE_FB_ID,
+				      plane_state->fb ? plane_state->fb->fb_id : 0);
+		ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_ID,
+				      plane_state->fb ? output->crtc_id : 0);
+		ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_X,
+				      plane_state->src_x);
+		ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_Y,
+				      plane_state->src_y);
+		ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_W,
+				      plane_state->src_w);
+		ret |= plane_add_prop(req, plane, WDRM_PLANE_SRC_H,
+				      plane_state->src_h);
+		ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_X,
+				      plane_state->dest_x);
+		ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_Y,
+				      plane_state->dest_y);
+		ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_W,
+				      plane_state->dest_w);
+		ret |= plane_add_prop(req, plane, WDRM_PLANE_CRTC_H,
+				      plane_state->dest_h);
+
+		if (ret != 0) {
+			weston_log("couldn't set plane state\n");
+			goto err;
+		}
+	}
+
+	return 0;
+
+err:
+	drm_output_state_free(state);
+	return -1;
+}
+
+static int
+drm_pending_state_apply_atomic(struct drm_pending_state *pending_state)
+{
+	struct drm_backend *b = pending_state->backend;
+	struct drm_output_state *output_state, *tmp;
+	drmModeAtomicReq *req = drmModeAtomicAlloc();
+	uint32_t *unused;
+	uint32_t flags = 0;
+	int ret = 0;
+
+	if (!req)
+		return -1;
+
+	if (b->state_invalid) {
+		struct drm_property_info *info;
+		int err;
+
+		/* If we need to reset all our state (e.g. because we've
+		 * just started, or just been VT-switched in), explicitly
+		 * disable all the CRTCs and connectors we aren't using. */
+		wl_array_for_each(unused, &b->unused_connectors) {
+			info = &b->props_conn[WDRM_CONNECTOR_CRTC_ID];
+			err = drmModeAtomicAddProperty(req, *unused,
+						       info->prop_id, 0);
+			if (err <= 0)
+				ret = -1;
+		}
+
+		wl_array_for_each(unused, &b->unused_crtcs) {
+			drmModeObjectProperties *props;
+			uint64_t active;
+
+			/* This is ugly; we can't emit a disable on a CRTC
+			 * that's already off, as the kernel will refuse to
+			 * generate an event for an off->off state and fail
+			 * the commit. Instead, we have to go out of our way
+			 * to avoid it here. */
+			props = drmModeObjectGetProperties(b->drm.fd,
+							   *unused,
+							   DRM_MODE_OBJECT_CRTC);
+			if (!props) {
+				ret = -1;
+				continue;
+			}
+			info = &b->props_crtc[WDRM_CRTC_ACTIVE];
+			active = drm_property_get_value(info, props, 0);
+			drmModeFreeObjectProperties(props);
+			if (active == 0)
+				continue;
+
+			err = drmModeAtomicAddProperty(req, *unused,
+						       info->prop_id, 0);
+			if (err <= 0)
+				ret = -1;
+		}
+
+		flags |= DRM_MODE_ATOMIC_ALLOW_MODESET;
+	}
+
+	wl_list_for_each(output_state, &pending_state->output_list, link)
+		ret |= drm_output_apply_state_atomic(output_state, req, &flags);
+
+	if (ret != 0) {
+		weston_log("atomic: couldn't compile atomic state\n");
+		goto out;
+	}
+
+	flags |= DRM_MODE_PAGE_FLIP_EVENT | DRM_MODE_ATOMIC_NONBLOCK;
+	ret = drmModeAtomicCommit(b->drm.fd, req, flags, b);
+	if (ret != 0) {
+		weston_log("atomic: couldn't commit new state: %m\n");
+		goto out;
+	}
+
+	wl_list_for_each_safe(output_state, tmp, &pending_state->output_list,
+			      link) {
+		drm_output_assign_state(output_state,
+					DRM_OUTPUT_STATE_UPDATE_ASYNCHRONOUS);
+	}
+
+	b->state_invalid = false;
+
+	assert(wl_list_empty(&pending_state->output_list));
+
+out:
+	drmModeAtomicFree(req);
+	return ret;
+}
+#endif
+
 static int
 drm_pending_state_apply(struct drm_pending_state *pending_state)
 {
@@ -1923,6 +2159,11 @@ drm_pending_state_apply(struct drm_pending_state *pending_state)
 	struct drm_output_state *output_state, *tmp;
 	uint32_t *unused;
 
+#ifdef HAVE_DRM_ATOMIC
+	if (b->atomic_modeset)
+		return drm_pending_state_apply_atomic(pending_state);
+#endif
+
 	if (b->state_invalid) {
 		/* If we need to reset all our state (e.g. because we've
 		 * just started, or just been VT-switched in), explicitly
@@ -1939,7 +2180,7 @@ drm_pending_state_apply(struct drm_pending_state *pending_state)
 		struct drm_output *output = output_state->output;
 		int ret;
 
-		ret = drm_output_apply_state(output_state);
+		ret = drm_output_apply_state_legacy(output_state);
 		if (ret != 0) {
 			weston_log("Couldn't apply state for output %s\n",
 				   output->base.name);
@@ -2093,9 +2334,12 @@ vblank_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec,
 	struct drm_plane_state *ps = (struct drm_plane_state *) data;
 	struct drm_output_state *os = ps->output_state;
 	struct drm_output *output = os->output;
+	struct drm_backend *b = to_drm_backend(output->base.compositor);
 	uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION |
 			 WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK;
 
+	assert(!b->atomic_modeset);
+
 	drm_output_update_msc(output, frame);
 	output->vblank_pending--;
 	assert(output->vblank_pending >= 0);
@@ -2113,12 +2357,14 @@ page_flip_handler(int fd, unsigned int frame,
 		  unsigned int sec, unsigned int usec, void *data)
 {
 	struct drm_output *output = data;
+	struct drm_backend *b = to_drm_backend(output->base.compositor);
 	uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC |
 			 WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION |
 			 WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK;
 
 	drm_output_update_msc(output, frame);
 
+	assert(!b->atomic_modeset);
 	assert(output->page_flip_pending);
 	output->page_flip_pending = 0;
 
@@ -2182,6 +2428,31 @@ drm_repaint_cancel(struct weston_compositor *compositor, void *repaint_data)
 	b->repaint_data = NULL;
 }
 
+static void
+atomic_flip_handler(int fd, unsigned int frame, unsigned int sec,
+		    unsigned int usec, unsigned int crtc_id, void *data)
+{
+	struct drm_backend *b = data;
+	struct drm_output *output = drm_output_find_by_crtc(b, crtc_id);
+	uint32_t flags = WP_PRESENTATION_FEEDBACK_KIND_VSYNC |
+			 WP_PRESENTATION_FEEDBACK_KIND_HW_COMPLETION |
+			 WP_PRESENTATION_FEEDBACK_KIND_HW_CLOCK;
+
+	/* During the initial modeset, we can disable CRTCs which we don't
+	 * actually handle during normal operation; this will give us events
+	 * for unknown outputs. Ignore them. */
+	if (!output)
+		return;
+
+	drm_output_update_msc(output, frame);
+
+	assert(b->atomic_modeset);
+	assert(output->atomic_complete_pending);
+	output->atomic_complete_pending = 0;
+
+	drm_output_update_complete(output, flags, sec, usec);
+}
+
 static uint32_t
 drm_output_check_plane_format(struct drm_plane *p,
 			       struct weston_view *ev, struct gbm_bo *bo)
@@ -2813,11 +3084,17 @@ drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mo
 static int
 on_drm_input(int fd, uint32_t mask, void *data)
 {
+	struct drm_backend *b = data;
 	drmEventContext evctx;
 
 	memset(&evctx, 0, sizeof evctx);
-	evctx.version = DRM_EVENT_CONTEXT_VERSION;
-	evctx.page_flip_handler = page_flip_handler;
+	evctx.version = 3;
+#ifdef HAVE_DRM_ATOMIC
+	if (b->atomic_modeset)
+		evctx.page_flip_handler2 = atomic_flip_handler;
+	else
+#endif
+		evctx.page_flip_handler = page_flip_handler;
 	evctx.vblank_handler = vblank_handler;
 	drmHandleEvent(fd, &evctx);
 
@@ -2863,8 +3140,11 @@ init_kms_caps(struct drm_backend *b)
 		   b->universal_planes ? "supports" : "does not support");
 
 #ifdef HAVE_DRM_ATOMIC
+	ret = drmGetCap(b->drm.fd, DRM_CAP_CRTC_IN_VBLANK_EVENT, &cap);
+	if (ret != 0)
+		cap = 0;
 	ret = drmSetClientCap(b->drm.fd, DRM_CLIENT_CAP_ATOMIC, 1);
-	b->atomic_modeset = (ret == 0);
+	b->atomic_modeset = ((ret == 0) && (cap == 1));
 #endif
 	weston_log("DRM: %s atomic modesetting\n",
 		   b->atomic_modeset ? "supports" : "does not support");
@@ -4190,7 +4470,8 @@ drm_output_destroy(struct weston_output *base)
 	struct drm_backend *b = to_drm_backend(base->compositor);
 	struct drm_mode *drm_mode, *next;
 
-	if (output->page_flip_pending || output->vblank_pending) {
+	if (output->page_flip_pending || output->vblank_pending ||
+	    output->atomic_complete_pending) {
 		output->destroy_pending = 1;
 		weston_log("destroy output while page flip pending\n");
 		return;
@@ -4228,7 +4509,8 @@ drm_output_disable(struct weston_output *base)
 	uint32_t *unused;
 	int ret;
 
-	if (output->page_flip_pending || output->vblank_pending) {
+	if (output->page_flip_pending || output->vblank_pending ||
+	    output->atomic_complete_pending) {
 		output->disable_pending = 1;
 		return -1;
 	}
-- 
2.12.2



More information about the wayland-devel mailing list