diff --git a/backend/drm/drm.c b/backend/drm/drm.c index 9247f931..e92c9d20 100644 --- a/backend/drm/drm.c +++ b/backend/drm/drm.c @@ -269,6 +269,7 @@ bool init_drm_resources(struct wlr_drm_backend *drm) { struct wlr_drm_crtc *crtc = &drm->crtcs[i]; crtc->id = res->crtcs[i]; crtc->legacy_crtc = drmModeGetCrtc(drm->fd, crtc->id); + crtc->lessee_id = 0; get_drm_crtc_props(drm->fd, crtc->id, &crtc->props); } @@ -578,6 +579,8 @@ static void drm_connector_cleanup(struct wlr_drm_connector *conn); bool drm_connector_set_mode(struct wlr_output *output, struct wlr_output_mode *mode) { struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + assert(conn->state != WLR_DRM_CONN_LEASED + && "Use of leased connector is a programming error"); struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); if (conn->crtc == NULL) { // Maybe we can steal a CRTC from a disabled output @@ -618,6 +621,8 @@ bool drm_connector_set_mode(struct wlr_output *output, bool wlr_drm_connector_add_mode(struct wlr_output *output, const drmModeModeInfo *modeinfo) { struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + assert(conn->state != WLR_DRM_CONN_LEASED + && "Use of leased connector is a programming error"); if (modeinfo->type != DRM_MODE_TYPE_USERDEF) { return false; @@ -654,6 +659,8 @@ static bool drm_connector_set_cursor(struct wlr_output *output, enum wl_output_transform transform, int32_t hotspot_x, int32_t hotspot_y, bool update_texture) { struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + assert((conn->state != WLR_DRM_CONN_LEASED || texture == NULL) + && "Use of leased connector is a programming error"); struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); struct wlr_drm_crtc *crtc = conn->crtc; @@ -791,6 +798,8 @@ static bool drm_connector_set_cursor(struct wlr_output *output, static bool drm_connector_move_cursor(struct wlr_output *output, int x, int y) { struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + assert(conn->state != WLR_DRM_CONN_LEASED + && "Use of leased connector is a programming error"); struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); if (!conn->crtc) { return false; @@ -827,6 +836,8 @@ static bool drm_connector_move_cursor(struct wlr_output *output, static bool drm_connector_schedule_frame(struct wlr_output *output) { struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + assert(conn->state != WLR_DRM_CONN_LEASED + && "Use of leased connector is a programming error"); struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); if (!drm->session->active) { return false; @@ -878,6 +889,8 @@ static uint32_t strip_alpha_channel(uint32_t format) { static bool drm_connector_attach_buffer(struct wlr_output *output, struct wlr_buffer *buffer) { struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + assert(conn->state != WLR_DRM_CONN_LEASED + && "Use of leased connector is a programming error"); struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); if (!drm->session->active) { return false; @@ -919,6 +932,9 @@ static bool drm_connector_attach_buffer(struct wlr_output *output, static void drm_connector_destroy(struct wlr_output *output) { struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + if (conn->state == WLR_DRM_CONN_LEASED) { + return; + } drm_connector_cleanup(conn); drmModeFreeCrtc(conn->old_crtc); wl_event_source_remove(conn->retry_pageflip); @@ -996,10 +1012,17 @@ static void realloc_crtcs(struct wlr_drm_backend *drm) { uint32_t new_match[drm->num_crtcs]; for (size_t i = 0; i < drm->num_crtcs; ++i) { - previous_match[i] = UNMATCHED; + if (drm->crtcs[i].lessee_id != 0) { + /* Do not consider leased CRTCs */ + previous_match[i] = SKIP; + } else { + previous_match[i] = UNMATCHED; + } } wlr_log(WLR_DEBUG, "State before reallocation:"); + wlr_log(WLR_DEBUG, "noutputs: %zd; ncrtc: %zd", + num_outputs, drm->num_crtcs); size_t i = 0; struct wlr_drm_connector *conn; wl_list_for_each(conn, &drm->outputs, link) { @@ -1037,7 +1060,7 @@ static void realloc_crtcs(struct wlr_drm_backend *drm) { connector_match[i] = -1; } for (size_t i = 0; i < drm->num_crtcs; ++i) { - if (new_match[i] != UNMATCHED) { + if (new_match[i] != UNMATCHED && new_match[i] != SKIP) { connector_match[new_match[i]] = i; } } @@ -1149,6 +1172,15 @@ static uint32_t get_possible_crtcs(int fd, drmModeRes *res, return ret; } +static void drm_conn_init_wlr_output(struct wlr_drm_backend *drm, + struct wlr_drm_connector *wlr_conn, drmModeConnector *drm_conn) { + wlr_output_init(&wlr_conn->output, + &drm->backend, &output_impl, drm->display); + snprintf(wlr_conn->output.name, sizeof(wlr_conn->output.name), + "%s-%"PRIu32, conn_get_name(drm_conn->connector_type), + drm_conn->connector_type_id); +} + void scan_drm_connectors(struct wlr_drm_backend *drm) { /* * This GPU is not really a modesetting device. @@ -1174,6 +1206,14 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) { size_t new_outputs_len = 0; struct wlr_drm_connector *new_outputs[res->count_connectors + 1]; + drmModeLesseeListPtr lessees = drmModeListLessees(drm->fd); + if (lessees->count > 0) { + wlr_log(WLR_INFO, "With active leases:"); + for (uint32_t i = 0; i < lessees->count; ++i) { + wlr_log(WLR_INFO, "%d", lessees->lessees[i]); + } + } + for (int i = 0; i < res->count_connectors; ++i) { drmModeConnector *drm_conn = drmModeGetConnector(drm->fd, res->connectors[i]); @@ -1202,8 +1242,8 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) { drmModeFreeConnector(drm_conn); continue; } - wlr_output_init(&wlr_conn->output, &drm->backend, &output_impl, - drm->display); + + drm_conn_init_wlr_output(drm, wlr_conn, drm_conn); struct wl_event_loop *ev = wl_display_get_event_loop(drm->display); wlr_conn->retry_pageflip = wl_event_loop_add_timer( @@ -1212,16 +1252,13 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) { wlr_conn->state = WLR_DRM_CONN_DISCONNECTED; wlr_conn->id = drm_conn->connector_id; - snprintf(wlr_conn->output.name, sizeof(wlr_conn->output.name), - "%s-%"PRIu32, conn_get_name(drm_conn->connector_type), - drm_conn->connector_type_id); - if (curr_enc) { wlr_conn->old_crtc = drmModeGetCrtc(drm->fd, curr_enc->crtc_id); } wl_list_insert(drm->outputs.prev, &wlr_conn->link); - wlr_log(WLR_INFO, "Found connector '%s'", wlr_conn->output.name); + wlr_log(WLR_INFO, "Found connector '%s' (%d)", + wlr_conn->output.name, drm_conn->connector_id); } else { seen[index] = true; } @@ -1255,6 +1292,33 @@ void scan_drm_connectors(struct wlr_drm_backend *drm) { } } + if (wlr_conn->state == WLR_DRM_CONN_LEASED) { + /* Was the lease terminated? */ + bool found = false; + for (uint32_t n = 0; n < lessees->count; ++n) { + if (lessees->lessees[n] == wlr_conn->lessee_id) { + found = true; + } + } + if (!found) { + /* Yes, it was */ + wlr_log(WLR_DEBUG, "DRM lease %d terminated by kernel", + wlr_conn->lessee_id); + wlr_conn->state = WLR_DRM_CONN_DISCONNECTED; + wlr_conn->lessee_id = 0; + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (drm->crtcs[i].lessee_id == wlr_conn->lessee_id) { + drm->crtcs[i].lessee_id = 0; + } + } + if (wlr_conn->lease_terminated_cb != NULL) { + wlr_conn->lease_terminated_cb( + wlr_conn, wlr_conn->lease_terminated_data); + } + drm_conn_init_wlr_output(drm, wlr_conn, drm_conn); + } + } + if (wlr_conn->state == WLR_DRM_CONN_DISCONNECTED && drm_conn->connection == DRM_MODE_CONNECTED) { wlr_log(WLR_INFO, "'%s' connected", wlr_conn->output.name); @@ -1476,7 +1540,7 @@ void restore_drm_outputs(struct wlr_drm_backend *drm) { wl_list_for_each(conn, &drm->outputs, link) { drmModeCrtc *crtc = conn->old_crtc; - if (!crtc) { + if (!crtc || conn->state == WLR_DRM_CONN_LEASED) { continue; } @@ -1531,7 +1595,87 @@ static void drm_connector_cleanup(struct wlr_drm_connector *conn) { break; case WLR_DRM_CONN_DISCONNECTED: break; + case WLR_DRM_CONN_LEASED: + return; // don't change state } conn->state = WLR_DRM_CONN_DISCONNECTED; } + +int drm_create_lease(struct wlr_drm_backend *backend, + struct wlr_drm_connector **conns, int nconns, uint32_t *lessee_id, + void (*lease_terminated_cb)(struct wlr_drm_connector *, void *), + void *lease_terminated_data) { + int nobjects = 0; + for (int i = 0; i < nconns; ++i) { + struct wlr_drm_connector *conn = conns[i]; + assert(conn->state != WLR_DRM_CONN_LEASED); + nobjects += 0 + + 1 /* connector */ + + 1 /* crtc */ + + 1 /* primary plane */ + + (conn->crtc->cursor != NULL ? 1 : 0) /* cursor plane */ + + conn->crtc->num_overlays; /* overlay planes */ + } + if (nobjects <= 0) { + wlr_log(WLR_ERROR, "Attempted DRM lease with <= 0 objects"); + return -1; + } + wlr_log(WLR_DEBUG, "Issuing DRM lease with the %d objects:", nobjects); + uint32_t objects[nobjects + 1]; + for (int i = 0, j = 0; i < nconns; ++i) { + struct wlr_drm_connector *conn = conns[i]; + objects[j++] = conn->id; + objects[j++] = conn->crtc->id; + objects[j++] = conn->crtc->primary->id; + wlr_log(WLR_DEBUG, "connector: %d crtc: %d primary plane: %d", + conn->id, conn->crtc->id, conn->crtc->primary->id); + if (conn->crtc->cursor) { + wlr_log(WLR_DEBUG, "cursor plane: %d", conn->crtc->cursor->id); + objects[j++] = conn->crtc->cursor->id; + } + if (conn->crtc->num_overlays > 0) { + wlr_log(WLR_DEBUG, "+%zd overlay planes:", conn->crtc->num_overlays); + } + for (size_t k = 0; k < conn->crtc->num_overlays; ++k) { + objects[j++] = conn->crtc->overlays[k]; + wlr_log(WLR_DEBUG, "\toverlay plane: %d", conn->crtc->overlays[k]); + } + } + int lease_fd = drmModeCreateLease(backend->fd, + objects, nobjects, 0, lessee_id); + if (lease_fd < 0) { + return lease_fd; + } + wlr_log(WLR_DEBUG, "Issued DRM lease %d", *lessee_id); + for (int i = 0; i < nconns; ++i) { + struct wlr_drm_connector *conn = conns[i]; + conn->lessee_id = *lessee_id; + conn->crtc->lessee_id = *lessee_id; + conn->state = WLR_DRM_CONN_LEASED; + conn->lease_terminated_cb = lease_terminated_cb; + conn->lease_terminated_data = lease_terminated_data; + wlr_output_destroy(&conn->output); + } + return lease_fd; +} + +int drm_terminate_lease(struct wlr_drm_backend *backend, uint32_t lessee_id) { + wlr_log(WLR_DEBUG, "Terminating DRM lease %d", lessee_id); + int r = drmModeRevokeLease(backend->fd, lessee_id); + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &backend->outputs, link) { + if (conn->state == WLR_DRM_CONN_LEASED + && conn->lessee_id == lessee_id) { + conn->state = WLR_DRM_CONN_DISCONNECTED; + conn->lessee_id = 0; + } + } + for (size_t i = 0; i < backend->num_crtcs; ++i) { + if (backend->crtcs[i].lessee_id == lessee_id) { + backend->crtcs[i].lessee_id = 0; + } + } + scan_drm_connectors(backend); + return r; +} diff --git a/include/backend/drm/drm.h b/include/backend/drm/drm.h index fdee3fad..9dbdc3cf 100644 --- a/include/backend/drm/drm.h +++ b/include/backend/drm/drm.h @@ -47,6 +47,8 @@ struct wlr_drm_crtc { // Legacy only drmModeCrtc *legacy_crtc; + uint32_t lessee_id; + struct wlr_drm_plane *primary; struct wlr_drm_plane *cursor; @@ -98,6 +100,8 @@ enum wlr_drm_connector_state { WLR_DRM_CONN_NEEDS_MODESET, WLR_DRM_CONN_CLEANUP, WLR_DRM_CONN_CONNECTED, + // Connector has been leased to another DRM master + WLR_DRM_CONN_LEASED, }; struct wlr_drm_mode { @@ -112,6 +116,9 @@ struct wlr_drm_connector { struct wlr_output_mode *desired_mode; bool desired_enabled; uint32_t id; + uint32_t lessee_id; + void (*lease_terminated_cb)(struct wlr_drm_connector *, void *); + void *lease_terminated_data; struct wlr_drm_crtc *crtc; uint32_t possible_crtc; @@ -149,4 +156,11 @@ bool set_drm_connector_gamma(struct wlr_output *output, size_t size, bool drm_connector_set_mode(struct wlr_output *output, struct wlr_output_mode *mode); +/** Returns the leased file descriptor */ +int drm_create_lease(struct wlr_drm_backend *backend, + struct wlr_drm_connector **conns, int nconns, uint32_t *lessee_id, + void (*lease_terminated_cb)(struct wlr_drm_connector *, void *), + void *lease_terminated_data); +int drm_terminate_lease(struct wlr_drm_backend *backend, uint32_t lessee_id); + #endif