backend/drm: don't free connector immediately

When a pageflip is pending, we'll get a DRM event for the connector
in the future. We don't want to free the connector immediately
otherwise we'll use-after-free in the pageflip handler.

This commit adds a new state, "DISAPPEARED". This asks the pageflip
handler to destroy the output after it's done pageflipping.
This commit is contained in:
emersion 2018-09-28 10:00:40 +02:00
parent 19f3804548
commit 79dd9ba151
2 changed files with 18 additions and 5 deletions

View file

@ -709,6 +709,7 @@ static bool drm_connector_move_cursor(struct wlr_output *output,
static void drm_connector_destroy(struct wlr_output *output) { static void drm_connector_destroy(struct wlr_output *output) {
struct wlr_drm_connector *conn = get_drm_connector_from_output(output); struct wlr_drm_connector *conn = get_drm_connector_from_output(output);
drm_connector_cleanup(conn); drm_connector_cleanup(conn);
drmModeFreeCrtc(conn->old_crtc);
wl_event_source_remove(conn->retry_pageflip); wl_event_source_remove(conn->retry_pageflip);
wl_list_remove(&conn->link); wl_list_remove(&conn->link);
free(conn); free(conn);
@ -1088,10 +1089,11 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) {
wlr_log(WLR_INFO, "'%s' disappeared", conn->output.name); wlr_log(WLR_INFO, "'%s' disappeared", conn->output.name);
drm_connector_cleanup(conn); drm_connector_cleanup(conn);
drmModeFreeCrtc(conn->old_crtc); if (conn->pageflip_pending) {
wl_event_source_remove(conn->retry_pageflip); conn->state = WLR_DRM_CONN_DISAPPEARED;
wl_list_remove(&conn->link); } else {
free(conn); wlr_output_destroy(&conn->output);
}
} }
bool changed_outputs[wl_list_length(&drm->outputs)]; bool changed_outputs[wl_list_length(&drm->outputs)];
@ -1133,6 +1135,12 @@ static void page_flip_handler(int fd, unsigned seq,
get_drm_backend_from_backend(conn->output.backend); get_drm_backend_from_backend(conn->output.backend);
conn->pageflip_pending = false; conn->pageflip_pending = false;
if (conn->state == WLR_DRM_CONN_DISAPPEARED) {
wlr_output_destroy(&conn->output);
return;
}
if (conn->state != WLR_DRM_CONN_CONNECTED || conn->crtc == NULL) { if (conn->state != WLR_DRM_CONN_CONNECTED || conn->crtc == NULL) {
return; return;
} }
@ -1193,7 +1201,6 @@ void restore_drm_outputs(struct wlr_drm_backend *drm) {
drmModeSetCrtc(drm->fd, crtc->crtc_id, crtc->buffer_id, crtc->x, crtc->y, drmModeSetCrtc(drm->fd, crtc->crtc_id, crtc->buffer_id, crtc->x, crtc->y,
&conn->id, 1, &crtc->mode); &conn->id, 1, &crtc->mode);
drmModeFreeCrtc(crtc);
} }
} }
@ -1248,6 +1255,8 @@ static void drm_connector_cleanup(struct wlr_drm_connector *conn) {
break; break;
case WLR_DRM_CONN_DISCONNECTED: case WLR_DRM_CONN_DISCONNECTED:
break; break;
case WLR_DRM_CONN_DISAPPEARED:
return; // don't change state
} }
conn->state = WLR_DRM_CONN_DISCONNECTED; conn->state = WLR_DRM_CONN_DISCONNECTED;

View file

@ -104,10 +104,14 @@ struct wlr_drm_backend {
}; };
enum wlr_drm_connector_state { enum wlr_drm_connector_state {
// Connector is available but no output is plugged in
WLR_DRM_CONN_DISCONNECTED, WLR_DRM_CONN_DISCONNECTED,
// An output just has been plugged in and is waiting for a modeset
WLR_DRM_CONN_NEEDS_MODESET, WLR_DRM_CONN_NEEDS_MODESET,
WLR_DRM_CONN_CLEANUP, WLR_DRM_CONN_CLEANUP,
WLR_DRM_CONN_CONNECTED, WLR_DRM_CONN_CONNECTED,
// Connector disappeared, waiting for being destroyed on next page-flip
WLR_DRM_CONN_DISAPPEARED,
}; };
struct wlr_drm_mode { struct wlr_drm_mode {