diff options
Diffstat (limited to 'backend/drm')
-rw-r--r-- | backend/drm/atomic.c | 273 | ||||
-rw-r--r-- | backend/drm/backend.c | 211 | ||||
-rw-r--r-- | backend/drm/drm.c | 1418 | ||||
-rw-r--r-- | backend/drm/legacy.c | 83 | ||||
-rw-r--r-- | backend/drm/properties.c | 151 | ||||
-rw-r--r-- | backend/drm/renderer.c | 264 | ||||
-rw-r--r-- | backend/drm/util.c | 348 |
7 files changed, 2748 insertions, 0 deletions
diff --git a/backend/drm/atomic.c b/backend/drm/atomic.c new file mode 100644 index 00000000..fc649d68 --- /dev/null +++ b/backend/drm/atomic.c @@ -0,0 +1,273 @@ +#include <gbm.h> +#include <stdlib.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "backend/drm/drm.h" +#include "backend/drm/iface.h" +#include "backend/drm/util.h" + +struct atomic { + drmModeAtomicReq *req; + int cursor; + bool failed; +}; + +static void atomic_begin(struct wlr_drm_crtc *crtc, struct atomic *atom) { + if (!crtc->atomic) { + crtc->atomic = drmModeAtomicAlloc(); + if (!crtc->atomic) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + atom->failed = true; + return; + } + } + + atom->req = crtc->atomic; + atom->cursor = drmModeAtomicGetCursor(atom->req); + atom->failed = false; +} + +static bool atomic_end(int drm_fd, struct atomic *atom) { + if (atom->failed) { + return false; + } + + uint32_t flags = DRM_MODE_ATOMIC_TEST_ONLY | DRM_MODE_ATOMIC_NONBLOCK; + if (drmModeAtomicCommit(drm_fd, atom->req, flags, NULL)) { + wlr_log_errno(WLR_ERROR, "Atomic test failed"); + drmModeAtomicSetCursor(atom->req, atom->cursor); + return false; + } + + return true; +} + +static bool atomic_commit(int drm_fd, struct atomic *atom, + struct wlr_drm_connector *conn, uint32_t flags, bool modeset) { + if (atom->failed) { + return false; + } + + int ret = drmModeAtomicCommit(drm_fd, atom->req, flags, conn); + if (ret) { + wlr_log_errno(WLR_ERROR, "%s: Atomic commit failed (%s)", + conn->output.name, modeset ? "modeset" : "pageflip"); + + // Try to commit without new changes + drmModeAtomicSetCursor(atom->req, atom->cursor); + if (drmModeAtomicCommit(drm_fd, atom->req, flags, conn)) { + wlr_log_errno(WLR_ERROR, + "%s: Atomic commit without new changes failed (%s)", + conn->output.name, modeset ? "modeset" : "pageflip"); + } + } + + drmModeAtomicSetCursor(atom->req, 0); + + return !ret; +} + +static inline void atomic_add(struct atomic *atom, uint32_t id, uint32_t prop, uint64_t val) { + if (!atom->failed && drmModeAtomicAddProperty(atom->req, id, prop, val) < 0) { + wlr_log_errno(WLR_ERROR, "Failed to add atomic DRM property"); + atom->failed = true; + } +} + +static void set_plane_props(struct atomic *atom, struct wlr_drm_plane *plane, + uint32_t crtc_id, uint32_t fb_id, bool set_crtc_xy) { + uint32_t id = plane->id; + const union wlr_drm_plane_props *props = &plane->props; + + // The src_* properties are in 16.16 fixed point + atomic_add(atom, id, props->src_x, 0); + atomic_add(atom, id, props->src_y, 0); + atomic_add(atom, id, props->src_w, (uint64_t)plane->surf.width << 16); + atomic_add(atom, id, props->src_h, (uint64_t)plane->surf.height << 16); + atomic_add(atom, id, props->crtc_w, plane->surf.width); + atomic_add(atom, id, props->crtc_h, plane->surf.height); + atomic_add(atom, id, props->fb_id, fb_id); + atomic_add(atom, id, props->crtc_id, crtc_id); + if (set_crtc_xy) { + atomic_add(atom, id, props->crtc_x, 0); + atomic_add(atom, id, props->crtc_y, 0); + } +} + +static bool atomic_crtc_pageflip(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, + struct wlr_drm_crtc *crtc, + uint32_t fb_id, drmModeModeInfo *mode) { + if (mode != NULL) { + if (crtc->mode_id != 0) { + drmModeDestroyPropertyBlob(drm->fd, crtc->mode_id); + } + + if (drmModeCreatePropertyBlob(drm->fd, mode, sizeof(*mode), + &crtc->mode_id)) { + wlr_log_errno(WLR_ERROR, "Unable to create property blob"); + return false; + } + } + + uint32_t flags = DRM_MODE_PAGE_FLIP_EVENT; + if (mode != NULL) { + flags |= DRM_MODE_ATOMIC_ALLOW_MODESET; + } else { + flags |= DRM_MODE_ATOMIC_NONBLOCK; + } + + struct atomic atom; + atomic_begin(crtc, &atom); + atomic_add(&atom, conn->id, conn->props.crtc_id, crtc->id); + if (mode != NULL && conn->props.link_status != 0) { + atomic_add(&atom, conn->id, conn->props.link_status, + DRM_MODE_LINK_STATUS_GOOD); + } + atomic_add(&atom, crtc->id, crtc->props.mode_id, crtc->mode_id); + atomic_add(&atom, crtc->id, crtc->props.active, 1); + set_plane_props(&atom, crtc->primary, crtc->id, fb_id, true); + return atomic_commit(drm->fd, &atom, conn, flags, mode); +} + +static bool atomic_conn_enable(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, bool enable) { + struct wlr_drm_crtc *crtc = conn->crtc; + if (crtc == NULL) { + return !enable; + } + + struct atomic atom; + atomic_begin(crtc, &atom); + atomic_add(&atom, crtc->id, crtc->props.active, enable); + if (enable) { + atomic_add(&atom, conn->id, conn->props.crtc_id, crtc->id); + atomic_add(&atom, crtc->id, crtc->props.mode_id, crtc->mode_id); + } else { + atomic_add(&atom, conn->id, conn->props.crtc_id, 0); + atomic_add(&atom, crtc->id, crtc->props.mode_id, 0); + } + return atomic_commit(drm->fd, &atom, conn, DRM_MODE_ATOMIC_ALLOW_MODESET, + true); +} + +bool legacy_crtc_set_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, struct gbm_bo *bo); + +static bool atomic_crtc_set_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, struct gbm_bo *bo) { + if (!crtc || !crtc->cursor) { + return true; + } + + struct wlr_drm_plane *plane = crtc->cursor; + // We can't use atomic operations on fake planes + if (plane->id == 0) { + return legacy_crtc_set_cursor(drm, crtc, bo); + } + + struct atomic atom; + + atomic_begin(crtc, &atom); + + if (bo) { + set_plane_props(&atom, plane, crtc->id, get_fb_for_bo(bo), false); + } else { + atomic_add(&atom, plane->id, plane->props.fb_id, 0); + atomic_add(&atom, plane->id, plane->props.crtc_id, 0); + } + + return atomic_end(drm->fd, &atom); +} + +bool legacy_crtc_move_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, int x, int y); + +static bool atomic_crtc_move_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, int x, int y) { + if (!crtc || !crtc->cursor) { + return true; + } + + struct wlr_drm_plane *plane = crtc->cursor; + // We can't use atomic operations on fake planes + if (plane->id == 0) { + return legacy_crtc_move_cursor(drm, crtc, x, y); + } + + struct atomic atom; + + atomic_begin(crtc, &atom); + atomic_add(&atom, plane->id, plane->props.crtc_x, x); + atomic_add(&atom, plane->id, plane->props.crtc_y, y); + return atomic_end(drm->fd, &atom); +} + +static bool atomic_crtc_set_gamma(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, size_t size, + uint16_t *r, uint16_t *g, uint16_t *b) { + // Fallback to legacy gamma interface when gamma properties are not available + // (can happen on older Intel GPUs that support gamma but not degamma). + // TEMP: This is broken on AMDGPU. Provide a fallback to legacy until they + // get it fixed. Ref https://bugs.freedesktop.org/show_bug.cgi?id=107459 + const char *no_atomic_str = getenv("WLR_DRM_NO_ATOMIC_GAMMA"); + bool no_atomic = no_atomic_str != NULL && strcmp(no_atomic_str, "1") == 0; + if (crtc->props.gamma_lut == 0 || no_atomic) { + return legacy_iface.crtc_set_gamma(drm, crtc, size, r, g, b); + } + + struct drm_color_lut *gamma = malloc(size * sizeof(struct drm_color_lut)); + if (gamma == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate gamma table"); + return false; + } + + for (size_t i = 0; i < size; i++) { + gamma[i].red = r[i]; + gamma[i].green = g[i]; + gamma[i].blue = b[i]; + } + + if (crtc->gamma_lut != 0) { + drmModeDestroyPropertyBlob(drm->fd, crtc->gamma_lut); + } + + if (drmModeCreatePropertyBlob(drm->fd, gamma, + size * sizeof(struct drm_color_lut), &crtc->gamma_lut)) { + free(gamma); + wlr_log_errno(WLR_ERROR, "Unable to create property blob"); + return false; + } + free(gamma); + + struct atomic atom; + atomic_begin(crtc, &atom); + atomic_add(&atom, crtc->id, crtc->props.gamma_lut, crtc->gamma_lut); + return atomic_end(drm->fd, &atom); +} + +static size_t atomic_crtc_get_gamma_size(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc) { + if (crtc->props.gamma_lut_size == 0) { + return legacy_iface.crtc_get_gamma_size(drm, crtc); + } + + uint64_t gamma_lut_size; + if (!get_drm_prop(drm->fd, crtc->id, crtc->props.gamma_lut_size, + &gamma_lut_size)) { + wlr_log(WLR_ERROR, "Unable to get gamma lut size"); + return 0; + } + + return (size_t)gamma_lut_size; +} + +const struct wlr_drm_interface atomic_iface = { + .conn_enable = atomic_conn_enable, + .crtc_pageflip = atomic_crtc_pageflip, + .crtc_set_cursor = atomic_crtc_set_cursor, + .crtc_move_cursor = atomic_crtc_move_cursor, + .crtc_set_gamma = atomic_crtc_set_gamma, + .crtc_get_gamma_size = atomic_crtc_get_gamma_size, +}; diff --git a/backend/drm/backend.c b/backend/drm/backend.c new file mode 100644 index 00000000..a9082077 --- /dev/null +++ b/backend/drm/backend.c @@ -0,0 +1,211 @@ +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wayland-server.h> +#include <wlr/backend/interface.h> +#include <wlr/backend/session.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/egl.h> +#include <wlr/types/wlr_list.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include "backend/drm/drm.h" +#include "util/signal.h" + +struct wlr_drm_backend *get_drm_backend_from_backend( + struct wlr_backend *wlr_backend) { + assert(wlr_backend_is_drm(wlr_backend)); + return (struct wlr_drm_backend *)wlr_backend; +} + +static bool backend_start(struct wlr_backend *backend) { + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + scan_drm_connectors(drm); + return true; +} + +static void backend_destroy(struct wlr_backend *backend) { + if (!backend) { + return; + } + + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + + restore_drm_outputs(drm); + + struct wlr_drm_connector *conn, *next; + wl_list_for_each_safe(conn, next, &drm->outputs, link) { + wlr_output_destroy(&conn->output); + } + + wlr_signal_emit_safe(&backend->events.destroy, backend); + + wl_list_remove(&drm->display_destroy.link); + wl_list_remove(&drm->session_signal.link); + wl_list_remove(&drm->drm_invalidated.link); + + finish_drm_resources(drm); + finish_drm_renderer(&drm->renderer); + wlr_session_close_file(drm->session, drm->fd); + wl_event_source_remove(drm->drm_event); + free(drm); +} + +static struct wlr_renderer *backend_get_renderer( + struct wlr_backend *backend) { + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + + if (drm->parent) { + return drm->parent->renderer.wlr_rend; + } else { + return drm->renderer.wlr_rend; + } +} + +static clockid_t backend_get_presentation_clock(struct wlr_backend *backend) { + struct wlr_drm_backend *drm = get_drm_backend_from_backend(backend); + return drm->clock; +} + +static struct wlr_backend_impl backend_impl = { + .start = backend_start, + .destroy = backend_destroy, + .get_renderer = backend_get_renderer, + .get_presentation_clock = backend_get_presentation_clock, +}; + +bool wlr_backend_is_drm(struct wlr_backend *b) { + return b->impl == &backend_impl; +} + +static void session_signal(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = + wl_container_of(listener, drm, session_signal); + struct wlr_session *session = data; + + if (session->active) { + wlr_log(WLR_INFO, "DRM fd resumed"); + scan_drm_connectors(drm); + + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link){ + if (conn->output.enabled) { + wlr_output_set_mode(&conn->output, conn->output.current_mode); + } else { + enable_drm_connector(&conn->output, false); + } + + if (!conn->crtc) { + continue; + } + + struct wlr_drm_plane *plane = conn->crtc->cursor; + drm->iface->crtc_set_cursor(drm, conn->crtc, + (plane && plane->cursor_enabled) ? plane->cursor_bo : NULL); + drm->iface->crtc_move_cursor(drm, conn->crtc, conn->cursor_x, + conn->cursor_y); + + if (conn->crtc->gamma_table != NULL) { + size_t size = conn->crtc->gamma_table_size; + uint16_t *r = conn->crtc->gamma_table; + uint16_t *g = conn->crtc->gamma_table + size; + uint16_t *b = conn->crtc->gamma_table + 2 * size; + drm->iface->crtc_set_gamma(drm, conn->crtc, size, r, g, b); + } else { + set_drm_connector_gamma(&conn->output, 0, NULL, NULL, NULL); + } + } + } else { + wlr_log(WLR_INFO, "DRM fd paused"); + } +} + +static void drm_invalidated(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = + wl_container_of(listener, drm, drm_invalidated); + + char *name = drmGetDeviceNameFromFd2(drm->fd); + wlr_log(WLR_DEBUG, "%s invalidated", name); + free(name); + + scan_drm_connectors(drm); +} + +static void handle_display_destroy(struct wl_listener *listener, void *data) { + struct wlr_drm_backend *drm = + wl_container_of(listener, drm, display_destroy); + backend_destroy(&drm->backend); +} + +struct wlr_backend *wlr_drm_backend_create(struct wl_display *display, + struct wlr_session *session, int gpu_fd, struct wlr_backend *parent, + wlr_renderer_create_func_t create_renderer_func) { + assert(display && session && gpu_fd >= 0); + assert(!parent || wlr_backend_is_drm(parent)); + + char *name = drmGetDeviceNameFromFd2(gpu_fd); + drmVersion *version = drmGetVersion(gpu_fd); + wlr_log(WLR_INFO, "Initializing DRM backend for %s (%s)", name, version->name); + free(name); + drmFreeVersion(version); + + struct wlr_drm_backend *drm = calloc(1, sizeof(struct wlr_drm_backend)); + if (!drm) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return NULL; + } + wlr_backend_init(&drm->backend, &backend_impl); + + drm->session = session; + wl_list_init(&drm->outputs); + + drm->fd = gpu_fd; + if (parent != NULL) { + drm->parent = get_drm_backend_from_backend(parent); + } + + drm->drm_invalidated.notify = drm_invalidated; + wlr_session_signal_add(session, gpu_fd, &drm->drm_invalidated); + + drm->display = display; + struct wl_event_loop *event_loop = wl_display_get_event_loop(display); + + drm->drm_event = wl_event_loop_add_fd(event_loop, drm->fd, + WL_EVENT_READABLE, handle_drm_event, NULL); + if (!drm->drm_event) { + wlr_log(WLR_ERROR, "Failed to create DRM event source"); + goto error_fd; + } + + drm->session_signal.notify = session_signal; + wl_signal_add(&session->session_signal, &drm->session_signal); + + if (!check_drm_features(drm)) { + goto error_event; + } + + if (!init_drm_resources(drm)) { + goto error_event; + } + + if (!init_drm_renderer(drm, &drm->renderer, create_renderer_func)) { + wlr_log(WLR_ERROR, "Failed to initialize renderer"); + goto error_event; + } + + drm->display_destroy.notify = handle_display_destroy; + wl_display_add_destroy_listener(display, &drm->display_destroy); + + return &drm->backend; + +error_event: + wl_list_remove(&drm->session_signal.link); + wl_event_source_remove(drm->drm_event); +error_fd: + wlr_session_close_file(drm->session, drm->fd); + free(drm); + return NULL; +} diff --git a/backend/drm/drm.c b/backend/drm/drm.c new file mode 100644 index 00000000..735b7c29 --- /dev/null +++ b/backend/drm/drm.c @@ -0,0 +1,1418 @@ +#define _POSIX_C_SOURCE 200112L +#include <assert.h> +#include <drm_mode.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <errno.h> +#include <gbm.h> +#include <GLES2/gl2.h> +#include <GLES2/gl2ext.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <wayland-server.h> +#include <wayland-util.h> +#include <wlr/backend/interface.h> +#include <wlr/interfaces/wlr_output.h> +#include <wlr/render/gles2.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "backend/drm/drm.h" +#include "backend/drm/iface.h" +#include "backend/drm/util.h" +#include "util/signal.h" + +bool check_drm_features(struct wlr_drm_backend *drm) { + uint64_t cap; + if (drm->parent) { + if (drmGetCap(drm->fd, DRM_CAP_PRIME, &cap) || + !(cap & DRM_PRIME_CAP_IMPORT)) { + wlr_log(WLR_ERROR, + "PRIME import not supported on secondary GPU"); + return false; + } + + if (drmGetCap(drm->parent->fd, DRM_CAP_PRIME, &cap) || + !(cap & DRM_PRIME_CAP_EXPORT)) { + wlr_log(WLR_ERROR, + "PRIME export not supported on primary GPU"); + return false; + } + } + + if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1)) { + wlr_log(WLR_ERROR, "DRM universal planes unsupported"); + return false; + } + + const char *no_atomic = getenv("WLR_DRM_NO_ATOMIC"); + if (no_atomic && strcmp(no_atomic, "1") == 0) { + wlr_log(WLR_DEBUG, + "WLR_DRM_NO_ATOMIC set, forcing legacy DRM interface"); + drm->iface = &legacy_iface; + } else if (drmSetClientCap(drm->fd, DRM_CLIENT_CAP_ATOMIC, 1)) { + wlr_log(WLR_DEBUG, + "Atomic modesetting unsupported, using legacy DRM interface"); + drm->iface = &legacy_iface; + } else { + wlr_log(WLR_DEBUG, "Using atomic DRM interface"); + drm->iface = &atomic_iface; + } + + int ret = drmGetCap(drm->fd, DRM_CAP_TIMESTAMP_MONOTONIC, &cap); + drm->clock = (ret == 0 && cap == 1) ? CLOCK_MONOTONIC : CLOCK_REALTIME; + + return true; +} + +static int cmp_plane(const void *arg1, const void *arg2) { + const struct wlr_drm_plane *a = arg1; + const struct wlr_drm_plane *b = arg2; + + return (int)a->type - (int)b->type; +} + +static bool init_planes(struct wlr_drm_backend *drm) { + drmModePlaneRes *plane_res = drmModeGetPlaneResources(drm->fd); + if (!plane_res) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM plane resources"); + return false; + } + + wlr_log(WLR_INFO, "Found %"PRIu32" DRM planes", plane_res->count_planes); + + if (plane_res->count_planes == 0) { + drmModeFreePlaneResources(plane_res); + return true; + } + + drm->num_planes = plane_res->count_planes; + drm->planes = calloc(drm->num_planes, sizeof(*drm->planes)); + if (!drm->planes) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error_res; + } + + for (size_t i = 0; i < drm->num_planes; ++i) { + struct wlr_drm_plane *p = &drm->planes[i]; + + drmModePlane *plane = drmModeGetPlane(drm->fd, plane_res->planes[i]); + if (!plane) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM plane"); + goto error_planes; + } + + p->id = plane->plane_id; + p->possible_crtcs = plane->possible_crtcs; + uint64_t type; + + if (!get_drm_plane_props(drm->fd, p->id, &p->props) || + !get_drm_prop(drm->fd, p->id, p->props.type, &type)) { + drmModeFreePlane(plane); + goto error_planes; + } + + p->type = type; + drm->num_type_planes[type]++; + + drmModeFreePlane(plane); + } + + wlr_log(WLR_INFO, "(%zu overlay, %zu primary, %zu cursor)", + drm->num_overlay_planes, + drm->num_primary_planes, + drm->num_cursor_planes); + + qsort(drm->planes, drm->num_planes, sizeof(*drm->planes), cmp_plane); + + drm->overlay_planes = drm->planes; + drm->primary_planes = drm->overlay_planes + + drm->num_overlay_planes; + drm->cursor_planes = drm->primary_planes + + drm->num_primary_planes; + + drmModeFreePlaneResources(plane_res); + return true; + +error_planes: + free(drm->planes); +error_res: + drmModeFreePlaneResources(plane_res); + return false; +} + +bool init_drm_resources(struct wlr_drm_backend *drm) { + drmModeRes *res = drmModeGetResources(drm->fd); + if (!res) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM resources"); + return false; + } + + wlr_log(WLR_INFO, "Found %d DRM CRTCs", res->count_crtcs); + + drm->num_crtcs = res->count_crtcs; + if (drm->num_crtcs == 0) { + drmModeFreeResources(res); + return true; + } + + drm->crtcs = calloc(drm->num_crtcs, sizeof(drm->crtcs[0])); + if (!drm->crtcs) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + goto error_res; + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + crtc->id = res->crtcs[i]; + crtc->legacy_crtc = drmModeGetCrtc(drm->fd, crtc->id); + get_drm_crtc_props(drm->fd, crtc->id, &crtc->props); + } + + if (!init_planes(drm)) { + goto error_crtcs; + } + + drmModeFreeResources(res); + + return true; + +error_crtcs: + free(drm->crtcs); +error_res: + drmModeFreeResources(res); + return false; +} + +void finish_drm_resources(struct wlr_drm_backend *drm) { + if (!drm) { + return; + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + struct wlr_drm_crtc *crtc = &drm->crtcs[i]; + drmModeAtomicFree(crtc->atomic); + drmModeFreeCrtc(crtc->legacy_crtc); + if (crtc->mode_id) { + drmModeDestroyPropertyBlob(drm->fd, crtc->mode_id); + } + if (crtc->gamma_lut) { + drmModeDestroyPropertyBlob(drm->fd, crtc->gamma_lut); + } + free(crtc->gamma_table); + } + for (size_t i = 0; i < drm->num_planes; ++i) { + struct wlr_drm_plane *plane = &drm->planes[i]; + if (plane->cursor_bo) { + gbm_bo_destroy(plane->cursor_bo); + } + } + + free(drm->crtcs); + free(drm->planes); +} + +static struct wlr_drm_connector *get_drm_connector_from_output( + struct wlr_output *wlr_output) { + assert(wlr_output_is_drm(wlr_output)); + return (struct wlr_drm_connector *)wlr_output; +} + +static bool drm_connector_make_current(struct wlr_output *output, + int *buffer_age) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + return make_drm_surface_current(&conn->crtc->primary->surf, buffer_age); +} + +static bool drm_connector_swap_buffers(struct wlr_output *output, + pixman_region32_t *damage) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + if (!drm->session->active) { + return false; + } + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return false; + } + struct wlr_drm_plane *plane = crtc->primary; + + struct gbm_bo *bo = swap_drm_surface_buffers(&plane->surf, damage); + if (drm->parent) { + bo = copy_drm_surface_mgpu(&plane->mgpu_surf, bo); + } + uint32_t fb_id = get_fb_for_bo(bo); + + if (conn->pageflip_pending) { + wlr_log(WLR_ERROR, "Skipping pageflip on output '%s'", conn->output.name); + return false; + } + + if (!drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, NULL)) { + return false; + } + + conn->pageflip_pending = true; + wlr_output_update_enabled(output, true); + return true; +} + +static void fill_empty_gamma_table(size_t size, + uint16_t *r, uint16_t *g, uint16_t *b) { + for (uint32_t i = 0; i < size; ++i) { + uint16_t val = (uint32_t)0xffff * i / (size - 1); + r[i] = g[i] = b[i] = val; + } +} + +static size_t drm_connector_get_gamma_size(struct wlr_output *output) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + + if (conn->crtc) { + return drm->iface->crtc_get_gamma_size(drm, conn->crtc); + } + + return 0; +} + +bool set_drm_connector_gamma(struct wlr_output *output, size_t size, + const uint16_t *r, const uint16_t *g, const uint16_t *b) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + + if (!conn->crtc) { + return false; + } + + bool reset = false; + if (size == 0) { + reset = true; + size = drm_connector_get_gamma_size(output); + if (size == 0) { + return false; + } + } + + uint16_t *gamma_table = malloc(3 * size * sizeof(uint16_t)); + if (gamma_table == NULL) { + wlr_log(WLR_ERROR, "Failed to allocate gamma table"); + return false; + } + uint16_t *_r = gamma_table; + uint16_t *_g = gamma_table + size; + uint16_t *_b = gamma_table + 2 * size; + + if (reset) { + fill_empty_gamma_table(size, _r, _g, _b); + } else { + memcpy(_r, r, size * sizeof(uint16_t)); + memcpy(_g, g, size * sizeof(uint16_t)); + memcpy(_b, b, size * sizeof(uint16_t)); + } + + bool ok = drm->iface->crtc_set_gamma(drm, conn->crtc, size, _r, _g, _b); + if (ok) { + wlr_output_update_needs_swap(output); + + free(conn->crtc->gamma_table); + conn->crtc->gamma_table = gamma_table; + conn->crtc->gamma_table_size = size; + } else { + free(gamma_table); + } + return ok; +} + +static bool drm_connector_export_dmabuf(struct wlr_output *output, + struct wlr_dmabuf_attributes *attribs) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + + if (!drm->session->active) { + return false; + } + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return false; + } + struct wlr_drm_plane *plane = crtc->primary; + struct wlr_drm_surface *surf = &plane->surf; + + return export_drm_bo(surf->back, attribs); +} + +static void drm_connector_start_renderer(struct wlr_drm_connector *conn) { + if (conn->state != WLR_DRM_CONN_CONNECTED) { + return; + } + + wlr_log(WLR_DEBUG, "Starting renderer on output '%s'", conn->output.name); + + struct wlr_drm_backend *drm = + get_drm_backend_from_backend(conn->output.backend); + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return; + } + struct wlr_drm_plane *plane = crtc->primary; + + struct gbm_bo *bo = get_drm_surface_front( + drm->parent ? &plane->mgpu_surf : &plane->surf); + uint32_t fb_id = get_fb_for_bo(bo); + + struct wlr_drm_mode *mode = (struct wlr_drm_mode *)conn->output.current_mode; + if (drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, &mode->drm_mode)) { + conn->pageflip_pending = true; + wlr_output_update_enabled(&conn->output, true); + } else { + wl_event_source_timer_update(conn->retry_pageflip, + 1000000.0f / conn->output.current_mode->refresh); + } +} + +static bool drm_connector_set_mode(struct wlr_output *output, + struct wlr_output_mode *mode); + +static void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs); + +static void attempt_enable_needs_modeset(struct wlr_drm_backend *drm) { + // Try to modeset any output that has a desired mode and a CRTC (ie. was + // lacking a CRTC on last modeset) + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + if (conn->state == WLR_DRM_CONN_NEEDS_MODESET && + conn->crtc != NULL && conn->desired_mode != NULL && + conn->desired_enabled) { + drm_connector_set_mode(&conn->output, conn->desired_mode); + } + } +} + +bool enable_drm_connector(struct wlr_output *output, bool enable) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + if (conn->state != WLR_DRM_CONN_CONNECTED + && conn->state != WLR_DRM_CONN_NEEDS_MODESET) { + return false; + } + + conn->desired_enabled = enable; + + if (enable && conn->crtc == NULL) { + // Maybe we can steal a CRTC from a disabled output + realloc_crtcs(drm, NULL); + } + + bool ok = drm->iface->conn_enable(drm, conn, enable); + if (!ok) { + return false; + } + + if (enable) { + drm_connector_start_renderer(conn); + } else { + realloc_crtcs(drm, NULL); + + attempt_enable_needs_modeset(drm); + } + + wlr_output_update_enabled(&conn->output, enable); + return true; +} + +static ssize_t connector_index_from_crtc(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc) { + size_t i = 0; + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + if (conn->crtc == crtc) { + return i; + } + ++i; + } + return -1; +} + +static void realloc_planes(struct wlr_drm_backend *drm, const uint32_t *crtc_in, + bool *changed_outputs) { + wlr_log(WLR_DEBUG, "Reallocating planes"); + + // overlay, primary, cursor + for (size_t type = 0; type < 3; ++type) { + if (drm->num_type_planes[type] == 0) { + continue; + } + + uint32_t possible[drm->num_type_planes[type] + 1]; + uint32_t crtc[drm->num_crtcs + 1]; + uint32_t crtc_res[drm->num_crtcs + 1]; + + for (size_t i = 0; i < drm->num_type_planes[type]; ++i) { + possible[i] = drm->type_planes[type][i].possible_crtcs; + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (crtc_in[i] == UNMATCHED) { + crtc[i] = SKIP; + } else if (drm->crtcs[i].planes[type]) { + crtc[i] = drm->crtcs[i].planes[type] + - drm->type_planes[type]; + } else { + crtc[i] = UNMATCHED; + } + } + + match_obj(drm->num_type_planes[type], possible, + drm->num_crtcs, crtc, crtc_res); + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (crtc_res[i] == UNMATCHED || crtc_res[i] == SKIP) { + continue; + } + + struct wlr_drm_crtc *c = &drm->crtcs[i]; + struct wlr_drm_plane **old = &c->planes[type]; + struct wlr_drm_plane *new = &drm->type_planes[type][crtc_res[i]]; + + if (*old != new) { + wlr_log(WLR_DEBUG, + "Assigning plane %d -> %d (type %zu) to CRTC %d", + *old ? (int)(*old)->id : -1, + new ? (int)new->id : -1, + type, + c->id); + + ssize_t conn_idx = connector_index_from_crtc(drm, c); + if (conn_idx >= 0) { + changed_outputs[conn_idx] = true; + } + if (*old) { + finish_drm_surface(&(*old)->surf); + } + finish_drm_surface(&new->surf); + *old = new; + } + } + } +} + +static void drm_connector_cleanup(struct wlr_drm_connector *conn); + +static 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); + 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 + realloc_crtcs(drm, NULL); + } + if (conn->crtc == NULL) { + wlr_log(WLR_ERROR, "Cannot modeset '%s': no CRTC for this connector", + conn->output.name); + // Save the desired mode for later, when we'll get a proper CRTC + conn->desired_mode = mode; + return false; + } + + wlr_log(WLR_INFO, "Modesetting '%s' with '%ux%u@%u mHz'", + conn->output.name, mode->width, mode->height, mode->refresh); + + if (!init_drm_plane_surfaces(conn->crtc->primary, drm, + mode->width, mode->height, GBM_FORMAT_XRGB8888)) { + wlr_log(WLR_ERROR, "Failed to initialize renderer for plane"); + return false; + } + + conn->state = WLR_DRM_CONN_CONNECTED; + conn->desired_mode = NULL; + wlr_output_update_mode(&conn->output, mode); + wlr_output_update_enabled(&conn->output, true); + conn->desired_enabled = true; + + drm_connector_start_renderer(conn); + + // When switching VTs, the mode is not updated but the buffers become + // invalid, so we need to manually damage the output here + wlr_output_damage_whole(&conn->output); + + return true; +} + +bool wlr_drm_connector_add_mode(struct wlr_output *output, + const drmModeModeInfo *modeinfo) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + + if (modeinfo->type != DRM_MODE_TYPE_USERDEF) { + return false; + } + + struct wlr_output_mode *wlr_mode; + wl_list_for_each(wlr_mode, &conn->output.modes, link) { + struct wlr_drm_mode *mode = (struct wlr_drm_mode *)wlr_mode; + if (memcmp(&mode->drm_mode, modeinfo, sizeof(*modeinfo)) == 0) { + return true; + } + } + + struct wlr_drm_mode *mode = calloc(1, sizeof(*mode)); + if (!mode) { + return false; + } + memcpy(&mode->drm_mode, modeinfo, sizeof(*modeinfo)); + + mode->wlr_mode.width = mode->drm_mode.hdisplay; + mode->wlr_mode.height = mode->drm_mode.vdisplay; + mode->wlr_mode.refresh = calculate_refresh_rate(modeinfo); + + wlr_log(WLR_INFO, "Registered custom mode " + "%"PRId32"x%"PRId32"@%"PRId32, + mode->wlr_mode.width, mode->wlr_mode.height, + mode->wlr_mode.refresh); + wl_list_insert(&conn->output.modes, &mode->wlr_mode.link); + return true; +} + +static void drm_connector_transform(struct wlr_output *output, + enum wl_output_transform transform) { + output->transform = transform; +} + +static bool drm_connector_set_cursor(struct wlr_output *output, + struct wlr_texture *texture, int32_t scale, + 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); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return false; + } + + struct wlr_drm_plane *plane = crtc->cursor; + if (!plane) { + // We don't have a real cursor plane, so we make a fake one + plane = calloc(1, sizeof(*plane)); + if (!plane) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + return false; + } + crtc->cursor = plane; + } + + if (!plane->surf.gbm) { + int ret; + uint64_t w, h; + ret = drmGetCap(drm->fd, DRM_CAP_CURSOR_WIDTH, &w); + w = ret ? 64 : w; + ret = drmGetCap(drm->fd, DRM_CAP_CURSOR_HEIGHT, &h); + h = ret ? 64 : h; + + struct wlr_drm_renderer *renderer = + drm->parent ? &drm->parent->renderer : &drm->renderer; + + if (!init_drm_surface(&plane->surf, renderer, w, h, + GBM_FORMAT_ARGB8888, 0)) { + wlr_log(WLR_ERROR, "Cannot allocate cursor resources"); + return false; + } + + plane->cursor_bo = gbm_bo_create(drm->renderer.gbm, w, h, + GBM_FORMAT_ARGB8888, GBM_BO_USE_CURSOR | GBM_BO_USE_WRITE); + if (!plane->cursor_bo) { + wlr_log_errno(WLR_ERROR, "Failed to create cursor bo"); + return false; + } + } + + wlr_matrix_projection(plane->matrix, plane->surf.width, + plane->surf.height, output->transform); + + struct wlr_box hotspot = { .x = hotspot_x, .y = hotspot_y }; + wlr_box_transform(&hotspot, &hotspot, + wlr_output_transform_invert(output->transform), + plane->surf.width, plane->surf.height); + + if (plane->cursor_hotspot_x != hotspot.x || + plane->cursor_hotspot_y != hotspot.y) { + // Update cursor hotspot + conn->cursor_x -= hotspot.x - plane->cursor_hotspot_x; + conn->cursor_y -= hotspot.y - plane->cursor_hotspot_y; + plane->cursor_hotspot_x = hotspot.x; + plane->cursor_hotspot_y = hotspot.y; + + if (!drm->iface->crtc_move_cursor(drm, conn->crtc, conn->cursor_x, + conn->cursor_y)) { + return false; + } + + wlr_output_update_needs_swap(output); + } + + if (!update_texture) { + // Don't update cursor image + return true; + } + + plane->cursor_enabled = false; + if (texture != NULL) { + int width, height; + wlr_texture_get_size(texture, &width, &height); + width = width * output->scale / scale; + height = height * output->scale / scale; + + if (width > (int)plane->surf.width || height > (int)plane->surf.height) { + wlr_log(WLR_ERROR, "Cursor too large (max %dx%d)", + (int)plane->surf.width, (int)plane->surf.height); + return false; + } + + uint32_t bo_width = gbm_bo_get_width(plane->cursor_bo); + uint32_t bo_height = gbm_bo_get_height(plane->cursor_bo); + + uint32_t bo_stride; + void *bo_data; + if (!gbm_bo_map(plane->cursor_bo, 0, 0, bo_width, bo_height, + GBM_BO_TRANSFER_WRITE, &bo_stride, &bo_data)) { + wlr_log_errno(WLR_ERROR, "Unable to map buffer"); + return false; + } + + make_drm_surface_current(&plane->surf, NULL); + + struct wlr_renderer *rend = plane->surf.renderer->wlr_rend; + + struct wlr_box cursor_box = { .width = width, .height = height }; + + float matrix[9]; + wlr_matrix_project_box(matrix, &cursor_box, transform, 0, plane->matrix); + + wlr_renderer_begin(rend, plane->surf.width, plane->surf.height); + wlr_renderer_clear(rend, (float[]){ 0.0, 0.0, 0.0, 0.0 }); + wlr_render_texture_with_matrix(rend, texture, matrix, 1.0); + wlr_renderer_end(rend); + + wlr_renderer_read_pixels(rend, WL_SHM_FORMAT_ARGB8888, NULL, bo_stride, + plane->surf.width, plane->surf.height, 0, 0, 0, 0, bo_data); + + swap_drm_surface_buffers(&plane->surf, NULL); + + gbm_bo_unmap(plane->cursor_bo, bo_data); + + plane->cursor_enabled = true; + } + + if (!drm->session->active) { + return true; // will be committed when session is resumed + } + + struct gbm_bo *bo = plane->cursor_enabled ? plane->cursor_bo : NULL; + bool ok = drm->iface->crtc_set_cursor(drm, crtc, bo); + if (ok) { + wlr_output_update_needs_swap(output); + } + return ok; +} + +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); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + if (!conn->crtc) { + return false; + } + struct wlr_drm_plane *plane = conn->crtc->cursor; + + struct wlr_box box = { .x = x, .y = y }; + + int width, height; + wlr_output_transformed_resolution(output, &width, &height); + + enum wl_output_transform transform = + wlr_output_transform_invert(output->transform); + wlr_box_transform(&box, &box, transform, width, height); + + if (plane != NULL) { + box.x -= plane->cursor_hotspot_x; + box.y -= plane->cursor_hotspot_y; + } + + conn->cursor_x = box.x; + conn->cursor_y = box.y; + + if (!drm->session->active) { + return true; // will be committed when session is resumed + } + + bool ok = drm->iface->crtc_move_cursor(drm, conn->crtc, box.x, box.y); + if (ok) { + wlr_output_update_needs_swap(output); + } + return ok; +} + +static bool drm_connector_schedule_frame(struct wlr_output *output) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + struct wlr_drm_backend *drm = get_drm_backend_from_backend(output->backend); + if (!drm->session->active) { + return false; + } + + // We need to figure out where we are in the vblank cycle + // TODO: try using drmWaitVBlank and fallback to pageflipping + + struct wlr_drm_crtc *crtc = conn->crtc; + if (!crtc) { + return false; + } + struct wlr_drm_plane *plane = crtc->primary; + struct gbm_bo *bo = plane->surf.back; + if (!bo) { + // We haven't swapped buffers yet -- can't do a pageflip + wlr_output_send_frame(output); + return true; + } + if (drm->parent) { + bo = copy_drm_surface_mgpu(&plane->mgpu_surf, bo); + } + uint32_t fb_id = get_fb_for_bo(bo); + + if (conn->pageflip_pending) { + wlr_log(WLR_ERROR, "Skipping pageflip on output '%s'", + conn->output.name); + return true; + } + + if (!drm->iface->crtc_pageflip(drm, conn, crtc, fb_id, NULL)) { + return false; + } + + conn->pageflip_pending = true; + wlr_output_update_enabled(output, true); + return true; +} + +static void drm_connector_destroy(struct wlr_output *output) { + struct wlr_drm_connector *conn = get_drm_connector_from_output(output); + drm_connector_cleanup(conn); + drmModeFreeCrtc(conn->old_crtc); + wl_event_source_remove(conn->retry_pageflip); + wl_list_remove(&conn->link); + free(conn); +} + +static const struct wlr_output_impl output_impl = { + .enable = enable_drm_connector, + .set_mode = drm_connector_set_mode, + .transform = drm_connector_transform, + .set_cursor = drm_connector_set_cursor, + .move_cursor = drm_connector_move_cursor, + .destroy = drm_connector_destroy, + .make_current = drm_connector_make_current, + .swap_buffers = drm_connector_swap_buffers, + .set_gamma = set_drm_connector_gamma, + .get_gamma_size = drm_connector_get_gamma_size, + .export_dmabuf = drm_connector_export_dmabuf, + .schedule_frame = drm_connector_schedule_frame, +}; + +bool wlr_output_is_drm(struct wlr_output *output) { + return output->impl == &output_impl; +} + +static int retry_pageflip(void *data) { + struct wlr_drm_connector *conn = data; + wlr_log(WLR_INFO, "%s: Retrying pageflip", conn->output.name); + drm_connector_start_renderer(conn); + return 0; +} + +static const int32_t subpixel_map[] = { + [DRM_MODE_SUBPIXEL_UNKNOWN] = WL_OUTPUT_SUBPIXEL_UNKNOWN, + [DRM_MODE_SUBPIXEL_HORIZONTAL_RGB] = WL_OUTPUT_SUBPIXEL_HORIZONTAL_RGB, + [DRM_MODE_SUBPIXEL_HORIZONTAL_BGR] = WL_OUTPUT_SUBPIXEL_HORIZONTAL_BGR, + [DRM_MODE_SUBPIXEL_VERTICAL_RGB] = WL_OUTPUT_SUBPIXEL_VERTICAL_RGB, + [DRM_MODE_SUBPIXEL_VERTICAL_BGR] = WL_OUTPUT_SUBPIXEL_VERTICAL_BGR, + [DRM_MODE_SUBPIXEL_NONE] = WL_OUTPUT_SUBPIXEL_NONE, +}; + +static void dealloc_crtc(struct wlr_drm_connector *conn) { + struct wlr_drm_backend *drm = + get_drm_backend_from_backend(conn->output.backend); + if (conn->crtc == NULL) { + return; + } + + wlr_log(WLR_DEBUG, "De-allocating CRTC %zu for output '%s'", + conn->crtc - drm->crtcs, conn->output.name); + + set_drm_connector_gamma(&conn->output, 0, NULL, NULL, NULL); + + for (size_t type = 0; type < 3; ++type) { + struct wlr_drm_plane *plane = conn->crtc->planes[type]; + if (plane == NULL) { + continue; + } + + finish_drm_surface(&plane->surf); + conn->crtc->planes[type] = NULL; + } + + drm->iface->conn_enable(drm, conn, false); + + conn->crtc = NULL; +} + +static void realloc_crtcs(struct wlr_drm_backend *drm, bool *changed_outputs) { + size_t num_outputs = wl_list_length(&drm->outputs); + + if (changed_outputs == NULL) { + changed_outputs = calloc(num_outputs, sizeof(bool)); + if (changed_outputs == NULL) { + wlr_log(WLR_ERROR, "Allocation failed"); + return; + } + } + + wlr_log(WLR_DEBUG, "Reallocating CRTCs"); + + uint32_t crtc[drm->num_crtcs + 1]; + for (size_t i = 0; i < drm->num_crtcs; ++i) { + crtc[i] = UNMATCHED; + } + + struct wlr_drm_connector *connectors[num_outputs + 1]; + + uint32_t possible_crtc[num_outputs + 1]; + memset(possible_crtc, 0, sizeof(possible_crtc)); + + wlr_log(WLR_DEBUG, "State before reallocation:"); + ssize_t i = -1; + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + i++; + connectors[i] = conn; + + wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d", + conn->output.name, + conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1, + conn->state, conn->desired_enabled); + + if (conn->crtc) { + crtc[conn->crtc - drm->crtcs] = i; + } + + // Only search CRTCs for user-enabled outputs (that are already + // connected or in need of a modeset) + if ((conn->state == WLR_DRM_CONN_CONNECTED || + conn->state == WLR_DRM_CONN_NEEDS_MODESET) && + conn->desired_enabled) { + possible_crtc[i] = conn->possible_crtc; + } + } + + uint32_t crtc_res[drm->num_crtcs + 1]; + match_obj(wl_list_length(&drm->outputs), possible_crtc, + drm->num_crtcs, crtc, crtc_res); + + bool matched[num_outputs + 1]; + memset(matched, false, sizeof(matched)); + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (crtc_res[i] != UNMATCHED) { + matched[crtc_res[i]] = true; + } + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + // We don't want any of the current monitors to be deactivated + if (crtc[i] != UNMATCHED && !matched[crtc[i]] && + connectors[crtc[i]]->desired_enabled) { + wlr_log(WLR_DEBUG, "Could not match a CRTC for connected output %d", + crtc[i]); + return; + } + } + + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (crtc_res[i] == crtc[i]) { + continue; + } + + // De-allocate this CRTC on previous output + if (crtc[i] != UNMATCHED) { + changed_outputs[crtc[i]] = true; + dealloc_crtc(connectors[crtc[i]]); + } + + // Assign this CRTC to next output + if (crtc_res[i] != UNMATCHED) { + changed_outputs[crtc_res[i]] = true; + + struct wlr_drm_connector *conn = connectors[crtc_res[i]]; + dealloc_crtc(conn); + conn->crtc = &drm->crtcs[i]; + + wlr_log(WLR_DEBUG, "Assigning CRTC %zu to output %d -> %d '%s'", + i, crtc[i], crtc_res[i], conn->output.name); + } + } + + wlr_log(WLR_DEBUG, "State after reallocation:"); + wl_list_for_each(conn, &drm->outputs, link) { + wlr_log(WLR_DEBUG, " '%s' crtc=%d state=%d desired_enabled=%d", + conn->output.name, + conn->crtc ? (int)(conn->crtc - drm->crtcs) : -1, + conn->state, conn->desired_enabled); + } + + realloc_planes(drm, crtc_res, changed_outputs); + + // We need to reinitialize any plane that has changed + i = -1; + wl_list_for_each(conn, &drm->outputs, link) { + i++; + struct wlr_output_mode *mode = conn->output.current_mode; + + if (conn->state != WLR_DRM_CONN_CONNECTED || !changed_outputs[i] + || conn->crtc == NULL) { + continue; + } + + if (!init_drm_plane_surfaces(conn->crtc->primary, drm, + mode->width, mode->height, GBM_FORMAT_XRGB8888)) { + wlr_log(WLR_ERROR, "Failed to initialize renderer for plane"); + drm_connector_cleanup(conn); + break; + } + + drm_connector_start_renderer(conn); + + wlr_output_damage_whole(&conn->output); + } +} + +static uint32_t get_possible_crtcs(int fd, drmModeRes *res, + drmModeConnector *conn, bool is_mst) { + uint32_t ret = 0; + + for (int i = 0; i < conn->count_encoders; ++i) { + drmModeEncoder *enc = drmModeGetEncoder(fd, conn->encoders[i]); + if (!enc) { + continue; + } + + ret |= enc->possible_crtcs; + + drmModeFreeEncoder(enc); + } + + // Sometimes DP MST connectors report no encoders, so we'll loop though + // all of the encoders of the MST type instead. + // TODO: See if there is a better solution. + + if (!is_mst || ret) { + return ret; + } + + for (int i = 0; i < res->count_encoders; ++i) { + drmModeEncoder *enc = drmModeGetEncoder(fd, res->encoders[i]); + if (!enc) { + continue; + } + + if (enc->encoder_type == DRM_MODE_ENCODER_DPMST) { + ret |= enc->possible_crtcs; + } + + drmModeFreeEncoder(enc); + } + + return ret; +} + +void scan_drm_connectors(struct wlr_drm_backend *drm) { + wlr_log(WLR_INFO, "Scanning DRM connectors"); + + drmModeRes *res = drmModeGetResources(drm->fd); + if (!res) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM resources"); + return; + } + + size_t seen_len = wl_list_length(&drm->outputs); + // +1 so length can never be 0, which is undefined behaviour. + // Last element isn't used. + bool seen[seen_len + 1]; + memset(seen, false, sizeof(seen)); + size_t new_outputs_len = 0; + struct wlr_drm_connector *new_outputs[res->count_connectors + 1]; + + for (int i = 0; i < res->count_connectors; ++i) { + drmModeConnector *drm_conn = drmModeGetConnector(drm->fd, + res->connectors[i]); + if (!drm_conn) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM connector"); + continue; + } + drmModeEncoder *curr_enc = drmModeGetEncoder(drm->fd, + drm_conn->encoder_id); + + ssize_t index = -1; + struct wlr_drm_connector *c, *wlr_conn = NULL; + wl_list_for_each(c, &drm->outputs, link) { + index++; + if (c->id == drm_conn->connector_id) { + wlr_conn = c; + break; + } + } + + if (!wlr_conn) { + wlr_conn = calloc(1, sizeof(*wlr_conn)); + if (!wlr_conn) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + drmModeFreeEncoder(curr_enc); + drmModeFreeConnector(drm_conn); + continue; + } + wlr_output_init(&wlr_conn->output, &drm->backend, &output_impl, + drm->display); + + struct wl_event_loop *ev = wl_display_get_event_loop(drm->display); + wlr_conn->retry_pageflip = wl_event_loop_add_timer(ev, retry_pageflip, + wlr_conn); + + 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); + } else { + seen[index] = true; + } + + if (curr_enc) { + for (size_t i = 0; i < drm->num_crtcs; ++i) { + if (drm->crtcs[i].id == curr_enc->crtc_id) { + wlr_conn->crtc = &drm->crtcs[i]; + break; + } + } + } else { + wlr_conn->crtc = NULL; + } + + // This can only happen *after* hotplug, since we haven't read the + // connector properties yet + if (wlr_conn->props.link_status != 0) { + uint64_t link_status; + if (!get_drm_prop(drm->fd, wlr_conn->id, + wlr_conn->props.link_status, &link_status)) { + wlr_log(WLR_ERROR, "Failed to get link status for '%s'", + wlr_conn->output.name); + continue; + } + + if (link_status == DRM_MODE_LINK_STATUS_BAD) { + // We need to reload our list of modes and force a modeset + wlr_log(WLR_INFO, "Bad link for '%s'", wlr_conn->output.name); + drm_connector_cleanup(wlr_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); + wlr_log(WLR_DEBUG, "Current CRTC: %d", + wlr_conn->crtc ? (int)wlr_conn->crtc->id : -1); + + wlr_conn->output.phys_width = drm_conn->mmWidth; + wlr_conn->output.phys_height = drm_conn->mmHeight; + wlr_log(WLR_INFO, "Physical size: %"PRId32"x%"PRId32, + wlr_conn->output.phys_width, wlr_conn->output.phys_height); + wlr_conn->output.subpixel = subpixel_map[drm_conn->subpixel]; + + get_drm_connector_props(drm->fd, wlr_conn->id, &wlr_conn->props); + + size_t edid_len = 0; + uint8_t *edid = get_drm_prop_blob(drm->fd, + wlr_conn->id, wlr_conn->props.edid, &edid_len); + parse_edid(&wlr_conn->output, edid_len, edid); + free(edid); + + wlr_log(WLR_INFO, "Detected modes:"); + + for (int i = 0; i < drm_conn->count_modes; ++i) { + struct wlr_drm_mode *mode = calloc(1, sizeof(*mode)); + if (!mode) { + wlr_log_errno(WLR_ERROR, "Allocation failed"); + continue; + } + + if (drm_conn->modes[i].flags & DRM_MODE_FLAG_INTERLACE) { + free(mode); + continue; + } + + mode->drm_mode = drm_conn->modes[i]; + mode->wlr_mode.width = mode->drm_mode.hdisplay; + mode->wlr_mode.height = mode->drm_mode.vdisplay; + mode->wlr_mode.refresh = calculate_refresh_rate(&mode->drm_mode); + + wlr_log(WLR_INFO, " %"PRId32"x%"PRId32"@%"PRId32, + mode->wlr_mode.width, mode->wlr_mode.height, + mode->wlr_mode.refresh); + + wl_list_insert(&wlr_conn->output.modes, &mode->wlr_mode.link); + } + + wlr_conn->possible_crtc = get_possible_crtcs(drm->fd, res, drm_conn, + wlr_conn->props.path != 0); + if (wlr_conn->possible_crtc == 0) { + wlr_log(WLR_ERROR, "No CRTC possible for connector '%s'", + wlr_conn->output.name); + } + + wlr_output_update_enabled(&wlr_conn->output, wlr_conn->crtc != NULL); + wlr_conn->desired_enabled = true; + + wlr_conn->state = WLR_DRM_CONN_NEEDS_MODESET; + new_outputs[new_outputs_len++] = wlr_conn; + } else if ((wlr_conn->state == WLR_DRM_CONN_CONNECTED || + wlr_conn->state == WLR_DRM_CONN_NEEDS_MODESET) && + drm_conn->connection != DRM_MODE_CONNECTED) { + wlr_log(WLR_INFO, "'%s' disconnected", wlr_conn->output.name); + + drm_connector_cleanup(wlr_conn); + } + + drmModeFreeEncoder(curr_enc); + drmModeFreeConnector(drm_conn); + } + + drmModeFreeResources(res); + + // Iterate in reverse order because we'll remove items from the list and + // still want indices to remain correct. + struct wlr_drm_connector *conn, *tmp_conn; + size_t index = wl_list_length(&drm->outputs); + wl_list_for_each_reverse_safe(conn, tmp_conn, &drm->outputs, link) { + index--; + if (index >= seen_len || seen[index]) { + continue; + } + + wlr_log(WLR_INFO, "'%s' disappeared", conn->output.name); + drm_connector_cleanup(conn); + + if (conn->pageflip_pending) { + conn->state = WLR_DRM_CONN_DISAPPEARED; + } else { + wlr_output_destroy(&conn->output); + } + } + + bool changed_outputs[wl_list_length(&drm->outputs) + 1]; + memset(changed_outputs, false, sizeof(changed_outputs)); + for (size_t i = 0; i < new_outputs_len; ++i) { + struct wlr_drm_connector *conn = new_outputs[i]; + + ssize_t pos = -1; + struct wlr_drm_connector *c; + wl_list_for_each(c, &drm->outputs, link) { + ++pos; + if (c == conn) { + break; + } + } + assert(pos >= 0); + + changed_outputs[pos] = true; + } + + realloc_crtcs(drm, changed_outputs); + + for (size_t i = 0; i < new_outputs_len; ++i) { + struct wlr_drm_connector *conn = new_outputs[i]; + + wlr_log(WLR_INFO, "Requesting modeset for '%s'", + conn->output.name); + wlr_signal_emit_safe(&drm->backend.events.new_output, + &conn->output); + } + + attempt_enable_needs_modeset(drm); +} + +static int mhz_to_nsec(int mhz) { + return 1000000000000LL / mhz; +} + +static void page_flip_handler(int fd, unsigned seq, + unsigned tv_sec, unsigned tv_usec, void *data) { + struct wlr_drm_connector *conn = data; + struct wlr_drm_backend *drm = + get_drm_backend_from_backend(conn->output.backend); + + 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) { + return; + } + + post_drm_surface(&conn->crtc->primary->surf); + if (drm->parent) { + post_drm_surface(&conn->crtc->primary->mgpu_surf); + } + + struct timespec present_time = { + .tv_sec = tv_sec, + .tv_nsec = tv_usec * 1000, + }; + struct wlr_output_event_present present_event = { + .when = &present_time, + .seq = seq, + .refresh = mhz_to_nsec(conn->output.refresh), + .flags = WLR_OUTPUT_PRESENT_VSYNC | WLR_OUTPUT_PRESENT_HW_CLOCK | + WLR_OUTPUT_PRESENT_HW_COMPLETION, + }; + wlr_output_send_present(&conn->output, &present_event); + + if (drm->session->active) { + wlr_output_send_frame(&conn->output); + } +} + +int handle_drm_event(int fd, uint32_t mask, void *data) { + drmEventContext event = { + .version = 2, + .page_flip_handler = page_flip_handler, + }; + + drmHandleEvent(fd, &event); + return 1; +} + +void restore_drm_outputs(struct wlr_drm_backend *drm) { + uint64_t to_close = (1L << wl_list_length(&drm->outputs)) - 1; + + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + if (conn->state == WLR_DRM_CONN_CONNECTED) { + conn->state = WLR_DRM_CONN_CLEANUP; + } + } + + time_t timeout = time(NULL) + 5; + + while (to_close && time(NULL) < timeout) { + handle_drm_event(drm->fd, 0, NULL); + size_t i = 0; + struct wlr_drm_connector *conn; + wl_list_for_each(conn, &drm->outputs, link) { + if (conn->state != WLR_DRM_CONN_CLEANUP || !conn->pageflip_pending) { + to_close &= ~(1 << i); + } + i++; + } + } + + if (to_close) { + wlr_log(WLR_ERROR, "Timed out stopping output renderers"); + } + + wl_list_for_each(conn, &drm->outputs, link) { + drmModeCrtc *crtc = conn->old_crtc; + if (!crtc) { + continue; + } + + drmModeSetCrtc(drm->fd, crtc->crtc_id, crtc->buffer_id, crtc->x, crtc->y, + &conn->id, 1, &crtc->mode); + } +} + +static void drm_connector_cleanup(struct wlr_drm_connector *conn) { + if (!conn) { + return; + } + + switch (conn->state) { + case WLR_DRM_CONN_CONNECTED: + case WLR_DRM_CONN_CLEANUP:; + struct wlr_drm_crtc *crtc = conn->crtc; + if (crtc != NULL) { + for (int i = 0; i < 3; ++i) { + if (!crtc->planes[i]) { + continue; + } + + finish_drm_surface(&crtc->planes[i]->surf); + finish_drm_surface(&crtc->planes[i]->mgpu_surf); + if (crtc->planes[i]->id == 0) { + free(crtc->planes[i]); + crtc->planes[i] = NULL; + } + } + } + + conn->output.current_mode = NULL; + conn->desired_mode = NULL; + struct wlr_drm_mode *mode, *tmp; + wl_list_for_each_safe(mode, tmp, &conn->output.modes, wlr_mode.link) { + wl_list_remove(&mode->wlr_mode.link); + free(mode); + } + + conn->output.enabled = false; + conn->output.width = conn->output.height = conn->output.refresh = 0; + + memset(&conn->output.make, 0, sizeof(conn->output.make)); + memset(&conn->output.model, 0, sizeof(conn->output.model)); + memset(&conn->output.serial, 0, sizeof(conn->output.serial)); + + if (conn->output.idle_frame != NULL) { + wl_event_source_remove(conn->output.idle_frame); + conn->output.idle_frame = NULL; + } + conn->output.needs_swap = false; + conn->output.frame_pending = false; + + /* Fallthrough */ + case WLR_DRM_CONN_NEEDS_MODESET: + wlr_log(WLR_INFO, "Emitting destruction signal for '%s'", + conn->output.name); + dealloc_crtc(conn); + conn->possible_crtc = 0; + conn->desired_mode = NULL; + wlr_signal_emit_safe(&conn->output.events.destroy, &conn->output); + break; + case WLR_DRM_CONN_DISCONNECTED: + break; + case WLR_DRM_CONN_DISAPPEARED: + return; // don't change state + } + + conn->state = WLR_DRM_CONN_DISCONNECTED; +} diff --git a/backend/drm/legacy.c b/backend/drm/legacy.c new file mode 100644 index 00000000..182c7a95 --- /dev/null +++ b/backend/drm/legacy.c @@ -0,0 +1,83 @@ +#include <gbm.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "backend/drm/drm.h" +#include "backend/drm/iface.h" +#include "backend/drm/util.h" + +static bool legacy_crtc_pageflip(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, struct wlr_drm_crtc *crtc, + uint32_t fb_id, drmModeModeInfo *mode) { + if (mode) { + if (drmModeSetCrtc(drm->fd, crtc->id, fb_id, 0, 0, + &conn->id, 1, mode)) { + wlr_log_errno(WLR_ERROR, "%s: Failed to set CRTC", conn->output.name); + return false; + } + } + + if (drmModePageFlip(drm->fd, crtc->id, fb_id, DRM_MODE_PAGE_FLIP_EVENT, conn)) { + wlr_log_errno(WLR_ERROR, "%s: Failed to page flip", conn->output.name); + return false; + } + + return true; +} + +static bool legacy_conn_enable(struct wlr_drm_backend *drm, + struct wlr_drm_connector *conn, bool enable) { + int ret = drmModeConnectorSetProperty(drm->fd, conn->id, conn->props.dpms, + enable ? DRM_MODE_DPMS_ON : DRM_MODE_DPMS_OFF); + return ret >= 0; +} + +bool legacy_crtc_set_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, struct gbm_bo *bo) { + if (!crtc || !crtc->cursor) { + return true; + } + + if (!bo) { + if (drmModeSetCursor(drm->fd, crtc->id, 0, 0, 0)) { + wlr_log_errno(WLR_DEBUG, "Failed to clear hardware cursor"); + return false; + } + return true; + } + + struct wlr_drm_plane *plane = crtc->cursor; + + if (drmModeSetCursor(drm->fd, crtc->id, gbm_bo_get_handle(bo).u32, + plane->surf.width, plane->surf.height)) { + wlr_log_errno(WLR_DEBUG, "Failed to set hardware cursor"); + return false; + } + + return true; +} + +bool legacy_crtc_move_cursor(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, int x, int y) { + return !drmModeMoveCursor(drm->fd, crtc->id, x, y); +} + +bool legacy_crtc_set_gamma(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc, size_t size, + uint16_t *r, uint16_t *g, uint16_t *b) { + return !drmModeCrtcSetGamma(drm->fd, crtc->id, (uint32_t)size, r, g, b); +} + +size_t legacy_crtc_get_gamma_size(struct wlr_drm_backend *drm, + struct wlr_drm_crtc *crtc) { + return (size_t)crtc->legacy_crtc->gamma_size; +} + +const struct wlr_drm_interface legacy_iface = { + .conn_enable = legacy_conn_enable, + .crtc_pageflip = legacy_crtc_pageflip, + .crtc_set_cursor = legacy_crtc_set_cursor, + .crtc_move_cursor = legacy_crtc_move_cursor, + .crtc_set_gamma = legacy_crtc_set_gamma, + .crtc_get_gamma_size = legacy_crtc_get_gamma_size, +}; diff --git a/backend/drm/properties.c b/backend/drm/properties.c new file mode 100644 index 00000000..5541d1be --- /dev/null +++ b/backend/drm/properties.c @@ -0,0 +1,151 @@ +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <wlr/util/log.h> +#include <xf86drm.h> +#include <xf86drmMode.h> +#include "backend/drm/properties.h" + +/* + * Creates a mapping between property names and an array index where to store + * the ids. The prop_info arrays must be sorted by name, as bsearch is used to + * search them. + */ +struct prop_info { + const char *name; + size_t index; +}; + +static const struct prop_info connector_info[] = { +#define INDEX(name) (offsetof(union wlr_drm_connector_props, name) / sizeof(uint32_t)) + { "CRTC_ID", INDEX(crtc_id) }, + { "DPMS", INDEX(dpms) }, + { "EDID", INDEX(edid) }, + { "PATH", INDEX(path) }, + { "link-status", INDEX(link_status) }, +#undef INDEX +}; + +static const struct prop_info crtc_info[] = { +#define INDEX(name) (offsetof(union wlr_drm_crtc_props, name) / sizeof(uint32_t)) + { "ACTIVE", INDEX(active) }, + { "GAMMA_LUT", INDEX(gamma_lut) }, + { "GAMMA_LUT_SIZE", INDEX(gamma_lut_size) }, + { "MODE_ID", INDEX(mode_id) }, + { "rotation", INDEX(rotation) }, + { "scaling mode", INDEX(scaling_mode) }, +#undef INDEX +}; + +static const struct prop_info plane_info[] = { +#define INDEX(name) (offsetof(union wlr_drm_plane_props, name) / sizeof(uint32_t)) + { "CRTC_H", INDEX(crtc_h) }, + { "CRTC_ID", INDEX(crtc_id) }, + { "CRTC_W", INDEX(crtc_w) }, + { "CRTC_X", INDEX(crtc_x) }, + { "CRTC_Y", INDEX(crtc_y) }, + { "FB_ID", INDEX(fb_id) }, + { "SRC_H", INDEX(src_h) }, + { "SRC_W", INDEX(src_w) }, + { "SRC_X", INDEX(src_x) }, + { "SRC_Y", INDEX(src_y) }, + { "type", INDEX(type) }, +#undef INDEX +}; + +static int cmp_prop_info(const void *arg1, const void *arg2) { + const char *key = arg1; + const struct prop_info *elem = arg2; + + return strcmp(key, elem->name); +} + +static bool scan_properties(int fd, uint32_t id, uint32_t type, uint32_t *result, + const struct prop_info *info, size_t info_len) { + drmModeObjectProperties *props = drmModeObjectGetProperties(fd, id, type); + if (!props) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM object properties"); + return false; + } + + for (uint32_t i = 0; i < props->count_props; ++i) { + drmModePropertyRes *prop = drmModeGetProperty(fd, props->props[i]); + if (!prop) { + wlr_log_errno(WLR_ERROR, "Failed to get DRM object property"); + continue; + } + + const struct prop_info *p = + bsearch(prop->name, info, info_len, sizeof(info[0]), cmp_prop_info); + if (p) { + result[p->index] = prop->prop_id; + } + + drmModeFreeProperty(prop); + } + + drmModeFreeObjectProperties(props); + return true; +} + +bool get_drm_connector_props(int fd, uint32_t id, + union wlr_drm_connector_props *out) { + return scan_properties(fd, id, DRM_MODE_OBJECT_CONNECTOR, out->props, + connector_info, sizeof(connector_info) / sizeof(connector_info[0])); +} + +bool get_drm_crtc_props(int fd, uint32_t id, union wlr_drm_crtc_props *out) { + return scan_properties(fd, id, DRM_MODE_OBJECT_CRTC, out->props, + crtc_info, sizeof(crtc_info) / sizeof(crtc_info[0])); +} + +bool get_drm_plane_props(int fd, uint32_t id, union wlr_drm_plane_props *out) { + return scan_properties(fd, id, DRM_MODE_OBJECT_PLANE, out->props, + plane_info, sizeof(plane_info) / sizeof(plane_info[0])); +} + +bool get_drm_prop(int fd, uint32_t obj, uint32_t prop, uint64_t *ret) { + drmModeObjectProperties *props = + drmModeObjectGetProperties(fd, obj, DRM_MODE_OBJECT_ANY); + if (!props) { + return false; + } + + bool found = false; + + for (uint32_t i = 0; i < props->count_props; ++i) { + if (props->props[i] == prop) { + *ret = props->prop_values[i]; + found = true; + break; + } + } + + drmModeFreeObjectProperties(props); + return found; +} + +void *get_drm_prop_blob(int fd, uint32_t obj, uint32_t prop, size_t *ret_len) { + uint64_t blob_id; + if (!get_drm_prop(fd, obj, prop, &blob_id)) { + return NULL; + } + + drmModePropertyBlobRes *blob = drmModeGetPropertyBlob(fd, blob_id); + if (!blob) { + return NULL; + } + + void *ptr = malloc(blob->length); + if (!ptr) { + drmModeFreePropertyBlob(blob); + return NULL; + } + + memcpy(ptr, blob->data, blob->length); + *ret_len = blob->length; + + drmModeFreePropertyBlob(blob); + return ptr; +} diff --git a/backend/drm/renderer.c b/backend/drm/renderer.c new file mode 100644 index 00000000..70b1bcbe --- /dev/null +++ b/backend/drm/renderer.c @@ -0,0 +1,264 @@ +#include <assert.h> +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <gbm.h> +#include <stdbool.h> +#include <stdlib.h> +#include <unistd.h> +#include <wayland-util.h> +#include <wlr/render/egl.h> +#include <wlr/render/gles2.h> +#include <wlr/render/wlr_renderer.h> +#include <wlr/types/wlr_matrix.h> +#include <wlr/util/log.h> +#include "backend/drm/drm.h" +#include "glapi.h" + +#ifndef DRM_FORMAT_MOD_LINEAR +#define DRM_FORMAT_MOD_LINEAR 0 +#endif + +bool init_drm_renderer(struct wlr_drm_backend *drm, + struct wlr_drm_renderer *renderer, wlr_renderer_create_func_t create_renderer_func) { + renderer->gbm = gbm_create_device(drm->fd); + if (!renderer->gbm) { + wlr_log(WLR_ERROR, "Failed to create GBM device"); + return false; + } + + if (!create_renderer_func) { + create_renderer_func = wlr_renderer_autocreate; + } + + static EGLint config_attribs[] = { + EGL_RED_SIZE, 1, + EGL_GREEN_SIZE, 1, + EGL_BLUE_SIZE, 1, + EGL_ALPHA_SIZE, 1, + EGL_NONE, + }; + + renderer->wlr_rend = create_renderer_func(&renderer->egl, + EGL_PLATFORM_GBM_MESA, renderer->gbm, + config_attribs, GBM_FORMAT_ARGB8888); + + if (!renderer->wlr_rend) { + wlr_log(WLR_ERROR, "Failed to create EGL/WLR renderer"); + goto error_gbm; + } + + renderer->fd = drm->fd; + return true; + +error_gbm: + gbm_device_destroy(renderer->gbm); + return false; +} + +void finish_drm_renderer(struct wlr_drm_renderer *renderer) { + if (!renderer) { + return; + } + + wlr_renderer_destroy(renderer->wlr_rend); + wlr_egl_finish(&renderer->egl); + gbm_device_destroy(renderer->gbm); +} + +bool init_drm_surface(struct wlr_drm_surface *surf, + struct wlr_drm_renderer *renderer, uint32_t width, uint32_t height, + uint32_t format, uint32_t flags) { + if (surf->width == width && surf->height == height) { + return true; + } + + surf->renderer = renderer; + surf->width = width; + surf->height = height; + + if (surf->gbm) { + if (surf->front) { + gbm_surface_release_buffer(surf->gbm, surf->front); + surf->front = NULL; + } + if (surf->back) { + gbm_surface_release_buffer(surf->gbm, surf->back); + surf->back = NULL; + } + gbm_surface_destroy(surf->gbm); + } + wlr_egl_destroy_surface(&surf->renderer->egl, surf->egl); + + surf->gbm = gbm_surface_create(renderer->gbm, width, height, + format, GBM_BO_USE_RENDERING | flags); + if (!surf->gbm) { + wlr_log_errno(WLR_ERROR, "Failed to create GBM surface"); + goto error_zero; + } + + surf->egl = wlr_egl_create_surface(&renderer->egl, surf->gbm); + if (surf->egl == EGL_NO_SURFACE) { + wlr_log(WLR_ERROR, "Failed to create EGL surface"); + goto error_gbm; + } + + return true; + +error_gbm: + gbm_surface_destroy(surf->gbm); +error_zero: + memset(surf, 0, sizeof(*surf)); + return false; +} + +void finish_drm_surface(struct wlr_drm_surface *surf) { + if (!surf || !surf->renderer) { + return; + } + + if (surf->front) { + gbm_surface_release_buffer(surf->gbm, surf->front); + } + if (surf->back) { + gbm_surface_release_buffer(surf->gbm, surf->back); + } + + wlr_egl_destroy_surface(&surf->renderer->egl, surf->egl); + if (surf->gbm) { + gbm_surface_destroy(surf->gbm); + } + + memset(surf, 0, sizeof(*surf)); +} + +bool make_drm_surface_current(struct wlr_drm_surface *surf, + int *buffer_damage) { + return wlr_egl_make_current(&surf->renderer->egl, surf->egl, buffer_damage); +} + +struct gbm_bo *swap_drm_surface_buffers(struct wlr_drm_surface *surf, + pixman_region32_t *damage) { + if (surf->front) { + gbm_surface_release_buffer(surf->gbm, surf->front); + } + + wlr_egl_swap_buffers(&surf->renderer->egl, surf->egl, damage); + + surf->front = surf->back; + surf->back = gbm_surface_lock_front_buffer(surf->gbm); + return surf->back; +} + +struct gbm_bo *get_drm_surface_front(struct wlr_drm_surface *surf) { + if (surf->front) { + return surf->front; + } + + make_drm_surface_current(surf, NULL); + struct wlr_renderer *renderer = surf->renderer->wlr_rend; + wlr_renderer_begin(renderer, surf->width, surf->height); + wlr_renderer_clear(renderer, (float[]){ 0.0, 0.0, 0.0, 1.0 }); + wlr_renderer_end(renderer); + return swap_drm_surface_buffers(surf, NULL); +} + +void post_drm_surface(struct wlr_drm_surface *surf) { + if (surf->front) { + gbm_surface_release_buffer(surf->gbm, surf->front); + surf->front = NULL; + } +} + +bool export_drm_bo(struct gbm_bo *bo, struct wlr_dmabuf_attributes *attribs) { + memset(attribs, 0, sizeof(struct wlr_dmabuf_attributes)); + + attribs->n_planes = gbm_bo_get_plane_count(bo); + if (attribs->n_planes > WLR_DMABUF_MAX_PLANES) { + return false; + } + + attribs->width = gbm_bo_get_width(bo); + attribs->height = gbm_bo_get_height(bo); + attribs->format = gbm_bo_get_format(bo); + attribs->modifier = gbm_bo_get_modifier(bo); + + for (int i = 0; i < attribs->n_planes; ++i) { + attribs->offset[i] = gbm_bo_get_offset(bo, i); + attribs->stride[i] = gbm_bo_get_stride_for_plane(bo, i); + attribs->fd[i] = gbm_bo_get_fd(bo); + if (attribs->fd[i] < 0) { + for (int j = 0; j < i; ++j) { + close(attribs->fd[j]); + } + return false; + } + } + + return true; +} + +static void free_tex(struct gbm_bo *bo, void *data) { + struct wlr_texture *tex = data; + wlr_texture_destroy(tex); +} + +static struct wlr_texture *get_tex_for_bo(struct wlr_drm_renderer *renderer, + struct gbm_bo *bo) { + struct wlr_texture *tex = gbm_bo_get_user_data(bo); + if (tex) { + return tex; + } + + struct wlr_dmabuf_attributes attribs; + if (!export_drm_bo(bo, &attribs)) { + return NULL; + } + + tex = wlr_texture_from_dmabuf(renderer->wlr_rend, &attribs); + if (tex) { + gbm_bo_set_user_data(bo, tex, free_tex); + } + + return tex; +} + +struct gbm_bo *copy_drm_surface_mgpu(struct wlr_drm_surface *dest, + struct gbm_bo *src) { + make_drm_surface_current(dest, NULL); + + struct wlr_texture *tex = get_tex_for_bo(dest->renderer, src); + assert(tex); + + float mat[9]; + wlr_matrix_projection(mat, 1, 1, WL_OUTPUT_TRANSFORM_NORMAL); + + struct wlr_renderer *renderer = dest->renderer->wlr_rend; + wlr_renderer_begin(renderer, dest->width, dest->height); + wlr_renderer_clear(renderer, (float[]){ 0.0, 0.0, 0.0, 1.0 }); + wlr_render_texture_with_matrix(renderer, tex, mat, 1.0f); + wlr_renderer_end(renderer); + + return swap_drm_surface_buffers(dest, NULL); +} + +bool init_drm_plane_surfaces(struct wlr_drm_plane *plane, + struct wlr_drm_backend *drm, int32_t width, uint32_t height, + uint32_t format) { + if (!drm->parent) { + return init_drm_surface(&plane->surf, &drm->renderer, width, height, + format, GBM_BO_USE_SCANOUT); + } + + if (!init_drm_surface(&plane->surf, &drm->parent->renderer, + width, height, format, GBM_BO_USE_LINEAR)) { + return false; + } + + if (!init_drm_surface(&plane->mgpu_surf, &drm->renderer, + width, height, format, GBM_BO_USE_SCANOUT)) { + finish_drm_surface(&plane->surf); + return false; + } + + return true; +} diff --git a/backend/drm/util.c b/backend/drm/util.c new file mode 100644 index 00000000..6f2dd5be --- /dev/null +++ b/backend/drm/util.c @@ -0,0 +1,348 @@ +#include <drm_mode.h> +#include <drm.h> +#include <gbm.h> +#include <stdio.h> +#include <string.h> +#include <wlr/util/log.h> +#include "backend/drm/util.h" + +int32_t calculate_refresh_rate(const drmModeModeInfo *mode) { + int32_t refresh = (mode->clock * 1000000LL / mode->htotal + + mode->vtotal / 2) / mode->vtotal; + + if (mode->flags & DRM_MODE_FLAG_INTERLACE) { + refresh *= 2; + } + + if (mode->flags & DRM_MODE_FLAG_DBLSCAN) { + refresh /= 2; + } + + if (mode->vscan > 1) { + refresh /= mode->vscan; + } + + return refresh; +} + +// Constructed from http://edid.tv/manufacturer +static const char *get_manufacturer(uint16_t id) { +#define ID(a, b, c) ((a & 0x1f) << 10) | ((b & 0x1f) << 5) | (c & 0x1f) + switch (id) { + case ID('A', 'A', 'A'): return "Avolites Ltd"; + case ID('A', 'C', 'I'): return "Ancor Communications Inc"; + case ID('A', 'C', 'R'): return "Acer Technologies"; + case ID('A', 'D', 'A'): return "Addi-Data GmbH"; + case ID('A', 'P', 'P'): return "Apple Computer Inc"; + case ID('A', 'S', 'K'): return "Ask A/S"; + case ID('A', 'V', 'T'): return "Avtek (Electronics) Pty Ltd"; + case ID('B', 'N', 'O'): return "Bang & Olufsen"; + case ID('C', 'M', 'N'): return "Chimei Innolux Corporation"; + case ID('C', 'M', 'O'): return "Chi Mei Optoelectronics corp."; + case ID('C', 'R', 'O'): return "Extraordinary Technologies PTY Limited"; + case ID('D', 'E', 'L'): return "Dell Inc."; + case ID('D', 'G', 'C'): return "Data General Corporation"; + case ID('D', 'O', 'N'): return "DENON, Ltd."; + case ID('E', 'N', 'C'): return "Eizo Nanao Corporation"; + case ID('E', 'P', 'H'): return "Epiphan Systems Inc."; + case ID('E', 'X', 'P'): return "Data Export Corporation"; + case ID('F', 'N', 'I'): return "Funai Electric Co., Ltd."; + case ID('F', 'U', 'S'): return "Fujitsu Siemens Computers GmbH"; + case ID('G', 'S', 'M'): return "Goldstar Company Ltd"; + case ID('H', 'I', 'Q'): return "Kaohsiung Opto Electronics Americas, Inc."; + case ID('H', 'S', 'D'): return "HannStar Display Corp"; + case ID('H', 'T', 'C'): return "Hitachi Ltd"; + case ID('H', 'W', 'P'): return "Hewlett Packard"; + case ID('I', 'N', 'T'): return "Interphase Corporation"; + case ID('I', 'N', 'X'): return "Communications Supply Corporation (A division of WESCO)"; + case ID('I', 'T', 'E'): return "Integrated Tech Express Inc"; + case ID('I', 'V', 'M'): return "Iiyama North America"; + case ID('L', 'E', 'N'): return "Lenovo Group Limited"; + case ID('M', 'A', 'X'): return "Rogen Tech Distribution Inc"; + case ID('M', 'E', 'G'): return "Abeam Tech Ltd"; + case ID('M', 'E', 'I'): return "Panasonic Industry Company"; + case ID('M', 'T', 'C'): return "Mars-Tech Corporation"; + case ID('M', 'T', 'X'): return "Matrox"; + case ID('N', 'E', 'C'): return "NEC Corporation"; + case ID('N', 'E', 'X'): return "Nexgen Mediatech Inc."; + case ID('O', 'N', 'K'): return "ONKYO Corporation"; + case ID('O', 'R', 'N'): return "ORION ELECTRIC CO., LTD."; + case ID('O', 'T', 'M'): return "Optoma Corporation"; + case ID('O', 'V', 'R'): return "Oculus VR, Inc."; + case ID('P', 'H', 'L'): return "Philips Consumer Electronics Company"; + case ID('P', 'I', 'O'): return "Pioneer Electronic Corporation"; + case ID('P', 'N', 'R'): return "Planar Systems, Inc."; + case ID('Q', 'D', 'S'): return "Quanta Display Inc."; + case ID('R', 'A', 'T'): return "Rent-A-Tech"; + case ID('R', 'E', 'N'): return "Renesas Technology Corp."; + case ID('S', 'A', 'M'): return "Samsung Electric Company"; + case ID('S', 'A', 'N'): return "Sanyo Electric Co., Ltd."; + case ID('S', 'E', 'C'): return "Seiko Epson Corporation"; + case ID('S', 'H', 'P'): return "Sharp Corporation"; + case ID('S', 'I', 'I'): return "Silicon Image, Inc."; + case ID('S', 'N', 'Y'): return "Sony"; + case ID('S', 'T', 'D'): return "STD Computer Inc"; + case ID('S', 'V', 'S'): return "SVSI"; + case ID('S', 'Y', 'N'): return "Synaptics Inc"; + case ID('T', 'C', 'L'): return "Technical Concepts Ltd"; + case ID('T', 'O', 'P'): return "Orion Communications Co., Ltd."; + case ID('T', 'S', 'B'): return "Toshiba America Info Systems Inc"; + case ID('T', 'S', 'T'): return "Transtream Inc"; + case ID('U', 'N', 'K'): return "Unknown"; + case ID('V', 'E', 'S'): return "Vestel Elektronik Sanayi ve Ticaret A. S."; + case ID('V', 'I', 'T'): return "Visitech AS"; + case ID('V', 'I', 'Z'): return "VIZIO, Inc"; + case ID('V', 'S', 'C'): return "ViewSonic Corporation"; + case ID('Y', 'M', 'H'): return "Yamaha Corporation"; + default: return "Unknown"; + } +#undef ID +} + +/* See https://en.wikipedia.org/wiki/Extended_Display_Identification_Data for layout of EDID data. + * We don't parse the EDID properly. We just expect to receive valid data. + */ +void parse_edid(struct wlr_output *restrict output, size_t len, const uint8_t *data) { + if (!data || len < 128) { + snprintf(output->make, sizeof(output->make), "<Unknown>"); + snprintf(output->model, sizeof(output->model), "<Unknown>"); + return; + } + + uint16_t id = (data[8] << 8) | data[9]; + snprintf(output->make, sizeof(output->make), "%s", get_manufacturer(id)); + + uint16_t model = data[10] | (data[11] << 8); + snprintf(output->model, sizeof(output->model), "0x%04X", model); + + uint32_t serial = data[12] | (data[13] << 8) | (data[14] << 8) | (data[15] << 8); + snprintf(output->serial, sizeof(output->serial), "0x%08X", serial); + + for (size_t i = 72; i <= 108; i += 18) { + uint16_t flag = (data[i] << 8) | data[i + 1]; + if (flag == 0 && data[i + 3] == 0xFC) { + sprintf(output->model, "%.13s", &data[i + 5]); + + // Monitor names are terminated by newline if they're too short + char *nl = strchr(output->model, '\n'); + if (nl) { + *nl = '\0'; + } + } else if (flag == 0 && data[i + 3] == 0xFF) { + sprintf(output->serial, "%.13s", &data[i + 5]); + + // Monitor serial numbers are terminated by newline if they're too + // short + char *nl = strchr(output->serial, '\n'); + if (nl) { + *nl = '\0'; + } + } + } +} + +const char *conn_get_name(uint32_t type_id) { + switch (type_id) { + case DRM_MODE_CONNECTOR_Unknown: return "Unknown"; + case DRM_MODE_CONNECTOR_VGA: return "VGA"; + case DRM_MODE_CONNECTOR_DVII: return "DVI-I"; + case DRM_MODE_CONNECTOR_DVID: return "DVI-D"; + case DRM_MODE_CONNECTOR_DVIA: return "DVI-A"; + case DRM_MODE_CONNECTOR_Composite: return "Composite"; + case DRM_MODE_CONNECTOR_SVIDEO: return "SVIDEO"; + case DRM_MODE_CONNECTOR_LVDS: return "LVDS"; + case DRM_MODE_CONNECTOR_Component: return "Component"; + case DRM_MODE_CONNECTOR_9PinDIN: return "DIN"; + case DRM_MODE_CONNECTOR_DisplayPort: return "DP"; + case DRM_MODE_CONNECTOR_HDMIA: return "HDMI-A"; + case DRM_MODE_CONNECTOR_HDMIB: return "HDMI-B"; + case DRM_MODE_CONNECTOR_TV: return "TV"; + case DRM_MODE_CONNECTOR_eDP: return "eDP"; + case DRM_MODE_CONNECTOR_VIRTUAL: return "Virtual"; + case DRM_MODE_CONNECTOR_DSI: return "DSI"; +#ifdef DRM_MODE_CONNECTOR_DPI + case DRM_MODE_CONNECTOR_DPI: return "DPI"; +#endif + default: return "Unknown"; + } +} + +static void free_fb(struct gbm_bo *bo, void *data) { + uint32_t id = (uintptr_t)data; + + if (id) { + struct gbm_device *gbm = gbm_bo_get_device(bo); + drmModeRmFB(gbm_device_get_fd(gbm), id); + } +} + +uint32_t get_fb_for_bo(struct gbm_bo *bo) { + uint32_t id = (uintptr_t)gbm_bo_get_user_data(bo); + if (id) { + return id; + } + + struct gbm_device *gbm = gbm_bo_get_device(bo); + + int fd = gbm_device_get_fd(gbm); + uint32_t width = gbm_bo_get_width(bo); + uint32_t height = gbm_bo_get_height(bo); + uint32_t handles[4] = {gbm_bo_get_handle(bo).u32}; + uint32_t pitches[4] = {gbm_bo_get_stride(bo)}; + uint32_t offsets[4] = {gbm_bo_get_offset(bo, 0)}; + uint32_t format = gbm_bo_get_format(bo); + + if (drmModeAddFB2(fd, width, height, format, handles, pitches, offsets, &id, 0)) { + wlr_log_errno(WLR_ERROR, "Unable to add DRM framebuffer"); + } + + gbm_bo_set_user_data(bo, (void *)(uintptr_t)id, free_fb); + + return id; +} + +static inline bool is_taken(size_t n, const uint32_t arr[static n], uint32_t key) { + for (size_t i = 0; i < n; ++i) { + if (arr[i] == key) { + return true; + } + } + return false; +} + +/* + * Store all of the non-recursive state in a struct, so we aren't literally + * passing 12 arguments to a function. + */ +struct match_state { + const size_t num_objs; + const uint32_t *restrict objs; + const size_t num_res; + size_t score; + size_t replaced; + uint32_t *restrict res; + uint32_t *restrict best; + const uint32_t *restrict orig; + bool exit_early; +}; + +/* + * skips: The number of SKIP elements encountered so far. + * score: The number of resources we've matched so far. + * replaced: The number of changes from the original solution. + * i: The index of the current element. + * + * This tries to match a solution as close to st->orig as it can. + * + * Returns whether we've set a new best element with this solution. + */ +static bool match_obj_(struct match_state *st, size_t skips, size_t score, size_t replaced, size_t i) { + // Finished + if (i >= st->num_res) { + if (score > st->score || + (score == st->score && replaced < st->replaced)) { + st->score = score; + st->replaced = replaced; + memcpy(st->best, st->res, sizeof(st->best[0]) * st->num_res); + + st->exit_early = (st->score == st->num_res - skips + || st->score == st->num_objs) + && st->replaced == 0; + + return true; + } else { + return false; + } + } + + if (st->orig[i] == SKIP) { + st->res[i] = SKIP; + return match_obj_(st, skips + 1, score, replaced, i + 1); + } + + bool has_best = false; + + /* + * Attempt to use the current solution first, to try and avoid + * recalculating everything + */ + if (st->orig[i] != UNMATCHED && !is_taken(i, st->res, st->orig[i])) { + st->res[i] = st->orig[i]; + size_t obj_score = st->objs[st->res[i]] != 0 ? 1 : 0; + if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) { + has_best = true; + } + } + if (st->orig[i] == UNMATCHED) { + st->res[i] = UNMATCHED; + if (match_obj_(st, skips, score, replaced, i + 1)) { + has_best = true; + } + } + if (st->exit_early) { + return true; + } + + if (st->orig[i] != UNMATCHED) { + ++replaced; + } + + for (size_t candidate = 0; candidate < st->num_objs; ++candidate) { + // We tried this earlier + if (candidate == st->orig[i]) { + continue; + } + + // Not compatible + if (!(st->objs[candidate] & (1 << i))) { + continue; + } + + // Already taken + if (is_taken(i, st->res, candidate)) { + continue; + } + + st->res[i] = candidate; + size_t obj_score = st->objs[candidate] != 0 ? 1 : 0; + if (match_obj_(st, skips, score + obj_score, replaced, i + 1)) { + has_best = true; + } + + if (st->exit_early) { + return true; + } + } + + if (has_best) { + return true; + } + + // Maybe this resource can't be matched + st->res[i] = UNMATCHED; + return match_obj_(st, skips, score, replaced, i + 1); +} + +size_t match_obj(size_t num_objs, const uint32_t objs[static restrict num_objs], + size_t num_res, const uint32_t res[static restrict num_res], + uint32_t out[static restrict num_res]) { + uint32_t solution[num_res]; + for (size_t i = 0; i < num_res; ++i) { + solution[i] = UNMATCHED; + } + + struct match_state st = { + .num_objs = num_objs, + .num_res = num_res, + .score = 0, + .replaced = SIZE_MAX, + .objs = objs, + .res = solution, + .best = out, + .orig = res, + .exit_early = false, + }; + + match_obj_(&st, 0, 0, 0, 0); + return st.score; +} |