| /* |
| * Copyright © 2008-2011 Kristian Høgsberg |
| * Copyright © 2011 Intel Corporation |
| * Copyright © 2017, 2018 Collabora, Ltd. |
| * Copyright © 2017, 2018 General Electric Company |
| * Copyright (c) 2018 DisplayLink (UK) Ltd. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining |
| * a copy of this software and associated documentation files (the |
| * "Software"), to deal in the Software without restriction, including |
| * without limitation the rights to use, copy, modify, merge, publish, |
| * distribute, sublicense, and/or sell copies of the Software, and to |
| * permit persons to whom the Software is furnished to do so, subject to |
| * the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the |
| * next paragraph) shall be included in all copies or substantial |
| * portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
| * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
| * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
| * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
| * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
| * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| * SOFTWARE. |
| */ |
| |
| #include "config.h" |
| |
| #include <errno.h> |
| #include <stdint.h> |
| #include <stdlib.h> |
| #include <ctype.h> |
| #include <string.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <linux/input.h> |
| #include <linux/vt.h> |
| #include <assert.h> |
| #include <sys/mman.h> |
| #include <time.h> |
| |
| #include <xf86drm.h> |
| #include <xf86drmMode.h> |
| |
| #include <libudev.h> |
| |
| #include <libweston/libweston.h> |
| #include <libweston/backend-drm.h> |
| #include <libweston/weston-log.h> |
| #include "drm-internal.h" |
| #include "shared/helpers.h" |
| #include "shared/timespec-util.h" |
| #include "shared/string-helpers.h" |
| #include "shared/weston-drm-fourcc.h" |
| #include "pixman-renderer.h" |
| #include "pixel-formats.h" |
| #include "libbacklight.h" |
| #include "libinput-seat.h" |
| #include "launcher-util.h" |
| #include "vaapi-recorder.h" |
| #include "presentation-time-server-protocol.h" |
| #include "linux-dmabuf.h" |
| #include "linux-dmabuf-unstable-v1-server-protocol.h" |
| #include "linux-explicit-synchronization.h" |
| |
| #include "aml-weston/aml-backend.h" |
| |
| static const char default_seat[] = "seat0"; |
| |
| static void |
| drm_backend_create_faked_zpos(struct drm_backend *b) |
| { |
| struct drm_plane *plane; |
| uint64_t zpos = 0ULL; |
| uint64_t zpos_min_primary; |
| uint64_t zpos_min_overlay; |
| uint64_t zpos_min_cursor; |
| |
| zpos_min_primary = zpos; |
| wl_list_for_each(plane, &b->plane_list, link) { |
| /* if the property is there, bail out sooner */ |
| if (plane->props[WDRM_PLANE_ZPOS].prop_id != 0) |
| return; |
| |
| if (plane->type != WDRM_PLANE_TYPE_PRIMARY) |
| continue; |
| zpos++; |
| } |
| |
| zpos_min_overlay = zpos; |
| wl_list_for_each(plane, &b->plane_list, link) { |
| if (plane->type != WDRM_PLANE_TYPE_OVERLAY) |
| continue; |
| zpos++; |
| } |
| |
| zpos_min_cursor = zpos; |
| wl_list_for_each(plane, &b->plane_list, link) { |
| if (plane->type != WDRM_PLANE_TYPE_CURSOR) |
| continue; |
| zpos++; |
| } |
| |
| drm_debug(b, "[drm-backend] zpos property not found. " |
| "Using invented immutable zpos values:\n"); |
| /* assume that invented zpos values are immutable */ |
| wl_list_for_each(plane, &b->plane_list, link) { |
| if (plane->type == WDRM_PLANE_TYPE_PRIMARY) { |
| plane->zpos_min = zpos_min_primary; |
| plane->zpos_max = zpos_min_primary; |
| } else if (plane->type == WDRM_PLANE_TYPE_OVERLAY) { |
| plane->zpos_min = zpos_min_overlay; |
| plane->zpos_max = zpos_min_overlay; |
| } else if (plane->type == WDRM_PLANE_TYPE_CURSOR) { |
| plane->zpos_min = zpos_min_cursor; |
| plane->zpos_max = zpos_min_cursor; |
| } |
| drm_debug(b, "\t[plane] %s plane %d, zpos_min %"PRIu64", " |
| "zpos_max %"PRIu64"\n", |
| drm_output_get_plane_type_name(plane), |
| plane->plane_id, plane->zpos_min, plane->zpos_max); |
| } |
| } |
| |
| static int |
| pageflip_timeout(void *data) { |
| /* |
| * Our timer just went off, that means we're not receiving drm |
| * page flip events anymore for that output. Let's gracefully exit |
| * weston with a return value so devs can debug what's going on. |
| */ |
| struct drm_output *output = data; |
| struct weston_compositor *compositor = output->base.compositor; |
| |
| weston_log("Pageflip timeout reached on output %s, your " |
| "driver is probably buggy! Exiting.\n", |
| output->base.name); |
| weston_compositor_exit_with_code(compositor, EXIT_FAILURE); |
| |
| return 0; |
| } |
| |
| /* Creates the pageflip timer. Note that it isn't armed by default */ |
| static int |
| drm_output_pageflip_timer_create(struct drm_output *output) |
| { |
| struct wl_event_loop *loop = NULL; |
| struct weston_compositor *ec = output->base.compositor; |
| |
| loop = wl_display_get_event_loop(ec->wl_display); |
| assert(loop); |
| output->pageflip_timer = wl_event_loop_add_timer(loop, |
| pageflip_timeout, |
| output); |
| |
| if (output->pageflip_timer == NULL) { |
| weston_log("creating drm pageflip timer failed: %s\n", |
| strerror(errno)); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void |
| drm_output_destroy(struct weston_output *output_base); |
| |
| /** |
| * Returns true if the plane can be used on the given output for its current |
| * repaint cycle. |
| */ |
| bool |
| drm_plane_is_available(struct drm_plane *plane, struct drm_output *output) |
| { |
| assert(plane->state_cur); |
| |
| if (output->virtual) |
| return false; |
| |
| /* The plane still has a request not yet completed by the kernel. */ |
| if (!plane->state_cur->complete) |
| return false; |
| |
| /* The plane is still active on another output. */ |
| if (plane->state_cur->output && plane->state_cur->output != output) { |
| //plane->state_cur->output = output; |
| //return false; |
| } |
| /* Check whether the plane can be used with this CRTC; possible_crtcs |
| * is a bitmask of CRTC indices (pipe), rather than CRTC object ID. */ |
| return !!(plane->possible_crtcs & (1 << output->crtc->pipe)); |
| } |
| |
| struct drm_crtc * |
| drm_crtc_find(struct drm_backend *b, uint32_t crtc_id) |
| { |
| struct drm_crtc *crtc; |
| |
| wl_list_for_each(crtc, &b->crtc_list, link) { |
| if (crtc->crtc_id == crtc_id) |
| return crtc; |
| } |
| |
| return NULL; |
| } |
| |
| struct drm_head * |
| drm_head_find_by_connector(struct drm_backend *backend, uint32_t connector_id) |
| { |
| struct weston_head *base; |
| struct drm_head *head; |
| |
| wl_list_for_each(base, |
| &backend->compositor->head_list, compositor_link) { |
| head = to_drm_head(base); |
| if (head->connector.connector_id == connector_id) |
| return head; |
| } |
| |
| return NULL; |
| } |
| |
| static struct drm_writeback * |
| drm_writeback_find_by_connector(struct drm_backend *backend, uint32_t connector_id) |
| { |
| struct drm_writeback *writeback; |
| |
| wl_list_for_each(writeback, &backend->writeback_connector_list, link) { |
| if (writeback->connector.connector_id == connector_id) |
| return writeback; |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * Get output state to disable output |
| * |
| * Returns a pointer to an output_state object which can be used to disable |
| * an output (e.g. DPMS off). |
| * |
| * @param pending_state The pending state object owning this update |
| * @param output The output to disable |
| * @returns A drm_output_state to disable the output |
| */ |
| static struct drm_output_state * |
| drm_output_get_disable_state(struct drm_pending_state *pending_state, |
| struct drm_output *output) |
| { |
| struct drm_output_state *output_state; |
| |
| output_state = drm_output_state_duplicate(output->state_cur, |
| pending_state, |
| DRM_OUTPUT_STATE_CLEAR_PLANES); |
| output_state->dpms = WESTON_DPMS_OFF; |
| |
| output_state->protection = WESTON_HDCP_DISABLE; |
| |
| return output_state; |
| } |
| |
| |
| /** |
| * Mark a drm_output_state (the output's last state) as complete. This handles |
| * any post-completion actions such as updating the repaint timer, disabling the |
| * output, and finally freeing the state. |
| */ |
| void |
| drm_output_update_complete(struct drm_output *output, uint32_t flags, |
| unsigned int sec, unsigned int usec) |
| { |
| struct drm_backend *b = to_drm_backend(output->base.compositor); |
| struct drm_plane_state *ps; |
| struct timespec ts; |
| |
| /* Stop the pageflip timer instead of rearming it here */ |
| if (output->pageflip_timer) |
| wl_event_source_timer_update(output->pageflip_timer, 0); |
| |
| wl_list_for_each(ps, &output->state_cur->plane_list, link) |
| ps->complete = true; |
| |
| drm_output_state_free(output->state_last); |
| output->state_last = NULL; |
| |
| if (output->destroy_pending) { |
| output->destroy_pending = false; |
| output->disable_pending = false; |
| output->dpms_off_pending = false; |
| drm_output_destroy(&output->base); |
| return; |
| } else if (output->disable_pending) { |
| output->disable_pending = false; |
| output->dpms_off_pending = false; |
| weston_output_disable(&output->base); |
| return; |
| } else if (output->dpms_off_pending) { |
| struct drm_pending_state *pending = drm_pending_state_alloc(b); |
| output->dpms_off_pending = false; |
| drm_output_get_disable_state(pending, output); |
| drm_pending_state_apply_sync(pending); |
| } |
| if (output->state_cur->dpms == WESTON_DPMS_OFF && |
| output->base.repaint_status != REPAINT_AWAITING_COMPLETION) { |
| /* DPMS can happen to us either in the middle of a repaint |
| * cycle (when we have painted fresh content, only to throw it |
| * away for DPMS off), or at any other random point. If the |
| * latter is true, then we cannot go through finish_frame, |
| * because the repaint machinery does not expect this. */ |
| return; |
| } |
| |
| ts.tv_sec = sec; |
| ts.tv_nsec = usec * 1000; |
| |
| if (output->state_cur->dpms != WESTON_DPMS_OFF) |
| weston_output_finish_frame(&output->base, &ts, flags); |
| else |
| weston_output_finish_frame(&output->base, NULL, |
| WP_PRESENTATION_FEEDBACK_INVALID); |
| |
| /* We can't call this from frame_notify, because the output's |
| * repaint needed flag is cleared just after that */ |
| if (output->recorder) |
| weston_output_schedule_repaint(&output->base); |
| } |
| |
| static struct drm_fb * |
| drm_output_render_pixman(struct drm_output_state *state, |
| pixman_region32_t *damage) |
| { |
| struct drm_output *output = state->output; |
| struct weston_compositor *ec = output->base.compositor; |
| |
| output->current_image ^= 1; |
| |
| pixman_renderer_output_set_buffer(&output->base, |
| output->image[output->current_image]); |
| pixman_renderer_output_set_hw_extra_damage(&output->base, |
| &output->previous_damage); |
| |
| ec->renderer->repaint_output(&output->base, damage); |
| |
| pixman_region32_copy(&output->previous_damage, damage); |
| |
| return drm_fb_ref(output->dumb[output->current_image]); |
| } |
| |
| void |
| drm_output_render(struct drm_output_state *state, pixman_region32_t *damage) |
| { |
| struct drm_output *output = state->output; |
| struct weston_compositor *c = output->base.compositor; |
| struct drm_plane_state *scanout_state; |
| struct drm_plane *scanout_plane = output->scanout_plane; |
| struct drm_property_info *damage_info = |
| &scanout_plane->props[WDRM_PLANE_FB_DAMAGE_CLIPS]; |
| struct drm_backend *b = to_drm_backend(c); |
| struct drm_fb *fb; |
| pixman_region32_t scanout_damage; |
| pixman_box32_t *rects; |
| int n_rects; |
| |
| /* If we already have a client buffer promoted to scanout, then we don't |
| * want to render. */ |
| scanout_state = drm_output_state_get_plane(state, scanout_plane); |
| if (scanout_state->fb) |
| return; |
| |
| /* |
| * If we don't have any damage on the primary plane, and we already |
| * have a renderer buffer active, we can reuse it; else we pass |
| * the damaged region into the renderer to re-render the affected |
| * area. But, we still have to call the renderer anyway if any screen |
| * capture is pending, otherwise the capture will not complete. |
| */ |
| if (!pixman_region32_not_empty(damage) && |
| wl_list_empty(&output->base.frame_signal.listener_list) && |
| scanout_plane->state_cur->fb && |
| (scanout_plane->state_cur->fb->type == BUFFER_GBM_SURFACE || |
| scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB)) { |
| fb = drm_fb_ref(scanout_plane->state_cur->fb); |
| } else if (b->use_pixman) { |
| fb = drm_output_render_pixman(state, damage); |
| } else { |
| fb = drm_output_render_gl(state, damage); |
| } |
| |
| if (!fb) { |
| drm_plane_state_put_back(scanout_state); |
| return; |
| } |
| |
| scanout_state->fb = fb; |
| scanout_state->output = output; |
| |
| scanout_state->src_x = 0; |
| scanout_state->src_y = 0; |
| scanout_state->src_w = fb->width << 16; |
| scanout_state->src_h = fb->height << 16; |
| |
| scanout_state->dest_x = 0; |
| scanout_state->dest_y = 0; |
| scanout_state->dest_w = output->base.current_mode->width; |
| scanout_state->dest_h = output->base.current_mode->height; |
| |
| drm_output_render_aml_config(output, scanout_state); |
| |
| pixman_region32_subtract(&c->primary_plane.damage, |
| &c->primary_plane.damage, damage); |
| |
| /* Don't bother calculating plane damage if the plane doesn't support it */ |
| if (damage_info->prop_id == 0) |
| return; |
| |
| pixman_region32_init(&scanout_damage); |
| pixman_region32_copy(&scanout_damage, damage); |
| |
| if (output->base.zoom.active) { |
| pixman_region32_t clip; |
| |
| weston_matrix_transform_region(&scanout_damage, |
| &output->base.matrix, |
| &scanout_damage); |
| pixman_region32_init_rect(&clip, 0, 0, |
| output->base.width, |
| output->base.height); |
| pixman_region32_intersect(&scanout_damage, &scanout_damage, &clip); |
| pixman_region32_fini(&clip); |
| } else { |
| pixman_region32_translate(&scanout_damage, |
| -output->base.x, -output->base.y); |
| weston_transformed_region(output->base.width, |
| output->base.height, |
| output->base.transform, |
| output->base.current_scale, |
| &scanout_damage, |
| &scanout_damage); |
| } |
| |
| assert(scanout_state->damage_blob_id == 0); |
| |
| rects = pixman_region32_rectangles(&scanout_damage, &n_rects); |
| |
| /* |
| * If this function fails, the blob id should still be 0. |
| * This tells the kernel there is no damage information, which means |
| * that it will consider the whole plane damaged. While this may |
| * affect efficiency, it should still produce correct results. |
| */ |
| drmModeCreatePropertyBlob(b->drm.fd, rects, |
| sizeof(*rects) * n_rects, |
| &scanout_state->damage_blob_id); |
| |
| pixman_region32_fini(&scanout_damage); |
| } |
| |
| static int |
| drm_output_repaint(struct weston_output *output_base, |
| pixman_region32_t *damage, |
| void *repaint_data) |
| { |
| struct drm_pending_state *pending_state = repaint_data; |
| struct drm_output *output = to_drm_output(output_base); |
| struct drm_output_state *state = NULL; |
| struct drm_plane_state *scanout_state; |
| |
| assert(!output->virtual); |
| |
| if (output->disable_pending || output->destroy_pending) |
| goto err; |
| |
| assert(!output->state_last); |
| |
| /* If planes have been disabled in the core, we might not have |
| * hit assign_planes at all, so might not have valid output state |
| * here. */ |
| state = drm_pending_state_get_output(pending_state, output); |
| if (!state) |
| state = drm_output_state_duplicate(output->state_cur, |
| pending_state, |
| DRM_OUTPUT_STATE_CLEAR_PLANES); |
| state->dpms = WESTON_DPMS_ON; |
| |
| if (output_base->allow_protection) |
| state->protection = output_base->desired_protection; |
| else |
| state->protection = WESTON_HDCP_DISABLE; |
| |
| drm_output_render(state, damage); |
| scanout_state = drm_output_state_get_plane(state, |
| output->scanout_plane); |
| if (!scanout_state || !scanout_state->fb) |
| goto err; |
| |
| return 0; |
| |
| err: |
| drm_output_state_free(state); |
| return -1; |
| } |
| |
| /* Determine the type of vblank synchronization to use for the output. |
| * |
| * The pipe parameter indicates which CRTC is in use. Knowing this, we |
| * can determine which vblank sequence type to use for it. Traditional |
| * cards had only two CRTCs, with CRTC 0 using no special flags, and |
| * CRTC 1 using DRM_VBLANK_SECONDARY. The first bit of the pipe |
| * parameter indicates this. |
| * |
| * Bits 1-5 of the pipe parameter are 5 bit wide pipe number between |
| * 0-31. If this is non-zero it indicates we're dealing with a |
| * multi-gpu situation and we need to calculate the vblank sync |
| * using DRM_BLANK_HIGH_CRTC_MASK. |
| */ |
| static unsigned int |
| drm_waitvblank_pipe(struct drm_crtc *crtc) |
| { |
| if (crtc->pipe > 1) |
| return (crtc->pipe << DRM_VBLANK_HIGH_CRTC_SHIFT) & |
| DRM_VBLANK_HIGH_CRTC_MASK; |
| else if (crtc->pipe > 0) |
| return DRM_VBLANK_SECONDARY; |
| else |
| return 0; |
| } |
| |
| static int |
| drm_output_start_repaint_loop(struct weston_output *output_base) |
| { |
| struct drm_output *output = to_drm_output(output_base); |
| struct drm_pending_state *pending_state; |
| struct drm_plane *scanout_plane = output->scanout_plane; |
| struct drm_backend *backend = |
| to_drm_backend(output_base->compositor); |
| struct timespec ts, tnow; |
| struct timespec vbl2now; |
| int64_t refresh_nsec; |
| int ret; |
| drmVBlank vbl = { |
| .request.type = DRM_VBLANK_RELATIVE, |
| .request.sequence = 0, |
| .request.signal = 0, |
| }; |
| |
| if (output->disable_pending || output->destroy_pending) |
| return 0; |
| |
| if (!scanout_plane->state_cur->fb) { |
| /* We can't page flip if there's no mode set */ |
| goto finish_frame; |
| } |
| |
| /* Need to smash all state in from scratch; current timings might not |
| * be what we want, page flip might not work, etc. |
| */ |
| if (backend->state_invalid) |
| goto finish_frame; |
| |
| assert(scanout_plane->state_cur->output == output); |
| |
| /* Try to get current msc and timestamp via instant query */ |
| vbl.request.type |= drm_waitvblank_pipe(output->crtc); |
| ret = drmWaitVBlank(backend->drm.fd, &vbl); |
| |
| /* Error ret or zero timestamp means failure to get valid timestamp */ |
| if ((ret == 0) && (vbl.reply.tval_sec > 0 || vbl.reply.tval_usec > 0)) { |
| ts.tv_sec = vbl.reply.tval_sec; |
| ts.tv_nsec = vbl.reply.tval_usec * 1000; |
| |
| /* Valid timestamp for most recent vblank - not stale? |
| * Stale ts could happen on Linux 3.17+, so make sure it |
| * is not older than 1 refresh duration since now. |
| */ |
| weston_compositor_read_presentation_clock(backend->compositor, |
| &tnow); |
| timespec_sub(&vbl2now, &tnow, &ts); |
| refresh_nsec = |
| millihz_to_nsec(output->base.current_mode->refresh); |
| if (timespec_to_nsec(&vbl2now) < refresh_nsec) { |
| drm_output_update_msc(output, vbl.reply.sequence); |
| weston_output_finish_frame(output_base, &ts, |
| WP_PRESENTATION_FEEDBACK_INVALID); |
| return 0; |
| } |
| } |
| |
| /* Immediate query didn't provide valid timestamp. |
| * Use pageflip fallback. |
| */ |
| |
| assert(!output->page_flip_pending); |
| assert(!output->state_last); |
| |
| pending_state = drm_pending_state_alloc(backend); |
| drm_output_state_duplicate(output->state_cur, pending_state, |
| DRM_OUTPUT_STATE_PRESERVE_PLANES); |
| |
| ret = drm_pending_state_apply(pending_state); |
| if (ret != 0) { |
| weston_log("applying repaint-start state failed: %s\n", |
| strerror(errno)); |
| if (ret == -EACCES) |
| return -1; |
| goto finish_frame; |
| } |
| |
| return 0; |
| |
| finish_frame: |
| /* if we cannot page-flip, immediately finish frame */ |
| weston_output_finish_frame(output_base, NULL, |
| WP_PRESENTATION_FEEDBACK_INVALID); |
| return 0; |
| } |
| |
| /** |
| * Begin a new repaint cycle |
| * |
| * Called by the core compositor at the beginning of a repaint cycle. Creates |
| * a new pending_state structure to own any output state created by individual |
| * output repaint functions until the repaint is flushed or cancelled. |
| */ |
| static void * |
| drm_repaint_begin(struct weston_compositor *compositor) |
| { |
| struct drm_backend *b = to_drm_backend(compositor); |
| struct drm_pending_state *ret; |
| |
| ret = drm_pending_state_alloc(b); |
| b->repaint_data = ret; |
| |
| if (weston_log_scope_is_enabled(b->debug)) { |
| char *dbg = weston_compositor_print_scene_graph(compositor); |
| drm_debug(b, "[repaint] Beginning repaint; pending_state %p\n", |
| ret); |
| drm_debug(b, "%s", dbg); |
| free(dbg); |
| } |
| |
| return ret; |
| } |
| |
| /** |
| * Flush a repaint set |
| * |
| * Called by the core compositor when a repaint cycle has been completed |
| * and should be flushed. Frees the pending state, transitioning ownership |
| * of the output state from the pending state, to the update itself. When |
| * the update completes (see drm_output_update_complete), the output |
| * state will be freed. |
| */ |
| static int |
| drm_repaint_flush(struct weston_compositor *compositor, void *repaint_data) |
| { |
| struct drm_backend *b = to_drm_backend(compositor); |
| struct drm_pending_state *pending_state = repaint_data; |
| int ret; |
| |
| ret = drm_pending_state_apply(pending_state); |
| if (ret != 0) |
| weston_log("repaint-flush failed: %s\n", strerror(errno)); |
| |
| drm_debug(b, "[repaint] flushed pending_state %p\n", pending_state); |
| b->repaint_data = NULL; |
| #ifdef ENABLE_DRM_HELP |
| help_do_repaint_cycle_completed(); |
| #endif |
| |
| return (ret == -EACCES) ? -1 : 0; |
| } |
| |
| /** |
| * Cancel a repaint set |
| * |
| * Called by the core compositor when a repaint has finished, so the data |
| * held across the repaint cycle should be discarded. |
| */ |
| static void |
| drm_repaint_cancel(struct weston_compositor *compositor, void *repaint_data) |
| { |
| struct drm_backend *b = to_drm_backend(compositor); |
| struct drm_pending_state *pending_state = repaint_data; |
| |
| drm_pending_state_free(pending_state); |
| drm_debug(b, "[repaint] cancel pending_state %p\n", pending_state); |
| b->repaint_data = NULL; |
| } |
| |
| static int |
| drm_output_init_pixman(struct drm_output *output, struct drm_backend *b); |
| static void |
| drm_output_fini_pixman(struct drm_output *output); |
| |
| static int |
| drm_output_switch_mode(struct weston_output *output_base, struct weston_mode *mode) |
| { |
| struct drm_output *output = to_drm_output(output_base); |
| struct drm_backend *b = to_drm_backend(output_base->compositor); |
| struct drm_mode *drm_mode = drm_output_choose_mode(output, mode); |
| |
| if (!drm_mode) { |
| weston_log("%s: invalid resolution %dx%d\n", |
| output_base->name, mode->width, mode->height); |
| return -1; |
| } |
| |
| if (&drm_mode->base == output->base.current_mode) |
| return 0; |
| |
| output->base.current_mode->flags = 0; |
| |
| drm_switch_mode_aml_config_ui_size(output, output_base, drm_mode); |
| |
| output->base.current_mode = &drm_mode->base; |
| output->base.current_mode->flags = |
| WL_OUTPUT_MODE_CURRENT | WL_OUTPUT_MODE_PREFERRED; |
| |
| drm_switch_mode_aml_config_help(output, output_base, drm_mode); |
| |
| /* XXX: This drops our current buffer too early, before we've started |
| * displaying it. Ideally this should be much more atomic and |
| * integrated with a full repaint cycle, rather than doing a |
| * sledgehammer modeswitch first, and only later showing new |
| * content. |
| */ |
| b->state_invalid = true; |
| |
| b->allow_modeset = true; |
| #ifdef MESON_DRM_FIX_UI_SIZE |
| // fixed ui mode needn't reset egl. |
| return 0; |
| #endif |
| |
| if (b->use_pixman) { |
| drm_output_fini_pixman(output); |
| if (drm_output_init_pixman(output, b) < 0) { |
| weston_log("failed to init output pixman state with " |
| "new mode\n"); |
| return -1; |
| } |
| } else { |
| drm_output_fini_egl(output); |
| if (drm_output_init_egl(output, b) < 0) { |
| weston_log("failed to init output egl state with " |
| "new mode"); |
| return -1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| static int |
| init_pixman(struct drm_backend *b) |
| { |
| return pixman_renderer_init(b->compositor); |
| } |
| |
| /** |
| * Create a drm_plane for a hardware plane |
| * |
| * Creates one drm_plane structure for a hardware plane, and initialises its |
| * properties and formats. |
| * |
| * This function does not add the plane to the list of usable planes in Weston |
| * itself; the caller is responsible for this. |
| * |
| * Call drm_plane_destroy to clean up the plane. |
| * |
| * @sa drm_output_find_special_plane |
| * @param b DRM compositor backend |
| * @param kplane DRM plane to create |
| */ |
| static struct drm_plane * |
| drm_plane_create(struct drm_backend *b, const drmModePlane *kplane) |
| { |
| struct drm_plane *plane; |
| drmModeObjectProperties *props; |
| uint64_t *zpos_range_values; |
| |
| plane = zalloc(sizeof(*plane)); |
| if (!plane) { |
| weston_log("%s: out of memory\n", __func__); |
| return NULL; |
| } |
| |
| plane->backend = b; |
| plane->plane_idx = b->next_plane_idx++; |
| plane->state_cur = drm_plane_state_alloc(NULL, plane); |
| plane->state_cur->complete = true; |
| plane->possible_crtcs = kplane->possible_crtcs; |
| plane->plane_id = kplane->plane_id; |
| |
| weston_drm_format_array_init(&plane->formats); |
| |
| props = drmModeObjectGetProperties(b->drm.fd, kplane->plane_id, |
| DRM_MODE_OBJECT_PLANE); |
| if (!props) { |
| weston_log("couldn't get plane properties\n"); |
| goto err; |
| } |
| |
| drm_property_info_populate(b, plane_props, plane->props, |
| WDRM_PLANE__COUNT, props); |
| plane->type = |
| drm_property_get_value(&plane->props[WDRM_PLANE_TYPE], |
| props, |
| WDRM_PLANE_TYPE__COUNT); |
| |
| zpos_range_values = |
| drm_property_get_range_values(&plane->props[WDRM_PLANE_ZPOS], |
| props); |
| |
| if (zpos_range_values) { |
| plane->zpos_min = zpos_range_values[0]; |
| plane->zpos_max = zpos_range_values[1]; |
| } else { |
| plane->zpos_min = DRM_PLANE_ZPOS_INVALID_PLANE; |
| plane->zpos_max = DRM_PLANE_ZPOS_INVALID_PLANE; |
| } |
| |
| if (drm_plane_populate_formats(plane, kplane, props, |
| b->fb_modifiers) < 0) { |
| drmModeFreeObjectProperties(props); |
| goto err; |
| } |
| |
| drmModeFreeObjectProperties(props); |
| |
| if (plane->type == WDRM_PLANE_TYPE__COUNT) |
| goto err_props; |
| |
| weston_plane_init(&plane->base, b->compositor, 0, 0); |
| wl_list_insert(&b->plane_list, &plane->link); |
| |
| return plane; |
| |
| err_props: |
| drm_property_info_free(plane->props, WDRM_PLANE__COUNT); |
| err: |
| weston_drm_format_array_fini(&plane->formats); |
| drm_plane_state_free(plane->state_cur, true); |
| free(plane); |
| return NULL; |
| } |
| |
| /** |
| * Find, or create, a special-purpose plane |
| * |
| * @param b DRM backend |
| * @param output Output to use for plane |
| * @param type Type of plane |
| */ |
| static struct drm_plane * |
| drm_output_find_special_plane(struct drm_backend *b, struct drm_output *output, |
| enum wdrm_plane_type type) |
| { |
| struct drm_plane *plane; |
| |
| wl_list_for_each(plane, &b->plane_list, link) { |
| struct drm_output *tmp; |
| bool found_elsewhere = false; |
| |
| if (plane->type != type) |
| continue; |
| if (!drm_plane_is_available(plane, output)) |
| continue; |
| |
| /* On some platforms, primary/cursor planes can roam |
| * between different CRTCs, so make sure we don't claim the |
| * same plane for two outputs. */ |
| wl_list_for_each(tmp, &b->compositor->output_list, base.link) { |
| if (tmp->cursor_plane == plane || |
| tmp->scanout_plane == plane) { |
| found_elsewhere = true; |
| break; |
| } |
| } |
| |
| if (found_elsewhere) |
| continue; |
| |
| plane->possible_crtcs = (1 << output->crtc->pipe); |
| return plane; |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * Destroy one DRM plane |
| * |
| * Destroy a DRM plane, removing it from screen and releasing its retained |
| * buffers in the process. The counterpart to drm_plane_create. |
| * |
| * @param plane Plane to deallocate (will be freed) |
| */ |
| static void |
| drm_plane_destroy(struct drm_plane *plane) |
| { |
| if (plane->type == WDRM_PLANE_TYPE_OVERLAY) |
| drmModeSetPlane(plane->backend->drm.fd, plane->plane_id, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); |
| drm_plane_state_free(plane->state_cur, true); |
| drm_property_info_free(plane->props, WDRM_PLANE__COUNT); |
| weston_plane_release(&plane->base); |
| weston_drm_format_array_fini(&plane->formats); |
| wl_list_remove(&plane->link); |
| free(plane); |
| } |
| |
| /** |
| * Initialise sprites (overlay planes) |
| * |
| * Walk the list of provided DRM planes, and add overlay planes. |
| * |
| * Call destroy_sprites to free these planes. |
| * |
| * @param b DRM compositor backend |
| */ |
| static void |
| create_sprites(struct drm_backend *b) |
| { |
| drmModePlaneRes *kplane_res; |
| drmModePlane *kplane; |
| struct drm_plane *drm_plane; |
| uint32_t i; |
| kplane_res = drmModeGetPlaneResources(b->drm.fd); |
| if (!kplane_res) { |
| weston_log("failed to get plane resources: %s\n", |
| strerror(errno)); |
| return; |
| } |
| for (i = kplane_res->count_planes; i >0; i--) { |
| kplane = drmModeGetPlane(b->drm.fd, kplane_res->planes[i-1]); |
| |
| if (!kplane) |
| continue; |
| |
| drm_plane = drm_plane_create(b, kplane); |
| drmModeFreePlane(kplane); |
| if (!drm_plane) |
| continue; |
| |
| if (drm_plane->type == WDRM_PLANE_TYPE_OVERLAY) |
| weston_compositor_stack_plane(b->compositor, |
| &drm_plane->base, |
| &b->compositor->primary_plane); |
| } |
| |
| drmModeFreePlaneResources(kplane_res); |
| } |
| |
| /** |
| * Clean up sprites (overlay planes) |
| * |
| * The counterpart to create_sprites. |
| * |
| * @param b DRM compositor backend |
| */ |
| static void |
| destroy_sprites(struct drm_backend *b) |
| { |
| struct drm_plane *plane, *next; |
| |
| wl_list_for_each_safe(plane, next, &b->plane_list, link) |
| drm_plane_destroy(plane); |
| } |
| |
| /* returns a value between 0-255 range, where higher is brighter */ |
| static uint32_t |
| drm_get_backlight(struct drm_head *head) |
| { |
| long brightness, max_brightness, norm; |
| |
| brightness = backlight_get_brightness(head->backlight); |
| max_brightness = backlight_get_max_brightness(head->backlight); |
| |
| /* convert it on a scale of 0 to 255 */ |
| norm = (brightness * 255)/(max_brightness); |
| |
| return (uint32_t) norm; |
| } |
| |
| /* values accepted are between 0-255 range */ |
| static void |
| drm_set_backlight(struct weston_output *output_base, uint32_t value) |
| { |
| struct drm_output *output = to_drm_output(output_base); |
| struct drm_head *head; |
| long max_brightness, new_brightness; |
| |
| if (value > 255) |
| return; |
| |
| wl_list_for_each(head, &output->base.head_list, base.output_link) { |
| if (!head->backlight) |
| return; |
| |
| max_brightness = backlight_get_max_brightness(head->backlight); |
| |
| /* get denormalized value */ |
| new_brightness = (value * max_brightness) / 255; |
| |
| backlight_set_brightness(head->backlight, new_brightness); |
| } |
| } |
| |
| static void |
| drm_output_init_backlight(struct drm_output *output) |
| { |
| struct weston_head *base; |
| struct drm_head *head; |
| |
| output->base.set_backlight = NULL; |
| |
| wl_list_for_each(base, &output->base.head_list, output_link) { |
| head = to_drm_head(base); |
| |
| if (head->backlight) { |
| weston_log("Initialized backlight for head '%s', device %s\n", |
| head->base.name, head->backlight->path); |
| |
| if (!output->base.set_backlight) { |
| output->base.set_backlight = drm_set_backlight; |
| output->base.backlight_current = |
| drm_get_backlight(head); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Power output on or off |
| * |
| * The DPMS/power level of an output is used to switch it on or off. This |
| * is DRM's hook for doing so, which can called either as part of repaint, |
| * or independently of the repaint loop. |
| * |
| * If we are called as part of repaint, we simply set the relevant bit in |
| * state and return. |
| * |
| * This function is never called on a virtual output. |
| */ |
| static void |
| drm_set_dpms(struct weston_output *output_base, enum dpms_enum level) |
| { |
| struct drm_output *output = to_drm_output(output_base); |
| struct drm_backend *b = to_drm_backend(output_base->compositor); |
| struct drm_pending_state *pending_state = b->repaint_data; |
| struct drm_output_state *state; |
| int ret; |
| |
| assert(!output->virtual); |
| |
| if (output->state_cur->dpms == level) |
| return; |
| |
| /* If we're being called during the repaint loop, then this is |
| * simple: discard any previously-generated state, and create a new |
| * state where we disable everything. When we come to flush, this |
| * will be applied. |
| * |
| * However, we need to be careful: we can be called whilst another |
| * output is in its repaint cycle (pending_state exists), but our |
| * output still has an incomplete state application outstanding. |
| * In that case, we need to wait until that completes. */ |
| if (pending_state && !output->state_last) { |
| /* The repaint loop already sets DPMS on; we don't need to |
| * explicitly set it on here, as it will already happen |
| * whilst applying the repaint state. */ |
| if (level == WESTON_DPMS_ON) |
| return; |
| |
| state = drm_pending_state_get_output(pending_state, output); |
| if (state) |
| drm_output_state_free(state); |
| state = drm_output_get_disable_state(pending_state, output); |
| return; |
| } |
| |
| /* As we throw everything away when disabling, just send us back through |
| * a repaint cycle. */ |
| if (level == WESTON_DPMS_ON) { |
| if (output->dpms_off_pending) |
| output->dpms_off_pending = false; |
| weston_output_schedule_repaint(output_base); |
| return; |
| } |
| |
| /* If we've already got a request in the pipeline, then we need to |
| * park our DPMS request until that request has quiesced. */ |
| if (output->state_last) { |
| output->dpms_off_pending = true; |
| return; |
| } |
| |
| pending_state = drm_pending_state_alloc(b); |
| drm_output_get_disable_state(pending_state, output); |
| ret = drm_pending_state_apply_sync(pending_state); |
| if (ret != 0) |
| weston_log("drm_set_dpms: couldn't disable output?\n"); |
| } |
| |
| static const char * const connector_type_names[] = { |
| [DRM_MODE_CONNECTOR_Unknown] = "Unknown", |
| [DRM_MODE_CONNECTOR_VGA] = "VGA", |
| [DRM_MODE_CONNECTOR_DVII] = "DVI-I", |
| [DRM_MODE_CONNECTOR_DVID] = "DVI-D", |
| [DRM_MODE_CONNECTOR_DVIA] = "DVI-A", |
| [DRM_MODE_CONNECTOR_Composite] = "Composite", |
| [DRM_MODE_CONNECTOR_SVIDEO] = "SVIDEO", |
| [DRM_MODE_CONNECTOR_LVDS] = "LVDS", |
| [DRM_MODE_CONNECTOR_Component] = "Component", |
| [DRM_MODE_CONNECTOR_9PinDIN] = "DIN", |
| [DRM_MODE_CONNECTOR_DisplayPort] = "DP", |
| [DRM_MODE_CONNECTOR_HDMIA] = "HDMI-A", |
| [DRM_MODE_CONNECTOR_HDMIB] = "HDMI-B", |
| [DRM_MODE_CONNECTOR_TV] = "TV", |
| [DRM_MODE_CONNECTOR_eDP] = "eDP", |
| [DRM_MODE_CONNECTOR_VIRTUAL] = "Virtual", |
| [DRM_MODE_CONNECTOR_DSI] = "DSI", |
| [DRM_MODE_CONNECTOR_DPI] = "DPI", |
| }; |
| |
| /** Create a name given a DRM connector |
| * |
| * \param con The DRM connector whose type and id form the name. |
| * \return A newly allocate string, or NULL on error. Must be free()'d |
| * after use. |
| * |
| * The name does not identify the DRM display device. |
| */ |
| static char * |
| make_connector_name(const drmModeConnector *con) |
| { |
| char *name; |
| const char *type_name = NULL; |
| int ret; |
| |
| if (con->connector_type < ARRAY_LENGTH(connector_type_names)) |
| type_name = connector_type_names[con->connector_type]; |
| |
| if (!type_name) |
| type_name = "UNNAMED"; |
| |
| ret = asprintf(&name, "%s-%d", type_name, con->connector_type_id); |
| if (ret < 0) |
| return NULL; |
| |
| return name; |
| } |
| |
| static int |
| drm_output_init_pixman(struct drm_output *output, struct drm_backend *b) |
| { |
| int w = output->base.current_mode->width; |
| int h = output->base.current_mode->height; |
| uint32_t format = output->gbm_format; |
| uint32_t pixman_format; |
| unsigned int i; |
| const struct pixman_renderer_output_options options = { |
| .use_shadow = b->use_pixman_shadow, |
| }; |
| |
| switch (format) { |
| case DRM_FORMAT_XRGB8888: |
| pixman_format = PIXMAN_x8r8g8b8; |
| break; |
| case DRM_FORMAT_RGB565: |
| pixman_format = PIXMAN_r5g6b5; |
| break; |
| default: |
| weston_log("Unsupported pixman format 0x%x\n", format); |
| return -1; |
| } |
| |
| /* FIXME error checking */ |
| for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { |
| output->dumb[i] = drm_fb_create_dumb(b, w, h, format); |
| if (!output->dumb[i]) |
| goto err; |
| |
| output->image[i] = |
| pixman_image_create_bits(pixman_format, w, h, |
| output->dumb[i]->map, |
| output->dumb[i]->strides[0]); |
| if (!output->image[i]) |
| goto err; |
| } |
| |
| if (pixman_renderer_output_create(&output->base, &options) < 0) |
| goto err; |
| |
| weston_log("DRM: output %s %s shadow framebuffer.\n", output->base.name, |
| b->use_pixman_shadow ? "uses" : "does not use"); |
| |
| pixman_region32_init_rect(&output->previous_damage, |
| output->base.x, output->base.y, output->base.width, output->base.height); |
| |
| return 0; |
| |
| err: |
| for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { |
| if (output->dumb[i]) |
| drm_fb_unref(output->dumb[i]); |
| if (output->image[i]) |
| pixman_image_unref(output->image[i]); |
| |
| output->dumb[i] = NULL; |
| output->image[i] = NULL; |
| } |
| |
| return -1; |
| } |
| |
| static void |
| drm_output_fini_pixman(struct drm_output *output) |
| { |
| struct drm_backend *b = to_drm_backend(output->base.compositor); |
| unsigned int i; |
| |
| /* Destroying the Pixman surface will destroy all our buffers, |
| * regardless of refcount. Ensure we destroy them here. */ |
| if (!b->shutting_down && |
| output->scanout_plane->state_cur->fb && |
| output->scanout_plane->state_cur->fb->type == BUFFER_PIXMAN_DUMB) { |
| drm_plane_reset_state(output->scanout_plane); |
| } |
| |
| pixman_renderer_output_destroy(&output->base); |
| pixman_region32_fini(&output->previous_damage); |
| |
| for (i = 0; i < ARRAY_LENGTH(output->dumb); i++) { |
| pixman_image_unref(output->image[i]); |
| drm_fb_unref(output->dumb[i]); |
| output->dumb[i] = NULL; |
| output->image[i] = NULL; |
| } |
| } |
| |
| static void |
| setup_output_seat_constraint(struct drm_backend *b, |
| struct weston_output *output, |
| const char *s) |
| { |
| if (strcmp(s, "") != 0) { |
| struct weston_pointer *pointer; |
| struct udev_seat *seat; |
| |
| seat = udev_seat_get_named(&b->input, s); |
| if (!seat) |
| return; |
| |
| seat->base.output = output; |
| |
| pointer = weston_seat_get_pointer(&seat->base); |
| if (pointer) |
| weston_pointer_clamp(pointer, |
| &pointer->x, |
| &pointer->y); |
| } |
| } |
| |
| static int |
| drm_output_attach_head(struct weston_output *output_base, |
| struct weston_head *head_base) |
| { |
| struct drm_backend *b = to_drm_backend(output_base->compositor); |
| |
| if (wl_list_length(&output_base->head_list) >= MAX_CLONED_CONNECTORS) |
| return -1; |
| |
| if (!output_base->enabled) |
| return 0; |
| |
| /* XXX: ensure the configuration will work. |
| * This is actually impossible without major infrastructure |
| * work. */ |
| |
| /* Need to go through modeset to add connectors. */ |
| /* XXX: Ideally we'd do this per-output, not globally. */ |
| /* XXX: Doing it globally, what guarantees another output's update |
| * will not clear the flag before this output is updated? |
| */ |
| b->state_invalid = true; |
| |
| weston_output_schedule_repaint(output_base); |
| |
| return 0; |
| } |
| |
| static void |
| drm_output_detach_head(struct weston_output *output_base, |
| struct weston_head *head_base) |
| { |
| struct drm_backend *b = to_drm_backend(output_base->compositor); |
| |
| if (!output_base->enabled) |
| return; |
| |
| /* Need to go through modeset to drop connectors that should no longer |
| * be driven. */ |
| /* XXX: Ideally we'd do this per-output, not globally. */ |
| b->state_invalid = true; |
| |
| weston_output_schedule_repaint(output_base); |
| } |
| |
| int |
| parse_gbm_format(const char *s, uint32_t default_value, uint32_t *gbm_format) |
| { |
| const struct pixel_format_info *pinfo; |
| |
| if (s == NULL) { |
| *gbm_format = default_value; |
| |
| return 0; |
| } |
| |
| pinfo = pixel_format_get_info_by_drm_name(s); |
| if (!pinfo) { |
| weston_log("fatal: unrecognized pixel format: %s\n", s); |
| |
| return -1; |
| } |
| |
| /* GBM formats and DRM formats are identical. */ |
| *gbm_format = pinfo->format; |
| |
| return 0; |
| } |
| |
| static int |
| drm_head_read_current_setup(struct drm_head *head, struct drm_backend *backend) |
| { |
| int drm_fd = backend->drm.fd; |
| drmModeConnector *conn = head->connector.conn; |
| drmModeEncoder *encoder; |
| drmModeCrtc *crtc; |
| |
| /* Get the current mode on the crtc that's currently driving |
| * this connector. */ |
| encoder = drmModeGetEncoder(drm_fd, conn->encoder_id); |
| if (encoder != NULL) { |
| head->inherited_crtc_id = encoder->crtc_id; |
| |
| crtc = drmModeGetCrtc(drm_fd, encoder->crtc_id); |
| drmModeFreeEncoder(encoder); |
| |
| if (crtc == NULL) |
| return -1; |
| if (crtc->mode_valid) |
| head->inherited_mode = crtc->mode; |
| drmModeFreeCrtc(crtc); |
| } |
| |
| return 0; |
| } |
| |
| static void |
| drm_output_set_gbm_format(struct weston_output *base, |
| const char *gbm_format) |
| { |
| struct drm_output *output = to_drm_output(base); |
| struct drm_backend *b = to_drm_backend(base->compositor); |
| |
| if (parse_gbm_format(gbm_format, b->gbm_format, &output->gbm_format) == -1) |
| output->gbm_format = b->gbm_format; |
| } |
| |
| static void |
| drm_output_set_seat(struct weston_output *base, |
| const char *seat) |
| { |
| struct drm_output *output = to_drm_output(base); |
| struct drm_backend *b = to_drm_backend(base->compositor); |
| |
| setup_output_seat_constraint(b, &output->base, |
| seat ? seat : ""); |
| } |
| |
| static int |
| drm_output_init_gamma_size(struct drm_output *output) |
| { |
| struct drm_backend *backend = to_drm_backend(output->base.compositor); |
| drmModeCrtc *crtc; |
| |
| assert(output->base.compositor); |
| assert(output->crtc); |
| crtc = drmModeGetCrtc(backend->drm.fd, output->crtc->crtc_id); |
| if (!crtc) |
| return -1; |
| |
| output->base.gamma_size = crtc->gamma_size; |
| |
| drmModeFreeCrtc(crtc); |
| |
| return 0; |
| } |
| |
| static uint32_t |
| drm_connector_get_possible_crtcs_mask(struct drm_connector *connector) |
| { |
| uint32_t possible_crtcs = 0; |
| drmModeConnector *conn = connector->conn; |
| drmModeEncoder *encoder; |
| int i; |
| |
| for (i = 0; i < conn->count_encoders; i++) { |
| encoder = drmModeGetEncoder(connector->backend->drm.fd, |
| conn->encoders[i]); |
| if (!encoder) |
| continue; |
| |
| possible_crtcs |= encoder->possible_crtcs; |
| drmModeFreeEncoder(encoder); |
| } |
| |
| return possible_crtcs; |
| } |
| |
| /** Pick a CRTC that might be able to drive all attached connectors |
| * |
| * @param output The output whose attached heads to include. |
| * @return CRTC object to pick, or NULL on failure or not found. |
| */ |
| static struct drm_crtc * |
| drm_output_pick_crtc(struct drm_output *output) |
| { |
| struct drm_backend *backend; |
| struct weston_head *base; |
| struct drm_head *head; |
| struct drm_crtc *crtc; |
| struct drm_crtc *best_crtc = NULL; |
| struct drm_crtc *fallback_crtc = NULL; |
| struct drm_crtc *existing_crtc[32]; |
| uint32_t possible_crtcs = 0xffffffff; |
| unsigned n = 0; |
| uint32_t crtc_id; |
| unsigned int i; |
| bool match; |
| |
| backend = to_drm_backend(output->base.compositor); |
| |
| /* This algorithm ignores drmModeEncoder::possible_clones restriction, |
| * because it is more often set wrong than not in the kernel. */ |
| |
| /* Accumulate a mask of possible crtcs and find existing routings. */ |
| wl_list_for_each(base, &output->base.head_list, output_link) { |
| head = to_drm_head(base); |
| |
| possible_crtcs &= |
| drm_connector_get_possible_crtcs_mask(&head->connector); |
| |
| crtc_id = head->inherited_crtc_id; |
| if (crtc_id > 0 && n < ARRAY_LENGTH(existing_crtc)) |
| existing_crtc[n++] = drm_crtc_find(backend, crtc_id); |
| } |
| |
| /* Find a crtc that could drive each connector individually at least, |
| * and prefer existing routings. */ |
| wl_list_for_each(crtc, &backend->crtc_list, link) { |
| |
| /* Could the crtc not drive each connector? */ |
| if (!(possible_crtcs & (1 << crtc->pipe))) |
| continue; |
| |
| /* Is the crtc already in use? */ |
| if (crtc->output) |
| continue; |
| |
| /* Try to preserve the existing CRTC -> connector routing; |
| * it makes initialisation faster, and also since we have a |
| * very dumb picking algorithm, may preserve a better |
| * choice. */ |
| for (i = 0; i < n; i++) { |
| if (existing_crtc[i] == crtc) |
| return crtc; |
| } |
| |
| /* Check if any other head had existing routing to this CRTC. |
| * If they did, this is not the best CRTC as it might be needed |
| * for another output we haven't enabled yet. */ |
| match = false; |
| wl_list_for_each(base, &backend->compositor->head_list, |
| compositor_link) { |
| head = to_drm_head(base); |
| |
| if (head->base.output == &output->base) |
| continue; |
| |
| if (weston_head_is_enabled(&head->base)) |
| continue; |
| |
| if (head->inherited_crtc_id == crtc->crtc_id) { |
| match = true; |
| break; |
| } |
| } |
| if (!match) |
| best_crtc = crtc; |
| |
| fallback_crtc = crtc; |
| } |
| |
| if (best_crtc) |
| return best_crtc; |
| |
| if (fallback_crtc) |
| return fallback_crtc; |
| |
| /* Likely possible_crtcs was empty due to asking for clones, |
| * but since the DRM documentation says the kernel lies, let's |
| * pick one crtc anyway. Trial and error is the only way to |
| * be sure if something doesn't work. */ |
| |
| /* First pick any existing assignment. */ |
| for (i = 0; i < n; i++) { |
| crtc = existing_crtc[i]; |
| if (!crtc->output) |
| return crtc; |
| } |
| |
| /* Otherwise pick any available crtc. */ |
| wl_list_for_each(crtc, &backend->crtc_list, link) { |
| if (!crtc->output) |
| return crtc; |
| } |
| |
| return NULL; |
| } |
| |
| /** Create an "empty" drm_crtc. It will only set its ID, pipe and props. After |
| * all, it adds the object to the DRM-backend CRTC list. |
| */ |
| static struct drm_crtc * |
| drm_crtc_create(struct drm_backend *b, uint32_t crtc_id, uint32_t pipe) |
| { |
| struct drm_crtc *crtc; |
| drmModeObjectPropertiesPtr props; |
| |
| props = drmModeObjectGetProperties(b->drm.fd, crtc_id, |
| DRM_MODE_OBJECT_CRTC); |
| if (!props) { |
| weston_log("failed to get CRTC properties\n"); |
| return NULL; |
| } |
| |
| crtc = zalloc(sizeof(*crtc)); |
| if (!crtc) |
| goto ret; |
| |
| drm_property_info_populate(b, crtc_props, crtc->props_crtc, |
| WDRM_CRTC__COUNT, props); |
| crtc->backend = b; |
| crtc->crtc_id = crtc_id; |
| crtc->pipe = pipe; |
| crtc->output = NULL; |
| |
| /* Add it to the last position of the DRM-backend CRTC list */ |
| wl_list_insert(b->crtc_list.prev, &crtc->link); |
| |
| ret: |
| drmModeFreeObjectProperties(props); |
| return crtc; |
| } |
| |
| /** Destroy a drm_crtc object that was created with drm_crtc_create(). It will |
| * also remove it from the DRM-backend CRTC list. |
| */ |
| static void |
| drm_crtc_destroy(struct drm_crtc *crtc) |
| { |
| /* TODO: address the issue below to be able to remove the comment |
| * from the assert. |
| * |
| * https://gitlab.freedesktop.org/wayland/weston/-/issues/421 |
| */ |
| |
| //assert(!crtc->output); |
| |
| wl_list_remove(&crtc->link); |
| drm_property_info_free(crtc->props_crtc, WDRM_CRTC__COUNT); |
| free(crtc); |
| } |
| |
| /** Find all CRTCs of the fd and create drm_crtc objects for them. |
| * |
| * The CRTCs are saved in a list of the drm_backend and will keep there until |
| * the fd gets closed. |
| * |
| * @param b The DRM-backend structure. |
| * @param resources The DRM resources, it is taken with drmModeGetResources |
| * @return 0 on success (at least one CRTC in the list), -1 on failure. |
| */ |
| static int |
| drm_backend_create_crtc_list(struct drm_backend *b, drmModeRes *resources) |
| { |
| struct drm_crtc *crtc, *crtc_tmp; |
| int i; |
| |
| /* Iterate through all CRTCs */ |
| for (i = 0; i < resources->count_crtcs; i++) { |
| |
| /* Let's create an object for the CRTC and add it to the list */ |
| crtc = drm_crtc_create(b, resources->crtcs[i], i); |
| if (!crtc) |
| goto err; |
| } |
| |
| return 0; |
| |
| err: |
| wl_list_for_each_safe(crtc, crtc_tmp, &b->crtc_list, link) |
| drm_crtc_destroy(crtc); |
| return -1; |
| } |
| |
| |
| /** Populates scanout and cursor planes for the output. Also sets the topology |
| * of the planes by adding them to the plane stacking list. |
| */ |
| static int |
| drm_output_init_planes(struct drm_output *output) |
| { |
| struct drm_backend *b = to_drm_backend(output->base.compositor); |
| |
| output->scanout_plane = |
| drm_output_find_special_plane(b, output, |
| WDRM_PLANE_TYPE_PRIMARY); |
| if (!output->scanout_plane) { |
| weston_log("Failed to find primary plane for output %s\n", |
| output->base.name); |
| return -1; |
| } |
| |
| weston_compositor_stack_plane(b->compositor, |
| &output->scanout_plane->base, |
| &b->compositor->primary_plane); |
| |
| /* Failing to find a cursor plane is not fatal, as we'll fall back |
| * to software cursor. */ |
| output->cursor_plane = |
| drm_output_find_special_plane(b, output, |
| WDRM_PLANE_TYPE_CURSOR); |
| |
| if (output->cursor_plane) |
| weston_compositor_stack_plane(b->compositor, |
| &output->cursor_plane->base, |
| NULL); |
| else |
| b->cursors_are_broken = true; |
| |
| return 0; |
| } |
| |
| /** The opposite of drm_output_init_planes(). First of all it removes the planes |
| * from the plane stacking list. After all it sets the planes of the output as NULL. |
| */ |
| static void |
| drm_output_deinit_planes(struct drm_output *output) |
| { |
| struct drm_backend *b = to_drm_backend(output->base.compositor); |
| |
| /* If the compositor is already shutting down, the planes have already |
| * been destroyed. */ |
| if (!b->shutting_down) { |
| wl_list_remove(&output->scanout_plane->base.link); |
| wl_list_init(&output->scanout_plane->base.link); |
| |
| if (output->cursor_plane) { |
| wl_list_remove(&output->cursor_plane->base.link); |
| wl_list_init(&output->cursor_plane->base.link); |
| /* Turn off hardware cursor */ |
| drmModeSetCursor(b->drm.fd, output->crtc->crtc_id, 0, 0, 0); |
| } |
| |
| /* With universal planes, the planes are allocated at startup, |
| * freed at shutdown, and live on the plane list in between. |
| * We want the planes to continue to exist and be freed up |
| * for other outputs. |
| */ |
| if (output->cursor_plane) |
| drm_plane_reset_state(output->cursor_plane); |
| if (output->scanout_plane) |
| drm_plane_reset_state(output->scanout_plane); |
| } |
| |
| output->cursor_plane = NULL; |
| output->scanout_plane = NULL; |
| } |
| |
| static struct weston_drm_format_array * |
| get_scanout_formats(struct drm_backend *b) |
| { |
| struct weston_compositor *ec = b->compositor; |
| const struct weston_drm_format_array *renderer_formats; |
| struct weston_drm_format_array *scanout_formats, union_planes_formats; |
| struct drm_plane *plane; |
| int ret; |
| |
| /* If we got here it means that dma-buf feedback is supported and that |
| * the renderer has formats/modifiers to expose. */ |
| assert(ec->renderer->get_supported_formats != NULL); |
| renderer_formats = ec->renderer->get_supported_formats(ec); |
| |
| scanout_formats = zalloc(sizeof(*scanout_formats)); |
| if (!scanout_formats) { |
| weston_log("%s: out of memory\n", __func__); |
| return NULL; |
| } |
| |
| weston_drm_format_array_init(&union_planes_formats); |
| weston_drm_format_array_init(scanout_formats); |
| |
| /* Compute the union of the format/modifiers of the KMS planes */ |
| wl_list_for_each(plane, &b->plane_list, link) { |
| /* The scanout formats are used by the dma-buf feedback. But for |
| * now cursor planes do not support dma-buf buffers, only wl_shm |
| * buffers. So we skip cursor planes here. */ |
| if (plane->type == WDRM_PLANE_TYPE_CURSOR) |
| continue; |
| |
| ret = weston_drm_format_array_join(&union_planes_formats, |
| &plane->formats); |
| if (ret < 0) |
| goto err; |
| } |
| |
| /* Compute the intersection between the union of format/modifiers of KMS |
| * planes and the formats supported by the renderer */ |
| ret = weston_drm_format_array_replace(scanout_formats, |
| renderer_formats); |
| if (ret < 0) |
| goto err; |
| |
| ret = weston_drm_format_array_intersect(scanout_formats, |
| &union_planes_formats); |
| if (ret < 0) |
| goto err; |
| |
| weston_drm_format_array_fini(&union_planes_formats); |
| |
| return scanout_formats; |
| |
| err: |
| weston_drm_format_array_fini(&union_planes_formats); |
| weston_drm_format_array_fini(scanout_formats); |
| free(scanout_formats); |
| return NULL; |
| } |
| |
| /** Pick a CRTC and reserve it for the output. |
| * |
| * On failure, the output remains without a CRTC. |
| * |
| * @param output The output with no CRTC associated. |
| * @return 0 on success, -1 on failure. |
| */ |
| static int |
| drm_output_attach_crtc(struct drm_output *output) |
| { |
| output->crtc = drm_output_pick_crtc(output); |
| if (!output->crtc) { |
| weston_log("Output '%s': No available CRTCs.\n", |
| output->base.name); |
| return -1; |
| } |
| |
| /* Reserve the CRTC for the output */ |
| output->crtc->output = output; |
| |
| return 0; |
| } |
| |
| /** Release reservation of the CRTC. |
| * |
| * Make the CRTC free to be reserved and used by another output. |
| * |
| * @param output The output that will release its CRTC. |
| */ |
| static void |
| drm_output_detach_crtc(struct drm_output *output) |
| { |
| struct drm_backend *b = output->backend; |
| struct drm_crtc *crtc = output->crtc; |
| |
| crtc->output = NULL; |
| output->crtc = NULL; |
| |
| /* Force resetting unused CRTCs */ |
| b->state_invalid = true; |
| } |
| |
| static int |
| drm_output_enable(struct weston_output *base) |
| { |
| struct drm_output *output = to_drm_output(base); |
| struct drm_backend *b = to_drm_backend(base->compositor); |
| int ret; |
| |
| assert(!output->virtual); |
| |
| ret = drm_output_attach_crtc(output); |
| if (ret < 0) |
| return -1; |
| |
| ret = drm_output_init_planes(output); |
| if (ret < 0) |
| goto err_crtc; |
| |
| if (drm_output_init_gamma_size(output) < 0) |
| goto err_planes; |
| |
| if (b->pageflip_timeout) |
| drm_output_pageflip_timer_create(output); |
| |
| if (b->use_pixman) { |
| if (drm_output_init_pixman(output, b) < 0) { |
| weston_log("Failed to init output pixman state\n"); |
| goto err_planes; |
| } |
| } else if (drm_output_init_egl(output, b) < 0) { |
| weston_log("Failed to init output gl state\n"); |
| goto err_planes; |
| } |
| |
| drm_enable_aml_output_config(output, base, b); |
| drm_output_init_backlight(output); |
| |
| output->base.start_repaint_loop = drm_output_start_repaint_loop; |
| output->base.repaint = drm_output_repaint; |
| output->base.assign_planes = drm_assign_planes; |
| output->base.set_dpms = drm_set_dpms; |
| output->base.switch_mode = drm_output_switch_mode; |
| output->base.set_gamma = drm_output_set_gamma; |
| |
| weston_log("Output %s (crtc %d) video modes:\n", |
| output->base.name, output->crtc->crtc_id); |
| drm_output_print_modes(output); |
| |
| return 0; |
| |
| err_planes: |
| drm_output_deinit_planes(output); |
| err_crtc: |
| drm_output_detach_crtc(output); |
| return -1; |
| } |
| |
| static void |
| drm_output_deinit(struct weston_output *base) |
| { |
| struct drm_output *output = to_drm_output(base); |
| struct drm_backend *b = to_drm_backend(base->compositor); |
| |
| if (b->use_pixman) |
| drm_output_fini_pixman(output); |
| else |
| drm_output_fini_egl(output); |
| |
| drm_output_deinit_planes(output); |
| drm_output_detach_crtc(output); |
| } |
| |
| static void |
| drm_head_destroy(struct drm_head *head); |
| |
| static void |
| drm_output_destroy(struct weston_output *base) |
| { |
| struct drm_output *output = to_drm_output(base); |
| struct drm_backend *b = to_drm_backend(base->compositor); |
| |
| assert(!output->virtual); |
| |
| if (output->page_flip_pending || output->atomic_complete_pending) { |
| output->destroy_pending = true; |
| weston_log("destroy output while page flip pending\n"); |
| return; |
| } |
| |
| drm_output_set_cursor_view(output, NULL); |
| |
| if (output->base.enabled) |
| drm_output_deinit(&output->base); |
| |
| drm_mode_list_destroy(b, &output->base.mode_list); |
| |
| if (output->pageflip_timer) |
| wl_event_source_remove(output->pageflip_timer); |
| |
| weston_output_release(&output->base); |
| |
| assert(!output->state_last); |
| drm_output_state_free(output->state_cur); |
| |
| free(output); |
| } |
| |
| static int |
| drm_output_disable(struct weston_output *base) |
| { |
| struct drm_output *output = to_drm_output(base); |
| |
| assert(!output->virtual); |
| drm_output_disable_aml(output, base); |
| |
| if (output->base.enabled) |
| drm_output_deinit(&output->base); |
| |
| output->disable_pending = false; |
| |
| return 0; |
| } |
| |
| /* |
| * This function converts the protection status from drm values to |
| * weston_hdcp_protection status. The drm values as read from the connector |
| * properties "Content Protection" and "HDCP Content Type" need to be converted |
| * to appropriate weston values, that can be sent to a client application. |
| */ |
| static int |
| get_weston_protection_from_drm(enum wdrm_content_protection_state protection, |
| enum wdrm_hdcp_content_type type, |
| enum weston_hdcp_protection *weston_protection) |
| |
| { |
| if (protection >= WDRM_CONTENT_PROTECTION__COUNT) |
| return -1; |
| if (protection == WDRM_CONTENT_PROTECTION_DESIRED || |
| protection == WDRM_CONTENT_PROTECTION_UNDESIRED) { |
| *weston_protection = WESTON_HDCP_DISABLE; |
| return 0; |
| } |
| if (type >= WDRM_HDCP_CONTENT_TYPE__COUNT) |
| return -1; |
| if (type == WDRM_HDCP_CONTENT_TYPE0) { |
| *weston_protection = WESTON_HDCP_ENABLE_TYPE_0; |
| return 0; |
| } |
| if (type == WDRM_HDCP_CONTENT_TYPE1) { |
| *weston_protection = WESTON_HDCP_ENABLE_TYPE_1; |
| return 0; |
| } |
| return -1; |
| } |
| |
| /** |
| * Get current content-protection status for a given head. |
| * |
| * @param head drm_head, whose protection is to be retrieved |
| * @return protection status in case of success, -1 otherwise |
| */ |
| static enum weston_hdcp_protection |
| drm_head_get_current_protection(struct drm_head *head) |
| { |
| drmModeObjectProperties *props = head->connector.props_drm; |
| struct drm_property_info *info; |
| enum wdrm_content_protection_state protection; |
| enum wdrm_hdcp_content_type type; |
| enum weston_hdcp_protection weston_hdcp = WESTON_HDCP_DISABLE; |
| |
| info = &head->connector.props[WDRM_CONNECTOR_CONTENT_PROTECTION]; |
| protection = drm_property_get_value(info, props, |
| WDRM_CONTENT_PROTECTION__COUNT); |
| |
| if (protection == WDRM_CONTENT_PROTECTION__COUNT) |
| return WESTON_HDCP_DISABLE; |
| |
| info = &head->connector.props[WDRM_CONNECTOR_HDCP_CONTENT_TYPE]; |
| type = drm_property_get_value(info, props, |
| WDRM_HDCP_CONTENT_TYPE__COUNT); |
| |
| /* |
| * In case of platforms supporting HDCP1.4, only property |
| * 'Content Protection' is exposed and not the 'HDCP Content Type' |
| * for such cases HDCP Type 0 should be considered as the content-type. |
| */ |
| |
| if (type == WDRM_HDCP_CONTENT_TYPE__COUNT) |
| type = WDRM_HDCP_CONTENT_TYPE0; |
| |
| if (get_weston_protection_from_drm(protection, type, |
| &weston_hdcp) == -1) { |
| weston_log("Invalid drm protection:%d type:%d, for head:%s connector-id:%d\n", |
| protection, type, head->base.name, |
| head->connector.connector_id); |
| return WESTON_HDCP_DISABLE; |
| } |
| |
| return weston_hdcp; |
| } |
| |
| static int |
| drm_connector_update_properties(struct drm_connector *connector) |
| { |
| drmModeObjectProperties *props; |
| |
| props = drmModeObjectGetProperties(connector->backend->drm.fd, |
| connector->connector_id, |
| DRM_MODE_OBJECT_CONNECTOR); |
| if (!props) { |
| weston_log("Error: failed to get connector properties\n"); |
| return -1; |
| } |
| |
| if (connector->props_drm) |
| drmModeFreeObjectProperties(connector->props_drm); |
| connector->props_drm = props; |
| |
| return 0; |
| } |
| |
| /** Replace connector data and monitor information |
| * |
| * @param connector The drm_connector object to be updated. |
| * @param conn The connector data to be owned by the drm_connector, must match |
| * the current drm_connector ID. |
| * @return 0 on success, -1 on failure. |
| * |
| * Takes ownership of @c connector on success, not on failure. |
| */ |
| static int |
| drm_connector_assign_connector_info(struct drm_connector *connector, |
| drmModeConnector *conn) |
| { |
| assert(connector->conn != conn); |
| assert(connector->connector_id == conn->connector_id); |
| |
| if (drm_connector_update_properties(connector) < 0) |
| return -1; |
| |
| drm_update_helper_connector(connector, conn); |
| |
| if (connector->conn) |
| drmModeFreeConnector(connector->conn); |
| connector->conn = conn; |
| |
| drm_property_info_free(connector->props, WDRM_CONNECTOR__COUNT); |
| drm_property_info_populate(connector->backend, connector_props, |
| connector->props, |
| WDRM_CONNECTOR__COUNT, connector->props_drm); |
| return 0; |
| } |
| |
| static void |
| drm_connector_init(struct drm_backend *b, struct drm_connector *connector, |
| uint32_t connector_id) |
| { |
| connector->backend = b; |
| connector->connector_id = connector_id; |
| connector->conn = NULL; |
| connector->props_drm = NULL; |
| } |
| |
| static void |
| drm_connector_fini(struct drm_connector *connector) |
| { |
| drmModeFreeConnector(connector->conn); |
| drmModeFreeObjectProperties(connector->props_drm); |
| drm_property_info_free(connector->props, WDRM_CONNECTOR__COUNT); |
| } |
| |
| static void |
| drm_head_log_info(struct drm_head *head, const char *msg) |
| { |
| if (head->base.connected) { |
| weston_log("DRM: head '%s' %s, connector %d is connected, " |
| "EDID make '%s', model '%s', serial '%s'\n", |
| head->base.name, msg, head->connector.connector_id, |
| head->base.make, head->base.model, |
| head->base.serial_number ?: ""); |
| } else { |
| weston_log("DRM: head '%s' %s, connector %d is disconnected.\n", |
| head->base.name, msg, head->connector.connector_id); |
| } |
| } |
| |
| /** Update connector and monitor information |
| * |
| * @param head The head to update. |
| * @param conn The DRM connector object. |
| * @returns 0 on success, -1 on failure. |
| * |
| * Updates monitor information and connection status. This may schedule a |
| * heads changed call to the user. |
| * |
| * Takes ownership of @c connector on success, not on failure. |
| */ |
| static int |
| drm_head_update_info(struct drm_head *head, drmModeConnector *conn) |
| { |
| int ret; |
| |
| ret = drm_connector_assign_connector_info(&head->connector, conn); |
| |
| update_head_from_connector(head); |
| weston_head_set_content_protection_status(&head->base, |
| drm_head_get_current_protection(head)); |
| |
| #ifdef ENABLE_MODE_POLICY |
| if (head->base.connected) |
| init_mode_policy_without_mode(&head->base, |
| head->backend->drm.fd, head->connector.conn); |
| if (head->base.device_changed) |
| mode_policy_set_hotplug(head->base.connected ? |
| AML_WESTON_HOTPLUG_PLUG : AML_WESTON_HOTPLUG_UNPLUG, false); |
| #endif |
| |
| if (head->base.device_changed) |
| drm_head_log_info(head, "updated"); |
| |
| return ret; |
| } |
| |
| /** Update writeback connector |
| * |
| * @param writeback The writeback to update. |
| * @param conn DRM connector object. |
| * @returns 0 on success, -1 on failure. |
| * |
| * Takes ownership of @c connector on success, not on failure. |
| */ |
| static int |
| drm_writeback_update_info(struct drm_writeback *writeback, drmModeConnector *conn) |
| { |
| int ret; |
| |
| ret = drm_connector_assign_connector_info(&writeback->connector, conn); |
| |
| return ret; |
| } |
| |
| /** |
| * Create a Weston head for a connector |
| * |
| * Given a DRM connector, create a matching drm_head structure and add it |
| * to Weston's head list. |
| * |
| * @param backend Weston backend structure |
| * @param conn DRM connector object |
| * @param drm_device udev device pointer |
| * @returns 0 on success, -1 on failure |
| * |
| * Takes ownership of @c connector on success, not on failure. |
| */ |
| static int |
| drm_head_create(struct drm_backend *backend, drmModeConnector *conn, |
| struct udev_device *drm_device) |
| { |
| struct drm_head *head; |
| char *name; |
| int ret; |
| |
| head = zalloc(sizeof *head); |
| if (!head) |
| return -1; |
| |
| head->backend = backend; |
| |
| drm_connector_init(backend, &head->connector, conn->connector_id); |
| |
| name = make_connector_name(conn); |
| if (!name) |
| goto err; |
| |
| weston_head_init(&head->base, name); |
| free(name); |
| |
| ret = drm_head_update_info(head, conn); |
| if (ret < 0) |
| goto err_update; |
| |
| head->backlight = backlight_init(drm_device, conn->connector_type); |
| |
| if (conn->connector_type == DRM_MODE_CONNECTOR_LVDS || |
| conn->connector_type == DRM_MODE_CONNECTOR_eDP) |
| weston_head_set_internal(&head->base); |
| |
| if (drm_head_read_current_setup(head, backend) < 0) { |
| weston_log("Failed to retrieve current mode from connector %d.\n", |
| head->connector.connector_id); |
| /* Not fatal. */ |
| } |
| |
| weston_compositor_add_head(backend->compositor, &head->base); |
| drm_head_log_info(head, "found"); |
| drm_append_helper_connector(head); |
| |
| return 0; |
| |
| err_update: |
| weston_head_release(&head->base); |
| err: |
| drm_connector_fini(&head->connector); |
| free(head); |
| return -1; |
| } |
| |
| static void |
| drm_head_destroy(struct drm_head *head) |
| { |
| drm_delete_helper_connector(head); |
| weston_head_release(&head->base); |
| |
| drm_connector_fini(&head->connector); |
| |
| if (head->backlight) |
| backlight_destroy(head->backlight); |
| |
| free(head); |
| } |
| |
| /** |
| * Create a Weston output structure |
| * |
| * Create an "empty" drm_output. This is the implementation of |
| * weston_backend::create_output. |
| * |
| * Creating an output is usually followed by drm_output_attach_head() |
| * and drm_output_enable() to make use of it. |
| * |
| * @param compositor The compositor instance. |
| * @param name Name for the new output. |
| * @returns The output, or NULL on failure. |
| */ |
| static struct weston_output * |
| drm_output_create(struct weston_compositor *compositor, const char *name) |
| { |
| struct drm_backend *b = to_drm_backend(compositor); |
| struct drm_output *output; |
| |
| output = zalloc(sizeof *output); |
| if (output == NULL) |
| return NULL; |
| |
| output->backend = b; |
| output->crtc = NULL; |
| |
| #ifdef BUILD_DRM_GBM |
| output->gbm_bo_flags = GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING; |
| #endif |
| |
| weston_output_init(&output->base, compositor, name); |
| |
| output->base.enable = drm_output_enable; |
| output->base.destroy = drm_output_destroy; |
| output->base.disable = drm_output_disable; |
| output->base.attach_head = drm_output_attach_head; |
| output->base.detach_head = drm_output_detach_head; |
| |
| output->destroy_pending = false; |
| output->disable_pending = false; |
| |
| output->state_cur = drm_output_state_alloc(output, NULL); |
| |
| drm_init_aml_output_config(compositor, output); |
| weston_compositor_add_pending_output(&output->base, b->compositor); |
| |
| return &output->base; |
| } |
| |
| /** |
| * Create a Weston writeback for a writeback connector |
| * |
| * Given a DRM connector of type writeback, create a matching drm_writeback |
| * structure and add it to Weston's writeback list. |
| * |
| * @param b Weston backend structure |
| * @param conn DRM connector object of type writeback |
| * @returns 0 on success, -1 on failure |
| * |
| * Takes ownership of @c connector on success, not on failure. |
| */ |
| static int |
| drm_writeback_create(struct drm_backend *b, drmModeConnector *conn) |
| { |
| struct drm_writeback *writeback; |
| int ret; |
| |
| writeback = zalloc(sizeof *writeback); |
| assert(writeback); |
| |
| writeback->backend = b; |
| |
| drm_connector_init(b, &writeback->connector, conn->connector_id); |
| |
| ret = drm_writeback_update_info(writeback, conn); |
| if (ret < 0) |
| goto err; |
| |
| wl_list_insert(&b->writeback_connector_list, &writeback->link); |
| return 0; |
| |
| err: |
| drm_connector_fini(&writeback->connector); |
| free(writeback); |
| return -1; |
| } |
| |
| static void |
| drm_writeback_destroy(struct drm_writeback *writeback) |
| { |
| drm_connector_fini(&writeback->connector); |
| wl_list_remove(&writeback->link); |
| |
| free(writeback); |
| } |
| |
| /** Given the DRM connector object of a connector, create drm_head or |
| * drm_writeback object (depending on the type of connector) for it. |
| * |
| * The object is then added to the DRM-backend list of heads or writebacks. |
| * |
| * @param b The DRM-backend structure |
| * @param conn The DRM connector object |
| * @param drm_device udev device pointer |
| * @return 0 on success, -1 on failure |
| */ |
| static int |
| drm_backend_add_connector(struct drm_backend *b, drmModeConnector *conn, |
| struct udev_device *drm_device) |
| { |
| int ret; |
| |
| if (conn->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) { |
| ret = drm_writeback_create(b, conn); |
| if (ret < 0) |
| weston_log("DRM: failed to create writeback for connector %d.\n", |
| conn->connector_id); |
| } else { |
| ret = drm_head_create(b, conn, drm_device); |
| if (ret < 0) |
| weston_log("DRM: failed to create head for connector %d.\n", |
| conn->connector_id); |
| } |
| |
| return ret; |
| } |
| |
| /** Find all connectors of the fd and create drm_head or drm_writeback objects |
| * (depending on the type of connector they are) for each of them |
| * |
| * These objects are added to the DRM-backend lists of heads and writebacks. |
| * |
| * @param b The DRM-backend structure |
| * @param drm_device udev device pointer |
| * @param resources The DRM resources, it is taken with drmModeGetResources |
| * @return 0 on success, -1 on failure |
| */ |
| static int |
| drm_backend_discover_connectors(struct drm_backend *b, struct udev_device *drm_device, |
| drmModeRes *resources) |
| { |
| drmModeConnector *conn; |
| int i, ret; |
| |
| b->min_width = resources->min_width; |
| b->max_width = resources->max_width; |
| b->min_height = resources->min_height; |
| b->max_height = resources->max_height; |
| |
| for (i = 0; i < resources->count_connectors; i++) { |
| uint32_t connector_id = resources->connectors[i]; |
| |
| conn = drmModeGetConnector(b->drm.fd, connector_id); |
| if (!conn) |
| continue; |
| |
| ret = drm_backend_add_connector(b, conn, drm_device); |
| if (ret < 0) |
| drmModeFreeConnector(conn); |
| } |
| |
| return 0; |
| } |
| |
| static bool |
| resources_has_connector(drmModeRes *resources, uint32_t connector_id) |
| { |
| for (int i = 0; i < resources->count_connectors; i++) { |
| if (resources->connectors[i] == connector_id) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| static void |
| drm_backend_update_connectors(struct drm_backend *b, struct udev_device *drm_device) |
| { |
| drmModeRes *resources; |
| drmModeConnector *conn; |
| struct weston_head *base, *base_next; |
| struct drm_head *head; |
| struct drm_writeback *writeback, *writeback_next; |
| uint32_t connector_id; |
| int i, ret; |
| |
| resources = drmModeGetResources(b->drm.fd); |
| if (!resources) { |
| weston_log("drmModeGetResources failed\n"); |
| return; |
| } |
| |
| /* collect new connectors that have appeared, e.g. MST */ |
| for (i = 0; i < resources->count_connectors; i++) { |
| connector_id = resources->connectors[i]; |
| |
| conn = drmModeGetConnector(b->drm.fd, connector_id); |
| if (!conn) |
| continue; |
| |
| head = drm_head_find_by_connector(b, connector_id); |
| writeback = drm_writeback_find_by_connector(b, connector_id); |
| |
| /* Connector can't be owned by both a head and a writeback, so |
| * one of the searches must fail. */ |
| assert(head == NULL || writeback == NULL); |
| |
| if (head) |
| ret = drm_head_update_info(head, conn); |
| else if (writeback) |
| ret = drm_writeback_update_info(writeback, conn); |
| else |
| ret = drm_backend_add_connector(b, conn, drm_device); |
| |
| if (ret < 0) |
| drmModeFreeConnector(conn); |
| } |
| |
| /* Destroy head objects of connectors (except writeback connectors) that |
| * have disappeared. */ |
| wl_list_for_each_safe(base, base_next, |
| &b->compositor->head_list, compositor_link) { |
| head = to_drm_head(base); |
| connector_id = head->connector.connector_id; |
| |
| if (resources_has_connector(resources, connector_id)) |
| continue; |
| |
| weston_log("DRM: head '%s' (connector %d) disappeared.\n", |
| head->base.name, connector_id); |
| drm_head_destroy(head); |
| } |
| |
| /* Destroy writeback objects of writeback connectors that have |
| * disappeared. */ |
| wl_list_for_each_safe(writeback, writeback_next, |
| &b->writeback_connector_list, link) { |
| connector_id = writeback->connector.connector_id; |
| |
| if (resources_has_connector(resources, connector_id)) |
| continue; |
| |
| weston_log("DRM: writeback connector (connector %d) disappeared.\n", |
| connector_id); |
| drm_writeback_destroy(writeback); |
| } |
| |
| drmModeFreeResources(resources); |
| } |
| |
| static enum wdrm_connector_property |
| drm_connector_find_property_by_id(struct drm_connector *connector, |
| uint32_t property_id) |
| { |
| int i; |
| enum wdrm_connector_property prop = WDRM_CONNECTOR__COUNT; |
| |
| if (!connector || !property_id) |
| return WDRM_CONNECTOR__COUNT; |
| |
| for (i = 0; i < WDRM_CONNECTOR__COUNT; i++) |
| if (connector->props[i].prop_id == property_id) { |
| prop = (enum wdrm_connector_property) i; |
| break; |
| } |
| return prop; |
| } |
| |
| static void |
| drm_backend_update_conn_props(struct drm_backend *b, |
| uint32_t connector_id, |
| uint32_t property_id) |
| { |
| struct drm_head *head; |
| enum wdrm_connector_property conn_prop; |
| |
| head = drm_head_find_by_connector(b, connector_id); |
| if (!head) { |
| weston_log("DRM: failed to find head for connector id: %d.\n", |
| connector_id); |
| return; |
| } |
| |
| conn_prop = drm_connector_find_property_by_id(&head->connector, property_id); |
| if (conn_prop >= WDRM_CONNECTOR__COUNT) |
| return; |
| |
| if (drm_connector_update_properties(&head->connector) < 0) |
| return; |
| |
| if (conn_prop == WDRM_CONNECTOR_CONTENT_PROTECTION) { |
| weston_head_set_content_protection_status(&head->base, |
| drm_head_get_current_protection(head)); |
| } |
| } |
| |
| static int |
| udev_event_is_hotplug(struct drm_backend *b, struct udev_device *device) |
| { |
| const char *sysnum; |
| const char *val; |
| |
| sysnum = udev_device_get_sysnum(device); |
| if (!sysnum || atoi(sysnum) != b->drm.id) |
| return 0; |
| |
| val = udev_device_get_property_value(device, "HOTPLUG"); |
| if (!val) |
| return 0; |
| |
| return strcmp(val, "1") == 0; |
| } |
| |
| static int |
| udev_event_is_conn_prop_change(struct drm_backend *b, |
| struct udev_device *device, |
| uint32_t *connector_id, |
| uint32_t *property_id) |
| |
| { |
| const char *val; |
| int id; |
| |
| val = udev_device_get_property_value(device, "CONNECTOR"); |
| if (!val || !safe_strtoint(val, &id)) |
| return 0; |
| else |
| *connector_id = id; |
| |
| val = udev_device_get_property_value(device, "PROPERTY"); |
| if (!val || !safe_strtoint(val, &id)) |
| return 0; |
| else |
| *property_id = id; |
| |
| return 1; |
| } |
| |
| static int |
| udev_drm_event(int fd, uint32_t mask, void *data) |
| { |
| struct drm_backend *b = data; |
| struct udev_device *event; |
| uint32_t conn_id, prop_id; |
| |
| event = udev_monitor_receive_device(b->udev_monitor); |
| |
| if (udev_event_is_hotplug(b, event) && b->display_enable) { |
| #ifdef ENABLE_MODE_POLICY |
| mode_policy_set_hotplug(AML_WESTON_HOTPLUG_PLUG | AML_WESTON_HOTPLUG_UNPLUG, true); |
| #endif |
| if (udev_event_is_conn_prop_change(b, event, &conn_id, &prop_id)) |
| drm_backend_update_conn_props(b, conn_id, prop_id); |
| else |
| drm_backend_update_connectors(b, event); |
| } |
| |
| #ifdef BUILD_AML_TV |
| drm_backend_afr_update(b, udev_event_frame_rate_hint(event), |
| udev_event_is_vdin_event(event)); |
| #endif |
| |
| udev_device_unref(event); |
| |
| return 1; |
| } |
| |
| static void |
| drm_destroy(struct weston_compositor *ec) |
| { |
| struct drm_backend *b = to_drm_backend(ec); |
| struct weston_head *base, *next; |
| struct drm_crtc *crtc, *crtc_tmp; |
| struct drm_writeback *writeback, *writeback_tmp; |
| |
| #ifdef ENABLE_DRM_HELP |
| stop_help_worker(); |
| #endif |
| |
| udev_input_destroy(&b->input); |
| |
| wl_event_source_remove(b->udev_drm_source); |
| wl_event_source_remove(b->drm_source); |
| |
| b->shutting_down = true; |
| |
| destroy_sprites(b); |
| |
| weston_log_scope_destroy(b->debug); |
| b->debug = NULL; |
| weston_compositor_shutdown(ec); |
| |
| wl_list_for_each_safe(crtc, crtc_tmp, &b->crtc_list, link) |
| drm_crtc_destroy(crtc); |
| |
| wl_list_for_each_safe(base, next, &ec->head_list, compositor_link) |
| drm_head_destroy(to_drm_head(base)); |
| |
| wl_list_for_each_safe(writeback, writeback_tmp, |
| &b->writeback_connector_list, link) |
| drm_writeback_destroy(writeback); |
| |
| #ifdef BUILD_DRM_GBM |
| if (b->gbm) |
| gbm_device_destroy(b->gbm); |
| #endif |
| |
| udev_monitor_unref(b->udev_monitor); |
| udev_unref(b->udev); |
| |
| weston_launcher_close(ec->launcher, b->drm.fd); |
| weston_launcher_destroy(ec->launcher); |
| |
| free(b->drm.filename); |
| free(b); |
| } |
| |
| static void |
| session_notify(struct wl_listener *listener, void *data) |
| { |
| struct weston_compositor *compositor = data; |
| struct drm_backend *b = to_drm_backend(compositor); |
| struct drm_plane *plane; |
| struct drm_output *output; |
| struct drm_crtc *crtc; |
| struct weston_mode *wst_mode; |
| |
| if (compositor->session_active) { |
| weston_log("activating session\n"); |
| |
| mode_policy_resume(); |
| wst_mode = mode_policy_choose_mode(NULL); |
| wl_list_for_each(output, &compositor->output_list, base.link) |
| weston_output_mode_set_native(&output->base, |
| wst_mode, output->base.current_scale); |
| |
| weston_compositor_wake(compositor); |
| weston_compositor_damage_all(compositor); |
| b->state_invalid = true; |
| b->allow_modeset = true; |
| b->display_enable = 1; |
| udev_input_enable(&b->input); |
| weston_compositor_resume_focus(compositor); |
| } else { |
| weston_log("deactivating session\n"); |
| drm_change_to_dummy_mode(b->compositor); |
| udev_input_disable(&b->input); |
| |
| weston_compositor_offscreen(compositor); |
| |
| /* If we have a repaint scheduled (either from a |
| * pending pageflip or the idle handler), make sure we |
| * cancel that so we don't try to pageflip when we're |
| * vt switched away. The OFFSCREEN state will prevent |
| * further attempts at repainting. When we switch |
| * back, we schedule a repaint, which will process |
| * pending frame callbacks. */ |
| |
| wl_list_for_each(output, &compositor->output_list, base.link) { |
| crtc = output->crtc; |
| output->base.repaint_needed = false; |
| if (output->cursor_plane) |
| drmModeSetCursor(b->drm.fd, crtc->crtc_id, |
| 0, 0, 0); |
| } |
| |
| output = container_of(compositor->output_list.next, |
| struct drm_output, base.link); |
| crtc = output->crtc; |
| |
| wl_list_for_each(plane, &b->plane_list, link) { |
| if (plane->type != WDRM_PLANE_TYPE_OVERLAY) |
| continue; |
| drmModeSetPlane(b->drm.fd, plane->plane_id, crtc->crtc_id, |
| 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); |
| } |
| } |
| } |
| |
| |
| /** |
| * Handle KMS GPU being added/removed |
| * |
| * If the device being added/removed is the KMS device, we activate/deactivate |
| * the compositor session. |
| * |
| * @param compositor The compositor instance. |
| * @param device The device being added/removed. |
| * @param added Whether the device is being added (or removed) |
| */ |
| static void |
| drm_device_changed(struct weston_compositor *compositor, |
| dev_t device, bool added) |
| { |
| struct drm_backend *b = to_drm_backend(compositor); |
| |
| if (b->drm.fd < 0 || b->drm.devnum != device || |
| compositor->session_active == added) |
| return; |
| |
| compositor->session_active = added; |
| wl_signal_emit(&compositor->session_signal, compositor); |
| } |
| |
| /** |
| * Determines whether or not a device is capable of modesetting. If successful, |
| * sets b->drm.fd and b->drm.filename to the opened device. |
| */ |
| static bool |
| drm_device_is_kms(struct drm_backend *b, struct udev_device *device) |
| { |
| const char *filename = udev_device_get_devnode(device); |
| const char *sysnum = udev_device_get_sysnum(device); |
| dev_t devnum = udev_device_get_devnum(device); |
| drmModeRes *res; |
| int id = -1, fd; |
| |
| if (!filename) |
| return false; |
| |
| fd = weston_launcher_open(b->compositor->launcher, filename, O_RDWR); |
| if (fd < 0) |
| return false; |
| |
| res = drmModeGetResources(fd); |
| if (!res) |
| goto out_fd; |
| |
| if (res->count_crtcs <= 0 || res->count_connectors <= 0 || |
| res->count_encoders <= 0) |
| goto out_res; |
| |
| if (sysnum) |
| id = atoi(sysnum); |
| if (!sysnum || id < 0) { |
| weston_log("couldn't get sysnum for device %s\n", filename); |
| goto out_res; |
| } |
| |
| /* We can be called successfully on multiple devices; if we have, |
| * clean up old entries. */ |
| if (b->drm.fd >= 0) |
| weston_launcher_close(b->compositor->launcher, b->drm.fd); |
| free(b->drm.filename); |
| |
| b->drm.fd = fd; |
| b->drm.id = id; |
| b->drm.filename = strdup(filename); |
| b->drm.devnum = devnum; |
| |
| drmModeFreeResources(res); |
| |
| return true; |
| |
| out_res: |
| drmModeFreeResources(res); |
| out_fd: |
| weston_launcher_close(b->compositor->launcher, fd); |
| return false; |
| } |
| |
| /* |
| * Find primary GPU |
| * Some systems may have multiple DRM devices attached to a single seat. This |
| * function loops over all devices and tries to find a PCI device with the |
| * boot_vga sysfs attribute set to 1. |
| * If no such device is found, the first DRM device reported by udev is used. |
| * Devices are also vetted to make sure they are are capable of modesetting, |
| * rather than pure render nodes (GPU with no display), or pure |
| * memory-allocation devices (VGEM). |
| */ |
| static struct udev_device* |
| find_primary_gpu(struct drm_backend *b, const char *seat) |
| { |
| struct udev_enumerate *e; |
| struct udev_list_entry *entry; |
| const char *path, *device_seat, *id; |
| struct udev_device *device, *drm_device, *pci; |
| |
| e = udev_enumerate_new(b->udev); |
| udev_enumerate_add_match_subsystem(e, "drm"); |
| udev_enumerate_add_match_sysname(e, "card[0-9]*"); |
| |
| udev_enumerate_scan_devices(e); |
| drm_device = NULL; |
| udev_list_entry_foreach(entry, udev_enumerate_get_list_entry(e)) { |
| bool is_boot_vga = false; |
| |
| path = udev_list_entry_get_name(entry); |
| device = udev_device_new_from_syspath(b->udev, path); |
| if (!device) |
| continue; |
| device_seat = udev_device_get_property_value(device, "ID_SEAT"); |
| if (!device_seat) |
| device_seat = default_seat; |
| if (strcmp(device_seat, seat)) { |
| udev_device_unref(device); |
| continue; |
| } |
| |
| pci = udev_device_get_parent_with_subsystem_devtype(device, |
| "pci", NULL); |
| if (pci) { |
| id = udev_device_get_sysattr_value(pci, "boot_vga"); |
| if (id && !strcmp(id, "1")) |
| is_boot_vga = true; |
| } |
| |
| /* If we already have a modesetting-capable device, and this |
| * device isn't our boot-VGA device, we aren't going to use |
| * it. */ |
| if (!is_boot_vga && drm_device) { |
| udev_device_unref(device); |
| continue; |
| } |
| |
| /* Make sure this device is actually capable of modesetting; |
| * if this call succeeds, b->drm.{fd,filename} will be set, |
| * and any old values freed. */ |
| if (!drm_device_is_kms(b, device)) { |
| udev_device_unref(device); |
| continue; |
| } |
| |
| /* There can only be one boot_vga device, and we try to use it |
| * at all costs. */ |
| if (is_boot_vga) { |
| if (drm_device) |
| udev_device_unref(drm_device); |
| drm_device = device; |
| break; |
| } |
| |
| /* Per the (!is_boot_vga && drm_device) test above, we only |
| * trump existing saved devices with boot-VGA devices, so if |
| * we end up here, this must be the first device we've seen. */ |
| assert(!drm_device); |
| drm_device = device; |
| } |
| |
| /* If we're returning a device to use, we must have an open FD for |
| * it. */ |
| assert(!!drm_device == (b->drm.fd >= 0)); |
| |
| udev_enumerate_unref(e); |
| return drm_device; |
| } |
| |
| static struct udev_device * |
| open_specific_drm_device(struct drm_backend *b, const char *name) |
| { |
| struct udev_device *device; |
| |
| device = udev_device_new_from_subsystem_sysname(b->udev, "drm", name); |
| if (!device) { |
| weston_log("ERROR: could not open DRM device '%s'\n", name); |
| return NULL; |
| } |
| |
| if (!drm_device_is_kms(b, device)) { |
| udev_device_unref(device); |
| weston_log("ERROR: DRM device '%s' is not a KMS device.\n", name); |
| return NULL; |
| } |
| |
| /* If we're returning a device to use, we must have an open FD for |
| * it. */ |
| assert(b->drm.fd >= 0); |
| |
| return device; |
| } |
| |
| static void |
| planes_binding(struct weston_keyboard *keyboard, const struct timespec *time, |
| uint32_t key, void *data) |
| { |
| struct drm_backend *b = data; |
| |
| switch (key) { |
| case KEY_C: |
| b->cursors_are_broken ^= true; |
| break; |
| case KEY_V: |
| /* We don't support overlay-plane usage with legacy KMS. */ |
| if (b->atomic_modeset) |
| b->sprites_are_broken ^= true; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| #ifdef BUILD_VAAPI_RECORDER |
| static void |
| recorder_destroy(struct drm_output *output) |
| { |
| vaapi_recorder_destroy(output->recorder); |
| output->recorder = NULL; |
| |
| weston_output_disable_planes_decr(&output->base); |
| |
| wl_list_remove(&output->recorder_frame_listener.link); |
| weston_log("[libva recorder] done\n"); |
| } |
| |
| static void |
| recorder_frame_notify(struct wl_listener *listener, void *data) |
| { |
| struct drm_output *output; |
| struct drm_backend *b; |
| int fd, ret; |
| |
| output = container_of(listener, struct drm_output, |
| recorder_frame_listener); |
| b = to_drm_backend(output->base.compositor); |
| |
| if (!output->recorder) |
| return; |
| |
| ret = drmPrimeHandleToFD(b->drm.fd, |
| output->scanout_plane->state_cur->fb->handles[0], |
| DRM_CLOEXEC, &fd); |
| if (ret) { |
| weston_log("[libva recorder] " |
| "failed to create prime fd for front buffer\n"); |
| return; |
| } |
| |
| ret = vaapi_recorder_frame(output->recorder, fd, |
| output->scanout_plane->state_cur->fb->strides[0]); |
| if (ret < 0) { |
| weston_log("[libva recorder] aborted: %s\n", strerror(errno)); |
| recorder_destroy(output); |
| } |
| } |
| |
| static void * |
| create_recorder(struct drm_backend *b, int width, int height, |
| const char *filename) |
| { |
| int fd; |
| drm_magic_t magic; |
| |
| fd = open(b->drm.filename, O_RDWR | O_CLOEXEC); |
| if (fd < 0) |
| return NULL; |
| |
| drmGetMagic(fd, &magic); |
| drmAuthMagic(b->drm.fd, magic); |
| |
| return vaapi_recorder_create(fd, width, height, filename); |
| } |
| |
| static void |
| recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, |
| uint32_t key, void *data) |
| { |
| struct drm_backend *b = data; |
| struct drm_output *output; |
| int width, height; |
| |
| output = container_of(b->compositor->output_list.next, |
| struct drm_output, base.link); |
| |
| if (!output->recorder) { |
| if (output->gbm_format != DRM_FORMAT_XRGB8888) { |
| weston_log("failed to start vaapi recorder: " |
| "output format not supported\n"); |
| return; |
| } |
| |
| width = output->base.current_mode->width; |
| height = output->base.current_mode->height; |
| |
| output->recorder = |
| create_recorder(b, width, height, "capture.h264"); |
| if (!output->recorder) { |
| weston_log("failed to create vaapi recorder\n"); |
| return; |
| } |
| |
| weston_output_disable_planes_incr(&output->base); |
| |
| output->recorder_frame_listener.notify = recorder_frame_notify; |
| wl_signal_add(&output->base.frame_signal, |
| &output->recorder_frame_listener); |
| |
| weston_output_schedule_repaint(&output->base); |
| |
| weston_log("[libva recorder] initialized\n"); |
| } else { |
| recorder_destroy(output); |
| } |
| } |
| #else |
| static void |
| recorder_binding(struct weston_keyboard *keyboard, const struct timespec *time, |
| uint32_t key, void *data) |
| { |
| weston_log("Compiled without libva support\n"); |
| } |
| #endif |
| |
| |
| static const struct weston_drm_output_api api = { |
| drm_output_set_mode, |
| drm_output_set_gbm_format, |
| drm_output_set_seat, |
| }; |
| |
| static struct drm_backend * |
| drm_backend_create(struct weston_compositor *compositor, |
| struct weston_drm_backend_config *config) |
| { |
| struct drm_backend *b; |
| struct udev_device *drm_device; |
| struct wl_event_loop *loop; |
| const char *seat_id = default_seat; |
| const char *session_seat; |
| struct weston_drm_format_array *scanout_formats; |
| drmModeRes *res; |
| int ret; |
| |
| session_seat = getenv("XDG_SEAT"); |
| if (session_seat) |
| seat_id = session_seat; |
| |
| if (config->seat_id) |
| seat_id = config->seat_id; |
| |
| weston_log("initializing drm backend\n"); |
| |
| b = zalloc(sizeof *b); |
| if (b == NULL) |
| return NULL; |
| |
| b->state_invalid = true; |
| b->allow_modeset = true; |
| b->drm.fd = -1; |
| b->display_enable = 1; |
| |
| #ifdef BUILD_AML_TV |
| b->vdin_detect_fd = -1; |
| #endif |
| |
| b->compositor = compositor; |
| b->use_pixman = config->use_pixman; |
| b->pageflip_timeout = config->pageflip_timeout; |
| b->use_pixman_shadow = config->use_pixman_shadow; |
| |
| b->debug = weston_compositor_add_log_scope(compositor, "drm-backend", |
| "Debug messages from DRM/KMS backend\n", |
| NULL, NULL, NULL); |
| |
| compositor->backend = &b->base; |
| |
| if (parse_gbm_format(config->gbm_format, DRM_FORMAT_XRGB8888, &b->gbm_format) < 0) |
| goto err_compositor; |
| |
| /* Check if we run drm-backend using weston-launch */ |
| compositor->launcher = weston_launcher_connect(compositor, config->tty, |
| seat_id, true); |
| if (compositor->launcher == NULL) { |
| weston_log("fatal: drm backend should be run using " |
| "weston-launch binary, or your system should " |
| "provide the logind D-Bus API.\n"); |
| goto err_compositor; |
| } |
| |
| b->udev = udev_new(); |
| if (b->udev == NULL) { |
| weston_log("failed to initialize udev context\n"); |
| goto err_launcher; |
| } |
| |
| b->session_listener.notify = session_notify; |
| wl_signal_add(&compositor->session_signal, &b->session_listener); |
| |
| drm_config_fixed_UIsize(b, config); |
| |
| if (config->specific_device) |
| drm_device = open_specific_drm_device(b, config->specific_device); |
| else |
| drm_device = find_primary_gpu(b, seat_id); |
| if (drm_device == NULL) { |
| weston_log("no drm device found\n"); |
| goto err_udev; |
| } |
| |
| if (init_kms_caps(b) < 0) { |
| weston_log("failed to initialize kms\n"); |
| goto err_udev_dev; |
| } |
| |
| if (b->use_pixman) { |
| if (init_pixman(b) < 0) { |
| weston_log("failed to initialize pixman renderer\n"); |
| goto err_udev_dev; |
| } |
| } else { |
| if (init_egl(b) < 0) { |
| weston_log("failed to initialize egl\n"); |
| goto err_udev_dev; |
| } |
| } |
| |
| b->base.destroy = drm_destroy; |
| b->base.repaint_begin = drm_repaint_begin; |
| b->base.repaint_flush = drm_repaint_flush; |
| b->base.repaint_cancel = drm_repaint_cancel; |
| b->base.create_output = drm_output_create; |
| b->base.device_changed = drm_device_changed; |
| b->base.can_scanout_dmabuf = drm_can_scanout_dmabuf; |
| |
| drm_add_aml_callback(b); |
| weston_setup_vt_switch_bindings(compositor); |
| |
| res = drmModeGetResources(b->drm.fd); |
| if (!res) { |
| weston_log("Failed to get drmModeRes\n"); |
| goto err_udev_dev; |
| } |
| |
| wl_list_init(&b->crtc_list); |
| if (drm_backend_create_crtc_list(b, res) == -1) { |
| weston_log("Failed to create CRTC list for DRM-backend\n"); |
| goto err_create_crtc_list; |
| } |
| |
| wl_list_init(&b->plane_list); |
| create_sprites(b); |
| |
| if (udev_input_init(&b->input, |
| compositor, b->udev, seat_id, |
| config->configure_device) < 0) { |
| weston_log("failed to create input devices\n"); |
| goto err_sprite; |
| } |
| |
| #ifdef ENABLE_DRM_HELP |
| weston_log("drm start_help_worker \n"); |
| start_help_worker(b->drm.fd, b->atomic_modeset); |
| #endif |
| |
| #ifdef ENABLE_MODE_POLICY |
| mode_policy_set_hotplug(AML_WESTON_HOTPLUG_BOOT, true); |
| #endif |
| |
| wl_list_init(&b->writeback_connector_list); |
| if (drm_backend_discover_connectors(b, drm_device, res) < 0) { |
| weston_log("Failed to create heads for %s\n", b->drm.filename); |
| goto err_udev_input; |
| } |
| |
| drmModeFreeResources(res); |
| |
| /* 'compute' faked zpos values in case HW doesn't expose any */ |
| drm_backend_create_faked_zpos(b); |
| |
| /* A this point we have some idea of whether or not we have a working |
| * cursor plane. */ |
| if (!b->cursors_are_broken) |
| compositor->capabilities |= WESTON_CAP_CURSOR_PLANE; |
| |
| loop = wl_display_get_event_loop(compositor->wl_display); |
| b->drm_source = |
| wl_event_loop_add_fd(loop, b->drm.fd, |
| WL_EVENT_READABLE, on_drm_input, b); |
| |
| b->udev_monitor = udev_monitor_new_from_netlink(b->udev, "udev"); |
| if (b->udev_monitor == NULL) { |
| weston_log("failed to initialize udev monitor\n"); |
| goto err_drm_source; |
| } |
| udev_monitor_filter_add_match_subsystem_devtype(b->udev_monitor, |
| "drm", NULL); |
| #ifdef BUILD_AML_TV |
| udev_monitor_filter_add_match_subsystem_devtype(b->udev_monitor, |
| "vdin", NULL); |
| udev_monitor_filter_add_match_subsystem_devtype(b->udev_monitor, |
| "framerate_adapter", NULL); |
| #endif |
| |
| b->udev_drm_source = |
| wl_event_loop_add_fd(loop, |
| udev_monitor_get_fd(b->udev_monitor), |
| WL_EVENT_READABLE, udev_drm_event, b); |
| |
| if (udev_monitor_enable_receiving(b->udev_monitor) < 0) { |
| weston_log("failed to enable udev-monitor receiving\n"); |
| goto err_udev_monitor; |
| } |
| |
| udev_device_unref(drm_device); |
| |
| weston_compositor_add_debug_binding(compositor, KEY_O, |
| planes_binding, b); |
| weston_compositor_add_debug_binding(compositor, KEY_C, |
| planes_binding, b); |
| weston_compositor_add_debug_binding(compositor, KEY_V, |
| planes_binding, b); |
| weston_compositor_add_debug_binding(compositor, KEY_Q, |
| recorder_binding, b); |
| weston_compositor_add_debug_binding(compositor, KEY_W, |
| renderer_switch_binding, b); |
| |
| if (compositor->renderer->import_dmabuf) { |
| if (linux_dmabuf_setup(compositor) < 0) |
| weston_log("Error: initializing dmabuf " |
| "support failed.\n"); |
| if (compositor->default_dmabuf_feedback) { |
| /* We were able to create the compositor's default |
| * dma-buf feedback in the renderer, that means that the |
| * table was already created and populated with |
| * renderer's format/modifier pairs. So now we must |
| * compute the scanout formats indices in the table */ |
| scanout_formats = get_scanout_formats(b); |
| if (!scanout_formats) |
| goto err_udev_monitor; |
| ret = weston_dmabuf_feedback_format_table_set_scanout_indices(compositor->dmabuf_feedback_format_table, |
| scanout_formats); |
| weston_drm_format_array_fini(scanout_formats); |
| free(scanout_formats); |
| if (ret < 0) |
| goto err_udev_monitor; |
| } |
| if (weston_direct_display_setup(compositor) < 0) |
| weston_log("Error: initializing direct-display " |
| "support failed.\n"); |
| } |
| |
| if (compositor->capabilities & WESTON_CAP_EXPLICIT_SYNC) { |
| if (linux_explicit_synchronization_setup(compositor) < 0) |
| weston_log("Error: initializing explicit " |
| " synchronization support failed.\n"); |
| } |
| |
| if (b->atomic_modeset) |
| if (weston_compositor_enable_content_protection(compositor) < 0) |
| weston_log("Error: initializing content-protection " |
| "support failed.\n"); |
| |
| ret = weston_plugin_api_register(compositor, WESTON_DRM_OUTPUT_API_NAME, |
| &api, sizeof(api)); |
| |
| if (ret < 0) { |
| weston_log("Failed to register output API.\n"); |
| goto err_udev_monitor; |
| } |
| |
| ret = drm_backend_init_virtual_output_api(compositor); |
| if (ret < 0) { |
| weston_log("Failed to register virtual output API.\n"); |
| goto err_udev_monitor; |
| } |
| |
| #ifdef ENABLE_DRM_HELP |
| help_set_printinfo_function(compositor, weston_print_info); |
| #endif |
| |
| #ifdef ENABLE_MODE_POLICY |
| weston_start_update_env_thread(); |
| #endif |
| |
| return b; |
| |
| err_udev_monitor: |
| wl_event_source_remove(b->udev_drm_source); |
| udev_monitor_unref(b->udev_monitor); |
| err_drm_source: |
| wl_event_source_remove(b->drm_source); |
| err_udev_input: |
| udev_input_destroy(&b->input); |
| err_sprite: |
| destroy_sprites(b); |
| err_create_crtc_list: |
| drmModeFreeResources(res); |
| err_udev_dev: |
| udev_device_unref(drm_device); |
| err_udev: |
| udev_unref(b->udev); |
| err_launcher: |
| weston_launcher_destroy(compositor->launcher); |
| err_compositor: |
| weston_compositor_shutdown(compositor); |
| #ifdef BUILD_DRM_GBM |
| if (b->gbm) |
| gbm_device_destroy(b->gbm); |
| #endif |
| free(b); |
| return NULL; |
| } |
| |
| static void |
| config_init_to_defaults(struct weston_drm_backend_config *config) |
| { |
| config->use_pixman_shadow = true; |
| } |
| |
| WL_EXPORT int |
| weston_backend_init(struct weston_compositor *compositor, |
| struct weston_backend_config *config_base) |
| { |
| struct drm_backend *b; |
| struct weston_drm_backend_config config = {{ 0, }}; |
| |
| if (config_base == NULL || |
| config_base->struct_version != WESTON_DRM_BACKEND_CONFIG_VERSION || |
| config_base->struct_size > sizeof(struct weston_drm_backend_config)) { |
| weston_log("drm backend config structure is invalid\n"); |
| return -1; |
| } |
| |
| config_init_to_defaults(&config); |
| memcpy(&config, config_base, config_base->struct_size); |
| |
| b = drm_backend_create(compositor, &config); |
| if (b == NULL) |
| return -1; |
| |
| return 0; |
| } |