mirror of
https://gitlab.freedesktop.org/wlroots/wlroots.git
synced 2026-04-18 06:47:31 -04:00
backend/drm: implement KMS state snapshot/restore
Following ideas from [1], snapshot the entire KMS state when the VT is switched away, and restore it when the VT is switched back. > Well the neat trick is that userspace doesn’t need to be able to > understand properties to save and restore them - the actual property > value transport between kernel and userspace is fully generic. That way, even if another DRM master changes a property we don't understand like CTM or HDR_OUTPUT_METADATA, we can switch it back and avoid getting garbage on screen. [1]: https://blog.ffwll.ch/2016/01/vt-switching-with-atomic-modeset.html
This commit is contained in:
parent
e0b2bf2a6b
commit
f0bb53c4ab
7 changed files with 155 additions and 20 deletions
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue