compositor-drm: pageflip timeout implementation
Weston will not repaint until previous update has been acked by a
pageflip event coming from the drm driver. However, some buggy drivers
won’t return those events or will stop sending them at some point and
Weston output repaints will completely freeze. To ease developers’ task
in testing their drivers, this patch makes compositor-drm use a timer
to detect cases where those pageflip events stop coming.
This timeout implementation is software only and includes basic
features usually found in a watchdog. We simply exit Weston gracefully
with a log message and an exit code when the timout is reached.
The timeout value can be set via weston.ini by adding a
pageflip-timeout=<MILLISECONDS> entry under [core]
section. Setting it to 0 disables the timeout feature.
v2:
- Made sure we would get both the pageflip and the vblank events before
stopping the timer.
- Reordered the error and success cases in
drm_output_pageflip_timer_create() to be more in line with the rest
of the code.
v3:
- Reordered (de)arming of the timer with the code around it to avoid it
being rearmed before the current dearming.
- Return the proper value for the dispatcher in the pageflip_timeout
callback.
- Also display the output name in case the timer fires.
v4:
- Reordered a forgotten timer rearming after its drmModePageFlip().
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=83884
Signed-off-by: Frederic Plourde <frederic.plourde at collabora.co.uk>
Signed-off-by: Emmanuel Gil Peyrot <emmanuel.peyrot@collabora.com>
Reviewed-by: Daniel Stone <daniels@collabora.com>
Reviewed-by: Pekka Paalanen <pekka.paalanen@collabora.co.uk>
diff --git a/libweston/compositor-drm.c b/libweston/compositor-drm.c
index 7f78699..0062307 100644
--- a/libweston/compositor-drm.c
+++ b/libweston/compositor-drm.c
@@ -119,6 +119,7 @@
int32_t cursor_height;
uint32_t connector;
+ uint32_t pageflip_timeout;
};
struct drm_mode {
@@ -182,6 +183,8 @@
struct vaapi_recorder *recorder;
struct wl_listener recorder_frame_listener;
+
+ struct wl_event_source *pageflip_timer;
};
/*
@@ -225,6 +228,45 @@
return container_of(base->backend, struct drm_backend, base);
}
+static int
+pageflip_timeout(void *data) {
+ /*
+ * Our timer just went off, that means we're not receiving drm
+ * page flip events anymore for that output. Let's gracefully exit
+ * weston with a return value so devs can debug what's going on.
+ */
+ struct drm_output *output = data;
+ struct weston_compositor *compositor = output->base.compositor;
+
+ weston_log("Pageflip timeout reached on output %s, your "
+ "driver is probably buggy! Exiting.\n",
+ output->base.name);
+ weston_compositor_exit_with_code(compositor, EXIT_FAILURE);
+
+ return 0;
+}
+
+/* Creates the pageflip timer. Note that it isn't armed by default */
+static int
+drm_output_pageflip_timer_create(struct drm_output *output)
+{
+ struct wl_event_loop *loop = NULL;
+ struct weston_compositor *ec = output->base.compositor;
+
+ loop = wl_display_get_event_loop(ec->wl_display);
+ assert(loop);
+ output->pageflip_timer = wl_event_loop_add_timer(loop,
+ pageflip_timeout,
+ output);
+
+ if (output->pageflip_timer == NULL) {
+ weston_log("creating drm pageflip timer failed: %m\n");
+ return -1;
+ }
+
+ return 0;
+}
+
static void
drm_output_set_cursor(struct drm_output *output);
@@ -758,6 +800,10 @@
output->page_flip_pending = 1;
+ if (output->pageflip_timer)
+ wl_event_source_timer_update(output->pageflip_timer,
+ backend->pageflip_timeout);
+
drm_output_set_cursor(output);
/*
@@ -878,6 +924,10 @@
goto finish_frame;
}
+ if (output->pageflip_timer)
+ wl_event_source_timer_update(output->pageflip_timer,
+ backend->pageflip_timeout);
+
return;
finish_frame:
@@ -916,6 +966,10 @@
s->next = NULL;
if (!output->page_flip_pending) {
+ /* Stop the pageflip timer instead of rearming it here */
+ if (output->pageflip_timer)
+ wl_event_source_timer_update(output->pageflip_timer, 0);
+
ts.tv_sec = sec;
ts.tv_nsec = usec * 1000;
weston_output_finish_frame(&output->base, &ts, flags);
@@ -953,6 +1007,10 @@
else if (output->disable_pending)
weston_output_disable(&output->base);
else if (!output->vblank_pending) {
+ /* Stop the pageflip timer instead of rearming it here */
+ if (output->pageflip_timer)
+ wl_event_source_timer_update(output->pageflip_timer, 0);
+
ts.tv_sec = sec;
ts.tv_nsec = usec * 1000;
weston_output_finish_frame(&output->base, &ts, flags);
@@ -2418,6 +2476,9 @@
output->dpms_prop = drm_get_prop(b->drm.fd, output->connector, "DPMS");
+ if (b->pageflip_timeout)
+ drm_output_pageflip_timer_create(output);
+
if (b->use_pixman) {
if (drm_output_init_pixman(output, b) < 0) {
weston_log("Failed to init output pixman state\n");
@@ -2532,6 +2593,9 @@
drmModeFreeCrtc(origcrtc);
}
+ if (output->pageflip_timer)
+ wl_event_source_remove(output->pageflip_timer);
+
weston_output_destroy(&output->base);
drmModeFreeConnector(output->connector);
@@ -3309,6 +3373,7 @@
b->sprites_are_broken = 1;
b->compositor = compositor;
b->use_pixman = config->use_pixman;
+ b->pageflip_timeout = config->pageflip_timeout;
if (parse_gbm_format(config->gbm_format, GBM_FORMAT_XRGB8888, &b->gbm_format) < 0)
goto err_compositor;
diff --git a/libweston/compositor-drm.h b/libweston/compositor-drm.h
index 2e2995a..0870719 100644
--- a/libweston/compositor-drm.h
+++ b/libweston/compositor-drm.h
@@ -138,6 +138,13 @@
*/
void (*configure_device)(struct weston_compositor *compositor,
struct libinput_device *device);
+
+ /** Maximum duration for a pageflip event to arrive, after which the
+ * compositor will consider the DRM driver crashed and will try to exit
+ * cleanly.
+ *
+ * It is exprimed in milliseconds, 0 means disabled. */
+ uint32_t pageflip_timeout;
};
#ifdef __cplusplus