diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c index c69d6abd5..47ae7fe55 100644 --- a/backend/drm/atomic.c +++ b/backend/drm/atomic.c @@ -342,6 +342,33 @@ static bool atomic_crtc_commit(struct wlr_drm_connector *conn, return ok; } +static bool atomic_snapshot_state(struct wlr_drm_backend *drm) { + drmModeAtomicFree(drm->atomic_snapshot); + drm->atomic_snapshot = snapshot_drm_state(drm->fd); + return drm->atomic_snapshot != NULL; +} + +static bool atomic_restore_state(struct wlr_drm_backend *drm) { + if (drm->atomic_snapshot == NULL) { + wlr_log(WLR_DEBUG, "No atomic snapshot, falling back to legacy state restoration"); + return legacy_restore_state(drm); + } + + uint32_t flags = DRM_MODE_ATOMIC_ALLOW_MODESET; + int ret = drmModeAtomicCommit(drm->fd, drm->atomic_snapshot, flags, NULL); + if (ret != 0) { + wlr_log_errno(WLR_ERROR, "drmModeAtomicCommit failed"); + return false; + } + + drmModeAtomicFree(drm->atomic_snapshot); + drm->atomic_snapshot = NULL; + + return true; +} + const struct wlr_drm_interface atomic_iface = { .crtc_commit = atomic_crtc_commit, + .snapshot_state = atomic_snapshot_state, + .restore_state = atomic_restore_state, }; diff --git a/backend/drm/backend.c b/backend/drm/backend.c index 1492c4d4c..93c3333cf 100644 --- a/backend/drm/backend.c +++ b/backend/drm/backend.c @@ -55,6 +55,7 @@ static void backend_destroy(struct wlr_backend *backend) { finish_drm_renderer(&drm->mgpu_renderer); } + drmModeAtomicFree(drm->atomic_snapshot); finish_drm_resources(drm); free(drm->name); @@ -101,30 +102,18 @@ static void handle_session_active(struct wl_listener *listener, void *data) { if (session->active) { wlr_log(WLR_INFO, "DRM fd resumed"); - scan_drm_connectors(drm, NULL); - struct wlr_drm_connector *conn; - wl_list_for_each(conn, &drm->connectors, link) { - struct wlr_output_mode *mode = NULL; - uint32_t committed = WLR_OUTPUT_STATE_ENABLED; - if (conn->status != DRM_MODE_DISCONNECTED && conn->output.enabled - && conn->output.current_mode != NULL) { - committed |= WLR_OUTPUT_STATE_MODE; - mode = conn->output.current_mode; - } - struct wlr_output_state state = { - .committed = committed, - .allow_artifacts = true, - .enabled = mode != NULL, - .mode_type = WLR_OUTPUT_STATE_MODE_FIXED, - .mode = mode, - }; - if (!drm_connector_commit_state(conn, &state)) { - wlr_drm_conn_log(conn, WLR_ERROR, "Failed to restore state after VT switch"); - } + if (!drm->iface->restore_state(drm)) { + wlr_log(WLR_ERROR, "Failed to restore KMS state after VT switch"); } + + scan_drm_connectors(drm, NULL); } else { wlr_log(WLR_INFO, "DRM fd paused"); + + if (drm->iface->snapshot_state && !drm->iface->snapshot_state(drm)) { + wlr_log(WLR_ERROR, "Failed to snapshot KMS state"); + } } } diff --git a/backend/drm/legacy.c b/backend/drm/legacy.c index a8f618cfb..8432d1503 100644 --- a/backend/drm/legacy.c +++ b/backend/drm/legacy.c @@ -227,6 +227,34 @@ bool drm_legacy_crtc_set_gamma(struct wlr_drm_backend *drm, return true; } +bool legacy_restore_state(struct wlr_drm_backend *drm) { + // Best-effort state restoration + bool ok = true; + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->connectors, link) { + struct wlr_output_mode *mode = NULL; + uint32_t committed = WLR_OUTPUT_STATE_ENABLED; + if (conn->status != DRM_MODE_DISCONNECTED && conn->output.enabled + && conn->output.current_mode != NULL) { + committed |= WLR_OUTPUT_STATE_MODE; + mode = conn->output.current_mode; + } + struct wlr_output_state state = { + .committed = committed, + .allow_artifacts = true, + .enabled = mode != NULL, + .mode_type = WLR_OUTPUT_STATE_MODE_FIXED, + .mode = mode, + }; + if (!drm_connector_commit_state(conn, &state)) { + wlr_drm_conn_log(conn, WLR_ERROR, "Failed to restore state"); + ok = false; + } + } + return ok; +} + const struct wlr_drm_interface legacy_iface = { .crtc_commit = legacy_crtc_commit, + .restore_state = legacy_restore_state, }; diff --git a/backend/drm/util.c b/backend/drm/util.c index adb9306e6..3feaa19d5 100644 --- a/backend/drm/util.c +++ b/backend/drm/util.c @@ -276,3 +276,87 @@ size_t match_obj(size_t num_objs, const uint32_t objs[static restrict num_objs], match_obj_(&st, 0, 0, 0, 0); return st.score; } + +static bool snapshot_drm_object(int drm_fd, drmModeAtomicReq *req, + uint32_t object_id) { + drmModeObjectProperties *props = + drmModeObjectGetProperties(drm_fd, object_id, DRM_MODE_OBJECT_ANY); + if (props == NULL) { + wlr_log_errno(WLR_ERROR, "drmModeObjectGetProperties failed"); + return false; + } + + bool ok = true; + for (uint32_t i = 0; i < props->count_props; i++) { + uint32_t prop_id = props->props[i]; + uint64_t value = props->prop_values[i]; + + drmModePropertyRes *prop = drmModeGetProperty(drm_fd, prop_id); + if (prop == NULL) { + wlr_log_errno(WLR_ERROR, "drmModeGetProperty failed"); + ok = false; + break; + } + bool immutable = prop->flags & DRM_MODE_PROP_IMMUTABLE; + bool dpms = strcmp(prop->name, "DPMS") == 0; + drmModeFreeProperty(prop); + if (immutable || dpms) { + // DPMS is a bit special: it can only be set from the legacy uAPI. Restoring it from the + // atomic uAPI will fail. + continue; + } + + if (drmModeAtomicAddProperty(req, object_id, prop_id, value) < 0) { + wlr_log_errno(WLR_ERROR, "drmModeAtomicAddProperty failed"); + ok = false; + break; + } + } + + drmModeFreeObjectProperties(props); + return ok; +} + +drmModeAtomicReq *snapshot_drm_state(int drm_fd) { + drmModeAtomicReq *req = drmModeAtomicAlloc(); + if (req == NULL) { + wlr_log_errno(WLR_ERROR, "drmModeAtomicAlloc failed"); + return false; + } + + bool ok = true; + drmModeRes *res = drmModeGetResources(drm_fd); + if (res == NULL) { + wlr_log_errno(WLR_ERROR, "drmModeGetResources failed"); + goto error; + } + for (int i = 0; i < res->count_crtcs; i++) { + ok = ok && snapshot_drm_object(drm_fd, req, res->crtcs[i]); + } + for (int i = 0; i < res->count_connectors; i++) { + ok = ok && snapshot_drm_object(drm_fd, req, res->connectors[i]); + } + drmModeFreeResources(res); + if (!ok) { + goto error; + } + + drmModePlaneRes *planes = drmModeGetPlaneResources(drm_fd); + if (planes == NULL) { + wlr_log_errno(WLR_ERROR, "drmModeGetPlaneResources failed"); + return false; + } + for (uint32_t i = 0; i < planes->count_planes; i++) { + ok = ok && snapshot_drm_object(drm_fd, req, planes->planes[i]); + } + drmModeFreePlaneResources(planes); + if (!ok) { + goto error; + } + + return req; + +error: + drmModeAtomicFree(req); + return NULL; +} diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index e93b5eaf9..dd02bf96b 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -85,6 +85,8 @@ struct wlr_drm_backend { uint64_t cursor_width, cursor_height; struct wlr_drm_format_set mgpu_formats; + + drmModeAtomicReq *atomic_snapshot; }; struct wlr_drm_mode { diff --git a/include/backend/drm/iface.h b/include/backend/drm/iface.h index d52bbd3d9..7cba27e03 100644 --- a/include/backend/drm/iface.h +++ b/include/backend/drm/iface.h @@ -17,6 +17,8 @@ struct wlr_drm_interface { bool (*crtc_commit)(struct wlr_drm_connector *conn, const struct wlr_drm_connector_state *state, uint32_t flags, bool test_only); + bool (*snapshot_state)(struct wlr_drm_backend *drm); + bool (*restore_state)(struct wlr_drm_backend *drm); }; extern const struct wlr_drm_interface atomic_iface; @@ -24,5 +26,6 @@ extern const struct wlr_drm_interface legacy_iface; bool drm_legacy_crtc_set_gamma(struct wlr_drm_backend *drm, struct wlr_drm_crtc *crtc, size_t size, uint16_t *lut); +bool legacy_restore_state(struct wlr_drm_backend *drm); #endif diff --git a/include/backend/drm/util.h b/include/backend/drm/util.h index ba48fe6a2..918f87907 100644 --- a/include/backend/drm/util.h +++ b/include/backend/drm/util.h @@ -16,6 +16,8 @@ const char *get_pnp_manufacturer(uint16_t code); void parse_edid(struct wlr_drm_connector *conn, size_t len, const uint8_t *data); const char *drm_connector_status_str(drmModeConnection status); +drmModeAtomicReq *snapshot_drm_state(int drm_fd); + // Part of match_obj enum { UNMATCHED = (uint32_t)-1,