|  | // SPDX-License-Identifier: GPL-2.0 OR MIT | 
|  |  | 
|  | /* | 
|  | *  Xen para-virtual DRM device | 
|  | * | 
|  | * Copyright (C) 2016-2018 EPAM Systems Inc. | 
|  | * | 
|  | * Author: Oleksandr Andrushchenko <oleksandr_andrushchenko@epam.com> | 
|  | */ | 
|  |  | 
|  | #include "xen_drm_front_kms.h" | 
|  |  | 
|  | #include <drm/drmP.h> | 
|  | #include <drm/drm_atomic.h> | 
|  | #include <drm/drm_atomic_helper.h> | 
|  | #include <drm/drm_crtc_helper.h> | 
|  | #include <drm/drm_gem.h> | 
|  | #include <drm/drm_gem_framebuffer_helper.h> | 
|  |  | 
|  | #include "xen_drm_front.h" | 
|  | #include "xen_drm_front_conn.h" | 
|  |  | 
|  | /* | 
|  | * Timeout in ms to wait for frame done event from the backend: | 
|  | * must be a bit more than IO time-out | 
|  | */ | 
|  | #define FRAME_DONE_TO_MS	(XEN_DRM_FRONT_WAIT_BACK_MS + 100) | 
|  |  | 
|  | static struct xen_drm_front_drm_pipeline * | 
|  | to_xen_drm_pipeline(struct drm_simple_display_pipe *pipe) | 
|  | { | 
|  | return container_of(pipe, struct xen_drm_front_drm_pipeline, pipe); | 
|  | } | 
|  |  | 
|  | static void fb_destroy(struct drm_framebuffer *fb) | 
|  | { | 
|  | struct xen_drm_front_drm_info *drm_info = fb->dev->dev_private; | 
|  | int idx; | 
|  |  | 
|  | if (drm_dev_enter(fb->dev, &idx)) { | 
|  | xen_drm_front_fb_detach(drm_info->front_info, | 
|  | xen_drm_front_fb_to_cookie(fb)); | 
|  | drm_dev_exit(idx); | 
|  | } | 
|  | drm_gem_fb_destroy(fb); | 
|  | } | 
|  |  | 
|  | static struct drm_framebuffer_funcs fb_funcs = { | 
|  | .destroy = fb_destroy, | 
|  | }; | 
|  |  | 
|  | static struct drm_framebuffer * | 
|  | fb_create(struct drm_device *dev, struct drm_file *filp, | 
|  | const struct drm_mode_fb_cmd2 *mode_cmd) | 
|  | { | 
|  | struct xen_drm_front_drm_info *drm_info = dev->dev_private; | 
|  | static struct drm_framebuffer *fb; | 
|  | struct drm_gem_object *gem_obj; | 
|  | int ret; | 
|  |  | 
|  | fb = drm_gem_fb_create_with_funcs(dev, filp, mode_cmd, &fb_funcs); | 
|  | if (IS_ERR_OR_NULL(fb)) | 
|  | return fb; | 
|  |  | 
|  | gem_obj = drm_gem_object_lookup(filp, mode_cmd->handles[0]); | 
|  | if (!gem_obj) { | 
|  | DRM_ERROR("Failed to lookup GEM object\n"); | 
|  | ret = -ENOENT; | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | drm_gem_object_put_unlocked(gem_obj); | 
|  |  | 
|  | ret = xen_drm_front_fb_attach(drm_info->front_info, | 
|  | xen_drm_front_dbuf_to_cookie(gem_obj), | 
|  | xen_drm_front_fb_to_cookie(fb), | 
|  | fb->width, fb->height, | 
|  | fb->format->format); | 
|  | if (ret < 0) { | 
|  | DRM_ERROR("Back failed to attach FB %p: %d\n", fb, ret); | 
|  | goto fail; | 
|  | } | 
|  |  | 
|  | return fb; | 
|  |  | 
|  | fail: | 
|  | drm_gem_fb_destroy(fb); | 
|  | return ERR_PTR(ret); | 
|  | } | 
|  |  | 
|  | static const struct drm_mode_config_funcs mode_config_funcs = { | 
|  | .fb_create = fb_create, | 
|  | .atomic_check = drm_atomic_helper_check, | 
|  | .atomic_commit = drm_atomic_helper_commit, | 
|  | }; | 
|  |  | 
|  | static void send_pending_event(struct xen_drm_front_drm_pipeline *pipeline) | 
|  | { | 
|  | struct drm_crtc *crtc = &pipeline->pipe.crtc; | 
|  | struct drm_device *dev = crtc->dev; | 
|  | unsigned long flags; | 
|  |  | 
|  | spin_lock_irqsave(&dev->event_lock, flags); | 
|  | if (pipeline->pending_event) | 
|  | drm_crtc_send_vblank_event(crtc, pipeline->pending_event); | 
|  | pipeline->pending_event = NULL; | 
|  | spin_unlock_irqrestore(&dev->event_lock, flags); | 
|  | } | 
|  |  | 
|  | static void display_enable(struct drm_simple_display_pipe *pipe, | 
|  | struct drm_crtc_state *crtc_state, | 
|  | struct drm_plane_state *plane_state) | 
|  | { | 
|  | struct xen_drm_front_drm_pipeline *pipeline = | 
|  | to_xen_drm_pipeline(pipe); | 
|  | struct drm_crtc *crtc = &pipe->crtc; | 
|  | struct drm_framebuffer *fb = plane_state->fb; | 
|  | int ret, idx; | 
|  |  | 
|  | if (!drm_dev_enter(pipe->crtc.dev, &idx)) | 
|  | return; | 
|  |  | 
|  | ret = xen_drm_front_mode_set(pipeline, crtc->x, crtc->y, | 
|  | fb->width, fb->height, | 
|  | fb->format->cpp[0] * 8, | 
|  | xen_drm_front_fb_to_cookie(fb)); | 
|  |  | 
|  | if (ret) { | 
|  | DRM_ERROR("Failed to enable display: %d\n", ret); | 
|  | pipeline->conn_connected = false; | 
|  | } | 
|  |  | 
|  | drm_dev_exit(idx); | 
|  | } | 
|  |  | 
|  | static void display_disable(struct drm_simple_display_pipe *pipe) | 
|  | { | 
|  | struct xen_drm_front_drm_pipeline *pipeline = | 
|  | to_xen_drm_pipeline(pipe); | 
|  | int ret = 0, idx; | 
|  |  | 
|  | if (drm_dev_enter(pipe->crtc.dev, &idx)) { | 
|  | ret = xen_drm_front_mode_set(pipeline, 0, 0, 0, 0, 0, | 
|  | xen_drm_front_fb_to_cookie(NULL)); | 
|  | drm_dev_exit(idx); | 
|  | } | 
|  | if (ret) | 
|  | DRM_ERROR("Failed to disable display: %d\n", ret); | 
|  |  | 
|  | /* Make sure we can restart with enabled connector next time */ | 
|  | pipeline->conn_connected = true; | 
|  |  | 
|  | /* release stalled event if any */ | 
|  | send_pending_event(pipeline); | 
|  | } | 
|  |  | 
|  | void xen_drm_front_kms_on_frame_done(struct xen_drm_front_drm_pipeline *pipeline, | 
|  | u64 fb_cookie) | 
|  | { | 
|  | /* | 
|  | * This runs in interrupt context, e.g. under | 
|  | * drm_info->front_info->io_lock, so we cannot call _sync version | 
|  | * to cancel the work | 
|  | */ | 
|  | cancel_delayed_work(&pipeline->pflip_to_worker); | 
|  |  | 
|  | send_pending_event(pipeline); | 
|  | } | 
|  |  | 
|  | static void pflip_to_worker(struct work_struct *work) | 
|  | { | 
|  | struct delayed_work *delayed_work = to_delayed_work(work); | 
|  | struct xen_drm_front_drm_pipeline *pipeline = | 
|  | container_of(delayed_work, | 
|  | struct xen_drm_front_drm_pipeline, | 
|  | pflip_to_worker); | 
|  |  | 
|  | DRM_ERROR("Frame done timed-out, releasing"); | 
|  | send_pending_event(pipeline); | 
|  | } | 
|  |  | 
|  | static bool display_send_page_flip(struct drm_simple_display_pipe *pipe, | 
|  | struct drm_plane_state *old_plane_state) | 
|  | { | 
|  | struct drm_plane_state *plane_state = | 
|  | drm_atomic_get_new_plane_state(old_plane_state->state, | 
|  | &pipe->plane); | 
|  |  | 
|  | /* | 
|  | * If old_plane_state->fb is NULL and plane_state->fb is not, | 
|  | * then this is an atomic commit which will enable display. | 
|  | * If old_plane_state->fb is not NULL and plane_state->fb is, | 
|  | * then this is an atomic commit which will disable display. | 
|  | * Ignore these and do not send page flip as this framebuffer will be | 
|  | * sent to the backend as a part of display_set_config call. | 
|  | */ | 
|  | if (old_plane_state->fb && plane_state->fb) { | 
|  | struct xen_drm_front_drm_pipeline *pipeline = | 
|  | to_xen_drm_pipeline(pipe); | 
|  | struct xen_drm_front_drm_info *drm_info = pipeline->drm_info; | 
|  | int ret; | 
|  |  | 
|  | schedule_delayed_work(&pipeline->pflip_to_worker, | 
|  | msecs_to_jiffies(FRAME_DONE_TO_MS)); | 
|  |  | 
|  | ret = xen_drm_front_page_flip(drm_info->front_info, | 
|  | pipeline->index, | 
|  | xen_drm_front_fb_to_cookie(plane_state->fb)); | 
|  | if (ret) { | 
|  | DRM_ERROR("Failed to send page flip request to backend: %d\n", ret); | 
|  |  | 
|  | pipeline->conn_connected = false; | 
|  | /* | 
|  | * Report the flip not handled, so pending event is | 
|  | * sent, unblocking user-space. | 
|  | */ | 
|  | return false; | 
|  | } | 
|  | /* | 
|  | * Signal that page flip was handled, pending event will be sent | 
|  | * on frame done event from the backend. | 
|  | */ | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static void display_update(struct drm_simple_display_pipe *pipe, | 
|  | struct drm_plane_state *old_plane_state) | 
|  | { | 
|  | struct xen_drm_front_drm_pipeline *pipeline = | 
|  | to_xen_drm_pipeline(pipe); | 
|  | struct drm_crtc *crtc = &pipe->crtc; | 
|  | struct drm_pending_vblank_event *event; | 
|  | int idx; | 
|  |  | 
|  | event = crtc->state->event; | 
|  | if (event) { | 
|  | struct drm_device *dev = crtc->dev; | 
|  | unsigned long flags; | 
|  |  | 
|  | WARN_ON(pipeline->pending_event); | 
|  |  | 
|  | spin_lock_irqsave(&dev->event_lock, flags); | 
|  | crtc->state->event = NULL; | 
|  |  | 
|  | pipeline->pending_event = event; | 
|  | spin_unlock_irqrestore(&dev->event_lock, flags); | 
|  | } | 
|  |  | 
|  | if (!drm_dev_enter(pipe->crtc.dev, &idx)) { | 
|  | send_pending_event(pipeline); | 
|  | return; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Send page flip request to the backend *after* we have event cached | 
|  | * above, so on page flip done event from the backend we can | 
|  | * deliver it and there is no race condition between this code and | 
|  | * event from the backend. | 
|  | * If this is not a page flip, e.g. no flip done event from the backend | 
|  | * is expected, then send now. | 
|  | */ | 
|  | if (!display_send_page_flip(pipe, old_plane_state)) | 
|  | send_pending_event(pipeline); | 
|  |  | 
|  | drm_dev_exit(idx); | 
|  | } | 
|  |  | 
|  | static enum drm_mode_status | 
|  | display_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode) | 
|  | { | 
|  | struct xen_drm_front_drm_pipeline *pipeline = | 
|  | container_of(crtc, struct xen_drm_front_drm_pipeline, | 
|  | pipe.crtc); | 
|  |  | 
|  | if (mode->hdisplay != pipeline->width) | 
|  | return MODE_ERROR; | 
|  |  | 
|  | if (mode->vdisplay != pipeline->height) | 
|  | return MODE_ERROR; | 
|  |  | 
|  | return MODE_OK; | 
|  | } | 
|  |  | 
|  | static const struct drm_simple_display_pipe_funcs display_funcs = { | 
|  | .mode_valid = display_mode_valid, | 
|  | .enable = display_enable, | 
|  | .disable = display_disable, | 
|  | .prepare_fb = drm_gem_fb_simple_display_pipe_prepare_fb, | 
|  | .update = display_update, | 
|  | }; | 
|  |  | 
|  | static int display_pipe_init(struct xen_drm_front_drm_info *drm_info, | 
|  | int index, struct xen_drm_front_cfg_connector *cfg, | 
|  | struct xen_drm_front_drm_pipeline *pipeline) | 
|  | { | 
|  | struct drm_device *dev = drm_info->drm_dev; | 
|  | const u32 *formats; | 
|  | int format_count; | 
|  | int ret; | 
|  |  | 
|  | pipeline->drm_info = drm_info; | 
|  | pipeline->index = index; | 
|  | pipeline->height = cfg->height; | 
|  | pipeline->width = cfg->width; | 
|  |  | 
|  | INIT_DELAYED_WORK(&pipeline->pflip_to_worker, pflip_to_worker); | 
|  |  | 
|  | ret = xen_drm_front_conn_init(drm_info, &pipeline->conn); | 
|  | if (ret) | 
|  | return ret; | 
|  |  | 
|  | formats = xen_drm_front_conn_get_formats(&format_count); | 
|  |  | 
|  | return drm_simple_display_pipe_init(dev, &pipeline->pipe, | 
|  | &display_funcs, formats, | 
|  | format_count, NULL, | 
|  | &pipeline->conn); | 
|  | } | 
|  |  | 
|  | int xen_drm_front_kms_init(struct xen_drm_front_drm_info *drm_info) | 
|  | { | 
|  | struct drm_device *dev = drm_info->drm_dev; | 
|  | int i, ret; | 
|  |  | 
|  | drm_mode_config_init(dev); | 
|  |  | 
|  | dev->mode_config.min_width = 0; | 
|  | dev->mode_config.min_height = 0; | 
|  | dev->mode_config.max_width = 4095; | 
|  | dev->mode_config.max_height = 2047; | 
|  | dev->mode_config.funcs = &mode_config_funcs; | 
|  |  | 
|  | for (i = 0; i < drm_info->front_info->cfg.num_connectors; i++) { | 
|  | struct xen_drm_front_cfg_connector *cfg = | 
|  | &drm_info->front_info->cfg.connectors[i]; | 
|  | struct xen_drm_front_drm_pipeline *pipeline = | 
|  | &drm_info->pipeline[i]; | 
|  |  | 
|  | ret = display_pipe_init(drm_info, i, cfg, pipeline); | 
|  | if (ret) { | 
|  | drm_mode_config_cleanup(dev); | 
|  | return ret; | 
|  | } | 
|  | } | 
|  |  | 
|  | drm_mode_config_reset(dev); | 
|  | drm_kms_helper_poll_init(dev); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | void xen_drm_front_kms_fini(struct xen_drm_front_drm_info *drm_info) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < drm_info->front_info->cfg.num_connectors; i++) { | 
|  | struct xen_drm_front_drm_pipeline *pipeline = | 
|  | &drm_info->pipeline[i]; | 
|  |  | 
|  | cancel_delayed_work_sync(&pipeline->pflip_to_worker); | 
|  |  | 
|  | send_pending_event(pipeline); | 
|  | } | 
|  | } |